UNPKG

6.46 kBJavaScriptView Raw
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'use strict';
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 */
27function 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
49HistoryWithHash.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
181module.exports = HistoryWithHash;