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