2016-06-02 06:13:56 +02:00
|
|
|
import {addEvt, removeEvt} from '../event';
|
2016-05-08 07:26:52 +02:00
|
|
|
import {root} from '../root';
|
2016-03-19 15:10:59 +01:00
|
|
|
|
2016-05-08 07:26:52 +02:00
|
|
|
const JSON = root.JSON;
|
|
|
|
const location = root.location;
|
|
|
|
const decodeURIComponent = root.decodeURIComponent;
|
2016-08-22 18:31:00 +02:00
|
|
|
const encodeURIComponent = root.encodeURIComponent;
|
2016-03-20 04:51:08 +01:00
|
|
|
|
2016-07-23 10:34:19 +02:00
|
|
|
/**
|
|
|
|
* Checks if browser has onhashchange event
|
|
|
|
*/
|
2016-03-20 04:51:08 +01:00
|
|
|
export const hasHashChange = () => {
|
2016-05-15 04:56:12 +02:00
|
|
|
let docMode = root.documentMode;
|
2016-05-08 07:26:52 +02:00
|
|
|
return ('onhashchange' in root) && (docMode === undefined || docMode > 7);
|
2016-03-19 15:10:59 +01:00
|
|
|
};
|
|
|
|
|
2016-03-20 09:56:18 +01:00
|
|
|
/**
|
2016-07-23 10:34:19 +02:00
|
|
|
* Manages state via URL hash changes
|
2016-03-20 09:56:18 +01:00
|
|
|
*
|
|
|
|
* @export
|
|
|
|
* @class Hash
|
|
|
|
*/
|
2016-03-19 15:10:59 +01:00
|
|
|
export class Hash {
|
|
|
|
|
2016-03-20 09:56:18 +01:00
|
|
|
/**
|
|
|
|
* Creates an instance of Hash
|
|
|
|
*
|
|
|
|
* @param {State} state Instance of State
|
|
|
|
*/
|
|
|
|
constructor(state) {
|
2016-07-23 10:34:19 +02:00
|
|
|
/**
|
|
|
|
* State object
|
2016-08-08 09:22:25 +02:00
|
|
|
* @type {State}
|
2016-07-23 10:34:19 +02:00
|
|
|
*/
|
2016-03-19 15:10:59 +01:00
|
|
|
this.state = state;
|
2016-07-23 10:34:19 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Cached URL hash
|
|
|
|
* @type {String} Hash string
|
|
|
|
* @private
|
|
|
|
*/
|
2016-03-20 04:51:08 +01:00
|
|
|
this.lastHash = null;
|
2016-07-23 10:34:19 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Application event emitter instance
|
|
|
|
* @type {Emitter}
|
|
|
|
*/
|
2016-03-19 15:10:59 +01:00
|
|
|
this.emitter = state.emitter;
|
2016-07-23 10:34:19 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Bound sync wrapper for future use
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
this.boundSync = null;
|
2016-03-19 15:10:59 +01:00
|
|
|
}
|
|
|
|
|
2016-03-20 09:56:18 +01:00
|
|
|
/**
|
|
|
|
* Initializes the Hash object
|
|
|
|
*/
|
2016-03-19 15:10:59 +01:00
|
|
|
init() {
|
2016-03-20 09:56:18 +01:00
|
|
|
if (!hasHashChange()) {
|
2016-03-19 15:10:59 +01:00
|
|
|
return;
|
|
|
|
}
|
2016-03-20 04:51:08 +01:00
|
|
|
|
|
|
|
this.lastHash = location.hash;
|
2016-07-23 10:34:19 +02:00
|
|
|
//Store a bound sync wrapper
|
2016-07-15 06:03:45 +02:00
|
|
|
this.boundSync = this.sync.bind(this);
|
2016-03-19 15:10:59 +01:00
|
|
|
this.emitter.on(['state-changed'], (tf, state) => this.update(state));
|
2016-07-14 05:06:55 +02:00
|
|
|
this.emitter.on(['initialized'], this.boundSync);
|
|
|
|
addEvt(root, 'hashchange', this.boundSync);
|
2016-03-19 15:10:59 +01:00
|
|
|
}
|
|
|
|
|
2016-03-20 09:56:18 +01:00
|
|
|
/**
|
|
|
|
* Updates the URL hash based on a state change
|
|
|
|
*
|
|
|
|
* @param {State} state Instance of State
|
|
|
|
*/
|
2016-03-19 15:10:59 +01:00
|
|
|
update(state) {
|
2016-08-22 18:31:00 +02:00
|
|
|
let hash = `#${encodeURIComponent(JSON.stringify(state))}`;
|
2016-03-19 15:10:59 +01:00
|
|
|
if (this.lastHash === hash) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
location.hash = hash;
|
|
|
|
this.lastHash = hash;
|
|
|
|
}
|
|
|
|
|
2016-03-20 09:56:18 +01:00
|
|
|
/**
|
2019-02-09 14:27:55 +01:00
|
|
|
* Converts a URL hash into a JSON object
|
2016-03-20 09:56:18 +01:00
|
|
|
*
|
|
|
|
* @param {String} hash URL hash fragment
|
|
|
|
* @returns {Object} JSON object
|
|
|
|
*/
|
2016-03-19 15:10:59 +01:00
|
|
|
parse(hash) {
|
|
|
|
if (hash.indexOf('#') === -1) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
hash = hash.substr(1);
|
2016-03-21 08:36:46 +01:00
|
|
|
return JSON.parse(decodeURIComponent(hash));
|
2016-03-19 15:10:59 +01:00
|
|
|
}
|
|
|
|
|
2016-03-20 09:56:18 +01:00
|
|
|
/**
|
|
|
|
* Applies current hash state to features
|
|
|
|
*/
|
|
|
|
sync() {
|
2016-03-20 04:51:08 +01:00
|
|
|
let state = this.parse(location.hash);
|
2016-03-20 09:56:18 +01:00
|
|
|
if (!state) {
|
2016-03-19 15:10:59 +01:00
|
|
|
return;
|
|
|
|
}
|
2016-04-08 17:43:55 +02:00
|
|
|
// override current state with persisted one and sync features
|
|
|
|
this.state.overrideAndSync(state);
|
2016-03-19 15:10:59 +01:00
|
|
|
}
|
|
|
|
|
2016-03-20 09:56:18 +01:00
|
|
|
/**
|
2016-04-08 17:43:55 +02:00
|
|
|
* Release Hash event subscriptions and clear fields
|
2016-03-20 09:56:18 +01:00
|
|
|
*/
|
2016-03-19 15:10:59 +01:00
|
|
|
destroy() {
|
|
|
|
this.emitter.off(['state-changed'], (tf, state) => this.update(state));
|
2016-07-15 00:11:55 +02:00
|
|
|
this.emitter.off(['initialized'], this.boundSync);
|
|
|
|
removeEvt(root, 'hashchange', this.boundSync);
|
2016-03-20 12:09:08 +01:00
|
|
|
|
|
|
|
this.state = null;
|
|
|
|
this.lastHash = null;
|
|
|
|
this.emitter = null;
|
2016-03-19 15:10:59 +01:00
|
|
|
}
|
|
|
|
}
|