This repository has been archived by the owner on Aug 1, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1k
/
html5history.js
342 lines (291 loc) · 9.97 KB
/
html5history.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview HTML5 based history implementation, compatible with
* goog.History.
*
* TODO(user): There should really be a history interface and multiple
* implementations.
*
*/
goog.provide('goog.history.Html5History');
goog.provide('goog.history.Html5History.TokenTransformer');
goog.require('goog.asserts');
goog.require('goog.events');
goog.require('goog.events.EventTarget');
goog.require('goog.events.EventType');
goog.require('goog.history.Event');
/**
* An implementation compatible with goog.History that uses the HTML5
* history APIs.
*
* @param {Window=} opt_win The window to listen/dispatch history events on.
* @param {goog.history.Html5History.TokenTransformer=} opt_transformer
* The token transformer that is used to create URL from the token
* when storing token without using hash fragment.
* @constructor
* @extends {goog.events.EventTarget}
* @final
*/
goog.history.Html5History = function(opt_win, opt_transformer) {
goog.events.EventTarget.call(this);
goog.asserts.assert(
goog.history.Html5History.isSupported(opt_win),
'HTML5 history is not supported.');
/**
* The window object to use for history tokens. Typically the top window.
* @type {Window}
* @private
*/
this.window_ = opt_win || window;
/**
* The token transformer that is used to create URL from the token
* when storing token without using hash fragment.
* @type {goog.history.Html5History.TokenTransformer}
* @private
*/
this.transformer_ = opt_transformer || null;
/**
* The fragment of the last navigation. Used to eliminate duplicate/redundant
* NAVIGATE events when a POPSTATE and HASHCHANGE event are triggered for the
* same navigation (e.g., back button click).
* @private {?string}
*/
this.lastFragment_ = null;
goog.events.listen(
this.window_, goog.events.EventType.POPSTATE, this.onHistoryEvent_, false,
this);
goog.events.listen(
this.window_, goog.events.EventType.HASHCHANGE, this.onHistoryEvent_,
false, this);
};
goog.inherits(goog.history.Html5History, goog.events.EventTarget);
/**
* Returns whether Html5History is supported.
* @param {Window=} opt_win Optional window to check.
* @return {boolean} Whether html5 history is supported.
*/
goog.history.Html5History.isSupported = function(opt_win) {
var win = opt_win || window;
return !!(win.history && win.history.pushState);
};
/**
* Status of when the object is active and dispatching events.
* @type {boolean}
* @private
*/
goog.history.Html5History.prototype.enabled_ = false;
/**
* Whether to use the fragment to store the token, defaults to true.
* @type {boolean}
* @private
*/
goog.history.Html5History.prototype.useFragment_ = true;
/**
* If useFragment is false the path will be used, the path prefix will be
* prepended to all tokens. Defaults to '/'.
* @type {string}
* @private
*/
goog.history.Html5History.prototype.pathPrefix_ = '/';
/**
* Starts or stops the History. When enabled, the History object
* will immediately fire an event for the current location. The caller can set
* up event listeners between the call to the constructor and the call to
* setEnabled.
*
* @param {boolean} enable Whether to enable history.
*/
goog.history.Html5History.prototype.setEnabled = function(enable) {
if (enable == this.enabled_) {
return;
}
this.enabled_ = enable;
if (enable) {
this.dispatchEvent(new goog.history.Event(this.getToken(), false));
}
};
/**
* Returns the current token.
* @return {string} The current token.
*/
goog.history.Html5History.prototype.getToken = function() {
if (this.useFragment_) {
return goog.asserts.assertString(this.getFragment_());
} else {
return this.transformer_ ?
this.transformer_.retrieveToken(
this.pathPrefix_, this.window_.location) :
this.window_.location.pathname.substr(this.pathPrefix_.length);
}
};
/**
* Sets the history state.
* @param {string} token The history state identifier.
* @param {string=} opt_title Optional title to associate with history entry.
*/
goog.history.Html5History.prototype.setToken = function(token, opt_title) {
if (token == this.getToken()) {
return;
}
// Per externs/gecko_dom.js document.title can be null.
this.window_.history.pushState(
null, opt_title || this.window_.document.title || '',
this.getUrl_(token));
this.dispatchEvent(new goog.history.Event(token, false));
};
/**
* Replaces the current history state without affecting the rest of the history
* stack.
* @param {string} token The history state identifier.
* @param {string=} opt_title Optional title to associate with history entry.
*/
goog.history.Html5History.prototype.replaceToken = function(token, opt_title) {
// Per externs/gecko_dom.js document.title can be null.
this.window_.history.replaceState(
null, opt_title || this.window_.document.title || '',
this.getUrl_(token));
this.dispatchEvent(new goog.history.Event(token, false));
};
/** @override */
goog.history.Html5History.prototype.disposeInternal = function() {
goog.events.unlisten(
this.window_, goog.events.EventType.POPSTATE, this.onHistoryEvent_, false,
this);
if (this.useFragment_) {
goog.events.unlisten(
this.window_, goog.events.EventType.HASHCHANGE, this.onHistoryEvent_,
false, this);
}
};
/**
* Sets whether to use the fragment to store tokens.
* @param {boolean} useFragment Whether to use the fragment.
*/
goog.history.Html5History.prototype.setUseFragment = function(useFragment) {
if (this.useFragment_ != useFragment) {
if (useFragment) {
goog.events.listen(
this.window_, goog.events.EventType.HASHCHANGE, this.onHistoryEvent_,
false, this);
} else {
goog.events.unlisten(
this.window_, goog.events.EventType.HASHCHANGE, this.onHistoryEvent_,
false, this);
}
this.useFragment_ = useFragment;
}
};
/**
* Sets the path prefix to use if storing tokens in the path. The path
* prefix should start and end with slash.
* @param {string} pathPrefix Sets the path prefix.
*/
goog.history.Html5History.prototype.setPathPrefix = function(pathPrefix) {
this.pathPrefix_ = pathPrefix;
};
/**
* Gets the path prefix.
* @return {string} The path prefix.
*/
goog.history.Html5History.prototype.getPathPrefix = function() {
return this.pathPrefix_;
};
/**
* Gets the current hash fragment, if useFragment_ is enabled.
* @return {?string} The hash fragment.
* @private
*/
goog.history.Html5History.prototype.getFragment_ = function() {
if (this.useFragment_) {
var loc = this.window_.location.href;
var index = loc.indexOf('#');
return index < 0 ? '' : loc.substring(index + 1);
} else {
return null;
}
};
/**
* Gets the URL to set when calling history.pushState
* @param {string} token The history token.
* @return {string} The URL.
* @private
*/
goog.history.Html5History.prototype.getUrl_ = function(token) {
if (this.useFragment_) {
return '#' + token;
} else {
return this.transformer_ ?
this.transformer_.createUrl(
token, this.pathPrefix_, this.window_.location) :
this.pathPrefix_ + token + this.window_.location.search;
}
};
/**
* Handles history events dispatched by the browser.
* @param {goog.events.BrowserEvent} e The browser event object.
* @private
*/
goog.history.Html5History.prototype.onHistoryEvent_ = function(e) {
if (this.enabled_) {
var fragment = this.getFragment_();
// Only fire NAVIGATE event if it's POPSTATE or if the fragment has changed
// without a POPSTATE event. The latter is an indication the browser doesn't
// support POPSTATE, and the event is a HASHCHANGE instead.
if (e.type == goog.events.EventType.POPSTATE ||
fragment != this.lastFragment_) {
this.lastFragment_ = fragment;
this.dispatchEvent(new goog.history.Event(this.getToken(), true));
}
}
};
/**
* A token transformer that can create a URL from a history
* token. This is used by `goog.history.Html5History` to create
* URL when storing token without the hash fragment.
*
* Given a `window.location` object containing the location
* created by `createUrl`, the token transformer allows
* retrieval of the token back via `retrieveToken`.
*
* @interface
*/
goog.history.Html5History.TokenTransformer = function() {};
/**
* Retrieves a history token given the path prefix and
* `window.location` object.
*
* @param {string} pathPrefix The path prefix to use when storing token
* in a path; always begin with a slash.
* @param {Location} location The `window.location` object.
* Treat this object as read-only.
* @return {string} token The history token.
*/
goog.history.Html5History.TokenTransformer.prototype.retrieveToken = function(
pathPrefix, location) {};
/**
* Creates a URL to be pushed into HTML5 history stack when storing
* token without using hash fragment.
*
* @param {string} token The history token.
* @param {string} pathPrefix The path prefix to use when storing token
* in a path; always begin with a slash.
* @param {Location} location The `window.location` object.
* Treat this object as read-only.
* @return {string} url The complete URL string from path onwards
* (without {@code protocol://host:port} part); must begin with a
* slash.
*/
goog.history.Html5History.TokenTransformer.prototype.createUrl = function(
token, pathPrefix, location) {};