1*2abb3134SXin Li// Sortable HTML table. 2*2abb3134SXin Li// 3*2abb3134SXin Li// Usage: 4*2abb3134SXin Li// 5*2abb3134SXin Li// Each page should have gTableStates and gUrlHash variables. This library 6*2abb3134SXin Li// only provides functions / classes, not instances. 7*2abb3134SXin Li// 8*2abb3134SXin Li// Then use these public functions on those variables. They should be hooked 9*2abb3134SXin Li// up to initialization and onhashchange events. 10*2abb3134SXin Li// 11*2abb3134SXin Li// - makeTablesSortable 12*2abb3134SXin Li// - updateTables 13*2abb3134SXin Li// 14*2abb3134SXin Li// Life of a click 15*2abb3134SXin Li// 16*2abb3134SXin Li// - query existing TableState object to find the new state 17*2abb3134SXin Li// - mutate urlHash 18*2abb3134SXin Li// - location.hash = urlHash.encode() 19*2abb3134SXin Li// - onhashchange 20*2abb3134SXin Li// - decode location.hash into urlHash 21*2abb3134SXin Li// - update DOM 22*2abb3134SXin Li// 23*2abb3134SXin Li// HTML generation requirements: 24*2abb3134SXin Li// - <table id="foo"> 25*2abb3134SXin Li// - need <colgroup> for types. 26*2abb3134SXin Li// - For numbers, class="num-cell" as well as <col type="number"> 27*2abb3134SXin Li// - single <thead> and <tbody> 28*2abb3134SXin Li 29*2abb3134SXin Li'use strict'; 30*2abb3134SXin Li 31*2abb3134SXin Lifunction appendMessage(elem, msg) { 32*2abb3134SXin Li // TODO: escape HTML? 33*2abb3134SXin Li elem.innerHTML += msg + '<br />'; 34*2abb3134SXin Li} 35*2abb3134SXin Li 36*2abb3134SXin Lifunction userError(errElem, msg) { 37*2abb3134SXin Li if (errElem) { 38*2abb3134SXin Li appendMessage(errElem, msg); 39*2abb3134SXin Li } else { 40*2abb3134SXin Li console.log(msg); 41*2abb3134SXin Li } 42*2abb3134SXin Li} 43*2abb3134SXin Li 44*2abb3134SXin Li// 45*2abb3134SXin Li// Key functions for column ordering 46*2abb3134SXin Li// 47*2abb3134SXin Li// TODO: better naming convention? 48*2abb3134SXin Li 49*2abb3134SXin Lifunction identity(x) { 50*2abb3134SXin Li return x; 51*2abb3134SXin Li} 52*2abb3134SXin Li 53*2abb3134SXin Lifunction lowerCase(x) { 54*2abb3134SXin Li return x.toLowerCase(); 55*2abb3134SXin Li} 56*2abb3134SXin Li 57*2abb3134SXin Li// Parse as number. 58*2abb3134SXin Lifunction asNumber(x) { 59*2abb3134SXin Li var stripped = x.replace(/[ \t\r\n]/g, ''); 60*2abb3134SXin Li if (stripped === 'NA') { 61*2abb3134SXin Li // return lowest value, so NA sorts below everything else. 62*2abb3134SXin Li return -Number.MAX_VALUE; 63*2abb3134SXin Li } 64*2abb3134SXin Li var numClean = x.replace(/[$,]/g, ''); // remove dollar signs and commas 65*2abb3134SXin Li return parseFloat(numClean); 66*2abb3134SXin Li} 67*2abb3134SXin Li 68*2abb3134SXin Li// as a date. 69*2abb3134SXin Li// 70*2abb3134SXin Li// TODO: Parse into JS date object? 71*2abb3134SXin Li// http://stackoverflow.com/questions/19430561/how-to-sort-a-javascript-array-of-objects-by-date 72*2abb3134SXin Li// Uses getTime(). Hm. 73*2abb3134SXin Li 74*2abb3134SXin Lifunction asDate(x) { 75*2abb3134SXin Li return x; 76*2abb3134SXin Li} 77*2abb3134SXin Li 78*2abb3134SXin Li// 79*2abb3134SXin Li// Table Implementation 80*2abb3134SXin Li// 81*2abb3134SXin Li 82*2abb3134SXin Li// Given a column array and a key function, construct a permutation of the 83*2abb3134SXin Li// indices [0, n). 84*2abb3134SXin Lifunction makePermutation(colArray, keyFunc) { 85*2abb3134SXin Li var pairs = []; // (index, result of keyFunc on cell) 86*2abb3134SXin Li 87*2abb3134SXin Li var n = colArray.length; 88*2abb3134SXin Li for (var i = 0; i < n; ++i) { 89*2abb3134SXin Li var value = colArray[i]; 90*2abb3134SXin Li 91*2abb3134SXin Li // NOTE: This could be a URL, so you need to extract that? 92*2abb3134SXin Li // If it's a URL, take the anchor text I guess. 93*2abb3134SXin Li var key = keyFunc(value); 94*2abb3134SXin Li 95*2abb3134SXin Li pairs.push([key, i]); 96*2abb3134SXin Li } 97*2abb3134SXin Li 98*2abb3134SXin Li // Sort by computed key 99*2abb3134SXin Li pairs.sort(function(a, b) { 100*2abb3134SXin Li if (a[0] < b[0]) { 101*2abb3134SXin Li return -1; 102*2abb3134SXin Li } else if (a[0] > b[0]) { 103*2abb3134SXin Li return 1; 104*2abb3134SXin Li } else { 105*2abb3134SXin Li return 0; 106*2abb3134SXin Li } 107*2abb3134SXin Li }); 108*2abb3134SXin Li 109*2abb3134SXin Li // Extract the permutation as second column 110*2abb3134SXin Li var perm = []; 111*2abb3134SXin Li for (var i = 0; i < pairs.length; ++i) { 112*2abb3134SXin Li perm.push(pairs[i][1]); // append index 113*2abb3134SXin Li } 114*2abb3134SXin Li return perm; 115*2abb3134SXin Li} 116*2abb3134SXin Li 117*2abb3134SXin Lifunction extractCol(rows, colIndex) { 118*2abb3134SXin Li var colArray = []; 119*2abb3134SXin Li for (var i = 0; i < rows.length; ++i) { 120*2abb3134SXin Li var row = rows[i]; 121*2abb3134SXin Li colArray.push(row.cells[colIndex].textContent); 122*2abb3134SXin Li } 123*2abb3134SXin Li return colArray; 124*2abb3134SXin Li} 125*2abb3134SXin Li 126*2abb3134SXin Li// Given an array of DOM row objects, and a list of sort functions (one per 127*2abb3134SXin Li// column), return a list of permutations. 128*2abb3134SXin Li// 129*2abb3134SXin Li// Right now this is eager. Could be lazy later. 130*2abb3134SXin Lifunction makeAllPermutations(rows, keyFuncs) { 131*2abb3134SXin Li var numCols = keyFuncs.length; 132*2abb3134SXin Li var permutations = []; 133*2abb3134SXin Li for (var i = 0; i < numCols; ++i) { 134*2abb3134SXin Li var colArray = extractCol(rows, i); 135*2abb3134SXin Li var keyFunc = keyFuncs[i]; 136*2abb3134SXin Li var p = makePermutation(colArray, keyFunc); 137*2abb3134SXin Li permutations.push(p); 138*2abb3134SXin Li } 139*2abb3134SXin Li return permutations; 140*2abb3134SXin Li} 141*2abb3134SXin Li 142*2abb3134SXin Li// Model object for a table. (Mostly) independent of the DOM. 143*2abb3134SXin Lifunction TableState(table, keyFuncs) { 144*2abb3134SXin Li this.table = table; 145*2abb3134SXin Li keyFuncs = keyFuncs || []; // array of column 146*2abb3134SXin Li 147*2abb3134SXin Li // these are mutated 148*2abb3134SXin Li this.sortCol = -1; // not sorted by any col 149*2abb3134SXin Li this.ascending = false; // if sortCol is sorted in ascending order 150*2abb3134SXin Li 151*2abb3134SXin Li if (table === null) { // hack so we can pass dummy table 152*2abb3134SXin Li console.log('TESTING'); 153*2abb3134SXin Li return; 154*2abb3134SXin Li } 155*2abb3134SXin Li 156*2abb3134SXin Li var bodyRows = table.tBodies[0].rows; 157*2abb3134SXin Li this.orig = []; // pointers to row objects in their original order 158*2abb3134SXin Li for (var i = 0; i < bodyRows.length; ++i) { 159*2abb3134SXin Li this.orig.push(bodyRows[i]); 160*2abb3134SXin Li } 161*2abb3134SXin Li 162*2abb3134SXin Li this.colElems = []; 163*2abb3134SXin Li var colgroup = table.getElementsByTagName('colgroup')[0]; 164*2abb3134SXin Li 165*2abb3134SXin Li // copy it into an array 166*2abb3134SXin Li if (!colgroup) { 167*2abb3134SXin Li throw new Error('<colgroup> is required'); 168*2abb3134SXin Li } 169*2abb3134SXin Li 170*2abb3134SXin Li for (var i = 0; i < colgroup.children.length; ++i) { 171*2abb3134SXin Li var colElem = colgroup.children[i]; 172*2abb3134SXin Li var colType = colElem.getAttribute('type'); 173*2abb3134SXin Li var keyFunc; 174*2abb3134SXin Li switch (colType) { 175*2abb3134SXin Li case 'case-sensitive': 176*2abb3134SXin Li keyFunc = identity; 177*2abb3134SXin Li break; 178*2abb3134SXin Li case 'case-insensitive': 179*2abb3134SXin Li keyFunc = lowerCase; 180*2abb3134SXin Li break; 181*2abb3134SXin Li case 'number': 182*2abb3134SXin Li keyFunc = asNumber; 183*2abb3134SXin Li break; 184*2abb3134SXin Li case 'date': 185*2abb3134SXin Li keyFunc = asDate; 186*2abb3134SXin Li break; 187*2abb3134SXin Li default: 188*2abb3134SXin Li throw new Error('Invalid column type ' + colType); 189*2abb3134SXin Li } 190*2abb3134SXin Li keyFuncs[i] = keyFunc; 191*2abb3134SXin Li 192*2abb3134SXin Li this.colElems.push(colElem); 193*2abb3134SXin Li } 194*2abb3134SXin Li 195*2abb3134SXin Li this.permutations = makeAllPermutations(this.orig, keyFuncs); 196*2abb3134SXin Li} 197*2abb3134SXin Li 198*2abb3134SXin Li// Reset sort state. 199*2abb3134SXin LiTableState.prototype.resetSort = function() { 200*2abb3134SXin Li this.sortCol = -1; // not sorted by any col 201*2abb3134SXin Li this.ascending = false; // if sortCol is sorted in ascending order 202*2abb3134SXin Li}; 203*2abb3134SXin Li 204*2abb3134SXin Li// Change state for a click on a column. 205*2abb3134SXin LiTableState.prototype.doClick = function(colIndex) { 206*2abb3134SXin Li if (this.sortCol === colIndex) { // same column; invert direction 207*2abb3134SXin Li this.ascending = !this.ascending; 208*2abb3134SXin Li } else { // different column 209*2abb3134SXin Li this.sortCol = colIndex; 210*2abb3134SXin Li // first click makes it *descending*. Typically you want to see the 211*2abb3134SXin Li // largest values first. 212*2abb3134SXin Li this.ascending = false; 213*2abb3134SXin Li } 214*2abb3134SXin Li}; 215*2abb3134SXin Li 216*2abb3134SXin LiTableState.prototype.decode = function(stateStr, errElem) { 217*2abb3134SXin Li var sortCol = parseInt(stateStr); // parse leading integer 218*2abb3134SXin Li var lastChar = stateStr[stateStr.length - 1]; 219*2abb3134SXin Li 220*2abb3134SXin Li var ascending; 221*2abb3134SXin Li if (lastChar === 'a') { 222*2abb3134SXin Li ascending = true; 223*2abb3134SXin Li } else if (lastChar === 'd') { 224*2abb3134SXin Li ascending = false; 225*2abb3134SXin Li } else { 226*2abb3134SXin Li // The user could have entered a bad ID 227*2abb3134SXin Li userError(errElem, 'Invalid state string ' + stateStr); 228*2abb3134SXin Li return; 229*2abb3134SXin Li } 230*2abb3134SXin Li 231*2abb3134SXin Li this.sortCol = sortCol; 232*2abb3134SXin Li this.ascending = ascending; 233*2abb3134SXin Li} 234*2abb3134SXin Li 235*2abb3134SXin Li 236*2abb3134SXin LiTableState.prototype.encode = function() { 237*2abb3134SXin Li if (this.sortCol === -1) { 238*2abb3134SXin Li return ''; // default state isn't serialized 239*2abb3134SXin Li } 240*2abb3134SXin Li 241*2abb3134SXin Li var s = this.sortCol.toString(); 242*2abb3134SXin Li s += this.ascending ? 'a' : 'd'; 243*2abb3134SXin Li return s; 244*2abb3134SXin Li}; 245*2abb3134SXin Li 246*2abb3134SXin Li// Update the DOM with using this object's internal state. 247*2abb3134SXin LiTableState.prototype.updateDom = function() { 248*2abb3134SXin Li var tHead = this.table.tHead; 249*2abb3134SXin Li setArrows(tHead, this.sortCol, this.ascending); 250*2abb3134SXin Li 251*2abb3134SXin Li // Highlight the column that the table is sorted by. 252*2abb3134SXin Li for (var i = 0; i < this.colElems.length; ++i) { 253*2abb3134SXin Li // set or clear it. NOTE: This means we can't have other classes on the 254*2abb3134SXin Li // <col> tags, which is OK. 255*2abb3134SXin Li var className = (i === this.sortCol) ? 'highlight' : ''; 256*2abb3134SXin Li this.colElems[i].className = className; 257*2abb3134SXin Li } 258*2abb3134SXin Li 259*2abb3134SXin Li var n = this.orig.length; 260*2abb3134SXin Li var tbody = this.table.tBodies[0]; 261*2abb3134SXin Li 262*2abb3134SXin Li if (this.sortCol === -1) { // reset it and return 263*2abb3134SXin Li for (var i = 0; i < n; ++i) { 264*2abb3134SXin Li tbody.appendChild(this.orig[i]); 265*2abb3134SXin Li } 266*2abb3134SXin Li return; 267*2abb3134SXin Li } 268*2abb3134SXin Li 269*2abb3134SXin Li var perm = this.permutations[this.sortCol]; 270*2abb3134SXin Li if (this.ascending) { 271*2abb3134SXin Li for (var i = 0; i < n; ++i) { 272*2abb3134SXin Li var index = perm[i]; 273*2abb3134SXin Li tbody.appendChild(this.orig[index]); 274*2abb3134SXin Li } 275*2abb3134SXin Li } else { // descending, apply the permutation in reverse order 276*2abb3134SXin Li for (var i = n - 1; i >= 0; --i) { 277*2abb3134SXin Li var index = perm[i]; 278*2abb3134SXin Li tbody.appendChild(this.orig[index]); 279*2abb3134SXin Li } 280*2abb3134SXin Li } 281*2abb3134SXin Li}; 282*2abb3134SXin Li 283*2abb3134SXin Livar kTablePrefix = 't:'; 284*2abb3134SXin Livar kTablePrefixLength = 2; 285*2abb3134SXin Li 286*2abb3134SXin Li// Given a UrlHash instance and a list of tables, mutate tableStates. 287*2abb3134SXin Lifunction decodeState(urlHash, tableStates, errElem) { 288*2abb3134SXin Li var keys = urlHash.getKeysWithPrefix(kTablePrefix); // by convention, t:foo=1a 289*2abb3134SXin Li for (var i = 0; i < keys.length; ++i) { 290*2abb3134SXin Li var key = keys[i]; 291*2abb3134SXin Li var tableId = key.substring(kTablePrefixLength); 292*2abb3134SXin Li 293*2abb3134SXin Li if (!tableStates.hasOwnProperty(tableId)) { 294*2abb3134SXin Li // The user could have entered a bad ID 295*2abb3134SXin Li userError(errElem, 'Invalid table ID [' + tableId + ']'); 296*2abb3134SXin Li return; 297*2abb3134SXin Li } 298*2abb3134SXin Li 299*2abb3134SXin Li var state = tableStates[tableId]; 300*2abb3134SXin Li var stateStr = urlHash.get(key); // e.g. '1d' 301*2abb3134SXin Li 302*2abb3134SXin Li state.decode(stateStr, errElem); 303*2abb3134SXin Li } 304*2abb3134SXin Li} 305*2abb3134SXin Li 306*2abb3134SXin Li// Add <span> element for sort arrows. 307*2abb3134SXin Lifunction addArrowSpans(tHead) { 308*2abb3134SXin Li var tHeadCells = tHead.rows[0].cells; 309*2abb3134SXin Li for (var i = 0; i < tHeadCells.length; ++i) { 310*2abb3134SXin Li var colHead = tHeadCells[i]; 311*2abb3134SXin Li // Put a space in so the width is relatively constant 312*2abb3134SXin Li colHead.innerHTML += ' <span class="sortArrow"> </span>'; 313*2abb3134SXin Li } 314*2abb3134SXin Li} 315*2abb3134SXin Li 316*2abb3134SXin Li// Go through all the cells in the header. Clear the arrow if there is one. 317*2abb3134SXin Li// Set the one on the correct column. 318*2abb3134SXin Li// 319*2abb3134SXin Li// How to do this? Each column needs a <span></span> modify the text? 320*2abb3134SXin Lifunction setArrows(tHead, sortCol, ascending) { 321*2abb3134SXin Li var tHeadCells = tHead.rows[0].cells; 322*2abb3134SXin Li 323*2abb3134SXin Li for (var i = 0; i < tHeadCells.length; ++i) { 324*2abb3134SXin Li var colHead = tHeadCells[i]; 325*2abb3134SXin Li var span = colHead.getElementsByTagName('span')[0]; 326*2abb3134SXin Li 327*2abb3134SXin Li if (i === sortCol) { 328*2abb3134SXin Li span.innerHTML = ascending ? '▴' : '▾'; 329*2abb3134SXin Li } else { 330*2abb3134SXin Li span.innerHTML = ' '; // clear it 331*2abb3134SXin Li } 332*2abb3134SXin Li } 333*2abb3134SXin Li} 334*2abb3134SXin Li 335*2abb3134SXin Li// Given the URL hash, table states, tableId, and column index that was 336*2abb3134SXin Li// clicked, visit a new location. 337*2abb3134SXin Lifunction makeClickHandler(urlHash, tableStates, id, colIndex) { 338*2abb3134SXin Li return function() { // no args for onclick= 339*2abb3134SXin Li var clickedState = tableStates[id]; 340*2abb3134SXin Li 341*2abb3134SXin Li clickedState.doClick(colIndex); 342*2abb3134SXin Li 343*2abb3134SXin Li // now urlHash has non-table state, and tableStates is the table state. 344*2abb3134SXin Li for (var tableId in tableStates) { 345*2abb3134SXin Li var state = tableStates[tableId]; 346*2abb3134SXin Li 347*2abb3134SXin Li var stateStr = state.encode(); 348*2abb3134SXin Li var key = kTablePrefix + tableId; 349*2abb3134SXin Li 350*2abb3134SXin Li if (stateStr === '') { 351*2abb3134SXin Li urlHash.del(key); 352*2abb3134SXin Li } else { 353*2abb3134SXin Li urlHash.set(key, stateStr); 354*2abb3134SXin Li } 355*2abb3134SXin Li } 356*2abb3134SXin Li 357*2abb3134SXin Li // move to new location 358*2abb3134SXin Li location.hash = urlHash.encode(); 359*2abb3134SXin Li }; 360*2abb3134SXin Li} 361*2abb3134SXin Li 362*2abb3134SXin Li// Go through cells and register onClick 363*2abb3134SXin Lifunction registerClick(table, urlHash, tableStates) { 364*2abb3134SXin Li var id = table.id; // id is required 365*2abb3134SXin Li 366*2abb3134SXin Li var tHeadCells = table.tHead.rows[0].cells; 367*2abb3134SXin Li for (var colIndex = 0; colIndex < tHeadCells.length; ++colIndex) { 368*2abb3134SXin Li var colHead = tHeadCells[colIndex]; 369*2abb3134SXin Li // NOTE: in ES5, could use 'bind'. 370*2abb3134SXin Li colHead.onclick = makeClickHandler(urlHash, tableStates, id, colIndex); 371*2abb3134SXin Li } 372*2abb3134SXin Li} 373*2abb3134SXin Li 374*2abb3134SXin Li// 375*2abb3134SXin Li// Public Functions (TODO: Make a module?) 376*2abb3134SXin Li// 377*2abb3134SXin Li 378*2abb3134SXin Li// Parse the URL fragment, and update all tables. Errors are printed to a DOM 379*2abb3134SXin Li// element. 380*2abb3134SXin Lifunction updateTables(urlHash, tableStates, statusElem) { 381*2abb3134SXin Li // State should come from the hash alone, so reset old state. (We want to 382*2abb3134SXin Li // keep the permutations though.) 383*2abb3134SXin Li for (var tableId in tableStates) { 384*2abb3134SXin Li tableStates[tableId].resetSort(); 385*2abb3134SXin Li } 386*2abb3134SXin Li 387*2abb3134SXin Li decodeState(urlHash, tableStates, statusElem); 388*2abb3134SXin Li 389*2abb3134SXin Li for (var name in tableStates) { 390*2abb3134SXin Li var state = tableStates[name]; 391*2abb3134SXin Li state.updateDom(); 392*2abb3134SXin Li } 393*2abb3134SXin Li} 394*2abb3134SXin Li 395*2abb3134SXin Li// Takes a {tableId: spec} object. The spec should be an array of sortable 396*2abb3134SXin Li// items. 397*2abb3134SXin Li// Returns a dictionary of table states. 398*2abb3134SXin Lifunction makeTablesSortable(urlHash, tables, tableStates) { 399*2abb3134SXin Li for (var i = 0; i < tables.length; ++i) { 400*2abb3134SXin Li var table = tables[i]; 401*2abb3134SXin Li var tableId = table.id; 402*2abb3134SXin Li 403*2abb3134SXin Li registerClick(table, urlHash, tableStates); 404*2abb3134SXin Li tableStates[tableId] = new TableState(table); 405*2abb3134SXin Li 406*2abb3134SXin Li addArrowSpans(table.tHead); 407*2abb3134SXin Li } 408*2abb3134SXin Li return tableStates; 409*2abb3134SXin Li} 410*2abb3134SXin Li 411*2abb3134SXin Li// table-sort.js can use t:holidays=1d 412*2abb3134SXin Li// 413*2abb3134SXin Li// metric.html can use: 414*2abb3134SXin Li// 415*2abb3134SXin Li// metric=Foo.bar 416*2abb3134SXin Li// 417*2abb3134SXin Li// day.html could use 418*2abb3134SXin Li// 419*2abb3134SXin Li// jobId=X&metric=Foo.bar&day=2015-06-01 420*2abb3134SXin Li 421*2abb3134SXin Li// helper 422*2abb3134SXin Lifunction _decode(s) { 423*2abb3134SXin Li var obj = {}; 424*2abb3134SXin Li var parts = s.split('&'); 425*2abb3134SXin Li for (var i = 0; i < parts.length; ++i) { 426*2abb3134SXin Li if (parts[i].length === 0) { 427*2abb3134SXin Li continue; // quirk: ''.split('&') is [''] ? Should be a 0-length array. 428*2abb3134SXin Li } 429*2abb3134SXin Li var pair = parts[i].split('='); 430*2abb3134SXin Li obj[pair[0]] = pair[1]; // for now, assuming no = 431*2abb3134SXin Li } 432*2abb3134SXin Li return obj; 433*2abb3134SXin Li} 434*2abb3134SXin Li 435*2abb3134SXin Li// UrlHash Constructor. 436*2abb3134SXin Li// Args: 437*2abb3134SXin Li// hashStr: location.hash 438*2abb3134SXin Lifunction UrlHash(hashStr) { 439*2abb3134SXin Li this.reset(hashStr); 440*2abb3134SXin Li} 441*2abb3134SXin Li 442*2abb3134SXin LiUrlHash.prototype.reset = function(hashStr) { 443*2abb3134SXin Li var h = hashStr.substring(1); // without leading # 444*2abb3134SXin Li // Internal storage is string -> string 445*2abb3134SXin Li this.dict = _decode(h); 446*2abb3134SXin Li} 447*2abb3134SXin Li 448*2abb3134SXin LiUrlHash.prototype.set = function(name, value) { 449*2abb3134SXin Li this.dict[name] = value; 450*2abb3134SXin Li}; 451*2abb3134SXin Li 452*2abb3134SXin LiUrlHash.prototype.del = function(name) { 453*2abb3134SXin Li delete this.dict[name]; 454*2abb3134SXin Li}; 455*2abb3134SXin Li 456*2abb3134SXin LiUrlHash.prototype.get = function(name ) { 457*2abb3134SXin Li return this.dict[name]; 458*2abb3134SXin Li}; 459*2abb3134SXin Li 460*2abb3134SXin Li// e.g. Table states have keys which start with 't:'. 461*2abb3134SXin LiUrlHash.prototype.getKeysWithPrefix = function(prefix) { 462*2abb3134SXin Li var keys = []; 463*2abb3134SXin Li for (var name in this.dict) { 464*2abb3134SXin Li if (name.indexOf(prefix) === 0) { 465*2abb3134SXin Li keys.push(name); 466*2abb3134SXin Li } 467*2abb3134SXin Li } 468*2abb3134SXin Li return keys; 469*2abb3134SXin Li}; 470*2abb3134SXin Li 471*2abb3134SXin Li// Return a string reflecting internal key-value pairs. 472*2abb3134SXin LiUrlHash.prototype.encode = function() { 473*2abb3134SXin Li var parts = []; 474*2abb3134SXin Li for (var name in this.dict) { 475*2abb3134SXin Li var s = name; 476*2abb3134SXin Li s += '='; 477*2abb3134SXin Li var value = this.dict[name]; 478*2abb3134SXin Li s += encodeURIComponent(value); 479*2abb3134SXin Li parts.push(s); 480*2abb3134SXin Li } 481*2abb3134SXin Li return parts.join('&'); 482*2abb3134SXin Li}; 483