historyjs-rails 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,78 +1,78 @@
1
- /**
2
- * History.js RightJS Adapter
3
- * @author Benjamin Arthur Lupton <contact@balupton.com>
4
- * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
5
- * @license New BSD License <http://creativecommons.org/licenses/BSD/>
6
- */
7
-
8
- // Closure
9
- (function(window,undefined){
10
- "use strict";
11
-
12
- // Localise Globals
13
- var
14
- History = window.History = window.History||{},
15
- document = window.document,
16
- RightJS = window.RightJS,
17
- $ = RightJS.$;
18
-
19
- // Check Existence
20
- if ( typeof History.Adapter !== 'undefined' ) {
21
- throw new Error('History.js Adapter has already been loaded...');
22
- }
23
-
24
- // Add the Adapter
25
- History.Adapter = {
26
- /**
27
- * History.Adapter.bind(el,event,callback)
28
- * @param {Element|Selector} el
29
- * @param {String} event - custom and standard events
30
- * @param {Function} callback
31
- * @return
32
- */
33
- bind: function(el,event,callback){
34
- $(el).on(event,callback);
35
- },
36
-
37
- /**
38
- * History.Adapter.trigger(el,event)
39
- * @param {Element|Selector} el
40
- * @param {String} event - custom and standard events
41
- * @param {Object} extraEventData - a object of extra event data
42
- * @return
43
- */
44
- trigger: function(el,event,extraEventData){
45
- $(el).fire(event,extraEventData);
46
- },
47
-
48
- /**
49
- * History.Adapter.extractEventData(key,event,extra)
50
- * @param {String} key - key for the event data to extract
51
- * @param {String} event - custom and standard events
52
- * @return {mixed}
53
- */
54
- extractEventData: function(key,event){
55
- // Right.js Native
56
- // Right.js Custom
57
- var result = (event && event._ && event._[key]) || undefined;
58
-
59
- // Return
60
- return result;
61
- },
62
-
63
- /**
64
- * History.Adapter.onDomLoad(callback)
65
- * @param {Function} callback
66
- * @return
67
- */
68
- onDomLoad: function(callback) {
69
- $(document).onReady(callback);
70
- }
71
- };
72
-
73
- // Try and Initialise History
74
- if ( typeof History.init !== 'undefined' ) {
75
- History.init();
76
- }
77
-
78
- })(window);
1
+ /**
2
+ * History.js RightJS Adapter
3
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
4
+ * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
5
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
6
+ */
7
+
8
+ // Closure
9
+ (function(window,undefined){
10
+ "use strict";
11
+
12
+ // Localise Globals
13
+ var
14
+ History = window.History = window.History||{},
15
+ document = window.document,
16
+ RightJS = window.RightJS,
17
+ $ = RightJS.$;
18
+
19
+ // Check Existence
20
+ if ( typeof History.Adapter !== 'undefined' ) {
21
+ throw new Error('History.js Adapter has already been loaded...');
22
+ }
23
+
24
+ // Add the Adapter
25
+ History.Adapter = {
26
+ /**
27
+ * History.Adapter.bind(el,event,callback)
28
+ * @param {Element|Selector} el
29
+ * @param {String} event - custom and standard events
30
+ * @param {Function} callback
31
+ * @return
32
+ */
33
+ bind: function(el,event,callback){
34
+ $(el).on(event,callback);
35
+ },
36
+
37
+ /**
38
+ * History.Adapter.trigger(el,event)
39
+ * @param {Element|Selector} el
40
+ * @param {String} event - custom and standard events
41
+ * @param {Object} extraEventData - a object of extra event data
42
+ * @return
43
+ */
44
+ trigger: function(el,event,extraEventData){
45
+ $(el).fire(event,extraEventData);
46
+ },
47
+
48
+ /**
49
+ * History.Adapter.extractEventData(key,event,extra)
50
+ * @param {String} key - key for the event data to extract
51
+ * @param {String} event - custom and standard events
52
+ * @return {mixed}
53
+ */
54
+ extractEventData: function(key,event){
55
+ // Right.js Native
56
+ // Right.js Custom
57
+ var result = (event && event._ && event._[key]) || undefined;
58
+
59
+ // Return
60
+ return result;
61
+ },
62
+
63
+ /**
64
+ * History.Adapter.onDomLoad(callback)
65
+ * @param {Function} callback
66
+ * @return
67
+ */
68
+ onDomLoad: function(callback) {
69
+ $(document).onReady(callback);
70
+ }
71
+ };
72
+
73
+ // Try and Initialise History
74
+ if ( typeof History.init !== 'undefined' ) {
75
+ History.init();
76
+ }
77
+
78
+ })(window);
@@ -1,74 +1,74 @@
1
- /**
2
- * History.js Zepto Adapter
3
- * @author Benjamin Arthur Lupton <contact@balupton.com>
4
- * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
5
- * @license New BSD License <http://creativecommons.org/licenses/BSD/>
6
- */
7
-
8
- // Closure
9
- (function(window,undefined){
10
- "use strict";
11
-
12
- // Localise Globals
13
- var
14
- History = window.History = window.History||{},
15
- Zepto = window.Zepto;
16
-
17
- // Check Existence
18
- if ( typeof History.Adapter !== 'undefined' ) {
19
- throw new Error('History.js Adapter has already been loaded...');
20
- }
21
-
22
- // Add the Adapter
23
- History.Adapter = {
24
- /**
25
- * History.Adapter.bind(el,event,callback)
26
- * @param {Element|string} el
27
- * @param {string} event - custom and standard events
28
- * @param {function} callback
29
- * @return {void}
30
- */
31
- bind: function(el,event,callback){
32
- new Zepto(el).bind(event,callback);
33
- },
34
-
35
- /**
36
- * History.Adapter.trigger(el,event)
37
- * @param {Element|string} el
38
- * @param {string} event - custom and standard events
39
- * @return {void}
40
- */
41
- trigger: function(el,event){
42
- new Zepto(el).trigger(event);
43
- },
44
-
45
- /**
46
- * History.Adapter.extractEventData(key,event,extra)
47
- * @param {string} key - key for the event data to extract
48
- * @param {string} event - custom and standard events
49
- * @return {mixed}
50
- */
51
- extractEventData: function(key,event){
52
- // Zepto Native
53
- var result = (event && event[key]) || undefined;
54
-
55
- // Return
56
- return result;
57
- },
58
-
59
- /**
60
- * History.Adapter.onDomLoad(callback)
61
- * @param {function} callback
62
- * @return {void}
63
- */
64
- onDomLoad: function(callback) {
65
- new Zepto(callback);
66
- }
67
- };
68
-
69
- // Try and Initialise History
70
- if ( typeof History.init !== 'undefined' ) {
71
- History.init();
72
- }
73
-
74
- })(window);
1
+ /**
2
+ * History.js Zepto Adapter
3
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
4
+ * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
5
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
6
+ */
7
+
8
+ // Closure
9
+ (function(window,undefined){
10
+ "use strict";
11
+
12
+ // Localise Globals
13
+ var
14
+ History = window.History = window.History||{},
15
+ Zepto = window.Zepto;
16
+
17
+ // Check Existence
18
+ if ( typeof History.Adapter !== 'undefined' ) {
19
+ throw new Error('History.js Adapter has already been loaded...');
20
+ }
21
+
22
+ // Add the Adapter
23
+ History.Adapter = {
24
+ /**
25
+ * History.Adapter.bind(el,event,callback)
26
+ * @param {Element|string} el
27
+ * @param {string} event - custom and standard events
28
+ * @param {function} callback
29
+ * @return {void}
30
+ */
31
+ bind: function(el,event,callback){
32
+ new Zepto(el).bind(event,callback);
33
+ },
34
+
35
+ /**
36
+ * History.Adapter.trigger(el,event)
37
+ * @param {Element|string} el
38
+ * @param {string} event - custom and standard events
39
+ * @return {void}
40
+ */
41
+ trigger: function(el,event){
42
+ new Zepto(el).trigger(event);
43
+ },
44
+
45
+ /**
46
+ * History.Adapter.extractEventData(key,event,extra)
47
+ * @param {string} key - key for the event data to extract
48
+ * @param {string} event - custom and standard events
49
+ * @return {mixed}
50
+ */
51
+ extractEventData: function(key,event){
52
+ // Zepto Native
53
+ var result = (event && event[key]) || undefined;
54
+
55
+ // Return
56
+ return result;
57
+ },
58
+
59
+ /**
60
+ * History.Adapter.onDomLoad(callback)
61
+ * @param {function} callback
62
+ * @return {void}
63
+ */
64
+ onDomLoad: function(callback) {
65
+ new Zepto(callback);
66
+ }
67
+ };
68
+
69
+ // Try and Initialise History
70
+ if ( typeof History.init !== 'undefined' ) {
71
+ History.init();
72
+ }
73
+
74
+ })(window);
@@ -1,1943 +1,1943 @@
1
- /**
2
- * History.js Core
3
- * @author Benjamin Arthur Lupton <contact@balupton.com>
4
- * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
5
- * @license New BSD License <http://creativecommons.org/licenses/BSD/>
6
- */
7
-
8
- (function(window,undefined){
9
- "use strict";
10
-
11
- // ========================================================================
12
- // Initialise
13
-
14
- // Localise Globals
15
- var
16
- console = window.console||undefined, // Prevent a JSLint complain
17
- document = window.document, // Make sure we are using the correct document
18
- navigator = window.navigator, // Make sure we are using the correct navigator
19
- sessionStorage = window.sessionStorage||false, // sessionStorage
20
- setTimeout = window.setTimeout,
21
- clearTimeout = window.clearTimeout,
22
- setInterval = window.setInterval,
23
- clearInterval = window.clearInterval,
24
- JSON = window.JSON,
25
- alert = window.alert,
26
- History = window.History = window.History||{}, // Public History Object
27
- history = window.history; // Old History Object
28
-
29
- // MooTools Compatibility
30
- JSON.stringify = JSON.stringify||JSON.encode;
31
- JSON.parse = JSON.parse||JSON.decode;
32
-
33
- // Check Existence
34
- if ( typeof History.init !== 'undefined' ) {
35
- throw new Error('History.js Core has already been loaded...');
36
- }
37
-
38
- // Initialise History
39
- History.init = function(){
40
- // Check Load Status of Adapter
41
- if ( typeof History.Adapter === 'undefined' ) {
42
- return false;
43
- }
44
-
45
- // Check Load Status of Core
46
- if ( typeof History.initCore !== 'undefined' ) {
47
- History.initCore();
48
- }
49
-
50
- // Check Load Status of HTML4 Support
51
- if ( typeof History.initHtml4 !== 'undefined' ) {
52
- History.initHtml4();
53
- }
54
-
55
- // Return true
56
- return true;
57
- };
58
-
59
-
60
- // ========================================================================
61
- // Initialise Core
62
-
63
- // Initialise Core
64
- History.initCore = function(){
65
- // Initialise
66
- if ( typeof History.initCore.initialized !== 'undefined' ) {
67
- // Already Loaded
68
- return false;
69
- }
70
- else {
71
- History.initCore.initialized = true;
72
- }
73
-
74
-
75
- // ====================================================================
76
- // Options
77
-
78
- /**
79
- * History.options
80
- * Configurable options
81
- */
82
- History.options = History.options||{};
83
-
84
- /**
85
- * History.options.hashChangeInterval
86
- * How long should the interval be before hashchange checks
87
- */
88
- History.options.hashChangeInterval = History.options.hashChangeInterval || 100;
89
-
90
- /**
91
- * History.options.safariPollInterval
92
- * How long should the interval be before safari poll checks
93
- */
94
- History.options.safariPollInterval = History.options.safariPollInterval || 500;
95
-
96
- /**
97
- * History.options.doubleCheckInterval
98
- * How long should the interval be before we perform a double check
99
- */
100
- History.options.doubleCheckInterval = History.options.doubleCheckInterval || 500;
101
-
102
- /**
103
- * History.options.storeInterval
104
- * How long should we wait between store calls
105
- */
106
- History.options.storeInterval = History.options.storeInterval || 1000;
107
-
108
- /**
109
- * History.options.busyDelay
110
- * How long should we wait between busy events
111
- */
112
- History.options.busyDelay = History.options.busyDelay || 250;
113
-
114
- /**
115
- * History.options.debug
116
- * If true will enable debug messages to be logged
117
- */
118
- History.options.debug = History.options.debug || false;
119
-
120
- /**
121
- * History.options.initialTitle
122
- * What is the title of the initial state
123
- */
124
- History.options.initialTitle = History.options.initialTitle || document.title;
125
-
126
-
127
- // ====================================================================
128
- // Interval record
129
-
130
- /**
131
- * History.intervalList
132
- * List of intervals set, to be cleared when document is unloaded.
133
- */
134
- History.intervalList = [];
135
-
136
- /**
137
- * History.clearAllIntervals
138
- * Clears all setInterval instances.
139
- */
140
- History.clearAllIntervals = function(){
141
- var i, il = History.intervalList;
142
- if (typeof il !== "undefined" && il !== null) {
143
- for (i = 0; i < il.length; i++) {
144
- clearInterval(il[i]);
145
- }
146
- History.intervalList = null;
147
- }
148
- };
149
-
150
-
151
- // ====================================================================
152
- // Debug
153
-
154
- /**
155
- * History.debug(message,...)
156
- * Logs the passed arguments if debug enabled
157
- */
158
- History.debug = function(){
159
- if ( (History.options.debug||false) ) {
160
- History.log.apply(History,arguments);
161
- }
162
- };
163
-
164
- /**
165
- * History.log(message,...)
166
- * Logs the passed arguments
167
- */
168
- History.log = function(){
169
- // Prepare
170
- var
171
- consoleExists = !(typeof console === 'undefined' || typeof console.log === 'undefined' || typeof console.log.apply === 'undefined'),
172
- textarea = document.getElementById('log'),
173
- message,
174
- i,n,
175
- args,arg
176
- ;
177
-
178
- // Write to Console
179
- if ( consoleExists ) {
180
- args = Array.prototype.slice.call(arguments);
181
- message = args.shift();
182
- if ( typeof console.debug !== 'undefined' ) {
183
- console.debug.apply(console,[message,args]);
184
- }
185
- else {
186
- console.log.apply(console,[message,args]);
187
- }
188
- }
189
- else {
190
- message = ("\n"+arguments[0]+"\n");
191
- }
192
-
193
- // Write to log
194
- for ( i=1,n=arguments.length; i<n; ++i ) {
195
- arg = arguments[i];
196
- if ( typeof arg === 'object' && typeof JSON !== 'undefined' ) {
197
- try {
198
- arg = JSON.stringify(arg);
199
- }
200
- catch ( Exception ) {
201
- // Recursive Object
202
- }
203
- }
204
- message += "\n"+arg+"\n";
205
- }
206
-
207
- // Textarea
208
- if ( textarea ) {
209
- textarea.value += message+"\n-----\n";
210
- textarea.scrollTop = textarea.scrollHeight - textarea.clientHeight;
211
- }
212
- // No Textarea, No Console
213
- else if ( !consoleExists ) {
214
- alert(message);
215
- }
216
-
217
- // Return true
218
- return true;
219
- };
220
-
221
-
222
- // ====================================================================
223
- // Emulated Status
224
-
225
- /**
226
- * History.getInternetExplorerMajorVersion()
227
- * Get's the major version of Internet Explorer
228
- * @return {integer}
229
- * @license Public Domain
230
- * @author Benjamin Arthur Lupton <contact@balupton.com>
231
- * @author James Padolsey <https://gist.github.com/527683>
232
- */
233
- History.getInternetExplorerMajorVersion = function(){
234
- var result = History.getInternetExplorerMajorVersion.cached =
235
- (typeof History.getInternetExplorerMajorVersion.cached !== 'undefined')
236
- ? History.getInternetExplorerMajorVersion.cached
237
- : (function(){
238
- var v = 3,
239
- div = document.createElement('div'),
240
- all = div.getElementsByTagName('i');
241
- while ( (div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->') && all[0] ) {}
242
- return (v > 4) ? v : false;
243
- })()
244
- ;
245
- return result;
246
- };
247
-
248
- /**
249
- * History.isInternetExplorer()
250
- * Are we using Internet Explorer?
251
- * @return {boolean}
252
- * @license Public Domain
253
- * @author Benjamin Arthur Lupton <contact@balupton.com>
254
- */
255
- History.isInternetExplorer = function(){
256
- var result =
257
- History.isInternetExplorer.cached =
258
- (typeof History.isInternetExplorer.cached !== 'undefined')
259
- ? History.isInternetExplorer.cached
260
- : Boolean(History.getInternetExplorerMajorVersion())
261
- ;
262
- return result;
263
- };
264
-
265
- /**
266
- * History.emulated
267
- * Which features require emulating?
268
- */
269
- History.emulated = {
270
- pushState: !Boolean(
271
- window.history && window.history.pushState && window.history.replaceState
272
- && !(
273
- (/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent) /* disable for versions of iOS before version 4.3 (8F190) */
274
- || (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent) /* disable for the mercury iOS browser, or at least older versions of the webkit engine */
275
- )
276
- ),
277
- hashChange: Boolean(
278
- !(('onhashchange' in window) || ('onhashchange' in document))
279
- ||
280
- (History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8)
281
- )
282
- };
283
-
284
- /**
285
- * History.enabled
286
- * Is History enabled?
287
- */
288
- History.enabled = !History.emulated.pushState;
289
-
290
- /**
291
- * History.bugs
292
- * Which bugs are present
293
- */
294
- History.bugs = {
295
- /**
296
- * Safari 5 and Safari iOS 4 fail to return to the correct state once a hash is replaced by a `replaceState` call
297
- * https://bugs.webkit.org/show_bug.cgi?id=56249
298
- */
299
- setHash: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
300
-
301
- /**
302
- * Safari 5 and Safari iOS 4 sometimes fail to apply the state change under busy conditions
303
- * https://bugs.webkit.org/show_bug.cgi?id=42940
304
- */
305
- safariPoll: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
306
-
307
- /**
308
- * MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function)
309
- */
310
- ieDoubleCheck: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8),
311
-
312
- /**
313
- * MSIE 6 requires the entire hash to be encoded for the hashes to trigger the onHashChange event
314
- */
315
- hashEscape: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 7)
316
- };
317
-
318
- /**
319
- * History.isEmptyObject(obj)
320
- * Checks to see if the Object is Empty
321
- * @param {Object} obj
322
- * @return {boolean}
323
- */
324
- History.isEmptyObject = function(obj) {
325
- for ( var name in obj ) {
326
- return false;
327
- }
328
- return true;
329
- };
330
-
331
- /**
332
- * History.cloneObject(obj)
333
- * Clones a object and eliminate all references to the original contexts
334
- * @param {Object} obj
335
- * @return {Object}
336
- */
337
- History.cloneObject = function(obj) {
338
- var hash,newObj;
339
- if ( obj ) {
340
- hash = JSON.stringify(obj);
341
- newObj = JSON.parse(hash);
342
- }
343
- else {
344
- newObj = {};
345
- }
346
- return newObj;
347
- };
348
-
349
-
350
- // ====================================================================
351
- // URL Helpers
352
-
353
- /**
354
- * History.getRootUrl()
355
- * Turns "http://mysite.com/dir/page.html?asd" into "http://mysite.com"
356
- * @return {String} rootUrl
357
- */
358
- History.getRootUrl = function(){
359
- // Create
360
- var rootUrl = document.location.protocol+'//'+(document.location.hostname||document.location.host);
361
- if ( document.location.port||false ) {
362
- rootUrl += ':'+document.location.port;
363
- }
364
- rootUrl += '/';
365
-
366
- // Return
367
- return rootUrl;
368
- };
369
-
370
- /**
371
- * History.getBaseHref()
372
- * Fetches the `href` attribute of the `<base href="...">` element if it exists
373
- * @return {String} baseHref
374
- */
375
- History.getBaseHref = function(){
376
- // Create
377
- var
378
- baseElements = document.getElementsByTagName('base'),
379
- baseElement = null,
380
- baseHref = '';
381
-
382
- // Test for Base Element
383
- if ( baseElements.length === 1 ) {
384
- // Prepare for Base Element
385
- baseElement = baseElements[0];
386
- baseHref = baseElement.href.replace(/[^\/]+$/,'');
387
- }
388
-
389
- // Adjust trailing slash
390
- baseHref = baseHref.replace(/\/+$/,'');
391
- if ( baseHref ) baseHref += '/';
392
-
393
- // Return
394
- return baseHref;
395
- };
396
-
397
- /**
398
- * History.getBaseUrl()
399
- * Fetches the baseHref or basePageUrl or rootUrl (whichever one exists first)
400
- * @return {String} baseUrl
401
- */
402
- History.getBaseUrl = function(){
403
- // Create
404
- var baseUrl = History.getBaseHref()||History.getBasePageUrl()||History.getRootUrl();
405
-
406
- // Return
407
- return baseUrl;
408
- };
409
-
410
- /**
411
- * History.getPageUrl()
412
- * Fetches the URL of the current page
413
- * @return {String} pageUrl
414
- */
415
- History.getPageUrl = function(){
416
- // Fetch
417
- var
418
- State = History.getState(false,false),
419
- stateUrl = (State||{}).url||document.location.href,
420
- pageUrl;
421
-
422
- // Create
423
- pageUrl = stateUrl.replace(/\/+$/,'').replace(/[^\/]+$/,function(part,index,string){
424
- return (/\./).test(part) ? part : part+'/';
425
- });
426
-
427
- // Return
428
- return pageUrl;
429
- };
430
-
431
- /**
432
- * History.getBasePageUrl()
433
- * Fetches the Url of the directory of the current page
434
- * @return {String} basePageUrl
435
- */
436
- History.getBasePageUrl = function(){
437
- // Create
438
- var basePageUrl = document.location.href.replace(/[#\?].*/,'').replace(/[^\/]+$/,function(part,index,string){
439
- return (/[^\/]$/).test(part) ? '' : part;
440
- }).replace(/\/+$/,'')+'/';
441
-
442
- // Return
443
- return basePageUrl;
444
- };
445
-
446
- /**
447
- * History.getFullUrl(url)
448
- * Ensures that we have an absolute URL and not a relative URL
449
- * @param {string} url
450
- * @param {Boolean} allowBaseHref
451
- * @return {string} fullUrl
452
- */
453
- History.getFullUrl = function(url,allowBaseHref){
454
- // Prepare
455
- var fullUrl = url, firstChar = url.substring(0,1);
456
- allowBaseHref = (typeof allowBaseHref === 'undefined') ? true : allowBaseHref;
457
-
458
- // Check
459
- if ( /[a-z]+\:\/\//.test(url) ) {
460
- // Full URL
461
- }
462
- else if ( firstChar === '/' ) {
463
- // Root URL
464
- fullUrl = History.getRootUrl()+url.replace(/^\/+/,'');
465
- }
466
- else if ( firstChar === '#' ) {
467
- // Anchor URL
468
- fullUrl = History.getPageUrl().replace(/#.*/,'')+url;
469
- }
470
- else if ( firstChar === '?' ) {
471
- // Query URL
472
- fullUrl = History.getPageUrl().replace(/[\?#].*/,'')+url;
473
- }
474
- else {
475
- // Relative URL
476
- if ( allowBaseHref ) {
477
- fullUrl = History.getBaseUrl()+url.replace(/^(\.\/)+/,'');
478
- } else {
479
- fullUrl = History.getBasePageUrl()+url.replace(/^(\.\/)+/,'');
480
- }
481
- // We have an if condition above as we do not want hashes
482
- // which are relative to the baseHref in our URLs
483
- // as if the baseHref changes, then all our bookmarks
484
- // would now point to different locations
485
- // whereas the basePageUrl will always stay the same
486
- }
487
-
488
- // Return
489
- return fullUrl.replace(/\#$/,'');
490
- };
491
-
492
- /**
493
- * History.getShortUrl(url)
494
- * Ensures that we have a relative URL and not a absolute URL
495
- * @param {string} url
496
- * @return {string} url
497
- */
498
- History.getShortUrl = function(url){
499
- // Prepare
500
- var shortUrl = url, baseUrl = History.getBaseUrl(), rootUrl = History.getRootUrl();
501
-
502
- // Trim baseUrl
503
- if ( History.emulated.pushState ) {
504
- // We are in a if statement as when pushState is not emulated
505
- // The actual url these short urls are relative to can change
506
- // So within the same session, we the url may end up somewhere different
507
- shortUrl = shortUrl.replace(baseUrl,'');
508
- }
509
-
510
- // Trim rootUrl
511
- shortUrl = shortUrl.replace(rootUrl,'/');
512
-
513
- // Ensure we can still detect it as a state
514
- if ( History.isTraditionalAnchor(shortUrl) ) {
515
- shortUrl = './'+shortUrl;
516
- }
517
-
518
- // Clean It
519
- shortUrl = shortUrl.replace(/^(\.\/)+/g,'./').replace(/\#$/,'');
520
-
521
- // Return
522
- return shortUrl;
523
- };
524
-
525
-
526
- // ====================================================================
527
- // State Storage
528
-
529
- /**
530
- * History.store
531
- * The store for all session specific data
532
- */
533
- History.store = {};
534
-
535
- /**
536
- * History.idToState
537
- * 1-1: State ID to State Object
538
- */
539
- History.idToState = History.idToState||{};
540
-
541
- /**
542
- * History.stateToId
543
- * 1-1: State String to State ID
544
- */
545
- History.stateToId = History.stateToId||{};
546
-
547
- /**
548
- * History.urlToId
549
- * 1-1: State URL to State ID
550
- */
551
- History.urlToId = History.urlToId||{};
552
-
553
- /**
554
- * History.storedStates
555
- * Store the states in an array
556
- */
557
- History.storedStates = History.storedStates||[];
558
-
559
- /**
560
- * History.savedStates
561
- * Saved the states in an array
562
- */
563
- History.savedStates = History.savedStates||[];
564
-
565
- /**
566
- * History.noramlizeStore()
567
- * Noramlize the store by adding necessary values
568
- */
569
- History.normalizeStore = function(){
570
- History.store.idToState = History.store.idToState||{};
571
- History.store.urlToId = History.store.urlToId||{};
572
- History.store.stateToId = History.store.stateToId||{};
573
- };
574
-
575
- /**
576
- * History.getState()
577
- * Get an object containing the data, title and url of the current state
578
- * @param {Boolean} friendly
579
- * @param {Boolean} create
580
- * @return {Object} State
581
- */
582
- History.getState = function(friendly,create){
583
- // Prepare
584
- if ( typeof friendly === 'undefined' ) { friendly = true; }
585
- if ( typeof create === 'undefined' ) { create = true; }
586
-
587
- // Fetch
588
- var State = History.getLastSavedState();
589
-
590
- // Create
591
- if ( !State && create ) {
592
- State = History.createStateObject();
593
- }
594
-
595
- // Adjust
596
- if ( friendly ) {
597
- State = History.cloneObject(State);
598
- State.url = State.cleanUrl||State.url;
599
- }
600
-
601
- // Return
602
- return State;
603
- };
604
-
605
- /**
606
- * History.getIdByState(State)
607
- * Gets a ID for a State
608
- * @param {State} newState
609
- * @return {String} id
610
- */
611
- History.getIdByState = function(newState){
612
-
613
- // Fetch ID
614
- var id = History.extractId(newState.url),
615
- str;
616
-
617
- if ( !id ) {
618
- // Find ID via State String
619
- str = History.getStateString(newState);
620
- if ( typeof History.stateToId[str] !== 'undefined' ) {
621
- id = History.stateToId[str];
622
- }
623
- else if ( typeof History.store.stateToId[str] !== 'undefined' ) {
624
- id = History.store.stateToId[str];
625
- }
626
- else {
627
- // Generate a new ID
628
- while ( true ) {
629
- id = (new Date()).getTime() + String(Math.random()).replace(/\D/g,'');
630
- if ( typeof History.idToState[id] === 'undefined' && typeof History.store.idToState[id] === 'undefined' ) {
631
- break;
632
- }
633
- }
634
-
635
- // Apply the new State to the ID
636
- History.stateToId[str] = id;
637
- History.idToState[id] = newState;
638
- }
639
- }
640
-
641
- // Return ID
642
- return id;
643
- };
644
-
645
- /**
646
- * History.normalizeState(State)
647
- * Expands a State Object
648
- * @param {object} State
649
- * @return {object}
650
- */
651
- History.normalizeState = function(oldState){
652
- // Variables
653
- var newState, dataNotEmpty;
654
-
655
- // Prepare
656
- if ( !oldState || (typeof oldState !== 'object') ) {
657
- oldState = {};
658
- }
659
-
660
- // Check
661
- if ( typeof oldState.normalized !== 'undefined' ) {
662
- return oldState;
663
- }
664
-
665
- // Adjust
666
- if ( !oldState.data || (typeof oldState.data !== 'object') ) {
667
- oldState.data = {};
668
- }
669
-
670
- // ----------------------------------------------------------------
671
-
672
- // Create
673
- newState = {};
674
- newState.normalized = true;
675
- newState.title = oldState.title||'';
676
- newState.url = History.getFullUrl(History.unescapeString(oldState.url||document.location.href));
677
- newState.hash = History.getShortUrl(newState.url);
678
- newState.data = History.cloneObject(oldState.data);
679
-
680
- // Fetch ID
681
- newState.id = History.getIdByState(newState);
682
-
683
- // ----------------------------------------------------------------
684
-
685
- // Clean the URL
686
- newState.cleanUrl = newState.url.replace(/\??\&_suid.*/,'');
687
- newState.url = newState.cleanUrl;
688
-
689
- // Check to see if we have more than just a url
690
- dataNotEmpty = !History.isEmptyObject(newState.data);
691
-
692
- // Apply
693
- if ( newState.title || dataNotEmpty ) {
694
- // Add ID to Hash
695
- newState.hash = History.getShortUrl(newState.url).replace(/\??\&_suid.*/,'');
696
- if ( !/\?/.test(newState.hash) ) {
697
- newState.hash += '?';
698
- }
699
- newState.hash += '&_suid='+newState.id;
700
- }
701
-
702
- // Create the Hashed URL
703
- newState.hashedUrl = History.getFullUrl(newState.hash);
704
-
705
- // ----------------------------------------------------------------
706
-
707
- // Update the URL if we have a duplicate
708
- if ( (History.emulated.pushState || History.bugs.safariPoll) && History.hasUrlDuplicate(newState) ) {
709
- newState.url = newState.hashedUrl;
710
- }
711
-
712
- // ----------------------------------------------------------------
713
-
714
- // Return
715
- return newState;
716
- };
717
-
718
- /**
719
- * History.createStateObject(data,title,url)
720
- * Creates a object based on the data, title and url state params
721
- * @param {object} data
722
- * @param {string} title
723
- * @param {string} url
724
- * @return {object}
725
- */
726
- History.createStateObject = function(data,title,url){
727
- // Hashify
728
- var State = {
729
- 'data': data,
730
- 'title': title,
731
- 'url': url
732
- };
733
-
734
- // Expand the State
735
- State = History.normalizeState(State);
736
-
737
- // Return object
738
- return State;
739
- };
740
-
741
- /**
742
- * History.getStateById(id)
743
- * Get a state by it's UID
744
- * @param {String} id
745
- */
746
- History.getStateById = function(id){
747
- // Prepare
748
- id = String(id);
749
-
750
- // Retrieve
751
- var State = History.idToState[id] || History.store.idToState[id] || undefined;
752
-
753
- // Return State
754
- return State;
755
- };
756
-
757
- /**
758
- * Get a State's String
759
- * @param {State} passedState
760
- */
761
- History.getStateString = function(passedState){
762
- // Prepare
763
- var State, cleanedState, str;
764
-
765
- // Fetch
766
- State = History.normalizeState(passedState);
767
-
768
- // Clean
769
- cleanedState = {
770
- data: State.data,
771
- title: passedState.title,
772
- url: passedState.url
773
- };
774
-
775
- // Fetch
776
- str = JSON.stringify(cleanedState);
777
-
778
- // Return
779
- return str;
780
- };
781
-
782
- /**
783
- * Get a State's ID
784
- * @param {State} passedState
785
- * @return {String} id
786
- */
787
- History.getStateId = function(passedState){
788
- // Prepare
789
- var State, id;
790
-
791
- // Fetch
792
- State = History.normalizeState(passedState);
793
-
794
- // Fetch
795
- id = State.id;
796
-
797
- // Return
798
- return id;
799
- };
800
-
801
- /**
802
- * History.getHashByState(State)
803
- * Creates a Hash for the State Object
804
- * @param {State} passedState
805
- * @return {String} hash
806
- */
807
- History.getHashByState = function(passedState){
808
- // Prepare
809
- var State, hash;
810
-
811
- // Fetch
812
- State = History.normalizeState(passedState);
813
-
814
- // Hash
815
- hash = State.hash;
816
-
817
- // Return
818
- return hash;
819
- };
820
-
821
- /**
822
- * History.extractId(url_or_hash)
823
- * Get a State ID by it's URL or Hash
824
- * @param {string} url_or_hash
825
- * @return {string} id
826
- */
827
- History.extractId = function ( url_or_hash ) {
828
- // Prepare
829
- var id,parts,url;
830
-
831
- // Extract
832
- parts = /(.*)\&_suid=([0-9]+)$/.exec(url_or_hash);
833
- url = parts ? (parts[1]||url_or_hash) : url_or_hash;
834
- id = parts ? String(parts[2]||'') : '';
835
-
836
- // Return
837
- return id||false;
838
- };
839
-
840
- /**
841
- * History.isTraditionalAnchor
842
- * Checks to see if the url is a traditional anchor or not
843
- * @param {String} url_or_hash
844
- * @return {Boolean}
845
- */
846
- History.isTraditionalAnchor = function(url_or_hash){
847
- // Check
848
- var isTraditional = !(/[\/\?\.]/.test(url_or_hash));
849
-
850
- // Return
851
- return isTraditional;
852
- };
853
-
854
- /**
855
- * History.extractState
856
- * Get a State by it's URL or Hash
857
- * @param {String} url_or_hash
858
- * @return {State|null}
859
- */
860
- History.extractState = function(url_or_hash,create){
861
- // Prepare
862
- var State = null, id, url;
863
- create = create||false;
864
-
865
- // Fetch SUID
866
- id = History.extractId(url_or_hash);
867
- if ( id ) {
868
- State = History.getStateById(id);
869
- }
870
-
871
- // Fetch SUID returned no State
872
- if ( !State ) {
873
- // Fetch URL
874
- url = History.getFullUrl(url_or_hash);
875
-
876
- // Check URL
877
- id = History.getIdByUrl(url)||false;
878
- if ( id ) {
879
- State = History.getStateById(id);
880
- }
881
-
882
- // Create State
883
- if ( !State && create && !History.isTraditionalAnchor(url_or_hash) ) {
884
- State = History.createStateObject(null,null,url);
885
- }
886
- }
887
-
888
- // Return
889
- return State;
890
- };
891
-
892
- /**
893
- * History.getIdByUrl()
894
- * Get a State ID by a State URL
895
- */
896
- History.getIdByUrl = function(url){
897
- // Fetch
898
- var id = History.urlToId[url] || History.store.urlToId[url] || undefined;
899
-
900
- // Return
901
- return id;
902
- };
903
-
904
- /**
905
- * History.getLastSavedState()
906
- * Get an object containing the data, title and url of the current state
907
- * @return {Object} State
908
- */
909
- History.getLastSavedState = function(){
910
- return History.savedStates[History.savedStates.length-1]||undefined;
911
- };
912
-
913
- /**
914
- * History.getLastStoredState()
915
- * Get an object containing the data, title and url of the current state
916
- * @return {Object} State
917
- */
918
- History.getLastStoredState = function(){
919
- return History.storedStates[History.storedStates.length-1]||undefined;
920
- };
921
-
922
- /**
923
- * History.hasUrlDuplicate
924
- * Checks if a Url will have a url conflict
925
- * @param {Object} newState
926
- * @return {Boolean} hasDuplicate
927
- */
928
- History.hasUrlDuplicate = function(newState) {
929
- // Prepare
930
- var hasDuplicate = false,
931
- oldState;
932
-
933
- // Fetch
934
- oldState = History.extractState(newState.url);
935
-
936
- // Check
937
- hasDuplicate = oldState && oldState.id !== newState.id;
938
-
939
- // Return
940
- return hasDuplicate;
941
- };
942
-
943
- /**
944
- * History.storeState
945
- * Store a State
946
- * @param {Object} newState
947
- * @return {Object} newState
948
- */
949
- History.storeState = function(newState){
950
- // Store the State
951
- History.urlToId[newState.url] = newState.id;
952
-
953
- // Push the State
954
- History.storedStates.push(History.cloneObject(newState));
955
-
956
- // Return newState
957
- return newState;
958
- };
959
-
960
- /**
961
- * History.isLastSavedState(newState)
962
- * Tests to see if the state is the last state
963
- * @param {Object} newState
964
- * @return {boolean} isLast
965
- */
966
- History.isLastSavedState = function(newState){
967
- // Prepare
968
- var isLast = false,
969
- newId, oldState, oldId;
970
-
971
- // Check
972
- if ( History.savedStates.length ) {
973
- newId = newState.id;
974
- oldState = History.getLastSavedState();
975
- oldId = oldState.id;
976
-
977
- // Check
978
- isLast = (newId === oldId);
979
- }
980
-
981
- // Return
982
- return isLast;
983
- };
984
-
985
- /**
986
- * History.saveState
987
- * Push a State
988
- * @param {Object} newState
989
- * @return {boolean} changed
990
- */
991
- History.saveState = function(newState){
992
- // Check Hash
993
- if ( History.isLastSavedState(newState) ) {
994
- return false;
995
- }
996
-
997
- // Push the State
998
- History.savedStates.push(History.cloneObject(newState));
999
-
1000
- // Return true
1001
- return true;
1002
- };
1003
-
1004
- /**
1005
- * History.getStateByIndex()
1006
- * Gets a state by the index
1007
- * @param {integer} index
1008
- * @return {Object}
1009
- */
1010
- History.getStateByIndex = function(index){
1011
- // Prepare
1012
- var State = null;
1013
-
1014
- // Handle
1015
- if ( typeof index === 'undefined' ) {
1016
- // Get the last inserted
1017
- State = History.savedStates[History.savedStates.length-1];
1018
- }
1019
- else if ( index < 0 ) {
1020
- // Get from the end
1021
- State = History.savedStates[History.savedStates.length+index];
1022
- }
1023
- else {
1024
- // Get from the beginning
1025
- State = History.savedStates[index];
1026
- }
1027
-
1028
- // Return State
1029
- return State;
1030
- };
1031
-
1032
-
1033
- // ====================================================================
1034
- // Hash Helpers
1035
-
1036
- /**
1037
- * History.getHash()
1038
- * Gets the current document hash
1039
- * @return {string}
1040
- */
1041
- History.getHash = function(){
1042
- var hash = History.unescapeHash(document.location.hash);
1043
- return hash;
1044
- };
1045
-
1046
- /**
1047
- * History.unescapeString()
1048
- * Unescape a string
1049
- * @param {String} str
1050
- * @return {string}
1051
- */
1052
- History.unescapeString = function(str){
1053
- // Prepare
1054
- var result = str,
1055
- tmp;
1056
-
1057
- // Unescape hash
1058
- while ( true ) {
1059
- tmp = window.unescape(result);
1060
- if ( tmp === result ) {
1061
- break;
1062
- }
1063
- result = tmp;
1064
- }
1065
-
1066
- // Return result
1067
- return result;
1068
- };
1069
-
1070
- /**
1071
- * History.unescapeHash()
1072
- * normalize and Unescape a Hash
1073
- * @param {String} hash
1074
- * @return {string}
1075
- */
1076
- History.unescapeHash = function(hash){
1077
- // Prepare
1078
- var result = History.normalizeHash(hash);
1079
-
1080
- // Unescape hash
1081
- result = History.unescapeString(result);
1082
-
1083
- // Return result
1084
- return result;
1085
- };
1086
-
1087
- /**
1088
- * History.normalizeHash()
1089
- * normalize a hash across browsers
1090
- * @return {string}
1091
- */
1092
- History.normalizeHash = function(hash){
1093
- // Prepare
1094
- var result = hash.replace(/[^#]*#/,'').replace(/#.*/, '');
1095
-
1096
- // Return result
1097
- return result;
1098
- };
1099
-
1100
- /**
1101
- * History.setHash(hash)
1102
- * Sets the document hash
1103
- * @param {string} hash
1104
- * @return {History}
1105
- */
1106
- History.setHash = function(hash,queue){
1107
- // Prepare
1108
- var adjustedHash, State, pageUrl;
1109
-
1110
- // Handle Queueing
1111
- if ( queue !== false && History.busy() ) {
1112
- // Wait + Push to Queue
1113
- //History.debug('History.setHash: we must wait', arguments);
1114
- History.pushQueue({
1115
- scope: History,
1116
- callback: History.setHash,
1117
- args: arguments,
1118
- queue: queue
1119
- });
1120
- return false;
1121
- }
1122
-
1123
- // Log
1124
- //History.debug('History.setHash: called',hash);
1125
-
1126
- // Prepare
1127
- adjustedHash = History.escapeHash(hash);
1128
-
1129
- // Make Busy + Continue
1130
- History.busy(true);
1131
-
1132
- // Check if hash is a state
1133
- State = History.extractState(hash,true);
1134
- if ( State && !History.emulated.pushState ) {
1135
- // Hash is a state so skip the setHash
1136
- //History.debug('History.setHash: Hash is a state so skipping the hash set with a direct pushState call',arguments);
1137
-
1138
- // PushState
1139
- History.pushState(State.data,State.title,State.url,false);
1140
- }
1141
- else if ( document.location.hash !== adjustedHash ) {
1142
- // Hash is a proper hash, so apply it
1143
-
1144
- // Handle browser bugs
1145
- if ( History.bugs.setHash ) {
1146
- // Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249
1147
-
1148
- // Fetch the base page
1149
- pageUrl = History.getPageUrl();
1150
-
1151
- // Safari hash apply
1152
- History.pushState(null,null,pageUrl+'#'+adjustedHash,false);
1153
- }
1154
- else {
1155
- // Normal hash apply
1156
- document.location.hash = adjustedHash;
1157
- }
1158
- }
1159
-
1160
- // Chain
1161
- return History;
1162
- };
1163
-
1164
- /**
1165
- * History.escape()
1166
- * normalize and Escape a Hash
1167
- * @return {string}
1168
- */
1169
- History.escapeHash = function(hash){
1170
- // Prepare
1171
- var result = History.normalizeHash(hash);
1172
-
1173
- // Escape hash
1174
- result = window.escape(result);
1175
-
1176
- // IE6 Escape Bug
1177
- if ( !History.bugs.hashEscape ) {
1178
- // Restore common parts
1179
- result = result
1180
- .replace(/\%21/g,'!')
1181
- .replace(/\%26/g,'&')
1182
- .replace(/\%3D/g,'=')
1183
- .replace(/\%3F/g,'?');
1184
- }
1185
-
1186
- // Return result
1187
- return result;
1188
- };
1189
-
1190
- /**
1191
- * History.getHashByUrl(url)
1192
- * Extracts the Hash from a URL
1193
- * @param {string} url
1194
- * @return {string} url
1195
- */
1196
- History.getHashByUrl = function(url){
1197
- // Extract the hash
1198
- var hash = String(url)
1199
- .replace(/([^#]*)#?([^#]*)#?(.*)/, '$2')
1200
- ;
1201
-
1202
- // Unescape hash
1203
- hash = History.unescapeHash(hash);
1204
-
1205
- // Return hash
1206
- return hash;
1207
- };
1208
-
1209
- /**
1210
- * History.setTitle(title)
1211
- * Applies the title to the document
1212
- * @param {State} newState
1213
- * @return {Boolean}
1214
- */
1215
- History.setTitle = function(newState){
1216
- // Prepare
1217
- var title = newState.title,
1218
- firstState;
1219
-
1220
- // Initial
1221
- if ( !title ) {
1222
- firstState = History.getStateByIndex(0);
1223
- if ( firstState && firstState.url === newState.url ) {
1224
- title = firstState.title||History.options.initialTitle;
1225
- }
1226
- }
1227
-
1228
- // Apply
1229
- try {
1230
- document.getElementsByTagName('title')[0].innerHTML = title.replace('<','&lt;').replace('>','&gt;').replace(' & ',' &amp; ');
1231
- }
1232
- catch ( Exception ) { }
1233
- document.title = title;
1234
-
1235
- // Chain
1236
- return History;
1237
- };
1238
-
1239
-
1240
- // ====================================================================
1241
- // Queueing
1242
-
1243
- /**
1244
- * History.queues
1245
- * The list of queues to use
1246
- * First In, First Out
1247
- */
1248
- History.queues = [];
1249
-
1250
- /**
1251
- * History.busy(value)
1252
- * @param {boolean} value [optional]
1253
- * @return {boolean} busy
1254
- */
1255
- History.busy = function(value){
1256
- // Apply
1257
- if ( typeof value !== 'undefined' ) {
1258
- //History.debug('History.busy: changing ['+(History.busy.flag||false)+'] to ['+(value||false)+']', History.queues.length);
1259
- History.busy.flag = value;
1260
- }
1261
- // Default
1262
- else if ( typeof History.busy.flag === 'undefined' ) {
1263
- History.busy.flag = false;
1264
- }
1265
-
1266
- // Queue
1267
- if ( !History.busy.flag ) {
1268
- // Execute the next item in the queue
1269
- clearTimeout(History.busy.timeout);
1270
- var fireNext = function(){
1271
- var i, queue, item;
1272
- if ( History.busy.flag ) return;
1273
- for ( i=History.queues.length-1; i >= 0; --i ) {
1274
- queue = History.queues[i];
1275
- if ( queue.length === 0 ) continue;
1276
- item = queue.shift();
1277
- History.fireQueueItem(item);
1278
- History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
1279
- }
1280
- };
1281
- History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
1282
- }
1283
-
1284
- // Return
1285
- return History.busy.flag;
1286
- };
1287
-
1288
- /**
1289
- * History.busy.flag
1290
- */
1291
- History.busy.flag = false;
1292
-
1293
- /**
1294
- * History.fireQueueItem(item)
1295
- * Fire a Queue Item
1296
- * @param {Object} item
1297
- * @return {Mixed} result
1298
- */
1299
- History.fireQueueItem = function(item){
1300
- return item.callback.apply(item.scope||History,item.args||[]);
1301
- };
1302
-
1303
- /**
1304
- * History.pushQueue(callback,args)
1305
- * Add an item to the queue
1306
- * @param {Object} item [scope,callback,args,queue]
1307
- */
1308
- History.pushQueue = function(item){
1309
- // Prepare the queue
1310
- History.queues[item.queue||0] = History.queues[item.queue||0]||[];
1311
-
1312
- // Add to the queue
1313
- History.queues[item.queue||0].push(item);
1314
-
1315
- // Chain
1316
- return History;
1317
- };
1318
-
1319
- /**
1320
- * History.queue (item,queue), (func,queue), (func), (item)
1321
- * Either firs the item now if not busy, or adds it to the queue
1322
- */
1323
- History.queue = function(item,queue){
1324
- // Prepare
1325
- if ( typeof item === 'function' ) {
1326
- item = {
1327
- callback: item
1328
- };
1329
- }
1330
- if ( typeof queue !== 'undefined' ) {
1331
- item.queue = queue;
1332
- }
1333
-
1334
- // Handle
1335
- if ( History.busy() ) {
1336
- History.pushQueue(item);
1337
- } else {
1338
- History.fireQueueItem(item);
1339
- }
1340
-
1341
- // Chain
1342
- return History;
1343
- };
1344
-
1345
- /**
1346
- * History.clearQueue()
1347
- * Clears the Queue
1348
- */
1349
- History.clearQueue = function(){
1350
- History.busy.flag = false;
1351
- History.queues = [];
1352
- return History;
1353
- };
1354
-
1355
-
1356
- // ====================================================================
1357
- // IE Bug Fix
1358
-
1359
- /**
1360
- * History.stateChanged
1361
- * States whether or not the state has changed since the last double check was initialised
1362
- */
1363
- History.stateChanged = false;
1364
-
1365
- /**
1366
- * History.doubleChecker
1367
- * Contains the timeout used for the double checks
1368
- */
1369
- History.doubleChecker = false;
1370
-
1371
- /**
1372
- * History.doubleCheckComplete()
1373
- * Complete a double check
1374
- * @return {History}
1375
- */
1376
- History.doubleCheckComplete = function(){
1377
- // Update
1378
- History.stateChanged = true;
1379
-
1380
- // Clear
1381
- History.doubleCheckClear();
1382
-
1383
- // Chain
1384
- return History;
1385
- };
1386
-
1387
- /**
1388
- * History.doubleCheckClear()
1389
- * Clear a double check
1390
- * @return {History}
1391
- */
1392
- History.doubleCheckClear = function(){
1393
- // Clear
1394
- if ( History.doubleChecker ) {
1395
- clearTimeout(History.doubleChecker);
1396
- History.doubleChecker = false;
1397
- }
1398
-
1399
- // Chain
1400
- return History;
1401
- };
1402
-
1403
- /**
1404
- * History.doubleCheck()
1405
- * Create a double check
1406
- * @return {History}
1407
- */
1408
- History.doubleCheck = function(tryAgain){
1409
- // Reset
1410
- History.stateChanged = false;
1411
- History.doubleCheckClear();
1412
-
1413
- // Fix IE6,IE7 bug where calling history.back or history.forward does not actually change the hash (whereas doing it manually does)
1414
- // Fix Safari 5 bug where sometimes the state does not change: https://bugs.webkit.org/show_bug.cgi?id=42940
1415
- if ( History.bugs.ieDoubleCheck ) {
1416
- // Apply Check
1417
- History.doubleChecker = setTimeout(
1418
- function(){
1419
- History.doubleCheckClear();
1420
- if ( !History.stateChanged ) {
1421
- //History.debug('History.doubleCheck: State has not yet changed, trying again', arguments);
1422
- // Re-Attempt
1423
- tryAgain();
1424
- }
1425
- return true;
1426
- },
1427
- History.options.doubleCheckInterval
1428
- );
1429
- }
1430
-
1431
- // Chain
1432
- return History;
1433
- };
1434
-
1435
-
1436
- // ====================================================================
1437
- // Safari Bug Fix
1438
-
1439
- /**
1440
- * History.safariStatePoll()
1441
- * Poll the current state
1442
- * @return {History}
1443
- */
1444
- History.safariStatePoll = function(){
1445
- // Poll the URL
1446
-
1447
- // Get the Last State which has the new URL
1448
- var
1449
- urlState = History.extractState(document.location.href),
1450
- newState;
1451
-
1452
- // Check for a difference
1453
- if ( !History.isLastSavedState(urlState) ) {
1454
- newState = urlState;
1455
- }
1456
- else {
1457
- return;
1458
- }
1459
-
1460
- // Check if we have a state with that url
1461
- // If not create it
1462
- if ( !newState ) {
1463
- //History.debug('History.safariStatePoll: new');
1464
- newState = History.createStateObject();
1465
- }
1466
-
1467
- // Apply the New State
1468
- //History.debug('History.safariStatePoll: trigger');
1469
- History.Adapter.trigger(window,'popstate');
1470
-
1471
- // Chain
1472
- return History;
1473
- };
1474
-
1475
-
1476
- // ====================================================================
1477
- // State Aliases
1478
-
1479
- /**
1480
- * History.back(queue)
1481
- * Send the browser history back one item
1482
- * @param {Integer} queue [optional]
1483
- */
1484
- History.back = function(queue){
1485
- //History.debug('History.back: called', arguments);
1486
-
1487
- // Handle Queueing
1488
- if ( queue !== false && History.busy() ) {
1489
- // Wait + Push to Queue
1490
- //History.debug('History.back: we must wait', arguments);
1491
- History.pushQueue({
1492
- scope: History,
1493
- callback: History.back,
1494
- args: arguments,
1495
- queue: queue
1496
- });
1497
- return false;
1498
- }
1499
-
1500
- // Make Busy + Continue
1501
- History.busy(true);
1502
-
1503
- // Fix certain browser bugs that prevent the state from changing
1504
- History.doubleCheck(function(){
1505
- History.back(false);
1506
- });
1507
-
1508
- // Go back
1509
- history.go(-1);
1510
-
1511
- // End back closure
1512
- return true;
1513
- };
1514
-
1515
- /**
1516
- * History.forward(queue)
1517
- * Send the browser history forward one item
1518
- * @param {Integer} queue [optional]
1519
- */
1520
- History.forward = function(queue){
1521
- //History.debug('History.forward: called', arguments);
1522
-
1523
- // Handle Queueing
1524
- if ( queue !== false && History.busy() ) {
1525
- // Wait + Push to Queue
1526
- //History.debug('History.forward: we must wait', arguments);
1527
- History.pushQueue({
1528
- scope: History,
1529
- callback: History.forward,
1530
- args: arguments,
1531
- queue: queue
1532
- });
1533
- return false;
1534
- }
1535
-
1536
- // Make Busy + Continue
1537
- History.busy(true);
1538
-
1539
- // Fix certain browser bugs that prevent the state from changing
1540
- History.doubleCheck(function(){
1541
- History.forward(false);
1542
- });
1543
-
1544
- // Go forward
1545
- history.go(1);
1546
-
1547
- // End forward closure
1548
- return true;
1549
- };
1550
-
1551
- /**
1552
- * History.go(index,queue)
1553
- * Send the browser history back or forward index times
1554
- * @param {Integer} queue [optional]
1555
- */
1556
- History.go = function(index,queue){
1557
- //History.debug('History.go: called', arguments);
1558
-
1559
- // Prepare
1560
- var i;
1561
-
1562
- // Handle
1563
- if ( index > 0 ) {
1564
- // Forward
1565
- for ( i=1; i<=index; ++i ) {
1566
- History.forward(queue);
1567
- }
1568
- }
1569
- else if ( index < 0 ) {
1570
- // Backward
1571
- for ( i=-1; i>=index; --i ) {
1572
- History.back(queue);
1573
- }
1574
- }
1575
- else {
1576
- throw new Error('History.go: History.go requires a positive or negative integer passed.');
1577
- }
1578
-
1579
- // Chain
1580
- return History;
1581
- };
1582
-
1583
-
1584
- // ====================================================================
1585
- // HTML5 State Support
1586
-
1587
- // Non-Native pushState Implementation
1588
- if ( History.emulated.pushState ) {
1589
- /*
1590
- * Provide Skeleton for HTML4 Browsers
1591
- */
1592
-
1593
- // Prepare
1594
- var emptyFunction = function(){};
1595
- History.pushState = History.pushState||emptyFunction;
1596
- History.replaceState = History.replaceState||emptyFunction;
1597
- } // History.emulated.pushState
1598
-
1599
- // Native pushState Implementation
1600
- else {
1601
- /*
1602
- * Use native HTML5 History API Implementation
1603
- */
1604
-
1605
- /**
1606
- * History.onPopState(event,extra)
1607
- * Refresh the Current State
1608
- */
1609
- History.onPopState = function(event,extra){
1610
- // Prepare
1611
- var stateId = false, newState = false, currentHash, currentState;
1612
-
1613
- // Reset the double check
1614
- History.doubleCheckComplete();
1615
-
1616
- // Check for a Hash, and handle apporiatly
1617
- currentHash = History.getHash();
1618
- if ( currentHash ) {
1619
- // Expand Hash
1620
- currentState = History.extractState(currentHash||document.location.href,true);
1621
- if ( currentState ) {
1622
- // We were able to parse it, it must be a State!
1623
- // Let's forward to replaceState
1624
- //History.debug('History.onPopState: state anchor', currentHash, currentState);
1625
- History.replaceState(currentState.data, currentState.title, currentState.url, false);
1626
- }
1627
- else {
1628
- // Traditional Anchor
1629
- //History.debug('History.onPopState: traditional anchor', currentHash);
1630
- History.Adapter.trigger(window,'anchorchange');
1631
- History.busy(false);
1632
- }
1633
-
1634
- // We don't care for hashes
1635
- History.expectedStateId = false;
1636
- return false;
1637
- }
1638
-
1639
- // Ensure
1640
- stateId = History.Adapter.extractEventData('state',event,extra) || false;
1641
-
1642
- // Fetch State
1643
- if ( stateId ) {
1644
- // Vanilla: Back/forward button was used
1645
- newState = History.getStateById(stateId);
1646
- }
1647
- else if ( History.expectedStateId ) {
1648
- // Vanilla: A new state was pushed, and popstate was called manually
1649
- newState = History.getStateById(History.expectedStateId);
1650
- }
1651
- else {
1652
- // Initial State
1653
- newState = History.extractState(document.location.href);
1654
- }
1655
-
1656
- // The State did not exist in our store
1657
- if ( !newState ) {
1658
- // Regenerate the State
1659
- newState = History.createStateObject(null,null,document.location.href);
1660
- }
1661
-
1662
- // Clean
1663
- History.expectedStateId = false;
1664
-
1665
- // Check if we are the same state
1666
- if ( History.isLastSavedState(newState) ) {
1667
- // There has been no change (just the page's hash has finally propagated)
1668
- //History.debug('History.onPopState: no change', newState, History.savedStates);
1669
- History.busy(false);
1670
- return false;
1671
- }
1672
-
1673
- // Store the State
1674
- History.storeState(newState);
1675
- History.saveState(newState);
1676
-
1677
- // Force update of the title
1678
- History.setTitle(newState);
1679
-
1680
- // Fire Our Event
1681
- History.Adapter.trigger(window,'statechange');
1682
- History.busy(false);
1683
-
1684
- // Return true
1685
- return true;
1686
- };
1687
- History.Adapter.bind(window,'popstate',History.onPopState);
1688
-
1689
- /**
1690
- * History.pushState(data,title,url)
1691
- * Add a new State to the history object, become it, and trigger onpopstate
1692
- * We have to trigger for HTML4 compatibility
1693
- * @param {object} data
1694
- * @param {string} title
1695
- * @param {string} url
1696
- * @return {true}
1697
- */
1698
- History.pushState = function(data,title,url,queue){
1699
- //History.debug('History.pushState: called', arguments);
1700
-
1701
- // Check the State
1702
- if ( History.getHashByUrl(url) && History.emulated.pushState ) {
1703
- throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
1704
- }
1705
-
1706
- // Handle Queueing
1707
- if ( queue !== false && History.busy() ) {
1708
- // Wait + Push to Queue
1709
- //History.debug('History.pushState: we must wait', arguments);
1710
- History.pushQueue({
1711
- scope: History,
1712
- callback: History.pushState,
1713
- args: arguments,
1714
- queue: queue
1715
- });
1716
- return false;
1717
- }
1718
-
1719
- // Make Busy + Continue
1720
- History.busy(true);
1721
-
1722
- // Create the newState
1723
- var newState = History.createStateObject(data,title,url);
1724
-
1725
- // Check it
1726
- if ( History.isLastSavedState(newState) ) {
1727
- // Won't be a change
1728
- History.busy(false);
1729
- }
1730
- else {
1731
- // Store the newState
1732
- History.storeState(newState);
1733
- History.expectedStateId = newState.id;
1734
-
1735
- // Push the newState
1736
- history.pushState(newState.id,newState.title,newState.url);
1737
-
1738
- // Fire HTML5 Event
1739
- History.Adapter.trigger(window,'popstate');
1740
- }
1741
-
1742
- // End pushState closure
1743
- return true;
1744
- };
1745
-
1746
- /**
1747
- * History.replaceState(data,title,url)
1748
- * Replace the State and trigger onpopstate
1749
- * We have to trigger for HTML4 compatibility
1750
- * @param {object} data
1751
- * @param {string} title
1752
- * @param {string} url
1753
- * @return {true}
1754
- */
1755
- History.replaceState = function(data,title,url,queue){
1756
- //History.debug('History.replaceState: called', arguments);
1757
-
1758
- // Check the State
1759
- if ( History.getHashByUrl(url) && History.emulated.pushState ) {
1760
- throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
1761
- }
1762
-
1763
- // Handle Queueing
1764
- if ( queue !== false && History.busy() ) {
1765
- // Wait + Push to Queue
1766
- //History.debug('History.replaceState: we must wait', arguments);
1767
- History.pushQueue({
1768
- scope: History,
1769
- callback: History.replaceState,
1770
- args: arguments,
1771
- queue: queue
1772
- });
1773
- return false;
1774
- }
1775
-
1776
- // Make Busy + Continue
1777
- History.busy(true);
1778
-
1779
- // Create the newState
1780
- var newState = History.createStateObject(data,title,url);
1781
-
1782
- // Check it
1783
- if ( History.isLastSavedState(newState) ) {
1784
- // Won't be a change
1785
- History.busy(false);
1786
- }
1787
- else {
1788
- // Store the newState
1789
- History.storeState(newState);
1790
- History.expectedStateId = newState.id;
1791
-
1792
- // Push the newState
1793
- history.replaceState(newState.id,newState.title,newState.url);
1794
-
1795
- // Fire HTML5 Event
1796
- History.Adapter.trigger(window,'popstate');
1797
- }
1798
-
1799
- // End replaceState closure
1800
- return true;
1801
- };
1802
-
1803
- } // !History.emulated.pushState
1804
-
1805
-
1806
- // ====================================================================
1807
- // Initialise
1808
-
1809
- /**
1810
- * Load the Store
1811
- */
1812
- if ( sessionStorage ) {
1813
- // Fetch
1814
- try {
1815
- History.store = JSON.parse(sessionStorage.getItem('History.store'))||{};
1816
- }
1817
- catch ( err ) {
1818
- History.store = {};
1819
- }
1820
-
1821
- // Normalize
1822
- History.normalizeStore();
1823
- }
1824
- else {
1825
- // Default Load
1826
- History.store = {};
1827
- History.normalizeStore();
1828
- }
1829
-
1830
- /**
1831
- * Clear Intervals on exit to prevent memory leaks
1832
- */
1833
- History.Adapter.bind(window,"beforeunload",History.clearAllIntervals);
1834
- History.Adapter.bind(window,"unload",History.clearAllIntervals);
1835
-
1836
- /**
1837
- * Create the initial State
1838
- */
1839
- History.saveState(History.storeState(History.extractState(document.location.href,true)));
1840
-
1841
- /**
1842
- * Bind for Saving Store
1843
- */
1844
- if ( sessionStorage ) {
1845
- // When the page is closed
1846
- History.onUnload = function(){
1847
- // Prepare
1848
- var currentStore, item;
1849
-
1850
- // Fetch
1851
- try {
1852
- currentStore = JSON.parse(sessionStorage.getItem('History.store'))||{};
1853
- }
1854
- catch ( err ) {
1855
- currentStore = {};
1856
- }
1857
-
1858
- // Ensure
1859
- currentStore.idToState = currentStore.idToState || {};
1860
- currentStore.urlToId = currentStore.urlToId || {};
1861
- currentStore.stateToId = currentStore.stateToId || {};
1862
-
1863
- // Sync
1864
- for ( item in History.idToState ) {
1865
- if ( !History.idToState.hasOwnProperty(item) ) {
1866
- continue;
1867
- }
1868
- currentStore.idToState[item] = History.idToState[item];
1869
- }
1870
- for ( item in History.urlToId ) {
1871
- if ( !History.urlToId.hasOwnProperty(item) ) {
1872
- continue;
1873
- }
1874
- currentStore.urlToId[item] = History.urlToId[item];
1875
- }
1876
- for ( item in History.stateToId ) {
1877
- if ( !History.stateToId.hasOwnProperty(item) ) {
1878
- continue;
1879
- }
1880
- currentStore.stateToId[item] = History.stateToId[item];
1881
- }
1882
-
1883
- // Update
1884
- History.store = currentStore;
1885
- History.normalizeStore();
1886
-
1887
- // Store
1888
- sessionStorage.setItem('History.store',JSON.stringify(currentStore));
1889
- };
1890
-
1891
- // For Internet Explorer
1892
- History.intervalList.push(setInterval(History.onUnload,History.options.storeInterval));
1893
-
1894
- // For Other Browsers
1895
- History.Adapter.bind(window,'beforeunload',History.onUnload);
1896
- History.Adapter.bind(window,'unload',History.onUnload);
1897
-
1898
- // Both are enabled for consistency
1899
- }
1900
-
1901
- // Non-Native pushState Implementation
1902
- if ( !History.emulated.pushState ) {
1903
- // Be aware, the following is only for native pushState implementations
1904
- // If you are wanting to include something for all browsers
1905
- // Then include it above this if block
1906
-
1907
- /**
1908
- * Setup Safari Fix
1909
- */
1910
- if ( History.bugs.safariPoll ) {
1911
- History.intervalList.push(setInterval(History.safariStatePoll, History.options.safariPollInterval));
1912
- }
1913
-
1914
- /**
1915
- * Ensure Cross Browser Compatibility
1916
- */
1917
- if ( navigator.vendor === 'Apple Computer, Inc.' || (navigator.appCodeName||'') === 'Mozilla' ) {
1918
- /**
1919
- * Fix Safari HashChange Issue
1920
- */
1921
-
1922
- // Setup Alias
1923
- History.Adapter.bind(window,'hashchange',function(){
1924
- History.Adapter.trigger(window,'popstate');
1925
- });
1926
-
1927
- // Initialise Alias
1928
- if ( History.getHash() ) {
1929
- History.Adapter.onDomLoad(function(){
1930
- History.Adapter.trigger(window,'hashchange');
1931
- });
1932
- }
1933
- }
1934
-
1935
- } // !History.emulated.pushState
1936
-
1937
-
1938
- }; // History.initCore
1939
-
1940
- // Try and Initialise History
1941
- History.init();
1942
-
1943
- })(window);
1
+ /**
2
+ * History.js Core
3
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
4
+ * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
5
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
6
+ */
7
+
8
+ (function(window,undefined){
9
+ "use strict";
10
+
11
+ // ========================================================================
12
+ // Initialise
13
+
14
+ // Localise Globals
15
+ var
16
+ console = window.console||undefined, // Prevent a JSLint complain
17
+ document = window.document, // Make sure we are using the correct document
18
+ navigator = window.navigator, // Make sure we are using the correct navigator
19
+ sessionStorage = window.sessionStorage||false, // sessionStorage
20
+ setTimeout = window.setTimeout,
21
+ clearTimeout = window.clearTimeout,
22
+ setInterval = window.setInterval,
23
+ clearInterval = window.clearInterval,
24
+ JSON = window.JSON,
25
+ alert = window.alert,
26
+ History = window.History = window.History||{}, // Public History Object
27
+ history = window.history; // Old History Object
28
+
29
+ // MooTools Compatibility
30
+ JSON.stringify = JSON.stringify||JSON.encode;
31
+ JSON.parse = JSON.parse||JSON.decode;
32
+
33
+ // Check Existence
34
+ if ( typeof History.init !== 'undefined' ) {
35
+ throw new Error('History.js Core has already been loaded...');
36
+ }
37
+
38
+ // Initialise History
39
+ History.init = function(){
40
+ // Check Load Status of Adapter
41
+ if ( typeof History.Adapter === 'undefined' ) {
42
+ return false;
43
+ }
44
+
45
+ // Check Load Status of Core
46
+ if ( typeof History.initCore !== 'undefined' ) {
47
+ History.initCore();
48
+ }
49
+
50
+ // Check Load Status of HTML4 Support
51
+ if ( typeof History.initHtml4 !== 'undefined' ) {
52
+ History.initHtml4();
53
+ }
54
+
55
+ // Return true
56
+ return true;
57
+ };
58
+
59
+
60
+ // ========================================================================
61
+ // Initialise Core
62
+
63
+ // Initialise Core
64
+ History.initCore = function(){
65
+ // Initialise
66
+ if ( typeof History.initCore.initialized !== 'undefined' ) {
67
+ // Already Loaded
68
+ return false;
69
+ }
70
+ else {
71
+ History.initCore.initialized = true;
72
+ }
73
+
74
+
75
+ // ====================================================================
76
+ // Options
77
+
78
+ /**
79
+ * History.options
80
+ * Configurable options
81
+ */
82
+ History.options = History.options||{};
83
+
84
+ /**
85
+ * History.options.hashChangeInterval
86
+ * How long should the interval be before hashchange checks
87
+ */
88
+ History.options.hashChangeInterval = History.options.hashChangeInterval || 100;
89
+
90
+ /**
91
+ * History.options.safariPollInterval
92
+ * How long should the interval be before safari poll checks
93
+ */
94
+ History.options.safariPollInterval = History.options.safariPollInterval || 500;
95
+
96
+ /**
97
+ * History.options.doubleCheckInterval
98
+ * How long should the interval be before we perform a double check
99
+ */
100
+ History.options.doubleCheckInterval = History.options.doubleCheckInterval || 500;
101
+
102
+ /**
103
+ * History.options.storeInterval
104
+ * How long should we wait between store calls
105
+ */
106
+ History.options.storeInterval = History.options.storeInterval || 1000;
107
+
108
+ /**
109
+ * History.options.busyDelay
110
+ * How long should we wait between busy events
111
+ */
112
+ History.options.busyDelay = History.options.busyDelay || 250;
113
+
114
+ /**
115
+ * History.options.debug
116
+ * If true will enable debug messages to be logged
117
+ */
118
+ History.options.debug = History.options.debug || false;
119
+
120
+ /**
121
+ * History.options.initialTitle
122
+ * What is the title of the initial state
123
+ */
124
+ History.options.initialTitle = History.options.initialTitle || document.title;
125
+
126
+
127
+ // ====================================================================
128
+ // Interval record
129
+
130
+ /**
131
+ * History.intervalList
132
+ * List of intervals set, to be cleared when document is unloaded.
133
+ */
134
+ History.intervalList = [];
135
+
136
+ /**
137
+ * History.clearAllIntervals
138
+ * Clears all setInterval instances.
139
+ */
140
+ History.clearAllIntervals = function(){
141
+ var i, il = History.intervalList;
142
+ if (typeof il !== "undefined" && il !== null) {
143
+ for (i = 0; i < il.length; i++) {
144
+ clearInterval(il[i]);
145
+ }
146
+ History.intervalList = null;
147
+ }
148
+ };
149
+
150
+
151
+ // ====================================================================
152
+ // Debug
153
+
154
+ /**
155
+ * History.debug(message,...)
156
+ * Logs the passed arguments if debug enabled
157
+ */
158
+ History.debug = function(){
159
+ if ( (History.options.debug||false) ) {
160
+ History.log.apply(History,arguments);
161
+ }
162
+ };
163
+
164
+ /**
165
+ * History.log(message,...)
166
+ * Logs the passed arguments
167
+ */
168
+ History.log = function(){
169
+ // Prepare
170
+ var
171
+ consoleExists = !(typeof console === 'undefined' || typeof console.log === 'undefined' || typeof console.log.apply === 'undefined'),
172
+ textarea = document.getElementById('log'),
173
+ message,
174
+ i,n,
175
+ args,arg
176
+ ;
177
+
178
+ // Write to Console
179
+ if ( consoleExists ) {
180
+ args = Array.prototype.slice.call(arguments);
181
+ message = args.shift();
182
+ if ( typeof console.debug !== 'undefined' ) {
183
+ console.debug.apply(console,[message,args]);
184
+ }
185
+ else {
186
+ console.log.apply(console,[message,args]);
187
+ }
188
+ }
189
+ else {
190
+ message = ("\n"+arguments[0]+"\n");
191
+ }
192
+
193
+ // Write to log
194
+ for ( i=1,n=arguments.length; i<n; ++i ) {
195
+ arg = arguments[i];
196
+ if ( typeof arg === 'object' && typeof JSON !== 'undefined' ) {
197
+ try {
198
+ arg = JSON.stringify(arg);
199
+ }
200
+ catch ( Exception ) {
201
+ // Recursive Object
202
+ }
203
+ }
204
+ message += "\n"+arg+"\n";
205
+ }
206
+
207
+ // Textarea
208
+ if ( textarea ) {
209
+ textarea.value += message+"\n-----\n";
210
+ textarea.scrollTop = textarea.scrollHeight - textarea.clientHeight;
211
+ }
212
+ // No Textarea, No Console
213
+ else if ( !consoleExists ) {
214
+ alert(message);
215
+ }
216
+
217
+ // Return true
218
+ return true;
219
+ };
220
+
221
+
222
+ // ====================================================================
223
+ // Emulated Status
224
+
225
+ /**
226
+ * History.getInternetExplorerMajorVersion()
227
+ * Get's the major version of Internet Explorer
228
+ * @return {integer}
229
+ * @license Public Domain
230
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
231
+ * @author James Padolsey <https://gist.github.com/527683>
232
+ */
233
+ History.getInternetExplorerMajorVersion = function(){
234
+ var result = History.getInternetExplorerMajorVersion.cached =
235
+ (typeof History.getInternetExplorerMajorVersion.cached !== 'undefined')
236
+ ? History.getInternetExplorerMajorVersion.cached
237
+ : (function(){
238
+ var v = 3,
239
+ div = document.createElement('div'),
240
+ all = div.getElementsByTagName('i');
241
+ while ( (div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->') && all[0] ) {}
242
+ return (v > 4) ? v : false;
243
+ })()
244
+ ;
245
+ return result;
246
+ };
247
+
248
+ /**
249
+ * History.isInternetExplorer()
250
+ * Are we using Internet Explorer?
251
+ * @return {boolean}
252
+ * @license Public Domain
253
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
254
+ */
255
+ History.isInternetExplorer = function(){
256
+ var result =
257
+ History.isInternetExplorer.cached =
258
+ (typeof History.isInternetExplorer.cached !== 'undefined')
259
+ ? History.isInternetExplorer.cached
260
+ : Boolean(History.getInternetExplorerMajorVersion())
261
+ ;
262
+ return result;
263
+ };
264
+
265
+ /**
266
+ * History.emulated
267
+ * Which features require emulating?
268
+ */
269
+ History.emulated = {
270
+ pushState: !Boolean(
271
+ window.history && window.history.pushState && window.history.replaceState
272
+ && !(
273
+ (/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent) /* disable for versions of iOS before version 4.3 (8F190) */
274
+ || (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent) /* disable for the mercury iOS browser, or at least older versions of the webkit engine */
275
+ )
276
+ ),
277
+ hashChange: Boolean(
278
+ !(('onhashchange' in window) || ('onhashchange' in document))
279
+ ||
280
+ (History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8)
281
+ )
282
+ };
283
+
284
+ /**
285
+ * History.enabled
286
+ * Is History enabled?
287
+ */
288
+ History.enabled = !History.emulated.pushState;
289
+
290
+ /**
291
+ * History.bugs
292
+ * Which bugs are present
293
+ */
294
+ History.bugs = {
295
+ /**
296
+ * Safari 5 and Safari iOS 4 fail to return to the correct state once a hash is replaced by a `replaceState` call
297
+ * https://bugs.webkit.org/show_bug.cgi?id=56249
298
+ */
299
+ setHash: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
300
+
301
+ /**
302
+ * Safari 5 and Safari iOS 4 sometimes fail to apply the state change under busy conditions
303
+ * https://bugs.webkit.org/show_bug.cgi?id=42940
304
+ */
305
+ safariPoll: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
306
+
307
+ /**
308
+ * MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function)
309
+ */
310
+ ieDoubleCheck: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8),
311
+
312
+ /**
313
+ * MSIE 6 requires the entire hash to be encoded for the hashes to trigger the onHashChange event
314
+ */
315
+ hashEscape: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 7)
316
+ };
317
+
318
+ /**
319
+ * History.isEmptyObject(obj)
320
+ * Checks to see if the Object is Empty
321
+ * @param {Object} obj
322
+ * @return {boolean}
323
+ */
324
+ History.isEmptyObject = function(obj) {
325
+ for ( var name in obj ) {
326
+ return false;
327
+ }
328
+ return true;
329
+ };
330
+
331
+ /**
332
+ * History.cloneObject(obj)
333
+ * Clones a object and eliminate all references to the original contexts
334
+ * @param {Object} obj
335
+ * @return {Object}
336
+ */
337
+ History.cloneObject = function(obj) {
338
+ var hash,newObj;
339
+ if ( obj ) {
340
+ hash = JSON.stringify(obj);
341
+ newObj = JSON.parse(hash);
342
+ }
343
+ else {
344
+ newObj = {};
345
+ }
346
+ return newObj;
347
+ };
348
+
349
+
350
+ // ====================================================================
351
+ // URL Helpers
352
+
353
+ /**
354
+ * History.getRootUrl()
355
+ * Turns "http://mysite.com/dir/page.html?asd" into "http://mysite.com"
356
+ * @return {String} rootUrl
357
+ */
358
+ History.getRootUrl = function(){
359
+ // Create
360
+ var rootUrl = document.location.protocol+'//'+(document.location.hostname||document.location.host);
361
+ if ( document.location.port||false ) {
362
+ rootUrl += ':'+document.location.port;
363
+ }
364
+ rootUrl += '/';
365
+
366
+ // Return
367
+ return rootUrl;
368
+ };
369
+
370
+ /**
371
+ * History.getBaseHref()
372
+ * Fetches the `href` attribute of the `<base href="...">` element if it exists
373
+ * @return {String} baseHref
374
+ */
375
+ History.getBaseHref = function(){
376
+ // Create
377
+ var
378
+ baseElements = document.getElementsByTagName('base'),
379
+ baseElement = null,
380
+ baseHref = '';
381
+
382
+ // Test for Base Element
383
+ if ( baseElements.length === 1 ) {
384
+ // Prepare for Base Element
385
+ baseElement = baseElements[0];
386
+ baseHref = baseElement.href.replace(/[^\/]+$/,'');
387
+ }
388
+
389
+ // Adjust trailing slash
390
+ baseHref = baseHref.replace(/\/+$/,'');
391
+ if ( baseHref ) baseHref += '/';
392
+
393
+ // Return
394
+ return baseHref;
395
+ };
396
+
397
+ /**
398
+ * History.getBaseUrl()
399
+ * Fetches the baseHref or basePageUrl or rootUrl (whichever one exists first)
400
+ * @return {String} baseUrl
401
+ */
402
+ History.getBaseUrl = function(){
403
+ // Create
404
+ var baseUrl = History.getBaseHref()||History.getBasePageUrl()||History.getRootUrl();
405
+
406
+ // Return
407
+ return baseUrl;
408
+ };
409
+
410
+ /**
411
+ * History.getPageUrl()
412
+ * Fetches the URL of the current page
413
+ * @return {String} pageUrl
414
+ */
415
+ History.getPageUrl = function(){
416
+ // Fetch
417
+ var
418
+ State = History.getState(false,false),
419
+ stateUrl = (State||{}).url||document.location.href,
420
+ pageUrl;
421
+
422
+ // Create
423
+ pageUrl = stateUrl.replace(/\/+$/,'').replace(/[^\/]+$/,function(part,index,string){
424
+ return (/\./).test(part) ? part : part+'/';
425
+ });
426
+
427
+ // Return
428
+ return pageUrl;
429
+ };
430
+
431
+ /**
432
+ * History.getBasePageUrl()
433
+ * Fetches the Url of the directory of the current page
434
+ * @return {String} basePageUrl
435
+ */
436
+ History.getBasePageUrl = function(){
437
+ // Create
438
+ var basePageUrl = document.location.href.replace(/[#\?].*/,'').replace(/[^\/]+$/,function(part,index,string){
439
+ return (/[^\/]$/).test(part) ? '' : part;
440
+ }).replace(/\/+$/,'')+'/';
441
+
442
+ // Return
443
+ return basePageUrl;
444
+ };
445
+
446
+ /**
447
+ * History.getFullUrl(url)
448
+ * Ensures that we have an absolute URL and not a relative URL
449
+ * @param {string} url
450
+ * @param {Boolean} allowBaseHref
451
+ * @return {string} fullUrl
452
+ */
453
+ History.getFullUrl = function(url,allowBaseHref){
454
+ // Prepare
455
+ var fullUrl = url, firstChar = url.substring(0,1);
456
+ allowBaseHref = (typeof allowBaseHref === 'undefined') ? true : allowBaseHref;
457
+
458
+ // Check
459
+ if ( /[a-z]+\:\/\//.test(url) ) {
460
+ // Full URL
461
+ }
462
+ else if ( firstChar === '/' ) {
463
+ // Root URL
464
+ fullUrl = History.getRootUrl()+url.replace(/^\/+/,'');
465
+ }
466
+ else if ( firstChar === '#' ) {
467
+ // Anchor URL
468
+ fullUrl = History.getPageUrl().replace(/#.*/,'')+url;
469
+ }
470
+ else if ( firstChar === '?' ) {
471
+ // Query URL
472
+ fullUrl = History.getPageUrl().replace(/[\?#].*/,'')+url;
473
+ }
474
+ else {
475
+ // Relative URL
476
+ if ( allowBaseHref ) {
477
+ fullUrl = History.getBaseUrl()+url.replace(/^(\.\/)+/,'');
478
+ } else {
479
+ fullUrl = History.getBasePageUrl()+url.replace(/^(\.\/)+/,'');
480
+ }
481
+ // We have an if condition above as we do not want hashes
482
+ // which are relative to the baseHref in our URLs
483
+ // as if the baseHref changes, then all our bookmarks
484
+ // would now point to different locations
485
+ // whereas the basePageUrl will always stay the same
486
+ }
487
+
488
+ // Return
489
+ return fullUrl.replace(/\#$/,'');
490
+ };
491
+
492
+ /**
493
+ * History.getShortUrl(url)
494
+ * Ensures that we have a relative URL and not a absolute URL
495
+ * @param {string} url
496
+ * @return {string} url
497
+ */
498
+ History.getShortUrl = function(url){
499
+ // Prepare
500
+ var shortUrl = url, baseUrl = History.getBaseUrl(), rootUrl = History.getRootUrl();
501
+
502
+ // Trim baseUrl
503
+ if ( History.emulated.pushState ) {
504
+ // We are in a if statement as when pushState is not emulated
505
+ // The actual url these short urls are relative to can change
506
+ // So within the same session, we the url may end up somewhere different
507
+ shortUrl = shortUrl.replace(baseUrl,'');
508
+ }
509
+
510
+ // Trim rootUrl
511
+ shortUrl = shortUrl.replace(rootUrl,'/');
512
+
513
+ // Ensure we can still detect it as a state
514
+ if ( History.isTraditionalAnchor(shortUrl) ) {
515
+ shortUrl = './'+shortUrl;
516
+ }
517
+
518
+ // Clean It
519
+ shortUrl = shortUrl.replace(/^(\.\/)+/g,'./').replace(/\#$/,'');
520
+
521
+ // Return
522
+ return shortUrl;
523
+ };
524
+
525
+
526
+ // ====================================================================
527
+ // State Storage
528
+
529
+ /**
530
+ * History.store
531
+ * The store for all session specific data
532
+ */
533
+ History.store = {};
534
+
535
+ /**
536
+ * History.idToState
537
+ * 1-1: State ID to State Object
538
+ */
539
+ History.idToState = History.idToState||{};
540
+
541
+ /**
542
+ * History.stateToId
543
+ * 1-1: State String to State ID
544
+ */
545
+ History.stateToId = History.stateToId||{};
546
+
547
+ /**
548
+ * History.urlToId
549
+ * 1-1: State URL to State ID
550
+ */
551
+ History.urlToId = History.urlToId||{};
552
+
553
+ /**
554
+ * History.storedStates
555
+ * Store the states in an array
556
+ */
557
+ History.storedStates = History.storedStates||[];
558
+
559
+ /**
560
+ * History.savedStates
561
+ * Saved the states in an array
562
+ */
563
+ History.savedStates = History.savedStates||[];
564
+
565
+ /**
566
+ * History.noramlizeStore()
567
+ * Noramlize the store by adding necessary values
568
+ */
569
+ History.normalizeStore = function(){
570
+ History.store.idToState = History.store.idToState||{};
571
+ History.store.urlToId = History.store.urlToId||{};
572
+ History.store.stateToId = History.store.stateToId||{};
573
+ };
574
+
575
+ /**
576
+ * History.getState()
577
+ * Get an object containing the data, title and url of the current state
578
+ * @param {Boolean} friendly
579
+ * @param {Boolean} create
580
+ * @return {Object} State
581
+ */
582
+ History.getState = function(friendly,create){
583
+ // Prepare
584
+ if ( typeof friendly === 'undefined' ) { friendly = true; }
585
+ if ( typeof create === 'undefined' ) { create = true; }
586
+
587
+ // Fetch
588
+ var State = History.getLastSavedState();
589
+
590
+ // Create
591
+ if ( !State && create ) {
592
+ State = History.createStateObject();
593
+ }
594
+
595
+ // Adjust
596
+ if ( friendly ) {
597
+ State = History.cloneObject(State);
598
+ State.url = State.cleanUrl||State.url;
599
+ }
600
+
601
+ // Return
602
+ return State;
603
+ };
604
+
605
+ /**
606
+ * History.getIdByState(State)
607
+ * Gets a ID for a State
608
+ * @param {State} newState
609
+ * @return {String} id
610
+ */
611
+ History.getIdByState = function(newState){
612
+
613
+ // Fetch ID
614
+ var id = History.extractId(newState.url),
615
+ str;
616
+
617
+ if ( !id ) {
618
+ // Find ID via State String
619
+ str = History.getStateString(newState);
620
+ if ( typeof History.stateToId[str] !== 'undefined' ) {
621
+ id = History.stateToId[str];
622
+ }
623
+ else if ( typeof History.store.stateToId[str] !== 'undefined' ) {
624
+ id = History.store.stateToId[str];
625
+ }
626
+ else {
627
+ // Generate a new ID
628
+ while ( true ) {
629
+ id = (new Date()).getTime() + String(Math.random()).replace(/\D/g,'');
630
+ if ( typeof History.idToState[id] === 'undefined' && typeof History.store.idToState[id] === 'undefined' ) {
631
+ break;
632
+ }
633
+ }
634
+
635
+ // Apply the new State to the ID
636
+ History.stateToId[str] = id;
637
+ History.idToState[id] = newState;
638
+ }
639
+ }
640
+
641
+ // Return ID
642
+ return id;
643
+ };
644
+
645
+ /**
646
+ * History.normalizeState(State)
647
+ * Expands a State Object
648
+ * @param {object} State
649
+ * @return {object}
650
+ */
651
+ History.normalizeState = function(oldState){
652
+ // Variables
653
+ var newState, dataNotEmpty;
654
+
655
+ // Prepare
656
+ if ( !oldState || (typeof oldState !== 'object') ) {
657
+ oldState = {};
658
+ }
659
+
660
+ // Check
661
+ if ( typeof oldState.normalized !== 'undefined' ) {
662
+ return oldState;
663
+ }
664
+
665
+ // Adjust
666
+ if ( !oldState.data || (typeof oldState.data !== 'object') ) {
667
+ oldState.data = {};
668
+ }
669
+
670
+ // ----------------------------------------------------------------
671
+
672
+ // Create
673
+ newState = {};
674
+ newState.normalized = true;
675
+ newState.title = oldState.title||'';
676
+ newState.url = History.getFullUrl(History.unescapeString(oldState.url||document.location.href));
677
+ newState.hash = History.getShortUrl(newState.url);
678
+ newState.data = History.cloneObject(oldState.data);
679
+
680
+ // Fetch ID
681
+ newState.id = History.getIdByState(newState);
682
+
683
+ // ----------------------------------------------------------------
684
+
685
+ // Clean the URL
686
+ newState.cleanUrl = newState.url.replace(/\??\&_suid.*/,'');
687
+ newState.url = newState.cleanUrl;
688
+
689
+ // Check to see if we have more than just a url
690
+ dataNotEmpty = !History.isEmptyObject(newState.data);
691
+
692
+ // Apply
693
+ if ( newState.title || dataNotEmpty ) {
694
+ // Add ID to Hash
695
+ newState.hash = History.getShortUrl(newState.url).replace(/\??\&_suid.*/,'');
696
+ if ( !/\?/.test(newState.hash) ) {
697
+ newState.hash += '?';
698
+ }
699
+ newState.hash += '&_suid='+newState.id;
700
+ }
701
+
702
+ // Create the Hashed URL
703
+ newState.hashedUrl = History.getFullUrl(newState.hash);
704
+
705
+ // ----------------------------------------------------------------
706
+
707
+ // Update the URL if we have a duplicate
708
+ if ( (History.emulated.pushState || History.bugs.safariPoll) && History.hasUrlDuplicate(newState) ) {
709
+ newState.url = newState.hashedUrl;
710
+ }
711
+
712
+ // ----------------------------------------------------------------
713
+
714
+ // Return
715
+ return newState;
716
+ };
717
+
718
+ /**
719
+ * History.createStateObject(data,title,url)
720
+ * Creates a object based on the data, title and url state params
721
+ * @param {object} data
722
+ * @param {string} title
723
+ * @param {string} url
724
+ * @return {object}
725
+ */
726
+ History.createStateObject = function(data,title,url){
727
+ // Hashify
728
+ var State = {
729
+ 'data': data,
730
+ 'title': title,
731
+ 'url': url
732
+ };
733
+
734
+ // Expand the State
735
+ State = History.normalizeState(State);
736
+
737
+ // Return object
738
+ return State;
739
+ };
740
+
741
+ /**
742
+ * History.getStateById(id)
743
+ * Get a state by it's UID
744
+ * @param {String} id
745
+ */
746
+ History.getStateById = function(id){
747
+ // Prepare
748
+ id = String(id);
749
+
750
+ // Retrieve
751
+ var State = History.idToState[id] || History.store.idToState[id] || undefined;
752
+
753
+ // Return State
754
+ return State;
755
+ };
756
+
757
+ /**
758
+ * Get a State's String
759
+ * @param {State} passedState
760
+ */
761
+ History.getStateString = function(passedState){
762
+ // Prepare
763
+ var State, cleanedState, str;
764
+
765
+ // Fetch
766
+ State = History.normalizeState(passedState);
767
+
768
+ // Clean
769
+ cleanedState = {
770
+ data: State.data,
771
+ title: passedState.title,
772
+ url: passedState.url
773
+ };
774
+
775
+ // Fetch
776
+ str = JSON.stringify(cleanedState);
777
+
778
+ // Return
779
+ return str;
780
+ };
781
+
782
+ /**
783
+ * Get a State's ID
784
+ * @param {State} passedState
785
+ * @return {String} id
786
+ */
787
+ History.getStateId = function(passedState){
788
+ // Prepare
789
+ var State, id;
790
+
791
+ // Fetch
792
+ State = History.normalizeState(passedState);
793
+
794
+ // Fetch
795
+ id = State.id;
796
+
797
+ // Return
798
+ return id;
799
+ };
800
+
801
+ /**
802
+ * History.getHashByState(State)
803
+ * Creates a Hash for the State Object
804
+ * @param {State} passedState
805
+ * @return {String} hash
806
+ */
807
+ History.getHashByState = function(passedState){
808
+ // Prepare
809
+ var State, hash;
810
+
811
+ // Fetch
812
+ State = History.normalizeState(passedState);
813
+
814
+ // Hash
815
+ hash = State.hash;
816
+
817
+ // Return
818
+ return hash;
819
+ };
820
+
821
+ /**
822
+ * History.extractId(url_or_hash)
823
+ * Get a State ID by it's URL or Hash
824
+ * @param {string} url_or_hash
825
+ * @return {string} id
826
+ */
827
+ History.extractId = function ( url_or_hash ) {
828
+ // Prepare
829
+ var id,parts,url;
830
+
831
+ // Extract
832
+ parts = /(.*)\&_suid=([0-9]+)$/.exec(url_or_hash);
833
+ url = parts ? (parts[1]||url_or_hash) : url_or_hash;
834
+ id = parts ? String(parts[2]||'') : '';
835
+
836
+ // Return
837
+ return id||false;
838
+ };
839
+
840
+ /**
841
+ * History.isTraditionalAnchor
842
+ * Checks to see if the url is a traditional anchor or not
843
+ * @param {String} url_or_hash
844
+ * @return {Boolean}
845
+ */
846
+ History.isTraditionalAnchor = function(url_or_hash){
847
+ // Check
848
+ var isTraditional = !(/[\/\?\.]/.test(url_or_hash));
849
+
850
+ // Return
851
+ return isTraditional;
852
+ };
853
+
854
+ /**
855
+ * History.extractState
856
+ * Get a State by it's URL or Hash
857
+ * @param {String} url_or_hash
858
+ * @return {State|null}
859
+ */
860
+ History.extractState = function(url_or_hash,create){
861
+ // Prepare
862
+ var State = null, id, url;
863
+ create = create||false;
864
+
865
+ // Fetch SUID
866
+ id = History.extractId(url_or_hash);
867
+ if ( id ) {
868
+ State = History.getStateById(id);
869
+ }
870
+
871
+ // Fetch SUID returned no State
872
+ if ( !State ) {
873
+ // Fetch URL
874
+ url = History.getFullUrl(url_or_hash);
875
+
876
+ // Check URL
877
+ id = History.getIdByUrl(url)||false;
878
+ if ( id ) {
879
+ State = History.getStateById(id);
880
+ }
881
+
882
+ // Create State
883
+ if ( !State && create && !History.isTraditionalAnchor(url_or_hash) ) {
884
+ State = History.createStateObject(null,null,url);
885
+ }
886
+ }
887
+
888
+ // Return
889
+ return State;
890
+ };
891
+
892
+ /**
893
+ * History.getIdByUrl()
894
+ * Get a State ID by a State URL
895
+ */
896
+ History.getIdByUrl = function(url){
897
+ // Fetch
898
+ var id = History.urlToId[url] || History.store.urlToId[url] || undefined;
899
+
900
+ // Return
901
+ return id;
902
+ };
903
+
904
+ /**
905
+ * History.getLastSavedState()
906
+ * Get an object containing the data, title and url of the current state
907
+ * @return {Object} State
908
+ */
909
+ History.getLastSavedState = function(){
910
+ return History.savedStates[History.savedStates.length-1]||undefined;
911
+ };
912
+
913
+ /**
914
+ * History.getLastStoredState()
915
+ * Get an object containing the data, title and url of the current state
916
+ * @return {Object} State
917
+ */
918
+ History.getLastStoredState = function(){
919
+ return History.storedStates[History.storedStates.length-1]||undefined;
920
+ };
921
+
922
+ /**
923
+ * History.hasUrlDuplicate
924
+ * Checks if a Url will have a url conflict
925
+ * @param {Object} newState
926
+ * @return {Boolean} hasDuplicate
927
+ */
928
+ History.hasUrlDuplicate = function(newState) {
929
+ // Prepare
930
+ var hasDuplicate = false,
931
+ oldState;
932
+
933
+ // Fetch
934
+ oldState = History.extractState(newState.url);
935
+
936
+ // Check
937
+ hasDuplicate = oldState && oldState.id !== newState.id;
938
+
939
+ // Return
940
+ return hasDuplicate;
941
+ };
942
+
943
+ /**
944
+ * History.storeState
945
+ * Store a State
946
+ * @param {Object} newState
947
+ * @return {Object} newState
948
+ */
949
+ History.storeState = function(newState){
950
+ // Store the State
951
+ History.urlToId[newState.url] = newState.id;
952
+
953
+ // Push the State
954
+ History.storedStates.push(History.cloneObject(newState));
955
+
956
+ // Return newState
957
+ return newState;
958
+ };
959
+
960
+ /**
961
+ * History.isLastSavedState(newState)
962
+ * Tests to see if the state is the last state
963
+ * @param {Object} newState
964
+ * @return {boolean} isLast
965
+ */
966
+ History.isLastSavedState = function(newState){
967
+ // Prepare
968
+ var isLast = false,
969
+ newId, oldState, oldId;
970
+
971
+ // Check
972
+ if ( History.savedStates.length ) {
973
+ newId = newState.id;
974
+ oldState = History.getLastSavedState();
975
+ oldId = oldState.id;
976
+
977
+ // Check
978
+ isLast = (newId === oldId);
979
+ }
980
+
981
+ // Return
982
+ return isLast;
983
+ };
984
+
985
+ /**
986
+ * History.saveState
987
+ * Push a State
988
+ * @param {Object} newState
989
+ * @return {boolean} changed
990
+ */
991
+ History.saveState = function(newState){
992
+ // Check Hash
993
+ if ( History.isLastSavedState(newState) ) {
994
+ return false;
995
+ }
996
+
997
+ // Push the State
998
+ History.savedStates.push(History.cloneObject(newState));
999
+
1000
+ // Return true
1001
+ return true;
1002
+ };
1003
+
1004
+ /**
1005
+ * History.getStateByIndex()
1006
+ * Gets a state by the index
1007
+ * @param {integer} index
1008
+ * @return {Object}
1009
+ */
1010
+ History.getStateByIndex = function(index){
1011
+ // Prepare
1012
+ var State = null;
1013
+
1014
+ // Handle
1015
+ if ( typeof index === 'undefined' ) {
1016
+ // Get the last inserted
1017
+ State = History.savedStates[History.savedStates.length-1];
1018
+ }
1019
+ else if ( index < 0 ) {
1020
+ // Get from the end
1021
+ State = History.savedStates[History.savedStates.length+index];
1022
+ }
1023
+ else {
1024
+ // Get from the beginning
1025
+ State = History.savedStates[index];
1026
+ }
1027
+
1028
+ // Return State
1029
+ return State;
1030
+ };
1031
+
1032
+
1033
+ // ====================================================================
1034
+ // Hash Helpers
1035
+
1036
+ /**
1037
+ * History.getHash()
1038
+ * Gets the current document hash
1039
+ * @return {string}
1040
+ */
1041
+ History.getHash = function(){
1042
+ var hash = History.unescapeHash(document.location.hash);
1043
+ return hash;
1044
+ };
1045
+
1046
+ /**
1047
+ * History.unescapeString()
1048
+ * Unescape a string
1049
+ * @param {String} str
1050
+ * @return {string}
1051
+ */
1052
+ History.unescapeString = function(str){
1053
+ // Prepare
1054
+ var result = str,
1055
+ tmp;
1056
+
1057
+ // Unescape hash
1058
+ while ( true ) {
1059
+ tmp = window.unescape(result);
1060
+ if ( tmp === result ) {
1061
+ break;
1062
+ }
1063
+ result = tmp;
1064
+ }
1065
+
1066
+ // Return result
1067
+ return result;
1068
+ };
1069
+
1070
+ /**
1071
+ * History.unescapeHash()
1072
+ * normalize and Unescape a Hash
1073
+ * @param {String} hash
1074
+ * @return {string}
1075
+ */
1076
+ History.unescapeHash = function(hash){
1077
+ // Prepare
1078
+ var result = History.normalizeHash(hash);
1079
+
1080
+ // Unescape hash
1081
+ result = History.unescapeString(result);
1082
+
1083
+ // Return result
1084
+ return result;
1085
+ };
1086
+
1087
+ /**
1088
+ * History.normalizeHash()
1089
+ * normalize a hash across browsers
1090
+ * @return {string}
1091
+ */
1092
+ History.normalizeHash = function(hash){
1093
+ // Prepare
1094
+ var result = hash.replace(/[^#]*#/,'').replace(/#.*/, '');
1095
+
1096
+ // Return result
1097
+ return result;
1098
+ };
1099
+
1100
+ /**
1101
+ * History.setHash(hash)
1102
+ * Sets the document hash
1103
+ * @param {string} hash
1104
+ * @return {History}
1105
+ */
1106
+ History.setHash = function(hash,queue){
1107
+ // Prepare
1108
+ var adjustedHash, State, pageUrl;
1109
+
1110
+ // Handle Queueing
1111
+ if ( queue !== false && History.busy() ) {
1112
+ // Wait + Push to Queue
1113
+ //History.debug('History.setHash: we must wait', arguments);
1114
+ History.pushQueue({
1115
+ scope: History,
1116
+ callback: History.setHash,
1117
+ args: arguments,
1118
+ queue: queue
1119
+ });
1120
+ return false;
1121
+ }
1122
+
1123
+ // Log
1124
+ //History.debug('History.setHash: called',hash);
1125
+
1126
+ // Prepare
1127
+ adjustedHash = History.escapeHash(hash);
1128
+
1129
+ // Make Busy + Continue
1130
+ History.busy(true);
1131
+
1132
+ // Check if hash is a state
1133
+ State = History.extractState(hash,true);
1134
+ if ( State && !History.emulated.pushState ) {
1135
+ // Hash is a state so skip the setHash
1136
+ //History.debug('History.setHash: Hash is a state so skipping the hash set with a direct pushState call',arguments);
1137
+
1138
+ // PushState
1139
+ History.pushState(State.data,State.title,State.url,false);
1140
+ }
1141
+ else if ( document.location.hash !== adjustedHash ) {
1142
+ // Hash is a proper hash, so apply it
1143
+
1144
+ // Handle browser bugs
1145
+ if ( History.bugs.setHash ) {
1146
+ // Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249
1147
+
1148
+ // Fetch the base page
1149
+ pageUrl = History.getPageUrl();
1150
+
1151
+ // Safari hash apply
1152
+ History.pushState(null,null,pageUrl+'#'+adjustedHash,false);
1153
+ }
1154
+ else {
1155
+ // Normal hash apply
1156
+ document.location.hash = adjustedHash;
1157
+ }
1158
+ }
1159
+
1160
+ // Chain
1161
+ return History;
1162
+ };
1163
+
1164
+ /**
1165
+ * History.escape()
1166
+ * normalize and Escape a Hash
1167
+ * @return {string}
1168
+ */
1169
+ History.escapeHash = function(hash){
1170
+ // Prepare
1171
+ var result = History.normalizeHash(hash);
1172
+
1173
+ // Escape hash
1174
+ result = window.escape(result);
1175
+
1176
+ // IE6 Escape Bug
1177
+ if ( !History.bugs.hashEscape ) {
1178
+ // Restore common parts
1179
+ result = result
1180
+ .replace(/\%21/g,'!')
1181
+ .replace(/\%26/g,'&')
1182
+ .replace(/\%3D/g,'=')
1183
+ .replace(/\%3F/g,'?');
1184
+ }
1185
+
1186
+ // Return result
1187
+ return result;
1188
+ };
1189
+
1190
+ /**
1191
+ * History.getHashByUrl(url)
1192
+ * Extracts the Hash from a URL
1193
+ * @param {string} url
1194
+ * @return {string} url
1195
+ */
1196
+ History.getHashByUrl = function(url){
1197
+ // Extract the hash
1198
+ var hash = String(url)
1199
+ .replace(/([^#]*)#?([^#]*)#?(.*)/, '$2')
1200
+ ;
1201
+
1202
+ // Unescape hash
1203
+ hash = History.unescapeHash(hash);
1204
+
1205
+ // Return hash
1206
+ return hash;
1207
+ };
1208
+
1209
+ /**
1210
+ * History.setTitle(title)
1211
+ * Applies the title to the document
1212
+ * @param {State} newState
1213
+ * @return {Boolean}
1214
+ */
1215
+ History.setTitle = function(newState){
1216
+ // Prepare
1217
+ var title = newState.title,
1218
+ firstState;
1219
+
1220
+ // Initial
1221
+ if ( !title ) {
1222
+ firstState = History.getStateByIndex(0);
1223
+ if ( firstState && firstState.url === newState.url ) {
1224
+ title = firstState.title||History.options.initialTitle;
1225
+ }
1226
+ }
1227
+
1228
+ // Apply
1229
+ try {
1230
+ document.getElementsByTagName('title')[0].innerHTML = title.replace('<','&lt;').replace('>','&gt;').replace(' & ',' &amp; ');
1231
+ }
1232
+ catch ( Exception ) { }
1233
+ document.title = title;
1234
+
1235
+ // Chain
1236
+ return History;
1237
+ };
1238
+
1239
+
1240
+ // ====================================================================
1241
+ // Queueing
1242
+
1243
+ /**
1244
+ * History.queues
1245
+ * The list of queues to use
1246
+ * First In, First Out
1247
+ */
1248
+ History.queues = [];
1249
+
1250
+ /**
1251
+ * History.busy(value)
1252
+ * @param {boolean} value [optional]
1253
+ * @return {boolean} busy
1254
+ */
1255
+ History.busy = function(value){
1256
+ // Apply
1257
+ if ( typeof value !== 'undefined' ) {
1258
+ //History.debug('History.busy: changing ['+(History.busy.flag||false)+'] to ['+(value||false)+']', History.queues.length);
1259
+ History.busy.flag = value;
1260
+ }
1261
+ // Default
1262
+ else if ( typeof History.busy.flag === 'undefined' ) {
1263
+ History.busy.flag = false;
1264
+ }
1265
+
1266
+ // Queue
1267
+ if ( !History.busy.flag ) {
1268
+ // Execute the next item in the queue
1269
+ clearTimeout(History.busy.timeout);
1270
+ var fireNext = function(){
1271
+ var i, queue, item;
1272
+ if ( History.busy.flag ) return;
1273
+ for ( i=History.queues.length-1; i >= 0; --i ) {
1274
+ queue = History.queues[i];
1275
+ if ( queue.length === 0 ) continue;
1276
+ item = queue.shift();
1277
+ History.fireQueueItem(item);
1278
+ History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
1279
+ }
1280
+ };
1281
+ History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
1282
+ }
1283
+
1284
+ // Return
1285
+ return History.busy.flag;
1286
+ };
1287
+
1288
+ /**
1289
+ * History.busy.flag
1290
+ */
1291
+ History.busy.flag = false;
1292
+
1293
+ /**
1294
+ * History.fireQueueItem(item)
1295
+ * Fire a Queue Item
1296
+ * @param {Object} item
1297
+ * @return {Mixed} result
1298
+ */
1299
+ History.fireQueueItem = function(item){
1300
+ return item.callback.apply(item.scope||History,item.args||[]);
1301
+ };
1302
+
1303
+ /**
1304
+ * History.pushQueue(callback,args)
1305
+ * Add an item to the queue
1306
+ * @param {Object} item [scope,callback,args,queue]
1307
+ */
1308
+ History.pushQueue = function(item){
1309
+ // Prepare the queue
1310
+ History.queues[item.queue||0] = History.queues[item.queue||0]||[];
1311
+
1312
+ // Add to the queue
1313
+ History.queues[item.queue||0].push(item);
1314
+
1315
+ // Chain
1316
+ return History;
1317
+ };
1318
+
1319
+ /**
1320
+ * History.queue (item,queue), (func,queue), (func), (item)
1321
+ * Either firs the item now if not busy, or adds it to the queue
1322
+ */
1323
+ History.queue = function(item,queue){
1324
+ // Prepare
1325
+ if ( typeof item === 'function' ) {
1326
+ item = {
1327
+ callback: item
1328
+ };
1329
+ }
1330
+ if ( typeof queue !== 'undefined' ) {
1331
+ item.queue = queue;
1332
+ }
1333
+
1334
+ // Handle
1335
+ if ( History.busy() ) {
1336
+ History.pushQueue(item);
1337
+ } else {
1338
+ History.fireQueueItem(item);
1339
+ }
1340
+
1341
+ // Chain
1342
+ return History;
1343
+ };
1344
+
1345
+ /**
1346
+ * History.clearQueue()
1347
+ * Clears the Queue
1348
+ */
1349
+ History.clearQueue = function(){
1350
+ History.busy.flag = false;
1351
+ History.queues = [];
1352
+ return History;
1353
+ };
1354
+
1355
+
1356
+ // ====================================================================
1357
+ // IE Bug Fix
1358
+
1359
+ /**
1360
+ * History.stateChanged
1361
+ * States whether or not the state has changed since the last double check was initialised
1362
+ */
1363
+ History.stateChanged = false;
1364
+
1365
+ /**
1366
+ * History.doubleChecker
1367
+ * Contains the timeout used for the double checks
1368
+ */
1369
+ History.doubleChecker = false;
1370
+
1371
+ /**
1372
+ * History.doubleCheckComplete()
1373
+ * Complete a double check
1374
+ * @return {History}
1375
+ */
1376
+ History.doubleCheckComplete = function(){
1377
+ // Update
1378
+ History.stateChanged = true;
1379
+
1380
+ // Clear
1381
+ History.doubleCheckClear();
1382
+
1383
+ // Chain
1384
+ return History;
1385
+ };
1386
+
1387
+ /**
1388
+ * History.doubleCheckClear()
1389
+ * Clear a double check
1390
+ * @return {History}
1391
+ */
1392
+ History.doubleCheckClear = function(){
1393
+ // Clear
1394
+ if ( History.doubleChecker ) {
1395
+ clearTimeout(History.doubleChecker);
1396
+ History.doubleChecker = false;
1397
+ }
1398
+
1399
+ // Chain
1400
+ return History;
1401
+ };
1402
+
1403
+ /**
1404
+ * History.doubleCheck()
1405
+ * Create a double check
1406
+ * @return {History}
1407
+ */
1408
+ History.doubleCheck = function(tryAgain){
1409
+ // Reset
1410
+ History.stateChanged = false;
1411
+ History.doubleCheckClear();
1412
+
1413
+ // Fix IE6,IE7 bug where calling history.back or history.forward does not actually change the hash (whereas doing it manually does)
1414
+ // Fix Safari 5 bug where sometimes the state does not change: https://bugs.webkit.org/show_bug.cgi?id=42940
1415
+ if ( History.bugs.ieDoubleCheck ) {
1416
+ // Apply Check
1417
+ History.doubleChecker = setTimeout(
1418
+ function(){
1419
+ History.doubleCheckClear();
1420
+ if ( !History.stateChanged ) {
1421
+ //History.debug('History.doubleCheck: State has not yet changed, trying again', arguments);
1422
+ // Re-Attempt
1423
+ tryAgain();
1424
+ }
1425
+ return true;
1426
+ },
1427
+ History.options.doubleCheckInterval
1428
+ );
1429
+ }
1430
+
1431
+ // Chain
1432
+ return History;
1433
+ };
1434
+
1435
+
1436
+ // ====================================================================
1437
+ // Safari Bug Fix
1438
+
1439
+ /**
1440
+ * History.safariStatePoll()
1441
+ * Poll the current state
1442
+ * @return {History}
1443
+ */
1444
+ History.safariStatePoll = function(){
1445
+ // Poll the URL
1446
+
1447
+ // Get the Last State which has the new URL
1448
+ var
1449
+ urlState = History.extractState(document.location.href),
1450
+ newState;
1451
+
1452
+ // Check for a difference
1453
+ if ( !History.isLastSavedState(urlState) ) {
1454
+ newState = urlState;
1455
+ }
1456
+ else {
1457
+ return;
1458
+ }
1459
+
1460
+ // Check if we have a state with that url
1461
+ // If not create it
1462
+ if ( !newState ) {
1463
+ //History.debug('History.safariStatePoll: new');
1464
+ newState = History.createStateObject();
1465
+ }
1466
+
1467
+ // Apply the New State
1468
+ //History.debug('History.safariStatePoll: trigger');
1469
+ History.Adapter.trigger(window,'popstate');
1470
+
1471
+ // Chain
1472
+ return History;
1473
+ };
1474
+
1475
+
1476
+ // ====================================================================
1477
+ // State Aliases
1478
+
1479
+ /**
1480
+ * History.back(queue)
1481
+ * Send the browser history back one item
1482
+ * @param {Integer} queue [optional]
1483
+ */
1484
+ History.back = function(queue){
1485
+ //History.debug('History.back: called', arguments);
1486
+
1487
+ // Handle Queueing
1488
+ if ( queue !== false && History.busy() ) {
1489
+ // Wait + Push to Queue
1490
+ //History.debug('History.back: we must wait', arguments);
1491
+ History.pushQueue({
1492
+ scope: History,
1493
+ callback: History.back,
1494
+ args: arguments,
1495
+ queue: queue
1496
+ });
1497
+ return false;
1498
+ }
1499
+
1500
+ // Make Busy + Continue
1501
+ History.busy(true);
1502
+
1503
+ // Fix certain browser bugs that prevent the state from changing
1504
+ History.doubleCheck(function(){
1505
+ History.back(false);
1506
+ });
1507
+
1508
+ // Go back
1509
+ history.go(-1);
1510
+
1511
+ // End back closure
1512
+ return true;
1513
+ };
1514
+
1515
+ /**
1516
+ * History.forward(queue)
1517
+ * Send the browser history forward one item
1518
+ * @param {Integer} queue [optional]
1519
+ */
1520
+ History.forward = function(queue){
1521
+ //History.debug('History.forward: called', arguments);
1522
+
1523
+ // Handle Queueing
1524
+ if ( queue !== false && History.busy() ) {
1525
+ // Wait + Push to Queue
1526
+ //History.debug('History.forward: we must wait', arguments);
1527
+ History.pushQueue({
1528
+ scope: History,
1529
+ callback: History.forward,
1530
+ args: arguments,
1531
+ queue: queue
1532
+ });
1533
+ return false;
1534
+ }
1535
+
1536
+ // Make Busy + Continue
1537
+ History.busy(true);
1538
+
1539
+ // Fix certain browser bugs that prevent the state from changing
1540
+ History.doubleCheck(function(){
1541
+ History.forward(false);
1542
+ });
1543
+
1544
+ // Go forward
1545
+ history.go(1);
1546
+
1547
+ // End forward closure
1548
+ return true;
1549
+ };
1550
+
1551
+ /**
1552
+ * History.go(index,queue)
1553
+ * Send the browser history back or forward index times
1554
+ * @param {Integer} queue [optional]
1555
+ */
1556
+ History.go = function(index,queue){
1557
+ //History.debug('History.go: called', arguments);
1558
+
1559
+ // Prepare
1560
+ var i;
1561
+
1562
+ // Handle
1563
+ if ( index > 0 ) {
1564
+ // Forward
1565
+ for ( i=1; i<=index; ++i ) {
1566
+ History.forward(queue);
1567
+ }
1568
+ }
1569
+ else if ( index < 0 ) {
1570
+ // Backward
1571
+ for ( i=-1; i>=index; --i ) {
1572
+ History.back(queue);
1573
+ }
1574
+ }
1575
+ else {
1576
+ throw new Error('History.go: History.go requires a positive or negative integer passed.');
1577
+ }
1578
+
1579
+ // Chain
1580
+ return History;
1581
+ };
1582
+
1583
+
1584
+ // ====================================================================
1585
+ // HTML5 State Support
1586
+
1587
+ // Non-Native pushState Implementation
1588
+ if ( History.emulated.pushState ) {
1589
+ /*
1590
+ * Provide Skeleton for HTML4 Browsers
1591
+ */
1592
+
1593
+ // Prepare
1594
+ var emptyFunction = function(){};
1595
+ History.pushState = History.pushState||emptyFunction;
1596
+ History.replaceState = History.replaceState||emptyFunction;
1597
+ } // History.emulated.pushState
1598
+
1599
+ // Native pushState Implementation
1600
+ else {
1601
+ /*
1602
+ * Use native HTML5 History API Implementation
1603
+ */
1604
+
1605
+ /**
1606
+ * History.onPopState(event,extra)
1607
+ * Refresh the Current State
1608
+ */
1609
+ History.onPopState = function(event,extra){
1610
+ // Prepare
1611
+ var stateId = false, newState = false, currentHash, currentState;
1612
+
1613
+ // Reset the double check
1614
+ History.doubleCheckComplete();
1615
+
1616
+ // Check for a Hash, and handle apporiatly
1617
+ currentHash = History.getHash();
1618
+ if ( currentHash ) {
1619
+ // Expand Hash
1620
+ currentState = History.extractState(currentHash||document.location.href,true);
1621
+ if ( currentState ) {
1622
+ // We were able to parse it, it must be a State!
1623
+ // Let's forward to replaceState
1624
+ //History.debug('History.onPopState: state anchor', currentHash, currentState);
1625
+ History.replaceState(currentState.data, currentState.title, currentState.url, false);
1626
+ }
1627
+ else {
1628
+ // Traditional Anchor
1629
+ //History.debug('History.onPopState: traditional anchor', currentHash);
1630
+ History.Adapter.trigger(window,'anchorchange');
1631
+ History.busy(false);
1632
+ }
1633
+
1634
+ // We don't care for hashes
1635
+ History.expectedStateId = false;
1636
+ return false;
1637
+ }
1638
+
1639
+ // Ensure
1640
+ stateId = History.Adapter.extractEventData('state',event,extra) || false;
1641
+
1642
+ // Fetch State
1643
+ if ( stateId ) {
1644
+ // Vanilla: Back/forward button was used
1645
+ newState = History.getStateById(stateId);
1646
+ }
1647
+ else if ( History.expectedStateId ) {
1648
+ // Vanilla: A new state was pushed, and popstate was called manually
1649
+ newState = History.getStateById(History.expectedStateId);
1650
+ }
1651
+ else {
1652
+ // Initial State
1653
+ newState = History.extractState(document.location.href);
1654
+ }
1655
+
1656
+ // The State did not exist in our store
1657
+ if ( !newState ) {
1658
+ // Regenerate the State
1659
+ newState = History.createStateObject(null,null,document.location.href);
1660
+ }
1661
+
1662
+ // Clean
1663
+ History.expectedStateId = false;
1664
+
1665
+ // Check if we are the same state
1666
+ if ( History.isLastSavedState(newState) ) {
1667
+ // There has been no change (just the page's hash has finally propagated)
1668
+ //History.debug('History.onPopState: no change', newState, History.savedStates);
1669
+ History.busy(false);
1670
+ return false;
1671
+ }
1672
+
1673
+ // Store the State
1674
+ History.storeState(newState);
1675
+ History.saveState(newState);
1676
+
1677
+ // Force update of the title
1678
+ History.setTitle(newState);
1679
+
1680
+ // Fire Our Event
1681
+ History.Adapter.trigger(window,'statechange');
1682
+ History.busy(false);
1683
+
1684
+ // Return true
1685
+ return true;
1686
+ };
1687
+ History.Adapter.bind(window,'popstate',History.onPopState);
1688
+
1689
+ /**
1690
+ * History.pushState(data,title,url)
1691
+ * Add a new State to the history object, become it, and trigger onpopstate
1692
+ * We have to trigger for HTML4 compatibility
1693
+ * @param {object} data
1694
+ * @param {string} title
1695
+ * @param {string} url
1696
+ * @return {true}
1697
+ */
1698
+ History.pushState = function(data,title,url,queue){
1699
+ //History.debug('History.pushState: called', arguments);
1700
+
1701
+ // Check the State
1702
+ if ( History.getHashByUrl(url) && History.emulated.pushState ) {
1703
+ throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
1704
+ }
1705
+
1706
+ // Handle Queueing
1707
+ if ( queue !== false && History.busy() ) {
1708
+ // Wait + Push to Queue
1709
+ //History.debug('History.pushState: we must wait', arguments);
1710
+ History.pushQueue({
1711
+ scope: History,
1712
+ callback: History.pushState,
1713
+ args: arguments,
1714
+ queue: queue
1715
+ });
1716
+ return false;
1717
+ }
1718
+
1719
+ // Make Busy + Continue
1720
+ History.busy(true);
1721
+
1722
+ // Create the newState
1723
+ var newState = History.createStateObject(data,title,url);
1724
+
1725
+ // Check it
1726
+ if ( History.isLastSavedState(newState) ) {
1727
+ // Won't be a change
1728
+ History.busy(false);
1729
+ }
1730
+ else {
1731
+ // Store the newState
1732
+ History.storeState(newState);
1733
+ History.expectedStateId = newState.id;
1734
+
1735
+ // Push the newState
1736
+ history.pushState(newState.id,newState.title,newState.url);
1737
+
1738
+ // Fire HTML5 Event
1739
+ History.Adapter.trigger(window,'popstate');
1740
+ }
1741
+
1742
+ // End pushState closure
1743
+ return true;
1744
+ };
1745
+
1746
+ /**
1747
+ * History.replaceState(data,title,url)
1748
+ * Replace the State and trigger onpopstate
1749
+ * We have to trigger for HTML4 compatibility
1750
+ * @param {object} data
1751
+ * @param {string} title
1752
+ * @param {string} url
1753
+ * @return {true}
1754
+ */
1755
+ History.replaceState = function(data,title,url,queue){
1756
+ //History.debug('History.replaceState: called', arguments);
1757
+
1758
+ // Check the State
1759
+ if ( History.getHashByUrl(url) && History.emulated.pushState ) {
1760
+ throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
1761
+ }
1762
+
1763
+ // Handle Queueing
1764
+ if ( queue !== false && History.busy() ) {
1765
+ // Wait + Push to Queue
1766
+ //History.debug('History.replaceState: we must wait', arguments);
1767
+ History.pushQueue({
1768
+ scope: History,
1769
+ callback: History.replaceState,
1770
+ args: arguments,
1771
+ queue: queue
1772
+ });
1773
+ return false;
1774
+ }
1775
+
1776
+ // Make Busy + Continue
1777
+ History.busy(true);
1778
+
1779
+ // Create the newState
1780
+ var newState = History.createStateObject(data,title,url);
1781
+
1782
+ // Check it
1783
+ if ( History.isLastSavedState(newState) ) {
1784
+ // Won't be a change
1785
+ History.busy(false);
1786
+ }
1787
+ else {
1788
+ // Store the newState
1789
+ History.storeState(newState);
1790
+ History.expectedStateId = newState.id;
1791
+
1792
+ // Push the newState
1793
+ history.replaceState(newState.id,newState.title,newState.url);
1794
+
1795
+ // Fire HTML5 Event
1796
+ History.Adapter.trigger(window,'popstate');
1797
+ }
1798
+
1799
+ // End replaceState closure
1800
+ return true;
1801
+ };
1802
+
1803
+ } // !History.emulated.pushState
1804
+
1805
+
1806
+ // ====================================================================
1807
+ // Initialise
1808
+
1809
+ /**
1810
+ * Load the Store
1811
+ */
1812
+ if ( sessionStorage ) {
1813
+ // Fetch
1814
+ try {
1815
+ History.store = JSON.parse(sessionStorage.getItem('History.store'))||{};
1816
+ }
1817
+ catch ( err ) {
1818
+ History.store = {};
1819
+ }
1820
+
1821
+ // Normalize
1822
+ History.normalizeStore();
1823
+ }
1824
+ else {
1825
+ // Default Load
1826
+ History.store = {};
1827
+ History.normalizeStore();
1828
+ }
1829
+
1830
+ /**
1831
+ * Clear Intervals on exit to prevent memory leaks
1832
+ */
1833
+ History.Adapter.bind(window,"beforeunload",History.clearAllIntervals);
1834
+ History.Adapter.bind(window,"unload",History.clearAllIntervals);
1835
+
1836
+ /**
1837
+ * Create the initial State
1838
+ */
1839
+ History.saveState(History.storeState(History.extractState(document.location.href,true)));
1840
+
1841
+ /**
1842
+ * Bind for Saving Store
1843
+ */
1844
+ if ( sessionStorage ) {
1845
+ // When the page is closed
1846
+ History.onUnload = function(){
1847
+ // Prepare
1848
+ var currentStore, item;
1849
+
1850
+ // Fetch
1851
+ try {
1852
+ currentStore = JSON.parse(sessionStorage.getItem('History.store'))||{};
1853
+ }
1854
+ catch ( err ) {
1855
+ currentStore = {};
1856
+ }
1857
+
1858
+ // Ensure
1859
+ currentStore.idToState = currentStore.idToState || {};
1860
+ currentStore.urlToId = currentStore.urlToId || {};
1861
+ currentStore.stateToId = currentStore.stateToId || {};
1862
+
1863
+ // Sync
1864
+ for ( item in History.idToState ) {
1865
+ if ( !History.idToState.hasOwnProperty(item) ) {
1866
+ continue;
1867
+ }
1868
+ currentStore.idToState[item] = History.idToState[item];
1869
+ }
1870
+ for ( item in History.urlToId ) {
1871
+ if ( !History.urlToId.hasOwnProperty(item) ) {
1872
+ continue;
1873
+ }
1874
+ currentStore.urlToId[item] = History.urlToId[item];
1875
+ }
1876
+ for ( item in History.stateToId ) {
1877
+ if ( !History.stateToId.hasOwnProperty(item) ) {
1878
+ continue;
1879
+ }
1880
+ currentStore.stateToId[item] = History.stateToId[item];
1881
+ }
1882
+
1883
+ // Update
1884
+ History.store = currentStore;
1885
+ History.normalizeStore();
1886
+
1887
+ // Store
1888
+ sessionStorage.setItem('History.store',JSON.stringify(currentStore));
1889
+ };
1890
+
1891
+ // For Internet Explorer
1892
+ History.intervalList.push(setInterval(History.onUnload,History.options.storeInterval));
1893
+
1894
+ // For Other Browsers
1895
+ History.Adapter.bind(window,'beforeunload',History.onUnload);
1896
+ History.Adapter.bind(window,'unload',History.onUnload);
1897
+
1898
+ // Both are enabled for consistency
1899
+ }
1900
+
1901
+ // Non-Native pushState Implementation
1902
+ if ( !History.emulated.pushState ) {
1903
+ // Be aware, the following is only for native pushState implementations
1904
+ // If you are wanting to include something for all browsers
1905
+ // Then include it above this if block
1906
+
1907
+ /**
1908
+ * Setup Safari Fix
1909
+ */
1910
+ if ( History.bugs.safariPoll ) {
1911
+ History.intervalList.push(setInterval(History.safariStatePoll, History.options.safariPollInterval));
1912
+ }
1913
+
1914
+ /**
1915
+ * Ensure Cross Browser Compatibility
1916
+ */
1917
+ if ( navigator.vendor === 'Apple Computer, Inc.' || (navigator.appCodeName||'') === 'Mozilla' ) {
1918
+ /**
1919
+ * Fix Safari HashChange Issue
1920
+ */
1921
+
1922
+ // Setup Alias
1923
+ History.Adapter.bind(window,'hashchange',function(){
1924
+ History.Adapter.trigger(window,'popstate');
1925
+ });
1926
+
1927
+ // Initialise Alias
1928
+ if ( History.getHash() ) {
1929
+ History.Adapter.onDomLoad(function(){
1930
+ History.Adapter.trigger(window,'hashchange');
1931
+ });
1932
+ }
1933
+ }
1934
+
1935
+ } // !History.emulated.pushState
1936
+
1937
+
1938
+ }; // History.initCore
1939
+
1940
+ // Try and Initialise History
1941
+ History.init();
1942
+
1943
+ })(window);