X-Git-Url: http://git.roojs.org/?a=blobdiff_plain;f=Roo%2FHistory.js;h=91ae8d22f71197b3e0a5a4c5f89d7ca2f31ac0da;hb=0b69a11b1055a6d3a3fd0719bdd58b5a235de0e4;hp=2e89fe1af0546cb90062f8b544efe62b98f9efc3;hpb=c082b7000a7a763aee1f71f530fad62f732d2fa9;p=roojs1 diff --git a/Roo/History.js b/Roo/History.js index 2e89fe1af0..91ae8d22f7 100644 --- a/Roo/History.js +++ b/Roo/History.js @@ -1,10 +1,20 @@ /** * Originally based of this code... - refactored for Roo... - * + * https://github.com/browserstate/history.js + * History.js Core * @author Benjamin Arthur Lupton * @copyright 2010-2011 Benjamin Arthur Lupton * @license New BSD License + * + * Hackily modifyed by alan@roojs.com + * + * this is not initialized automatically.. + * must call Roo.History.init( { ... options... }); + * + * TOTALLY UNTESTED... + * + * Documentation to be done.... */ Roo.History = { @@ -185,9 +195,14 @@ Roo.History = { // Initialise History - init : function(options){ + init : function(options) + { + + var emptyFunction = function(){}; + var _this = this; - initialTitle : window.document.title, + + this.initialTitle = window.document.title; this.store = {}; this.idToState={}; this.stateToId={}; @@ -198,36 +213,111 @@ Roo.History = { Roo.apply(this,options) - // Check Load Status of Adapter - //if ( typeof this.Adapter === 'undefined' ) { - // return false; - //} - + // Check Load Status of Core - if ( typeof this.initCore !== 'undefined' ) { - this.initCore(); - } - + this.initCore(); + // Check Load Status of HTML4 Support - if ( typeof this.initHtml4 !== 'undefined' ) { - this.initHtml4(); - } + //if ( typeof this.initHtml4 !== 'undefined' ) { + // this.initHtml4(); + //} this.initEmulated(); + this.enabled = !this.emulated.pushState; if ( this.emulated.pushState ) { // Prepare - var emptyFunction = function(){}; + this.pushState = emptyFunction; this.replaceState = emptyFunction; } + this.initBugs(); + + + + + Roo.get(window).on('popstate',this.onPopState, this); + + + /** + * Load the Store + */ + if ( this.sessionStorage ) { + // Fetch + try { + this.store = JSON.parse(this.sessionStorage.getItem('Roo.History.store'))||{}; + } + catch ( err ) { + this.store = {}; + } + this.intervalList.push(setInterval(this.onUnload,this.storeInterval)); + + // For Other Browsers + Roo.get(window).on('beforeunload',this.onUnload,this); + Roo.get(window).on('unload',this.onUnload, this); + + } else { + this.onUnload = emptyFunction; + } + this.normalizeStore(); + /** + * Clear Intervals on exit to prevent memory leaks + */ + Roo.get(window).on('unload',this.clearAllIntervals, this); + + /** + * Create the initial State + */ + this.saveState(this.storeState(this.extractState(this.getLocationHref(),true))); + + + // Non-Native pushState Implementation + if ( !this.emulated.pushState ) { + // Be aware, the following is only for native pushState implementations + // If you are wanting to include something for all browsers + // Then include it above this if block + + /** + * Setup Safari Fix + */ + if ( this.bugs.safariPoll ) { + this.intervalList.push(setInterval(this.safariStatePoll, this.safariPollInterval)); + } + + /** + * Ensure Cross Browser Compatibility + */ + //if ( window.navigator.vendor === 'Apple Computer, Inc.' || (window.navigator.appCodeName||'') === 'Mozilla' ) { + if (Roo.isSafari) { + //code + + /** + * Fix Safari HashChange Issue + */ + + // Setup Alias + Roo.get(window).on('hashchange',function(){ + Roo.get(window).fireEvent('popstate'); + }, this); + + // Initialise Alias + if ( this.getHash() ) { + Roo.onReady(function(){ + Roo.get(window).fireEvent('hashchange'); + }); + } + } + + } // !History.emulated.pushState + + // Return true @@ -372,9 +462,38 @@ Roo.History = { (this.isInternetExplorer() && this.getInternetExplorerMajorVersion() < 8) ); - } + }, - + initBugs : function () + { + + + + /** + * Safari 5 and Safari iOS 4 fail to return to the correct state once a hash is replaced by a `replaceState` call + * https://bugs.webkit.org/show_bug.cgi?id=56249 + */ + this.bugs.setHash = Boolean(!this.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' + && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)); + + /** + * Safari 5 and Safari iOS 4 sometimes fail to apply the state change under busy conditions + * https://bugs.webkit.org/show_bug.cgi?id=42940 + */ + this.bugs.safariPoll = Boolean(!this.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' + && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)); + + /** + * MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function) + */ + this.bugs.ieDoubleCheck = Boolean(this.isInternetExplorer() && this.getInternetExplorerMajorVersion() < 8); + + /** + * MSIE 6 requires the entire hash to be encoded for the hashes to trigger the onHashChange event + */ + this.bugs.hashEscape = Boolean(this.isInternetExplorer() && this.getInternetExplorerMajorVersion() < 7); + }, + /** * isEmptyObject(obj) @@ -382,14 +501,14 @@ Roo.History = { * @param {Object} obj * @return {boolean} */ - isEmptyObject = function(obj) { + isEmptyObject : function(obj) { for ( var name in obj ) { if ( obj.hasOwnProperty(name) ) { return false; } } return true; - }; + }, /** * cloneObject(obj) @@ -397,7 +516,7 @@ Roo.History = { * @param {Object} obj * @return {Object} */ - cloneObject = function(obj) { + cloneObject : function(obj) { var hash,newObj; if ( obj ) { hash = JSON.stringify(obj); @@ -407,7 +526,7 @@ Roo.History = { newObj = {}; } return newObj; - }; + }, // ==================================================================== @@ -418,7 +537,7 @@ Roo.History = { * Turns "http://mysite.com/dir/page.html?asd" into "http://mysite.com" * @return {String} rootUrl */ - getRootUrl = function(){ + getRootUrl : function(){ // Create var rootUrl = window.document.location.protocol+'//'+(window.document.location.hostname||window.document.location.host); if ( window.document.location.port||false ) { @@ -428,14 +547,14 @@ Roo.History = { // Return return rootUrl; - }; + }, /** * getBaseHref() * Fetches the `href` attribute of the `` element if it exists * @return {String} baseHref */ - getBaseHref = function(){ + getBaseHref : function(){ // Create var baseElements = window.document.getElementsByTagName('base'), @@ -455,27 +574,27 @@ Roo.History = { // Return return baseHref; - }; + }, /** * getBaseUrl() * Fetches the baseHref or basePageUrl or rootUrl (whichever one exists first) * @return {String} baseUrl */ - getBaseUrl = function(){ + getBaseUrl : function(){ // Create var baseUrl = this.getBaseHref()||this.getBasePageUrl()||this.getRootUrl(); // Return return baseUrl; - }; + }, /** * getPageUrl() * Fetches the URL of the current page * @return {String} pageUrl */ - getPageUrl = function(){ + getPageUrl : function(){ // Fetch var State = this.getState(false,false), @@ -489,14 +608,14 @@ Roo.History = { // Return return pageUrl; - }; + }, /** * getBasePageUrl() * Fetches the Url of the directory of the current page * @return {String} basePageUrl */ - getBasePageUrl = function(){ + getBasePageUrl : function(){ // Create var basePageUrl = (this.getLocationHref()).replace(/[#\?].*/,'').replace(/[^\/]+$/,function(part,index,string){ return (/[^\/]$/).test(part) ? '' : part; @@ -504,7 +623,7 @@ Roo.History = { // Return return basePageUrl; - }; + }, /** * getFullUrl(url) @@ -513,7 +632,7 @@ Roo.History = { * @param {Boolean} allowBaseHref * @return {string} fullUrl */ - getFullUrl = function(url,allowBaseHref){ + getFullUrl : function(url,allowBaseHref){ // Prepare var fullUrl = url, firstChar = url.substring(0,1); allowBaseHref = (typeof allowBaseHref === 'undefined') ? true : allowBaseHref; @@ -550,7 +669,7 @@ Roo.History = { // Return return fullUrl.replace(/\#$/,''); - }; + }, /** * getShortUrl(url) @@ -558,7 +677,7 @@ Roo.History = { * @param {string} url * @return {string} url */ - getShortUrl = function(url){ + getShortUrl : function(url){ // Prepare var shortUrl = url, baseUrl = this.getBaseUrl(), rootUrl = this.getRootUrl(); @@ -583,7 +702,7 @@ Roo.History = { // Return return shortUrl; - }; + }, /** * getLocationHref(document) @@ -595,7 +714,7 @@ Roo.History = { * @param {object} document * @return {string} url */ - getLocationHref = function(doc) { + getLocationHref : function(doc) { doc = doc || window.document; // most of the time, this will be true @@ -616,7 +735,7 @@ Roo.History = { return doc.location.href; return doc.URL || doc.location.href; - }; + }, @@ -624,11 +743,13 @@ Roo.History = { * noramlizeStore() * Noramlize the store by adding necessary values */ - normalizeStore = function(){ + normalizeStore : function() + { + this.store.idToState = this.store.idToState||{}; this.store.urlToId = this.store.urlToId||{}; this.store.stateToId = this.store.stateToId||{}; - }; + }, /** * getState() @@ -658,1342 +779,1265 @@ Roo.History = { // Return return State; - }; + }, - /** - * getIdByState(State) - * Gets a ID for a State - * @param {State} newState - * @return {String} id - */ - getIdByState = function(newState){ + /** + * getIdByState(State) + * Gets a ID for a State + * @param {State} newState + * @return {String} id + */ + getIdByState : function(newState){ - // Fetch ID - var id = this.extractId(newState.url), - str; + // Fetch ID + var id = this.extractId(newState.url), + str; - if ( !id ) { - // Find ID via State String - str = this.getStateString(newState); - if ( typeof this.stateToId[str] !== 'undefined' ) { - id = this.stateToId[str]; - } - else if ( typeof this.store.stateToId[str] !== 'undefined' ) { - id = this.store.stateToId[str]; - } - else { - // Generate a new ID - while ( true ) { - id = (new Date()).getTime() + String(Math.random()).replace(/\D/g,''); - if ( typeof this.idToState[id] === 'undefined' && typeof this.store.idToState[id] === 'undefined' ) { - break; - } - } - - // Apply the new State to the ID - this.stateToId[str] = id; - this.idToState[id] = newState; - } - } + if ( !id ) { + // Find ID via State String + str = this.getStateString(newState); + if ( typeof this.stateToId[str] !== 'undefined' ) { + id = this.stateToId[str]; + } + else if ( typeof this.store.stateToId[str] !== 'undefined' ) { + id = this.store.stateToId[str]; + } + else { + // Generate a new ID + while ( true ) { + id = (new Date()).getTime() + String(Math.random()).replace(/\D/g,''); + if ( typeof this.idToState[id] === 'undefined' && typeof this.store.idToState[id] === 'undefined' ) { + break; + } + } + + // Apply the new State to the ID + this.stateToId[str] = id; + this.idToState[id] = newState; + } + } - // Return ID - return id; - }; + // Return ID + return id; + }, - /** - * normalizeState(State) - * Expands a State Object - * @param {object} State - * @return {object} - */ - normalizeState = function(oldState){ - // Variables - var newState, dataNotEmpty; + /** + * normalizeState(State) + * Expands a State Object + * @param {object} State + * @return {object} + */ + normalizeState : function(oldState){ + // Variables + var newState, dataNotEmpty; - // Prepare - if ( !oldState || (typeof oldState !== 'object') ) { - oldState = {}; - } + // Prepare + if ( !oldState || (typeof oldState !== 'object') ) { + oldState = {}; + } - // Check - if ( typeof oldState.normalized !== 'undefined' ) { - return oldState; - } + // Check + if ( typeof oldState.normalized !== 'undefined' ) { + return oldState; + } - // Adjust - if ( !oldState.data || (typeof oldState.data !== 'object') ) { - oldState.data = {}; - } + // Adjust + if ( !oldState.data || (typeof oldState.data !== 'object') ) { + oldState.data = {}; + } - // ---------------------------------------------------------------- + // ---------------------------------------------------------------- - // Create - newState = {}; - newState.normalized = true; - newState.title = oldState.title||''; - newState.url = this.getFullUrl(oldState.url?oldState.url:(this.getLocationHref())); - newState.hash = this.getShortUrl(newState.url); - newState.data = this.cloneObject(oldState.data); + // Create + newState = {}; + newState.normalized = true; + newState.title = oldState.title||''; + newState.url = this.getFullUrl(oldState.url?oldState.url:(this.getLocationHref())); + newState.hash = this.getShortUrl(newState.url); + newState.data = this.cloneObject(oldState.data); + + // Fetch ID + newState.id = this.getIdByState(newState); + + // ---------------------------------------------------------------- + + // Clean the URL + newState.cleanUrl = newState.url.replace(/\??\&_suid.*/,''); + newState.url = newState.cleanUrl; + + // Check to see if we have more than just a url + dataNotEmpty = !this.isEmptyObject(newState.data); + + // Apply + if ( (newState.title || dataNotEmpty) && this.disableSuid !== true ) { + // Add ID to Hash + newState.hash = this.getShortUrl(newState.url).replace(/\??\&_suid.*/,''); + if ( !/\?/.test(newState.hash) ) { + newState.hash += '?'; + } + newState.hash += '&_suid='+newState.id; + } - // Fetch ID - newState.id = this.getIdByState(newState); + // Create the Hashed URL + newState.hashedUrl = this.getFullUrl(newState.hash); - // ---------------------------------------------------------------- + // ---------------------------------------------------------------- - // Clean the URL - newState.cleanUrl = newState.url.replace(/\??\&_suid.*/,''); - newState.url = newState.cleanUrl; + // Update the URL if we have a duplicate + if ( (this.emulated.pushState || this.bugs.safariPoll) && this.hasUrlDuplicate(newState) ) { + newState.url = newState.hashedUrl; + } - // Check to see if we have more than just a url - dataNotEmpty = !this.isEmptyObject(newState.data); + // ---------------------------------------------------------------- - // Apply - if ( (newState.title || dataNotEmpty) && this.disableSuid !== true ) { - // Add ID to Hash - newState.hash = this.getShortUrl(newState.url).replace(/\??\&_suid.*/,''); - if ( !/\?/.test(newState.hash) ) { - newState.hash += '?'; - } - newState.hash += '&_suid='+newState.id; - } + // Return + return newState; + }, - // Create the Hashed URL - newState.hashedUrl = this.getFullUrl(newState.hash); + /** + * createStateObject(data,title,url) + * Creates a object based on the data, title and url state params + * @param {object} data + * @param {string} title + * @param {string} url + * @return {object} + */ + createStateObject : function(data,title,url){ + // Hashify + var State = { + 'data': data, + 'title': title, + 'url': url + }; + + // Expand the State + State = this.normalizeState(State); + + // Return object + return State; + }, - // ---------------------------------------------------------------- + /** + * getStateById(id) + * Get a state by it's UID + * @param {String} id + */ + getStateById : function(id){ + // Prepare + id = String(id); - // Update the URL if we have a duplicate - if ( (this.emulated.pushState || this.bugs.safariPoll) && this.hasUrlDuplicate(newState) ) { - newState.url = newState.hashedUrl; - } + // Retrieve + var State = this.idToState[id] || this.store.idToState[id] || undefined; - // ---------------------------------------------------------------- + // Return State + return State; + }, - // Return - return newState; - }; + /** + * Get a State's String + * @param {State} passedState + */ + getStateString : function(passedState){ + // Prepare + var State, cleanedState, str; - /** - * createStateObject(data,title,url) - * Creates a object based on the data, title and url state params - * @param {object} data - * @param {string} title - * @param {string} url - * @return {object} - */ - createStateObject = function(data,title,url){ - // Hashify - var State = { - 'data': data, - 'title': title, - 'url': url - }; + // Fetch + State = this.normalizeState(passedState); - // Expand the State - State = this.normalizeState(State); + // Clean + cleanedState = { + data: State.data, + title: passedState.title, + url: passedState.url + }; - // Return object - return State; - }; + // Fetch + str = JSON.stringify(cleanedState); - /** - * getStateById(id) - * Get a state by it's UID - * @param {String} id - */ - getStateById = function(id){ - // Prepare - id = String(id); + // Return + return str; + }, - // Retrieve - var State = this.idToState[id] || this.store.idToState[id] || undefined; + /** + * Get a State's ID + * @param {State} passedState + * @return {String} id + */ + getStateId : function(passedState){ + // Prepare + var State, id; - // Return State - return State; - }; + // Fetch + State = this.normalizeState(passedState); - /** - * Get a State's String - * @param {State} passedState - */ - getStateString = function(passedState){ - // Prepare - var State, cleanedState, str; + // Fetch + id = State.id; - // Fetch - State = this.normalizeState(passedState); + // Return + return id; + }, - // Clean - cleanedState = { - data: State.data, - title: passedState.title, - url: passedState.url - }; + /** + * getHashByState(State) + * Creates a Hash for the State Object + * @param {State} passedState + * @return {String} hash + */ + getHashByState : function(passedState){ + // Prepare + var State, hash; - // Fetch - str = JSON.stringify(cleanedState); + // Fetch + State = this.normalizeState(passedState); - // Return - return str; - }; + // Hash + hash = State.hash; - /** - * Get a State's ID - * @param {State} passedState - * @return {String} id - */ - getStateId = function(passedState){ - // Prepare - var State, id; + // Return + return hash; + }, - // Fetch - State = this.normalizeState(passedState); + /** + * extractId(url_or_hash) + * Get a State ID by it's URL or Hash + * @param {string} url_or_hash + * @return {string} id + */ + extractId : function ( url_or_hash ) { + // Prepare + var id,parts,url, tmp; - // Fetch - id = State.id; + // Extract + + // If the URL has a #, use the id from before the # + if (url_or_hash.indexOf('#') != -1) + { + tmp = url_or_hash.split("#")[0]; + } + else + { + tmp = url_or_hash; + } + + parts = /(.*)\&_suid=([0-9]+)$/.exec(tmp); + url = parts ? (parts[1]||url_or_hash) : url_or_hash; + id = parts ? String(parts[2]||'') : ''; - // Return - return id; - }; + // Return + return id||false; + }, - /** - * getHashByState(State) - * Creates a Hash for the State Object - * @param {State} passedState - * @return {String} hash - */ - getHashByState = function(passedState){ - // Prepare - var State, hash; + /** + * isTraditionalAnchor + * Checks to see if the url is a traditional anchor or not + * @param {String} url_or_hash + * @return {Boolean} + */ + isTraditionalAnchor : function(url_or_hash){ + // Check + var isTraditional = !(/[\/\?\.]/.test(url_or_hash)); - // Fetch - State = this.normalizeState(passedState); + // Return + return isTraditional; + }, - // Hash - hash = State.hash; + /** + * extractState + * Get a State by it's URL or Hash + * @param {String} url_or_hash + * @return {State|null} + */ + extractState : function(url_or_hash,create){ + // Prepare + var State = null, id, url; + create = create||false; - // Return - return hash; - }; + // Fetch SUID + id = this.extractId(url_or_hash); + if ( id ) { + State = this.getStateById(id); + } - /** - * extractId(url_or_hash) - * Get a State ID by it's URL or Hash - * @param {string} url_or_hash - * @return {string} id - */ - this.extractId = function ( url_or_hash ) { - // Prepare - var id,parts,url, tmp; + // Fetch SUID returned no State + if ( !State ) { + // Fetch URL + url = this.getFullUrl(url_or_hash); - // Extract - - // If the URL has a #, use the id from before the # - if (url_or_hash.indexOf('#') != -1) - { - tmp = url_or_hash.split("#")[0]; - } - else - { - tmp = url_or_hash; - } - - parts = /(.*)\&_suid=([0-9]+)$/.exec(tmp); - url = parts ? (parts[1]||url_or_hash) : url_or_hash; - id = parts ? String(parts[2]||'') : ''; + // Check URL + id = this.getIdByUrl(url)||false; + if ( id ) { + State = this.getStateById(id); + } - // Return - return id||false; - }; + // Create State + if ( !State && create && !this.isTraditionalAnchor(url_or_hash) ) { + State = this.createStateObject(null,null,url); + } + } - /** - * isTraditionalAnchor - * Checks to see if the url is a traditional anchor or not - * @param {String} url_or_hash - * @return {Boolean} - */ - isTraditionalAnchor = function(url_or_hash){ - // Check - var isTraditional = !(/[\/\?\.]/.test(url_or_hash)); + // Return + return State; + }, - // Return - return isTraditional; - }; + /** + * getIdByUrl() + * Get a State ID by a State URL + */ + getIdByUrl : function(url){ + // Fetch + var id = this.urlToId[url] || this.store.urlToId[url] || undefined; - /** - * extractState - * Get a State by it's URL or Hash - * @param {String} url_or_hash - * @return {State|null} - */ - extractState = function(url_or_hash,create){ - // Prepare - var State = null, id, url; - create = create||false; + // Return + return id; + }, - // Fetch SUID - id = this.extractId(url_or_hash); - if ( id ) { - State = this.getStateById(id); - } + /** + * getLastSavedState() + * Get an object containing the data, title and url of the current state + * @return {Object} State + */ + getLastSavedState : function(){ + return this.savedStates[this.savedStates.length-1]||undefined; + }, - // Fetch SUID returned no State - if ( !State ) { - // Fetch URL - url = this.getFullUrl(url_or_hash); + /** + * getLastStoredState() + * Get an object containing the data, title and url of the current state + * @return {Object} State + */ + getLastStoredState : function(){ + return this.storedStates[this.storedStates.length-1]||undefined; + }, - // Check URL - id = this.getIdByUrl(url)||false; - if ( id ) { - State = this.getStateById(id); - } + /** + * hasUrlDuplicate + * Checks if a Url will have a url conflict + * @param {Object} newState + * @return {Boolean} hasDuplicate + */ + hasUrlDuplicate : function(newState) { + // Prepare + var hasDuplicate = false, + oldState; - // Create State - if ( !State && create && !this.isTraditionalAnchor(url_or_hash) ) { - State = this.createStateObject(null,null,url); - } - } + // Fetch + oldState = this.extractState(newState.url); - // Return - return State; - }; + // Check + hasDuplicate = oldState && oldState.id !== newState.id; - /** - * getIdByUrl() - * Get a State ID by a State URL - */ - getIdByUrl = function(url){ - // Fetch - var id = this.urlToId[url] || this.store.urlToId[url] || undefined; + // Return + return hasDuplicate; + }, - // Return - return id; - }; + /** + * storeState + * Store a State + * @param {Object} newState + * @return {Object} newState + */ + storeState : function(newState){ + // Store the State + this.urlToId[newState.url] = newState.id; - /** - * getLastSavedState() - * Get an object containing the data, title and url of the current state - * @return {Object} State - */ - getLastSavedState = function(){ - return this.savedStates[this.savedStates.length-1]||undefined; - }; + // Push the State + this.storedStates.push(this.cloneObject(newState)); - /** - * getLastStoredState() - * Get an object containing the data, title and url of the current state - * @return {Object} State - */ - getLastStoredState = function(){ - return this.storedStates[this.storedStates.length-1]||undefined; - }; + // Return newState + return newState; + }, - /** - * hasUrlDuplicate - * Checks if a Url will have a url conflict - * @param {Object} newState - * @return {Boolean} hasDuplicate - */ - hasUrlDuplicate = function(newState) { - // Prepare - var hasDuplicate = false, - oldState; - - // Fetch - oldState = this.extractState(newState.url); - - // Check - hasDuplicate = oldState && oldState.id !== newState.id; - - // Return - return hasDuplicate; - }; - - /** - * storeState - * Store a State - * @param {Object} newState - * @return {Object} newState - */ - storeState = function(newState){ - // Store the State - this.urlToId[newState.url] = newState.id; - - // Push the State - this.storedStates.push(this.cloneObject(newState)); - - // Return newState - return newState; - }; - - /** - * isLastSavedState(newState) - * Tests to see if the state is the last state - * @param {Object} newState - * @return {boolean} isLast - */ - isLastSavedState = function(newState){ - // Prepare - var isLast = false, - newId, oldState, oldId; + /** + * isLastSavedState(newState) + * Tests to see if the state is the last state + * @param {Object} newState + * @return {boolean} isLast + */ + isLastSavedState : function(newState){ + // Prepare + var isLast = false, + newId, oldState, oldId; - // Check - if ( this.savedStates.length ) { - newId = newState.id; - oldState = this.getLastSavedState(); - oldId = oldState.id; + // Check + if ( this.savedStates.length ) { + newId = newState.id; + oldState = this.getLastSavedState(); + oldId = oldState.id; - // Check - isLast = (newId === oldId); - } + // Check + isLast = (newId === oldId); + } - // Return - return isLast; - }; + // Return + return isLast; + }, - /** - * saveState - * Push a State - * @param {Object} newState - * @return {boolean} changed - */ - saveState = function(newState){ - // Check Hash - if ( this.isLastSavedState(newState) ) { - return false; - } + /** + * saveState + * Push a State + * @param {Object} newState + * @return {boolean} changed + */ + saveState : function(newState){ + // Check Hash + if ( this.isLastSavedState(newState) ) { + return false; + } - // Push the State - this.savedStates.push(this.cloneObject(newState)); + // Push the State + this.savedStates.push(this.cloneObject(newState)); - // Return true - return true; - }; + // Return true + return true; + }, - /** - * getStateByIndex() - * Gets a state by the index - * @param {integer} index - * @return {Object} - */ - getStateByIndex = function(index){ - // Prepare - var State = null; + /** + * getStateByIndex() + * Gets a state by the index + * @param {integer} index + * @return {Object} + */ + getStateByIndex : function(index){ + // Prepare + var State = null; - // Handle - if ( typeof index === 'undefined' ) { - // Get the last inserted - State = this.savedStates[this.savedStates.length-1]; - } - else if ( index < 0 ) { - // Get from the end - State = this.savedStates[this.savedStates.length+index]; - } - else { - // Get from the beginning - State = this.savedStates[index]; - } + // Handle + if ( typeof index === 'undefined' ) { + // Get the last inserted + State = this.savedStates[this.savedStates.length-1]; + } + else if ( index < 0 ) { + // Get from the end + State = this.savedStates[this.savedStates.length+index]; + } + else { + // Get from the beginning + State = this.savedStates[index]; + } - // Return State - return State; - }; - - /** - * getCurrentIndex() - * Gets the current index - * @return (integer) - */ - getCurrentIndex = function(){ - // Prepare - var index = null; - - // No states saved - if(this.savedStates.length < 1) { - index = 0; - } - else { - index = this.savedStates.length-1; - } - return index; - }; + // Return State + return State; + }, + + /** + * getCurrentIndex() + * Gets the current index + * @return (integer) + */ + getCurrentIndex : function(){ + // Prepare + var index = null; + + // No states saved + if(this.savedStates.length < 1) { + index = 0; + } + else { + index = this.savedStates.length-1; + } + return index; + }, - // ==================================================================== - // Hash Helpers + // ==================================================================== + // Hash Helpers - /** - * getHash() - * @param {Location=} location - * Gets the current document hash - * Note: unlike location.hash, this is guaranteed to return the escaped hash in all browsers - * @return {string} - */ - getHash = function(doc){ - var url = this.getLocationHref(doc), - hash; - hash = this.getHashByUrl(url); - return hash; - }; + /** + * getHash() + * @param {Location=} location + * Gets the current document hash + * Note: unlike location.hash, this is guaranteed to return the escaped hash in all browsers + * @return {string} + */ + getHash : function(doc){ + var url = this.getLocationHref(doc), + hash; + hash = this.getHashByUrl(url); + return hash; + }, - /** - * unescapeHash() - * normalize and Unescape a Hash - * @param {String} hash - * @return {string} - */ - unescapeHash = function(hash){ - // Prepare - var result = this.normalizeHash(hash); + /** + * unescapeHash() + * normalize and Unescape a Hash + * @param {String} hash + * @return {string} + */ + unescapeHash : function(hash){ + // Prepare + var result = this.normalizeHash(hash); - // Unescape hash - result = decodeURIComponent(result); + // Unescape hash + result = decodeURIComponent(result); - // Return result - return result; - }; + // Return result + return result; + }, - /** - * normalizeHash() - * normalize a hash across browsers - * @return {string} - */ - normalizeHash = function(hash){ - // Prepare - var result = hash.replace(/[^#]*#/,'').replace(/#.*/, ''); + /** + * normalizeHash() + * normalize a hash across browsers + * @return {string} + */ + normalizeHash : function(hash){ + // Prepare + var result = hash.replace(/[^#]*#/,'').replace(/#.*/, ''); - // Return result - return result; - }; + // Return result + return result; + }, - /** - * setHash(hash) - * Sets the document hash - * @param {string} hash - * @return {Roo.History} - */ - setHash = function(hash,queue){ - // Prepare - var State, pageUrl; - - // Handle Queueing - if ( queue !== false && this.busy() ) { - // Wait + Push to Queue - //this.debug('this.setHash: we must wait', arguments); - this.pushQueue({ - scope: this. - callback: this.setHash, - args: arguments, - queue: queue - }); - return false; - } + /** + * setHash(hash) + * Sets the document hash + * @param {string} hash + * @return {Roo.History} + */ + setHash : function(hash,queue){ + // Prepare + var State, pageUrl; + + // Handle Queueing + if ( queue !== false && this.busy() ) { + // Wait + Push to Queue + //this.debug('this.setHash: we must wait', arguments); + this.pushQueue({ + scope: this, + callback: this.setHash, + args: arguments, + queue: queue + }); + return false; + } - // Log - //this.debug('this.setHash: called',hash); + // Log + //this.debug('this.setHash: called',hash); - // Make Busy + Continue - this.busy(true); + // Make Busy + Continue + this.busy(true); - // Check if hash is a state - State = this.extractState(hash,true); - if ( State && !this.emulated.pushState ) { - // Hash is a state so skip the setHash - //this.debug('this.setHash: Hash is a state so skipping the hash set with a direct pushState call',arguments); + // Check if hash is a state + State = this.extractState(hash,true); + if ( State && !this.emulated.pushState ) { + // Hash is a state so skip the setHash + //this.debug('this.setHash: Hash is a state so skipping the hash set with a direct pushState call',arguments); - // PushState - this.pushState(State.data,State.title,State.url,false); - } - else if ( this.getHash() !== hash ) { - // Hash is a proper hash, so apply it + // PushState + this.pushState(State.data,State.title,State.url,false); + } + else if ( this.getHash() !== hash ) { + // Hash is a proper hash, so apply it - // Handle browser bugs - if ( this.bugs.setHash ) { - // Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249 + // Handle browser bugs + if ( this.bugs.setHash ) { + // Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249 - // Fetch the base page - pageUrl = this.getPageUrl(); + // Fetch the base page + pageUrl = this.getPageUrl(); - // Safari hash apply - this.pushState(null,null,pageUrl+'#'+hash,false); - } - else { - // Normal hash apply - window.document.location.hash = hash; - } - } + // Safari hash apply + this.pushState(null,null,pageUrl+'#'+hash,false); + } + else { + // Normal hash apply + window.document.location.hash = hash; + } + } - // Chain - return this; - }; + // Chain + return this; + }, - /** - * escape() - * normalize and Escape a Hash - * @return {string} - */ - escapeHash = function(hash){ - // Prepare - var result = normalizeHash(hash); - - // Escape hash - result = window.encodeURIComponent(result); - - // IE6 Escape Bug - if ( !this.bugs.hashEscape ) { - // Restore common parts - result = result - .replace(/\%21/g,'!') - .replace(/\%26/g,'&') - .replace(/\%3D/g,'=') - .replace(/\%3F/g,'?'); - } + /** + * escape() + * normalize and Escape a Hash + * @return {string} + */ + escapeHash : function(hash){ + // Prepare + var result = normalizeHash(hash); + + // Escape hash + result = window.encodeURIComponent(result); + + // IE6 Escape Bug + if ( !this.bugs.hashEscape ) { + // Restore common parts + result = result + .replace(/\%21/g,'!') + .replace(/\%26/g,'&') + .replace(/\%3D/g,'=') + .replace(/\%3F/g,'?'); + } - // Return result - return result; - }; + // Return result + return result; + }, - /** - * getHashByUrl(url) - * Extracts the Hash from a URL - * @param {string} url - * @return {string} url - */ - getHashByUrl = function(url){ - // Extract the hash - var hash = String(url) - .replace(/([^#]*)#?([^#]*)#?(.*)/, '$2') - ; + /** + * getHashByUrl(url) + * Extracts the Hash from a URL + * @param {string} url + * @return {string} url + */ + getHashByUrl : function(url){ + // Extract the hash + var hash = String(url) + .replace(/([^#]*)#?([^#]*)#?(.*)/, '$2') + ; - // Unescape hash - hash = this.unescapeHash(hash); + // Unescape hash + hash = this.unescapeHash(hash); - // Return hash - return hash; - }; + // Return hash + return hash; + }, - /** - * setTitle(title) - * Applies the title to the document - * @param {State} newState - * @return {Boolean} - */ - setTitle = function(newState){ - // Prepare - var title = newState.title, - firstState; - - // Initial - if ( !title ) { - firstState = this.getStateByIndex(0); - if ( firstState && firstState.url === newState.url ) { - title = firstState.title||this.initialTitle; - } - } + /** + * setTitle(title) + * Applies the title to the document + * @param {State} newState + * @return {Boolean} + */ + setTitle : function(newState){ + // Prepare + var title = newState.title, + firstState; + + // Initial + if ( !title ) { + firstState = this.getStateByIndex(0); + if ( firstState && firstState.url === newState.url ) { + title = firstState.title||this.initialTitle; + } + } - // Apply - try { - window.document.getElementsByTagName('title')[0].innerHTML = title.replace('<','<').replace('>','>').replace(' & ',' & '); - } - catch ( Exception ) { } - window.document.title = title; + // Apply + try { + window.document.getElementsByTagName('title')[0].innerHTML = title.replace('<','<').replace('>','>').replace(' & ',' & '); + } + catch ( Exception ) { } + window.document.title = title; - // Chain - return this; - }; + // Chain + return this; + }, - // ==================================================================== - // Queueing + // ==================================================================== + // Queueing - /** - * busy(value) - * @param {boolean} value [optional] - * @return {boolean} busy - */ - busy = function(value){ - // Apply - if ( typeof value !== 'undefined' ) { - //this.debug('this.busy: changing ['+(this.busy.flag||false)+'] to ['+(value||false)+']', this.queues.length); - this.busy_flag = value; - } - // Default - else if ( typeof this.busy_flag === 'undefined' ) { - this.busy_flag = false; - } + /** + * busy(value) + * @param {boolean} value [optional] + * @return {boolean} busy + */ + busy : function(value){ + // Apply + + var _this = this; + if ( typeof value !== 'undefined' ) { + //this.debug('this.busy: changing ['+(this.busy.flag||false)+'] to ['+(value||false)+']', this.queues.length); + this.busy_flag = value; + } + // Default + else if ( typeof this.busy_flag === 'undefined' ) { + this.busy_flag = false; + } - // Queue - if ( !this.busy_flag ) { - // Execute the next item in the queue - window.clearTimeout(this.busy.timeout); - var fireNext = function(){ - var i, queue, item; - if ( this.busy_flag ) return; - for ( i=this.queues.length-1; i >= 0; --i ) { - queue = this.queues[i]; - if ( queue.length === 0 ) continue; - item = queue.shift(); - this.fireQueueItem(item); - this.busy.timeout = window.setTimeout(fireNext,this.busyDelay); - } - }; - this.busy.timeout = window.setTimeout(fireNext,this.busyDelay); - } + // Queue + if ( !this.busy_flag ) { + + + + // Execute the next item in the queue + window.clearTimeout(this.busy.timeout); + var fireNext = function(){ + var i, queue, item; + if ( _this.busy_flag ) return; + for ( i=_this.queues.length-1; i >= 0; --i ) { + queue = _this.queues[i]; + if ( queue.length === 0 ) continue; + item = queue.shift(); + _this.fireQueueItem(item); + _this.busy.timeout = window.setTimeout(fireNext,_this.busyDelay); + } + }; + this.busy.timeout = window.setTimeout(fireNext,this.busyDelay); + } - // Return - return this.busy_flag; - }; + // Return + return this.busy_flag; + }, - + - /** - * fireQueueItem(item) - * Fire a Queue Item - * @param {Object} item - * @return {Mixed} result - */ - fireQueueItem = function(item){ - return item.callback.apply(item.scope||this,item.args||[]); - }; + /** + * fireQueueItem(item) + * Fire a Queue Item + * @param {Object} item + * @return {Mixed} result + */ + fireQueueItem : function(item){ + return item.callback.apply(item.scope||this,item.args||[]); + }, - /** - * pushQueue(callback,args) - * Add an item to the queue - * @param {Object} item [scope,callback,args,queue] - */ - pushQueue = function(item){ - // Prepare the queue - this.queues[item.queue||0] = this.queues[item.queue||0]||[]; + /** + * pushQueue(callback,args) + * Add an item to the queue + * @param {Object} item [scope,callback,args,queue] + */ + pushQueue : function(item){ + // Prepare the queue + this.queues[item.queue||0] = this.queues[item.queue||0]||[]; - // Add to the queue - this.queues[item.queue||0].push(item); + // Add to the queue + this.queues[item.queue||0].push(item); - // Chain - return this; - }; + // Chain + return this; + }, - /** - * queue (item,queue), (func,queue), (func), (item) - * Either firs the item now if not busy, or adds it to the queue - */ - queue = function(item,queue){ - // Prepare - if ( typeof item === 'function' ) { - item = { - callback: item - }; - } - if ( typeof queue !== 'undefined' ) { - item.queue = queue; - } + /** + * queue (item,queue), (func,queue), (func), (item) + * Either firs the item now if not busy, or adds it to the queue + */ + queue : function(item,queue){ + // Prepare + if ( typeof item === 'function' ) { + item = { + callback: item + }; + } + if ( typeof queue !== 'undefined' ) { + item.queue = queue; + } - // Handle - if ( this.busy() ) { - this.pushQueue(item); - } else { - this.fireQueueItem(item); - } + // Handle + if ( this.busy() ) { + this.pushQueue(item); + } else { + this.fireQueueItem(item); + } - // Chain - return this; - }; + // Chain + return this; + }, - /** - * clearQueue() - * Clears the Queue - */ - clearQueue = function(){ - this.busy_flag = false; - this.queues = []; - return this; - }; + /** + * clearQueue() + * Clears the Queue + */ + clearQueue : function(){ + this.busy_flag = false; + this.queues = []; + return this; + }, - /** - * doubleCheckComplete() - * Complete a double check - * @return {Roo.History} - */ - doubleCheckComplete = function(){ - // Update - this.stateChanged = true; + /** + * doubleCheckComplete() + * Complete a double check + * @return {Roo.History} + */ + doubleCheckComplete : function(){ + // Update + this.stateChanged = true; - // Clear - this.doubleCheckClear(); + // Clear + this.doubleCheckClear(); - // Chain - return this;; - }; + // Chain + return this;; + }, - /** - * doubleCheckClear() - * Clear a double check - * @return {Roo.History} - */ - doubleCheckClear = function(){ - // Clear - if ( this.doubleChecker ) { - window.clearTimeout(this.doubleChecker); - this.doubleChecker = false; - } + /** + * doubleCheckClear() + * Clear a double check + * @return {Roo.History} + */ + doubleCheckClear : function(){ + // Clear + if ( this.doubleChecker ) { + window.clearTimeout(this.doubleChecker); + this.doubleChecker = false; + } - // Chain - return this; - }; + // Chain + return this; + }, - /** - * doubleCheck() - * Create a double check - * @return {Roo.History} - */ - doubleCheck = function(tryAgain){ - // Reset - this.stateChanged = false; - this.doubleCheckClear(); - - // Fix IE6,IE7 bug where calling history.back or history.forward does not actually change the hash (whereas doing it manually does) - // Fix Safari 5 bug where sometimes the state does not change: https://bugs.webkit.org/show_bug.cgi?id=42940 - if ( this.bugs.ieDoubleCheck ) { - // Apply Check - this.doubleChecker = window.setTimeout( - function(){ - this.doubleCheckClear(); - if ( !this.stateChanged ) { - //this.debug('History.doubleCheck: State has not yet changed, trying again', arguments); - // Re-Attempt - tryAgain(); - } - return true; - }, - this.doubleCheckInterval - ); - } + /** + * doubleCheck() + * Create a double check + * @return {Roo.History} + */ + doubleCheck : function(tryAgain) + { + var _this = this; + // Reset + this.stateChanged = false; + this.doubleCheckClear(); + + // Fix IE6,IE7 bug where calling history.back or history.forward does not actually change the hash (whereas doing it manually does) + // Fix Safari 5 bug where sometimes the state does not change: https://bugs.webkit.org/show_bug.cgi?id=42940 + if ( this.bugs.ieDoubleCheck ) { + // Apply Check + this.doubleChecker = window.setTimeout( + function(){ + _this.doubleCheckClear(); + if ( !_this.stateChanged ) { + //this.debug('History.doubleCheck: State has not yet changed, trying again', arguments); + // Re-Attempt + tryAgain(); + } + return true; + }, + this.doubleCheckInterval + ); + } - // Chain - return this; - }; + // Chain + return this; + }, - // ==================================================================== - // Safari Bug Fix + // ==================================================================== + // Safari Bug Fix - /** - * safariStatePoll() - * Poll the current state - * @return {Roo.History} - */ - safariStatePoll = function(){ - // Poll the URL + /** + * safariStatePoll() + * Poll the current state + * @return {Roo.History} + */ + safariStatePoll : function(){ + // Poll the URL - // Get the Last State which has the new URL - var - urlState = this.extractState(this.getLocationHref()), - newState; + // Get the Last State which has the new URL + var + urlState = this.extractState(this.getLocationHref()), + newState; - // Check for a difference - if ( !this.isLastSavedState(urlState) ) { - newState = urlState; - } - else { - return; - } + // Check for a difference + if ( !this.isLastSavedState(urlState) ) { + newState = urlState; + } + else { + return; + } - // Check if we have a state with that url - // If not create it - if ( !newState ) { - //this.debug('this.safariStatePoll: new'); - newState = this.createStateObject(); - } + // Check if we have a state with that url + // If not create it + if ( !newState ) { + //this.debug('this.safariStatePoll: new'); + newState = this.createStateObject(); + } - // Apply the New State - //this.debug('this.safariStatePoll: trigger'); - this.Adapter.trigger(window,'popstate'); + // Apply the New State + //this.debug('this.safariStatePoll: trigger'); + Roo.get(window).fireEvent('popstate'); - // Chain - return this; - }; + // Chain + return this; + }, - // ==================================================================== - // State Aliases + // ==================================================================== + // State Aliases - /** - * back(queue) - * Send the browser history back one item - * @param {Integer} queue [optional] - */ - back = function(queue){ - //this.debug('this.back: called', arguments); - - // Handle Queueing - if ( queue !== false && this.busy() ) { - // Wait + Push to Queue - //this.debug('this.back: we must wait', arguments); - this.pushQueue({ - scope: this, - callback: this.back, - args: arguments, - queue: queue - }); - return false; - } + /** + * back(queue) + * Send the browser history back one item + * @param {Integer} queue [optional] + */ + back : function(queue) + { + //this.debug('this.back: called', arguments); + var _this = this; + // Handle Queueing + if ( queue !== false && this.busy() ) { + // Wait + Push to Queue + //this.debug('this.back: we must wait', arguments); + this.pushQueue({ + scope: this, + callback: this.back, + args: arguments, + queue: queue + }); + return false; + } - // Make Busy + Continue - this.busy(true); + // Make Busy + Continue + this.busy(true); - // Fix certain browser bugs that prevent the state from changing - this.doubleCheck(function(){ - this.back(false); - }); + // Fix certain browser bugs that prevent the state from changing + this.doubleCheck(function(){ + _this.back(false); + }); - // Go back - history.go(-1); + // Go back + history.go(-1); - // End back closure - return true; - }; + // End back closure + return true; + }, - /** - * forward(queue) - * Send the browser history forward one item - * @param {Integer} queue [optional] - */ - forward = function(queue){ - //this.debug('this.forward: called', arguments); - - // Handle Queueing - if ( queue !== false && this.busy() ) { - // Wait + Push to Queue - //this.debug('this.forward: we must wait', arguments); - this.pushQueue({ - scope: this, - callback: this.forward, - args: arguments, - queue: queue - }); - return false; - } + /** + * forward(queue) + * Send the browser history forward one item + * @param {Integer} queue [optional] + */ + forward : function(queue){ + //this.debug('this.forward: called', arguments); + + // Handle Queueing + if ( queue !== false && this.busy() ) { + // Wait + Push to Queue + //this.debug('this.forward: we must wait', arguments); + this.pushQueue({ + scope: this, + callback: this.forward, + args: arguments, + queue: queue + }); + return false; + } - // Make Busy + Continue - this.busy(true); - - var _t = this; - // Fix certain browser bugs that prevent the state from changing - this.doubleCheck(function(){ - _t.forward(false); - }); + // Make Busy + Continue + this.busy(true); + + var _t = this; + // Fix certain browser bugs that prevent the state from changing + this.doubleCheck(function(){ + _t.forward(false); + }); - // Go forward - history.go(1); + // Go forward + history.go(1); - // End forward closure - return true; - }; + // End forward closure + return true; + }, - /** - * go(index,queue) - * Send the browser history back or forward index times - * @param {Integer} queue [optional] - */ - go = function(index,queue){ - //this.debug('this.go: called', arguments); + /** + * go(index,queue) + * Send the browser history back or forward index times + * @param {Integer} queue [optional] + */ + go : function(index,queue){ + //this.debug('this.go: called', arguments); - // Prepare - var i; + // Prepare + var i; - // Handle - if ( index > 0 ) { - // Forward - for ( i=1; i<=index; ++i ) { - this.forward(queue); - } - } - else if ( index < 0 ) { - // Backward - for ( i=-1; i>=index; --i ) { - this.back(queue); - } - } - else { - throw new Error('History.go: History.go requires a positive or negative integer passed.'); - } + // Handle + if ( index > 0 ) { + // Forward + for ( i=1; i<=index; ++i ) { + this.forward(queue); + } + } + else if ( index < 0 ) { + // Backward + for ( i=-1; i>=index; --i ) { + this.back(queue); + } + } + else { + throw new Error('History.go: History.go requires a positive or negative integer passed.'); + } - // Chain - return this; - }; + // Chain + return this; + }, - // ==================================================================== - // HTML5 State Support + // ==================================================================== + // HTML5 State Support - // Non-Native pushState Implementation - - // Native pushState Implementation - else { - /* - * Use native HTML5 History API Implementation - */ + + /* + * Use native HTML5 History API Implementation + */ - /** - * History.onPopState(event,extra) - * Refresh the Current State - */ - History.onPopState = function(event,extra){ - // Prepare - var stateId = false, newState = false, currentHash, currentState; - - // Reset the double check - History.doubleCheckComplete(); - - // Check for a Hash, and handle apporiatly - currentHash = History.getHash(); - if ( currentHash ) { - // Expand Hash - currentState = History.extractState(currentHash||History.getLocationHref(),true); - if ( currentState ) { - // We were able to parse it, it must be a State! - // Let's forward to replaceState - //History.debug('History.onPopState: state anchor', currentHash, currentState); - History.replaceState(currentState.data, currentState.title, currentState.url, false); - } - else { - // Traditional Anchor - //History.debug('History.onPopState: traditional anchor', currentHash); - History.Adapter.trigger(window,'anchorchange'); - History.busy(false); - } - - // We don't care for hashes - History.expectedStateId = false; - return false; - } + /** + * onPopState(event,extra) + * Refresh the Current State + */ + onPopState : function(event,extra){ + // Prepare + var stateId = false, newState = false, currentHash, currentState; + + // Reset the double check + this.doubleCheckComplete(); + + // Check for a Hash, and handle apporiatly + currentHash = this.getHash(); + if ( currentHash ) { + // Expand Hash + currentState = this.extractState(currentHash||this.getLocationHref(),true); + if ( currentState ) { + // We were able to parse it, it must be a State! + // Let's forward to replaceState + //this.debug('this.onPopState: state anchor', currentHash, currentState); + this.replaceState(currentState.data, currentState.title, currentState.url, false); + } + else { + // Traditional Anchor + //this.debug('this.onPopState: traditional anchor', currentHash); + Roo.get(window).fireEvent('anchorchange'); + this.busy(false); + } - // Ensure - stateId = History.Adapter.extractEventData('state',event,extra) || false; + // We don't care for hashes + this.expectedStateId = false; + return false; + } + stateId = (event && event.browserEvent && event.browserEvent['state']) || (extra && extra['state']) || undefined; - // Fetch State - if ( stateId ) { - // Vanilla: Back/forward button was used - newState = History.getStateById(stateId); - } - else if ( History.expectedStateId ) { - // Vanilla: A new state was pushed, and popstate was called manually - newState = History.getStateById(History.expectedStateId); - } - else { - // Initial State - newState = History.extractState(History.getLocationHref()); - } + // Ensure + //stateId = this.Adapter.extractEventData('state',event,extra) || false; - // The State did not exist in our store - if ( !newState ) { - // Regenerate the State - newState = History.createStateObject(null,null,History.getLocationHref()); - } + // Fetch State + if ( stateId ) { + // Vanilla: Back/forward button was used + newState = this.getStateById(stateId); + } + else if ( this.expectedStateId ) { + // Vanilla: A new state was pushed, and popstate was called manually + newState = this.getStateById(this.expectedStateId); + } + else { + // Initial State + newState = this.extractState(this.getLocationHref()); + } - // Clean - History.expectedStateId = false; + // The State did not exist in our store + if ( !newState ) { + // Regenerate the State + newState = this.createStateObject(null,null,this.getLocationHref()); + } - // Check if we are the same state - if ( History.isLastSavedState(newState) ) { - // There has been no change (just the page's hash has finally propagated) - //History.debug('History.onPopState: no change', newState, History.savedStates); - History.busy(false); - return false; - } + // Clean + this.expectedStateId = false; - // Store the State - History.storeState(newState); - History.saveState(newState); + // Check if we are the same state + if ( this.isLastSavedState(newState) ) { + // There has been no change (just the page's hash has finally propagated) + //this.debug('this.onPopState: no change', newState, this.savedStates); + this.busy(false); + return false; + } - // Force update of the title - History.setTitle(newState); + // Store the State + this.storeState(newState); + this.saveState(newState); - // Fire Our Event - History.Adapter.trigger(window,'statechange'); - History.busy(false); + // Force update of the title + this.setTitle(newState); - // Return true - return true; - }; - History.Adapter.bind(window,'popstate',History.onPopState); + // Fire Our Event + Roo.get(window).fireEvent('statechange'); + this.busy(false); - /** - * History.pushState(data,title,url) - * Add a new State to the history object, become it, and trigger onpopstate - * We have to trigger for HTML4 compatibility - * @param {object} data - * @param {string} title - * @param {string} url - * @return {true} - */ - History.pushState = function(data,title,url,queue){ - //History.debug('History.pushState: called', arguments); + // Return true + return true; + }, + + + + + + - // Check the State - if ( History.getHashByUrl(url) && History.emulated.pushState ) { - throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).'); - } + /** + * pushState(data,title,url) + * Add a new State to the history object, become it, and trigger onpopstate + * We have to trigger for HTML4 compatibility + * @param {object} data + * @param {string} title + * @param {string} url + * @return {true} + */ + pushState : function(data,title,url,queue){ + //this.debug('this.pushState: called', arguments); - // Handle Queueing - if ( queue !== false && History.busy() ) { - // Wait + Push to Queue - //History.debug('History.pushState: we must wait', arguments); - History.pushQueue({ - scope: History, - callback: History.pushState, - args: arguments, - queue: queue - }); - return false; - } + // Check the State + if ( this.getHashByUrl(url) && this.emulated.pushState ) { + throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).'); + } - // Make Busy + Continue - History.busy(true); + // Handle Queueing + if ( queue !== false && this.busy() ) { + // Wait + Push to Queue + //this.debug('this.pushState: we must wait', arguments); + this.pushQueue({ + scope: this, + callback: this.pushState, + args: arguments, + queue: queue + }); + return false; + } - // Create the newState - var newState = History.createStateObject(data,title,url); + // Make Busy + Continue + this.busy(true); - // Check it - if ( History.isLastSavedState(newState) ) { - // Won't be a change - History.busy(false); - } - else { - // Store the newState - History.storeState(newState); - History.expectedStateId = newState.id; + // Create the newState + var newState = this.createStateObject(data,title,url); - // Push the newState - history.pushState(newState.id,newState.title,newState.url); + // Check it + if ( this.isLastSavedState(newState) ) { + // Won't be a change + this.busy(false); + } + else { + // Store the newState + this.storeState(newState); + this.expectedStateId = newState.id; - // Fire HTML5 Event - History.Adapter.trigger(window,'popstate'); - } + // Push the newState + history.pushState(newState.id,newState.title,newState.url); - // End pushState closure - return true; - }; + // Fire HTML5 Event + Roo.get(window).fireEvent('popstate'); + } - /** - * History.replaceState(data,title,url) - * Replace the State and trigger onpopstate - * We have to trigger for HTML4 compatibility - * @param {object} data - * @param {string} title - * @param {string} url - * @return {true} - */ - History.replaceState = function(data,title,url,queue){ - //History.debug('History.replaceState: called', arguments); + // End pushState closure + return true; + }, - // Check the State - if ( History.getHashByUrl(url) && History.emulated.pushState ) { - throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).'); - } + /** + * replaceState(data,title,url) + * Replace the State and trigger onpopstate + * We have to trigger for HTML4 compatibility + * @param {object} data + * @param {string} title + * @param {string} url + * @return {true} + */ + replaceState : function(data,title,url,queue){ + //this.debug('this.replaceState: called', arguments); - // Handle Queueing - if ( queue !== false && History.busy() ) { - // Wait + Push to Queue - //History.debug('History.replaceState: we must wait', arguments); - History.pushQueue({ - scope: History, - callback: History.replaceState, - args: arguments, - queue: queue - }); - return false; - } + // Check the State + if ( this.getHashByUrl(url) && this.emulated.pushState ) { + throw new Error('this.js does not support states with fragement-identifiers (hashes/anchors).'); + } - // Make Busy + Continue - History.busy(true); + // Handle Queueing + if ( queue !== false && this.busy() ) { + // Wait + Push to Queue + //this.debug('this.replaceState: we must wait', arguments); + this.pushQueue({ + scope: this, + callback: this.replaceState, + args: arguments, + queue: queue + }); + return false; + } - // Create the newState - var newState = History.createStateObject(data,title,url); + // Make Busy + Continue + this.busy(true); - // Check it - if ( History.isLastSavedState(newState) ) { - // Won't be a change - History.busy(false); - } - else { - // Store the newState - History.storeState(newState); - History.expectedStateId = newState.id; + // Create the newState + var newState = this.createStateObject(data,title,url); - // Push the newState - history.replaceState(newState.id,newState.title,newState.url); + // Check it + if ( this.isLastSavedState(newState) ) { + // Won't be a change + this.busy(false); + } + else { + // Store the newState + this.storeState(newState); + this.expectedStateId = newState.id; - // Fire HTML5 Event - History.Adapter.trigger(window,'popstate'); - } + // Push the newState + history.replaceState(newState.id,newState.title,newState.url); - // End replaceState closure - return true; - }; + // Fire HTML5 Event + Roo.get(window).fireEvent('popstate'); + } - } // !History.emulated.pushState + // End replaceState closure + return true; + }, // ==================================================================== // Initialise - /** - * Load the Store - */ - if ( sessionStorage ) { - // Fetch - try { - History.store = JSON.parse(sessionStorage.getItem('History.store'))||{}; - } - catch ( err ) { - History.store = {}; - } - - // Normalize - History.normalizeStore(); - } - else { - // Default Load - History.store = {}; - History.normalizeStore(); - } - - /** - * Clear Intervals on exit to prevent memory leaks - */ - History.Adapter.bind(window,"unload",History.clearAllIntervals); - - /** - * Create the initial State - */ - History.saveState(History.storeState(History.extractState(History.getLocationHref(),true))); - /** * Bind for Saving Store */ - if ( sessionStorage ) { - // When the page is closed - History.onUnload = function(){ - // Prepare - var currentStore, item, currentStoreString; - - // Fetch - try { - currentStore = JSON.parse(sessionStorage.getItem('History.store'))||{}; - } - catch ( err ) { - currentStore = {}; - } - - // Ensure - currentStore.idToState = currentStore.idToState || {}; - currentStore.urlToId = currentStore.urlToId || {}; - currentStore.stateToId = currentStore.stateToId || {}; - - // Sync - for ( item in History.idToState ) { - if ( !History.idToState.hasOwnProperty(item) ) { - continue; - } - currentStore.idToState[item] = History.idToState[item]; - } - for ( item in History.urlToId ) { - if ( !History.urlToId.hasOwnProperty(item) ) { - continue; - } - currentStore.urlToId[item] = History.urlToId[item]; - } - for ( item in History.stateToId ) { - if ( !History.stateToId.hasOwnProperty(item) ) { - continue; - } - currentStore.stateToId[item] = History.stateToId[item]; - } - - // Update - History.store = currentStore; - History.normalizeStore(); - - // In Safari, going into Private Browsing mode causes the - // Session Storage object to still exist but if you try and use - // or set any property/function of it it throws the exception - // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to - // add something to storage that exceeded the quota." infinitely - // every second. - currentStoreString = JSON.stringify(currentStore); - try { - // Store - sessionStorage.setItem('History.store', currentStoreString); - } - catch (e) { - if (e.code === DOMException.QUOTA_EXCEEDED_ERR) { - if (sessionStorage.length) { - // Workaround for a bug seen on iPads. Sometimes the quota exceeded error comes up and simply - // removing/resetting the storage can work. - sessionStorage.removeItem('History.store'); - sessionStorage.setItem('History.store', currentStoreString); - } else { - // Otherwise, we're probably private browsing in Safari, so we'll ignore the exception. - } - } else { - throw e; - } - } - }; - - // For Internet Explorer - History.intervalList.push(setInterval(History.onUnload,this.storeInterval)); - - // For Other Browsers - History.Adapter.bind(window,'beforeunload',History.onUnload); - History.Adapter.bind(window,'unload',History.onUnload); - - // Both are enabled for consistency - } - - // Non-Native pushState Implementation - if ( !History.emulated.pushState ) { - // Be aware, the following is only for native pushState implementations - // If you are wanting to include something for all browsers - // Then include it above this if block - - /** - * Setup Safari Fix - */ - if ( History.bugs.safariPoll ) { - History.intervalList.push(setInterval(History.safariStatePoll, this.safariPollInterval)); - } - - /** - * Ensure Cross Browser Compatibility - */ - if ( window.navigator.vendor === 'Apple Computer, Inc.' || (window.navigator.appCodeName||'') === 'Mozilla' ) { - /** - * Fix Safari HashChange Issue - */ - - // Setup Alias - History.Adapter.bind(window,'hashchange',function(){ - History.Adapter.trigger(window,'popstate'); - }); - - // Initialise Alias - if ( History.getHash() ) { - History.Adapter.onDomLoad(function(){ - History.Adapter.trigger(window,'hashchange'); - }); - } - } - - } // !History.emulated.pushState - - - }; // History.initCore - - // Try to Initialise History - if (!History.options || !History.options.delayInit) { - History.init(); - } - -})(window); \ No newline at end of file + + // When the page is closed + onUnload : function(){ + // Prepare + var currentStore, item, currentStoreString; + + // Fetch + try { + currentStore = JSON.parse(this.sessionStorage.getItem('Roo.History.store'))||{}; + } + catch ( err ) { + currentStore = {}; + } + + // Ensure + currentStore.idToState = currentStore.idToState || {}; + currentStore.urlToId = currentStore.urlToId || {}; + currentStore.stateToId = currentStore.stateToId || {}; + + // Sync + for ( item in this.idToState ) { + if ( !this.idToState.hasOwnProperty(item) ) { + continue; + } + currentStore.idToState[item] = this.idToState[item]; + } + for ( item in this.urlToId ) { + if ( !this.urlToId.hasOwnProperty(item) ) { + continue; + } + currentStore.urlToId[item] = this.urlToId[item]; + } + for ( item in this.stateToId ) { + if ( !this.stateToId.hasOwnProperty(item) ) { + continue; + } + currentStore.stateToId[item] = this.stateToId[item]; + } + + // Update + this.store = currentStore; + this.normalizeStore(); + + // In Safari, going into Private Browsing mode causes the + // Session Storage object to still exist but if you try and use + // or set any property/function of it it throws the exception + // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to + // add something to storage that exceeded the quota." infinitely + // every second. + currentStoreString = JSON.stringify(currentStore); + try { + // Store + this.sessionStorage.setItem('Roo.History.store', currentStoreString); + } + catch (e) { + if (e.code === DOMException.QUOTA_EXCEEDED_ERR) { + if (this.sessionStorage.length) { + // Workaround for a bug seen on iPads. Sometimes the quota exceeded error comes up and simply + // removing/resetting the storage can work. + this.sessionStorage.removeItem('Roo.History.store'); + this.sessionStorage.setItem('Roo.History.store', currentStoreString); + } else { + // Otherwise, we're probably private browsing in Safari, so we'll ignore the exception. + } + } else { + throw e; + } + } + } +}; \ No newline at end of file