jquery-historyjs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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);