hat-trick 0.1.2 → 0.1.3

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