hashState.js (3338B)
1 function arrayItemsEqual(l1, l2) { 2 if (l1.length !== l2.length) { 3 return false; 4 } 5 for (var i = 0; i < l1.length; i++) { 6 if (l1[i] !== l2[i]) { 7 return false; 8 } 9 } 10 return true; 11 } 12 13 function parseHashParams(s) { 14 const args = {}, 15 i = s.indexOf('#'); 16 if (i >= 0) { 17 s = s.substr(i + 1).trim(); 18 let m, key, value; 19 for (let pair of s.split('&')) { 20 m = /^([^=]+?)(?:=(.*))?$/u.exec(pair); 21 if (m) { 22 [, key, value] = m; 23 if (value) { 24 value = value.split('/'); 25 } else { 26 value = []; 27 } 28 args[key] = value; 29 } 30 } 31 } 32 return args; 33 } 34 35 const hashState = { 36 _listeners: [], 37 _state: {}, 38 39 addEventListener: function(key, callback) { 40 for (let [k, c] of this._listeners) { 41 if (k === key && c === callback) { 42 return; 43 } 44 } 45 this._listeners.push([key, callback]); 46 }, 47 48 updateState: function(key, values) { 49 if (values) { 50 this._state[key] = values; 51 } else { 52 delete this._state[key]; 53 } 54 this._saveStateToHash(); 55 }, 56 57 getState: function(key) { 58 return this._state[key]; 59 }, 60 61 hasKey: function(key) { 62 return this._state.hasOwnProperty(key); 63 }, 64 65 _saveStateToHash: function() { 66 var stateItems = []; 67 for (let key of Object.keys(this._state)) { 68 if (this._state[key]) { 69 if (this._state[key].length) { 70 var values = this._state[key].join('/'); 71 stateItems.push(key + '=' + values); 72 } else { 73 stateItems.push(key); 74 } 75 } 76 } 77 const hash = stateItems.join('&'); 78 const href = `${location.origin}${location.pathname}${location.search}#${hash}`; 79 this._ignoreChanges = true; 80 if (href !== location.href) { 81 location.replace(href); 82 } 83 this._ignoreChanges = false; 84 }, 85 86 onHashChanged: function() { 87 if (this._ignoreChanges) { 88 return; 89 } 90 const newState = parseHashParams(location.hash); 91 const changedKeys = {}; 92 for (let key of Object.keys(newState)) { 93 if (!(key in this._state) || !arrayItemsEqual(newState[key], this._state[key])) { 94 changedKeys[key] = 1; 95 } 96 } 97 98 for (let key of Object.keys(this._state)) { 99 if (!(key in newState)) { 100 changedKeys[key] = 1; 101 } 102 } 103 104 for (let [key, callback] of this._listeners) { 105 if (key in changedKeys) { 106 callback(newState[key]); 107 } 108 } 109 this._state = newState; 110 } 111 }; 112 113 function bindHashStateReadOnly(key, target, once) { 114 function onChange() { 115 target(hashState.getState(key)); 116 hashState.updateState(key, null); 117 } 118 if (!once) { 119 hashState.addEventListener(key, onChange); 120 } 121 onChange(); 122 } 123 124 window.addEventListener('hashchange', hashState.onHashChanged.bind(hashState)); 125 hashState.onHashChanged(); 126 127 export {hashState, bindHashStateReadOnly, parseHashParams}; 128