UNPKG

3.11 kBJavaScriptView Raw
1import {addEvt, removeEvt} from '../event';
2import {root} from '../root';
3
4const JSON = root.JSON;
5const location = root.location;
6const decodeURIComponent = root.decodeURIComponent;
7const encodeURIComponent = root.encodeURIComponent;
8
9/**
10 * Checks if browser has onhashchange event
11 */
12export const hasHashChange = () => {
13 let docMode = root.documentMode;
14 return ('onhashchange' in root) && (docMode === undefined || docMode > 7);
15};
16
17/**
18 * Manages state via URL hash changes
19 *
20 * @export
21 * @class Hash
22 */
23export class Hash {
24
25 /**
26 * Creates an instance of Hash
27 *
28 * @param {State} state Instance of State
29 */
30 constructor(state) {
31 /**
32 * State object
33 * @type {State}
34 */
35 this.state = state;
36
37 /**
38 * Cached URL hash
39 * @type {String} Hash string
40 * @private
41 */
42 this.lastHash = null;
43
44 /**
45 * Application event emitter instance
46 * @type {Emitter}
47 */
48 this.emitter = state.emitter;
49
50 /**
51 * Bound sync wrapper for future use
52 * @private
53 */
54 this.boundSync = null;
55 }
56
57 /**
58 * Initializes the Hash object
59 */
60 init() {
61 if (!hasHashChange()) {
62 return;
63 }
64
65 this.lastHash = location.hash;
66 //Store a bound sync wrapper
67 this.boundSync = this.sync.bind(this);
68 this.emitter.on(['state-changed'], (tf, state) => this.update(state));
69 this.emitter.on(['initialized'], this.boundSync);
70 addEvt(root, 'hashchange', this.boundSync);
71 }
72
73 /**
74 * Updates the URL hash based on a state change
75 *
76 * @param {State} state Instance of State
77 */
78 update(state) {
79 let hash = `#${encodeURIComponent(JSON.stringify(state))}`;
80 if (this.lastHash === hash) {
81 return;
82 }
83
84 location.hash = hash;
85 this.lastHash = hash;
86 }
87
88 /**
89 * Converts a URL hash into a state JSON object
90 *
91 * @param {String} hash URL hash fragment
92 * @returns {Object} JSON object
93 */
94 parse(hash) {
95 if (hash.indexOf('#') === -1) {
96 return null;
97 }
98 hash = hash.substr(1);
99 return JSON.parse(decodeURIComponent(hash));
100 }
101
102 /**
103 * Applies current hash state to features
104 */
105 sync() {
106 let state = this.parse(location.hash);
107 if (!state) {
108 return;
109 }
110 // override current state with persisted one and sync features
111 this.state.overrideAndSync(state);
112 }
113
114 /**
115 * Release Hash event subscriptions and clear fields
116 */
117 destroy() {
118 this.emitter.off(['state-changed'], (tf, state) => this.update(state));
119 this.emitter.off(['initialized'], this.boundSync);
120 removeEvt(root, 'hashchange', this.boundSync);
121
122 this.state = null;
123 this.lastHash = null;
124 this.emitter = null;
125 }
126}