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