1 | /**
|
2 | * Copyright 2014, Yahoo! Inc.
|
3 | * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
|
4 | */
|
5 | /*global window */
|
6 | ;
|
7 |
|
8 | /**
|
9 | * @class HistoryWithHash
|
10 | * @constructor
|
11 | * @param {Object} [options] The options object
|
12 | * @param {Window} [options.win=window] The window object
|
13 | * @param {Boolean} [options.useHashRoute] Whether to use hash for routing url.
|
14 | * If nothing specified, it will be evaluated as true if pushState feature
|
15 | * is not available in the window object's history object; false otherwise.
|
16 | * @param {String} [options.defaultHashRoute='/'] Only used when options.useHashRoute is enabled and
|
17 | the location url does not have any hash fragment.
|
18 | * @param {Object} [options.hashRouteTransformer] A custom transformer can be provided
|
19 | * to transform the hash to the desired syntax.
|
20 | * @param {Function} [options.hashRouteTransformer.transform] transforms hash route string
|
21 | * to custom syntax to be used in setting browser history state or url.
|
22 | * E.g. transforms from '/about/this/path' to 'about-this-path'
|
23 | * @param {Function} [options.hashRouteTransformer.reverse] reverse-transforms
|
24 | * hash route string of custom syntax back to standard url syntax.
|
25 | * E.g. transforms 'about-this-path' back to '/about/this/path'
|
26 | */
|
27 | function HistoryWithHash(options) {
|
28 | options = options || {};
|
29 | this.win = options.win || window;
|
30 |
|
31 | this._hasPushState = !!(this.win && this.win.history && this.win.history.pushState);
|
32 | this._popstateEvt = this._hasPushState ? 'popstate' : 'hashchange';
|
33 |
|
34 | // check whether to use hash for routing
|
35 | if (typeof options.useHashRoute === 'boolean') {
|
36 | this._useHashRoute = options.useHashRoute;
|
37 | } else {
|
38 | // default behavior is to check whether browser has pushState support
|
39 | this._useHashRoute = !this._hasPushState;
|
40 | }
|
41 | this._defaultHashRoute = options.defaultHashRoute || '/';
|
42 |
|
43 | // allow custom syntax for hash
|
44 | if (options.hashRouteTransformer) {
|
45 | this._hashRouteTransformer = options.hashRouteTransformer;
|
46 | }
|
47 | }
|
48 |
|
49 | HistoryWithHash.prototype = {
|
50 | /**
|
51 | * Add the given listener for 'popstate' event (fall backs to 'hashchange' event
|
52 | * for browsers don't support popstate event).
|
53 | * @method on
|
54 | * @param {Function} listener
|
55 | */
|
56 | on: function (listener) {
|
57 | this.win.addEventListener(this._popstateEvt, listener);
|
58 | },
|
59 |
|
60 | /**
|
61 | * Remove the given listener for 'popstate' event (fall backs to 'hashchange' event
|
62 | * for browsers don't support popstate event).
|
63 | * @method off
|
64 | * @param {Function} listener
|
65 | */
|
66 | off: function (listener) {
|
67 | this.win.removeEventListener(this._popstateEvt, listener);
|
68 | },
|
69 |
|
70 | /**
|
71 | * Returns the hash fragment in current window location.
|
72 | * @method _getHashRoute
|
73 | * @return {String} The hash fragment string (without the # prefix).
|
74 | * @private
|
75 | */
|
76 | _getHashRoute: function () {
|
77 | var hash = this.win.location.hash,
|
78 | transformer = this._hashRouteTransformer;
|
79 |
|
80 | // remove the '#' prefix
|
81 | hash = hash.substring(1) || this._defaultHashRoute;
|
82 |
|
83 | return (transformer && transformer.reverse) ? transformer.reverse(hash) : hash;
|
84 | },
|
85 |
|
86 | /**
|
87 | * @method getState
|
88 | * @return {Object|null} The state object in history
|
89 | */
|
90 | getState: function () {
|
91 | return (this.win.history && this.win.history.state) || null;
|
92 | },
|
93 |
|
94 | /**
|
95 | * Gets the path string (or hash fragment if the history object is
|
96 | * configured to use hash for routing),
|
97 | * including the pathname and search query (if it exists).
|
98 | * @method getUrl
|
99 | * @return {String} The url string that denotes current path and query
|
100 | */
|
101 | getUrl: function () {
|
102 | var location = this.win.location,
|
103 | path = location.pathname + location.search;
|
104 |
|
105 | if (this._useHashRoute) {
|
106 | return this._getHashRoute();
|
107 | }
|
108 | return path;
|
109 | },
|
110 |
|
111 | /**
|
112 | * Same as HTML5 pushState API, but with old browser support
|
113 | * @method pushState
|
114 | * @param {Object} state The state object
|
115 | * @param {String} title The title string
|
116 | * @param {String} url The new url
|
117 | */
|
118 | pushState: function (state, title, url) {
|
119 | var win = this.win,
|
120 | history = win.history,
|
121 | location = win.location,
|
122 | hash,
|
123 | transformer = this._hashRouteTransformer;
|
124 |
|
125 | if (this._useHashRoute) {
|
126 | hash = (transformer && transformer.transform) ? transformer.transform(url) : url;
|
127 | if (hash) {
|
128 | hash = '#' + hash;
|
129 | }
|
130 | if (this._hasPushState) {
|
131 | url = hash ? location.pathname + location.search + hash : null;
|
132 | history.pushState(state, title, url);
|
133 | } else if (hash) {
|
134 | location.hash = hash;
|
135 | }
|
136 | } else {
|
137 | if (this._hasPushState) {
|
138 | history.pushState.apply(history, arguments);
|
139 | } else if (url) {
|
140 | location.href = url;
|
141 | }
|
142 | }
|
143 | },
|
144 |
|
145 | /**
|
146 | * Same as HTML5 replaceState API, but with old browser support
|
147 | * @method replaceState
|
148 | * @param {Object} state The state object
|
149 | * @param {String} title The title string
|
150 | * @param {String} url The new url
|
151 | */
|
152 | replaceState: function (state, title, url) {
|
153 | var win = this.win,
|
154 | history = win.history,
|
155 | location = win.location,
|
156 | hash,
|
157 | transformer = this._hashRouteTransformer;
|
158 |
|
159 | if (this._useHashRoute) {
|
160 | hash = (transformer && transformer.transform) ? transformer.transform(url) : url;
|
161 | if (hash) {
|
162 | hash = '#' + hash;
|
163 | }
|
164 | if (this._hasPushState) {
|
165 | url = hash ? (location.pathname + location.search + hash) : null;
|
166 | history.replaceState(state, title, url);
|
167 | } else if (url) {
|
168 | url = location.pathname + location.search + hash;
|
169 | location.replace(url);
|
170 | }
|
171 | } else {
|
172 | if (this._hasPushState) {
|
173 | history.replaceState.apply(history, arguments);
|
174 | } else if (url) {
|
175 | location.replace(url);
|
176 | }
|
177 | }
|
178 | }
|
179 | };
|
180 |
|
181 | module.exports = HistoryWithHash;
|