xref: /aosp_15_r20/external/rappor/ui/table-lib.js (revision 2abb31345f6c95944768b5222a9a5ed3fc68cc00)
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">&nbsp;</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 ? '&#x25B4;' : '&#x25BE;';
329*2abb3134SXin Li    } else {
330*2abb3134SXin Li      span.innerHTML = '&nbsp;';  // 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