jquery-historyjs 0.1.0

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