templates/jquery/history.js in compass-jquery-plugin-0.3.2.7 vs templates/jquery/history.js in compass-jquery-plugin-0.3.2.8

- old
+ new

@@ -3,1676 +3,1846 @@ * @author Benjamin Arthur Lupton <contact@balupton.com> * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com> * @license New BSD License <http://creativecommons.org/licenses/BSD/> */ -(function(window,undefined){ - "use strict"; +(function(window, undefined) { + "use strict"; - // -------------------------------------------------------------------------- - // Initialise + // -------------------------------------------------------------------------- + // Initialise - // Localise Globals - var - console = window.console||undefined, // Prevent a JSLint complain - document = window.document, // Make sure we are using the correct document - navigator = window.navigator, // Make sure we are using the correct navigator - History = window.History = window.History||{}, // Public History Object - history = window.history; // Old History Object + // Localise Globals + var + console = window.console || undefined, // Prevent a JSLint complain + document = window.document, // Make sure we are using the correct document + navigator = window.navigator, // Make sure we are using the correct navigator + amplify = window.amplify || false, // Amplify.js + setTimeout = window.setTimeout, + clearTimeout = window.clearTimeout, + setInterval = window.setInterval, + JSON = window.JSON, + History = window.History = window.History || {}, // Public History Object + history = window.history; // Old History Object - // Check Existence - if ( typeof History.init !== 'undefined' ) { - throw new Error('History.js Core has already been loaded...'); - } + // MooTools Compatibility + JSON.stringify = JSON.stringify || JSON.encode; + JSON.parse = JSON.parse || JSON.decode; - // Initialise History - History.init = function(){ - // Check Load Status of Adapter - if ( typeof History.Adapter === 'undefined' ) { - return false; - } + // Check Existence + if (typeof History.init !== 'undefined') { + throw new Error('History.js Core has already been loaded...'); + } - // Check Load Status of Core - if ( typeof History.initCore !== 'undefined' ) { - History.initCore(); - } + // Initialise History + History.init = function() { + // Check Load Status of Adapter + if (typeof History.Adapter === 'undefined') { + return false; + } - // Check Load Status of HTML4 Support - if ( typeof History.initHtml4 !== 'undefined' ) { - History.initHtml4(); - } + // Check Load Status of Core + if (typeof History.initCore !== 'undefined') { + History.initCore(); + } - // Return true - return true; - }; + // Check Load Status of HTML4 Support + if (typeof History.initHtml4 !== 'undefined') { + History.initHtml4(); + } - // -------------------------------------------------------------------------- - // Initialise Core + // Return true + return true; + }; - // Initialise Core - History.initCore = function(){ - // Initialise - if ( typeof History.initCore.initialized !== 'undefined' ) { - // Already Loaded - return false; - } - else { - History.initCore.initialized = true; - } + // -------------------------------------------------------------------------- + // Initialise Core - // ---------------------------------------------------------------------- - // Options + // Initialise Core + History.initCore = function() { + // Initialise + if (typeof History.initCore.initialized !== 'undefined') { + // Already Loaded + return false; + } + else { + History.initCore.initialized = true; + } - /** - * History.options - * Configurable options - */ - History.options = History.options||{}; + // ---------------------------------------------------------------------- + // Options - /** - * History.options.hashChangeInterval - * How long should the interval be before hashchange checks - */ - History.options.hashChangeInterval = History.options.hashChangeInterval || 100; + /** + * History.options + * Configurable options + */ + History.options = History.options || {}; - /** - * History.options.safariPollInterval - * How long should the interval be before safari poll checks - */ - History.options.safariPollInterval = History.options.safariPollInterval || 500; + /** + * History.options.hashChangeInterval + * How long should the interval be before hashchange checks + */ + History.options.hashChangeInterval = History.options.hashChangeInterval || 100; - /** - * History.options.doubleCheckInterval - * How long should the interval be before we perform a double check - */ - History.options.doubleCheckInterval = History.options.doubleCheckInterval || 500; + /** + * History.options.safariPollInterval + * How long should the interval be before safari poll checks + */ + History.options.safariPollInterval = History.options.safariPollInterval || 500; - /** - * History.options.busyDelay - * How long should we wait between busy events - */ - History.options.busyDelay = History.options.busyDelay || 250; + /** + * History.options.doubleCheckInterval + * How long should the interval be before we perform a double check + */ + History.options.doubleCheckInterval = History.options.doubleCheckInterval || 500; - /** - * History.options.debug - * If true will enable debug messages to be logged - */ - History.options.debug = History.options.debug || false; + /** + * History.options.storeInterval + * How long should we wait between store calls + */ + History.options.storeInterval = History.options.storeInterval || 1000; - /** - * History.options.initialTitle - * What is the title of the initial state - */ - History.options.initialTitle = History.options.initialTitle || document.title; + /** + * History.options.busyDelay + * How long should we wait between busy events + */ + History.options.busyDelay = History.options.busyDelay || 250; + /** + * History.options.debug + * If true will enable debug messages to be logged + */ + History.options.debug = History.options.debug || false; - // ---------------------------------------------------------------------- - // Debug + /** + * History.options.initialTitle + * What is the title of the initial state + */ + History.options.initialTitle = History.options.initialTitle || document.title; - /** - * History.debug(message,...) - * Logs the passed arguments if debug enabled - */ - History.debug = function(){ - if ( (History.options.debug||false) ) { - History.log.apply(History,arguments); - } - }; - /** - * History.log(message,...) - * Logs the passed arguments - */ - History.log = function(){ - // Prepare - var - consoleExists = (typeof console !== 'undefined' && typeof console.log !== 'undefined' && typeof console.log.apply !== 'undefined'), - textarea = document.getElementById('log'), - message, - i,n - ; + // ---------------------------------------------------------------------- + // Debug - // Write to Console - if ( consoleExists ) { - var args = Array.prototype.slice.call(arguments); - message = args.shift(); - if ( typeof console.debug !== 'undefined' ) { - console.debug.apply(console,[message,args]); - } - else { - console.log.apply(console,[message,args]); - } - } - else { - message = ("\n"+arguments[0]+"\n"); - } + /** + * History.debug(message,...) + * Logs the passed arguments if debug enabled + */ + History.debug = function() { + if ((History.options.debug || false)) { + History.log.apply(History, arguments); + } + }; - // Write to log - for ( i=1,n=arguments.length; i<n; ++i ) { - var arg = arguments[i]; - if ( typeof arg === 'object' && typeof JSON !== 'undefined' ) { - try { - arg = JSON.stringify(arg); - } - catch ( Exception ) { - // Recursive Object - } - } - message += "\n"+arg+"\n"; - } + /** + * History.log(message,...) + * Logs the passed arguments + */ + History.log = function() { + // Prepare + var + consoleExists = !(typeof console === 'undefined' || typeof console.log === 'undefined' || typeof console.log.apply === 'undefined'), + textarea = document.getElementById('log'), + message, + i,n + ; - // Textarea - if ( textarea ) { - textarea.value += message+"\n-----\n"; - textarea.scrollTop = textarea.scrollHeight - textarea.clientHeight; - } - // No Textarea, No Console - else if ( !consoleExists ) { - alert(message); - } + // Write to Console + if (consoleExists) { + var args = Array.prototype.slice.call(arguments); + message = args.shift(); + if (typeof console.debug !== 'undefined') { + console.debug.apply(console, [message,args]); + } + else { + console.log.apply(console, [message,args]); + } + } + else { + message = ("\n" + arguments[0] + "\n"); + } - // Return true - return true; - }; + // Write to log + for (i = 1,n = arguments.length; i < n; ++i) { + var arg = arguments[i]; + if (typeof arg === 'object' && typeof JSON !== 'undefined') { + try { + arg = JSON.stringify(arg); + } + catch (Exception) { + // Recursive Object + } + } + message += "\n" + arg + "\n"; + } - // ---------------------------------------------------------------------- - // Emulated Status + // Textarea + if (textarea) { + textarea.value += message + "\n-----\n"; + textarea.scrollTop = textarea.scrollHeight - textarea.clientHeight; + } + // No Textarea, No Console + else if (!consoleExists) { + alert(message); + } - /** - * History.getInternetExplorerMajorVersion() - * Get's the major version of Internet Explorer - * @return {integer} - * @license Public Domain - * @author Benjamin Arthur Lupton <contact@balupton.com> - * @author James Padolsey <https://gist.github.com/527683> - */ - History.getInternetExplorerMajorVersion = function(){ - var result = History.getInternetExplorerMajorVersion.cached = - (typeof History.getInternetExplorerMajorVersion.cached !== 'undefined') - ? History.getInternetExplorerMajorVersion.cached - : (function(){ - var v = 3, - div = document.createElement('div'), - all = div.getElementsByTagName('i'); - while ( (div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->') && all[0] ) {} - return (v > 4) ? v : false; - })() - ; - return result; - }; + // Return true + return true; + }; - /** - * History.isInternetExplorer() - * Are we using Internet Explorer? - * @return {boolean} - * @license Public Domain - * @author Benjamin Arthur Lupton <contact@balupton.com> - */ - History.isInternetExplorer = function(){ - var result = - History.isInternetExplorer.cached = - (typeof History.isInternetExplorer.cached !== 'undefined') - ? History.isInternetExplorer.cached - : Boolean(History.getInternetExplorerMajorVersion()) - ; - return result; - }; + // ---------------------------------------------------------------------- + // Emulated Status - /** - * History.emulated - * Which features require emulating? - */ - History.emulated = { - pushState: !Boolean( - window.history && window.history.pushState && window.history.replaceState - && !(/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent) /* disable for versions of iOS before version 4.3 (8F190) */ - ), - hashChange: Boolean( - !(('onhashchange' in window) || ('onhashchange' in document)) - || - (History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8) - ) - }; + /** + * History.getInternetExplorerMajorVersion() + * Get's the major version of Internet Explorer + * @return {integer} + * @license Public Domain + * @author Benjamin Arthur Lupton <contact@balupton.com> + * @author James Padolsey <https://gist.github.com/527683> + */ + History.getInternetExplorerMajorVersion = function() { + var result = History.getInternetExplorerMajorVersion.cached = + (typeof History.getInternetExplorerMajorVersion.cached !== 'undefined') + ? History.getInternetExplorerMajorVersion.cached + : (function() { + var v = 3, + div = document.createElement('div'), + all = div.getElementsByTagName('i'); + while ((div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->') && all[0]) { + } + return (v > 4) ? v : false; + })() + ; + return result; + }; - /** - * History.bugs - * Which bugs are present - */ - History.bugs = { - /** - * 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 - */ - setHash: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2][0-9]|3[0-3])/.test(navigator.userAgent)), + /** + * History.isInternetExplorer() + * Are we using Internet Explorer? + * @return {boolean} + * @license Public Domain + * @author Benjamin Arthur Lupton <contact@balupton.com> + */ + History.isInternetExplorer = function() { + var result = + History.isInternetExplorer.cached = + (typeof History.isInternetExplorer.cached !== 'undefined') + ? History.isInternetExplorer.cached + : Boolean(History.getInternetExplorerMajorVersion()) + ; + return result; + }; - /** - * 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 - */ - safariPoll: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2][0-9]|3[0-3])/.test(navigator.userAgent)), + /** + * History.emulated + * Which features require emulating? + */ + History.emulated = { + pushState: !Boolean( + window.history && window.history.pushState && window.history.replaceState + && !( + (/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent) /* disable for versions of iOS before version 4.3 (8F190) */ + || (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent) /* disable for the mercury iOS browser, or at least older versions of the webkit engine */ + ) + ), + hashChange: Boolean( + !(('onhashchange' in window) || ('onhashchange' in document)) + || + (History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8) + ) + }; - /** - * MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function) - */ - ieDoubleCheck: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8), + /** + * History.enabled + * Is History enabled? + */ + History.enabled = !History.emulated.pushState; - /** - * MSIE 6 requires the entire hash to be encoded for the hashes to trigger the onHashChange event - */ - hashEscape: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 7) - }; + /** + * History.bugs + * Which bugs are present + */ + History.bugs = { + /** + * 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 + */ + setHash: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)), - /** - * History.isEmptyObject(obj) - * Checks to see if the Object is Empty - * @param {Object} obj - * @return {boolean} - */ - History.isEmptyObject = function(obj) { - for ( var name in obj ) { - return false; - } - return true; - }; + /** + * 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 + */ + safariPoll: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)), - /** - * History.cloneObject(obj) - * Clones a object - * @param {Object} obj - * @return {Object} - */ - History.cloneObject = function(obj) { - var hash,newObj; - if ( obj ) { - hash = JSON.stringify(obj); - newObj = JSON.parse(hash); - } - else { - newObj = {}; - } - return newObj; - }; + /** + * MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function) + */ + ieDoubleCheck: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8), - // ---------------------------------------------------------------------- - // URL Helpers + /** + * MSIE 6 requires the entire hash to be encoded for the hashes to trigger the onHashChange event + */ + hashEscape: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 7) + }; - /** - * History.getRootUrl() - * Turns "http://mysite.com/dir/page.html?asd" into "http://mysite.com" - * @return {String} rootUrl - */ - History.getRootUrl = function(){ - // Create - var rootUrl = document.location.protocol+'//'+(document.location.hostname||document.location.host); - if ( document.location.port||false ) { - rootUrl += ':'+document.location.port; - } - rootUrl += '/'; + /** + * History.isEmptyObject(obj) + * Checks to see if the Object is Empty + * @param {Object} obj + * @return {boolean} + */ + History.isEmptyObject = function(obj) { + for (var name in obj) { + return false; + } + return true; + }; - // Return - return rootUrl; - }; + /** + * History.cloneObject(obj) + * Clones a object + * @param {Object} obj + * @return {Object} + */ + History.cloneObject = function(obj) { + var hash,newObj; + if (obj) { + hash = JSON.stringify(obj); + newObj = JSON.parse(hash); + } + else { + newObj = {}; + } + return newObj; + }; - /** - * History.getBaseHref() - * Fetches the `href` attribute of the `<base href="...">` element if it exists - * @return {String} baseHref - */ - History.getBaseHref = function(){ - // Create - var - baseElements = document.getElementsByTagName('base'), - baseElement = null, - baseHref = ''; + // ---------------------------------------------------------------------- + // URL Helpers - // Test for Base Element - if ( baseElements.length === 1 ) { - // Prepare for Base Element - baseElement = baseElements[0]; - baseHref = baseElement.href.replace(/[^\/]+$/,''); - } + /** + * History.getRootUrl() + * Turns "http://mysite.com/dir/page.html?asd" into "http://mysite.com" + * @return {String} rootUrl + */ + History.getRootUrl = function() { + // Create + var rootUrl = document.location.protocol + '//' + (document.location.hostname || document.location.host); + if (document.location.port || false) { + rootUrl += ':' + document.location.port; + } + rootUrl += '/'; - // Adjust trailing slash - baseHref = baseHref.replace(/\/+$/,''); - if ( baseHref ) baseHref += '/'; + // Return + return rootUrl; + }; - // Return - return baseHref; - }; + /** + * History.getBaseHref() + * Fetches the `href` attribute of the `<base href="...">` element if it exists + * @return {String} baseHref + */ + History.getBaseHref = function() { + // Create + var + baseElements = document.getElementsByTagName('base'), + baseElement = null, + baseHref = ''; - /** - * History.getBaseUrl() - * Fetches the baseHref or basePageUrl or rootUrl (whichever one exists first) - * @return {String} baseUrl - */ - History.getBaseUrl = function(){ - // Create - var baseUrl = History.getBaseHref()||History.getBasePageUrl()||History.getRootUrl(); + // Test for Base Element + if (baseElements.length === 1) { + // Prepare for Base Element + baseElement = baseElements[0]; + baseHref = baseElement.href.replace(/[^\/]+$/, ''); + } - // Return - return baseUrl; - }; + // Adjust trailing slash + baseHref = baseHref.replace(/\/+$/, ''); + if (baseHref) baseHref += '/'; - /** - * History.getPageUrl() - * Fetches the URL of the current page - * @return {String} pageUrl - */ - History.getPageUrl = function(){ - // Fetch - var - State = History.getState(), - stateUrl = State.url||document.location.href; + // Return + return baseHref; + }; - // Create - var pageUrl = stateUrl.replace(/\/+$/,'').replace(/[^\/]+$/,function(part,index,string){ - return (/\./).test(part) ? part : part+'/'; - }); + /** + * History.getBaseUrl() + * Fetches the baseHref or basePageUrl or rootUrl (whichever one exists first) + * @return {String} baseUrl + */ + History.getBaseUrl = function() { + // Create + var baseUrl = History.getBaseHref() || History.getBasePageUrl() || History.getRootUrl(); - // Return - return pageUrl; - }; + // Return + return baseUrl; + }; - /** - * History.getBasePageUrl() - * Fetches the Url of the directory of the current page - * @return {String} basePageUrl - */ - History.getBasePageUrl = function(){ - // Create - var basePageUrl = document.location.href.replace(/[#\?].*/,'').replace(/[^\/]+$/,function(part,index,string){ - return (/\./).test(part) ? '' : part; - }).replace(/\/+$/,'')+'/'; + /** + * History.getPageUrl() + * Fetches the URL of the current page + * @return {String} pageUrl + */ + History.getPageUrl = function() { + // Fetch + var + State = History.getState(false, false), + stateUrl = (State || {}).url || document.location.href; - // Return - return basePageUrl; - }; + // Create + var pageUrl = stateUrl.replace(/\/+$/, '').replace(/[^\/]+$/, function(part, index, string) { + return (/\./).test(part) ? part : part + '/'; + }); - /** - * History.getFullUrl(url) - * Ensures that we have an absolute URL and not a relative URL - * @param {string} url - * @return {string} fullUrl - */ - History.getFullUrl = function(url){ - // Prepare - var fullUrl = url, firstChar = url.substring(0,1); + // Return + return pageUrl; + }; - // Check - if ( /[a-z]+\:\/\//.test(url) ) { - // Full URL - } - else if ( firstChar === '/' ) { - // Root URL - fullUrl = History.getRootUrl()+url.replace(/^\/+/,''); - } - else if ( firstChar === '#' ) { - // Anchor URL - fullUrl = History.getPageUrl().replace(/#.*/,'')+url; - } - else if ( firstChar === '?' ) { - // Query URL - fullUrl = History.getPageUrl().replace(/[\?#].*/,'')+url; - } - else { - // Relative URL - fullUrl = History.getBaseUrl()+url; - } + /** + * History.getBasePageUrl() + * Fetches the Url of the directory of the current page + * @return {String} basePageUrl + */ + History.getBasePageUrl = function() { + // Create + var basePageUrl = document.location.href.replace(/[#\?].*/, '').replace(/[^\/]+$/, + function(part, index, string) { + return (/[^\/]$/).test(part) ? '' : part; + }).replace(/\/+$/, '') + '/'; - // Return - return fullUrl.replace(/\#$/,''); - }; + // Return + return basePageUrl; + }; - /** - * History.getShortUrl(url) - * Ensures that we have a relative URL and not a absolute URL - * @param {string} url - * @return {string} url - */ - History.getShortUrl = function(url){ - // Prepare - var shortUrl, rootUrl = History.getRootUrl(); // History.getBaseHref()||History.getBasePageUrl() + /** + * History.getFullUrl(url) + * Ensures that we have an absolute URL and not a relative URL + * @param {string} url + * @param {Boolean} allowBaseHref + * @return {string} fullUrl + */ + History.getFullUrl = function(url, allowBaseHref) { + // Prepare + var fullUrl = url, firstChar = url.substring(0, 1); + allowBaseHref = (typeof allowBaseHref === 'undefined') ? true : allowBaseHref; - // Adjust - shortUrl = url.replace(rootUrl,'/'); + // Check + if (/[a-z]+\:\/\//.test(url)) { + // Full URL + } + else if (firstChar === '/') { + // Root URL + fullUrl = History.getRootUrl() + url.replace(/^\/+/, ''); + } + else if (firstChar === '#') { + // Anchor URL + fullUrl = History.getPageUrl().replace(/#.*/, '') + url; + } + else if (firstChar === '?') { + // Query URL + fullUrl = History.getPageUrl().replace(/[\?#].*/, '') + url; + } + else { + // Relative URL + if (allowBaseHref) { + fullUrl = History.getBaseUrl() + url.replace(/^(\.\/)+/, ''); + } else { + fullUrl = History.getBasePageUrl() + url.replace(/^(\.\/)+/, ''); + } + // We have an if condition above as we do not want hashes + // which are relative to the baseHref in our URLs + // as if the baseHref changes, then all our bookmarks + // would now point to different locations + // whereas the basePageUrl will always stay the same + } - // Return - return shortUrl.replace(/\#$/,''); - }; + // Return + return fullUrl.replace(/\#$/, ''); + }; - // ---------------------------------------------------------------------- - // State Storage + /** + * History.getShortUrl(url) + * Ensures that we have a relative URL and not a absolute URL + * @param {string} url + * @return {string} url + */ + History.getShortUrl = function(url) { + // Prepare + var shortUrl = url, baseUrl = History.getBaseUrl(), rootUrl = History.getRootUrl(); - /** - * History.idToState - * 1-1: State ID to State Object - */ - History.idToState = {}; + // Trim baseUrl + if (History.emulated.pushState) { + // We are in a if statement as when pushState is not emulated + // The actual url these short urls are relative to can change + // So within the same session, we the url may end up somewhere different + shortUrl = shortUrl.replace(baseUrl, ''); + } - /** - * History.stateToId - * 1-1: State String to State ID - */ - History.stateToId = {}; + // Trim rootUrl + shortUrl = shortUrl.replace(rootUrl, '/'); - /** - * History.urlToId - * 1-1: State URL to State ID - */ - History.urlToId = {}; + // Ensure we can still detect it as a state + if (History.isTraditionalAnchor(shortUrl)) { + shortUrl = './' + shortUrl; + } - /** - * History.storedStates - * Store the states in an array - */ - History.storedStates = []; + // Clean It + shortUrl = shortUrl.replace(/^(\.\/)+/g, './').replace(/\#$/, ''); - /** - * History.savedStates - * Saved the states in an array - */ - History.savedStates = []; + // Return + return shortUrl; + }; - /** - * History.getState() - * Get an object containing the data, title and url of the current state - * @return {Object} State - */ - History.getState = function(friendly){ - // Prepare - if ( typeof friendly === 'undefined' ) { friendly = true; } + // ---------------------------------------------------------------------- + // State Storage - // Fetch - var State = History.getLastSavedState()||History.createStateObject(); + /** + * History.store + * The store for all session specific data + */ + History.store = amplify ? (amplify.store('History.store') || {}) : {}; + History.store.idToState = History.store.idToState || {}; + History.store.urlToId = History.store.urlToId || {}; + History.store.stateToId = History.store.stateToId || {}; - // Adjust - if ( friendly ) { - State = History.cloneObject(State); - State.url = State.cleanUrl||State.url; - } + /** + * History.idToState + * 1-1: State ID to State Object + */ + History.idToState = History.idToState || {}; - // Return - return State; - }; + /** + * History.stateToId + * 1-1: State String to State ID + */ + History.stateToId = History.stateToId || {}; - /** - * History.getIdByState(State) - * Gets a ID for a State - * @param {State} newState - * @return {String} id - */ - History.getIdByState = function(newState){ + /** + * History.urlToId + * 1-1: State URL to State ID + */ + History.urlToId = History.urlToId || {}; - // Fetch ID - var id = History.extractId(newState.url); - if ( !id ) { - // Find ID via State String - var str = History.getStateString(newState); - if ( typeof History.stateToId[str] !== 'undefined' ) { - id = History.stateToId[str]; - } - else { - // Generate a new ID - while ( true ) { - id = String(Math.floor(Math.random()*1000)); - if ( typeof History.idToState[id] === 'undefined' ) { - break; - } - } + /** + * History.storedStates + * Store the states in an array + */ + History.storedStates = History.storedStates || []; - // Apply the new State to the ID - History.stateToId[str] = id; - History.idToState[id] = newState; - } - } + /** + * History.savedStates + * Saved the states in an array + */ + History.savedStates = History.savedStates || []; - // Return ID - return id; - }; + /** + * History.getState() + * Get an object containing the data, title and url of the current state + * @param {Boolean} friendly + * @param {Boolean} create + * @return {Object} State + */ + History.getState = function(friendly, create) { + // Prepare + if (typeof friendly === 'undefined') { + friendly = true; + } + if (typeof create === 'undefined') { + create = true; + } - /** - * History.normalizeState(State) - * Expands a State Object - * @param {object} State - * @return {object} - */ - History.normalizeState = function(oldState){ - // Prepare - if ( !oldState || (typeof oldState !== 'object') ) { - oldState = {}; - } + // Fetch + var State = History.getLastSavedState(); - // Check - if ( typeof oldState.normalized !== 'undefined' ) { - return oldState; - } + // Create + if (!State && create) { + State = History.createStateObject(); + } - // Adjust - if ( !oldState.data || (typeof oldState.data !== 'object') ) { - oldState.data = {}; - } + // Adjust + if (friendly) { + State = History.cloneObject(State); + State.url = State.cleanUrl || State.url; + } - // ---------------------------------------------------------------------- + // Return + return State; + }; - // Create - var newState = {}; - newState.normalized = true; - newState.title = oldState.title||''; - newState.url = History.getFullUrl(oldState.url||document.location.href); - newState.hash = History.getShortUrl(newState.url); - newState.data = History.cloneObject(oldState.data); + /** + * History.getIdByState(State) + * Gets a ID for a State + * @param {State} newState + * @return {String} id + */ + History.getIdByState = function(newState) { - // Fetch ID - newState.id = History.getIdByState(newState); + // Fetch ID + var id = History.extractId(newState.url); + if (!id) { + // Find ID via State String + var str = History.getStateString(newState); + if (typeof History.stateToId[str] !== 'undefined') { + id = History.stateToId[str]; + } + else if (typeof History.store.stateToId[str] !== 'undefined') { + id = History.store.stateToId[str]; + } + else { + // Generate a new ID + while (true) { + id = String(Math.floor(Math.random() * 1000)); + if (typeof History.idToState[id] === 'undefined' && typeof History.store.idToState[id] === 'undefined') { + break; + } + } - // ---------------------------------------------------------------------- + // Apply the new State to the ID + History.stateToId[str] = id; + History.idToState[id] = newState; + } + } - // Check to see if we have more than just a url - var dataNotEmpty = !History.isEmptyObject(newState.data); + // Return ID + return id; + }; - // Apply - if ( newState.title || dataNotEmpty ) { - // Add ID to Hash - newState.hash = History.getShortUrl(newState.url).replace(/\&_suid.*/,''); - if ( !/\?/.test(newState.hash) ) { - newState.hash += '?'; - } - newState.hash += '&_suid='+newState.id; - } + /** + * History.normalizeState(State) + * Expands a State Object + * @param {object} State + * @return {object} + */ + History.normalizeState = function(oldState) { + // Prepare + if (!oldState || (typeof oldState !== 'object')) { + oldState = {}; + } - // Create the Hashed URL - newState.hashedUrl = History.getFullUrl(newState.hash); - newState.cleanUrl = newState.url.replace(/\&_suid.*/,''); + // Check + if (typeof oldState.normalized !== 'undefined') { + return oldState; + } - // ---------------------------------------------------------------------- + // Adjust + if (!oldState.data || (typeof oldState.data !== 'object')) { + oldState.data = {}; + } - // Update the URL if we have a duplicate - if ( History.hasUrlDuplicate(newState) ) { - newState.url = newState.hashedUrl; - } + // ---------------------------------------------------------------------- - // ---------------------------------------------------------------------- + // Create + var newState = {}; + newState.normalized = true; + newState.title = oldState.title || ''; + newState.url = History.getFullUrl(History.unescapeString(oldState.url || document.location.href)); + newState.hash = History.getShortUrl(newState.url); + newState.data = History.cloneObject(oldState.data); - // Return - return newState; - }; + // Fetch ID + newState.id = History.getIdByState(newState); - /** - * History.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} - */ - History.createStateObject = function(data,title,url){ - // Hashify - var State = { - 'data': data, - 'title': title, - 'url': url - }; + // ---------------------------------------------------------------------- - // Expand the State - State = History.normalizeState(State); + // Clean the URL + newState.cleanUrl = newState.url.replace(/\??\&_suid.*/, ''); + newState.url = newState.cleanUrl; - // Return object - return State; - }; + // Check to see if we have more than just a url + var dataNotEmpty = !History.isEmptyObject(newState.data); - /** - * History.getStateById(id) - * Get a state by it's UID - * @param {String} id - */ - History.getStateById = function(id){ - id = String(id); - var State = History.idToState[id]||undefined; - return State; - }; + // Apply + if (newState.title || dataNotEmpty) { + // Add ID to Hash + newState.hash = History.getShortUrl(newState.url).replace(/\??\&_suid.*/, ''); + if (!/\?/.test(newState.hash)) { + newState.hash += '?'; + } + newState.hash += '&_suid=' + newState.id; + } - /** - * Get a State's String - * @param {State} passedState - */ - History.getStateString = function(passedState){ - // Prepare - var State = History.normalizeState(passedState); + // Create the Hashed URL + newState.hashedUrl = History.getFullUrl(newState.hash); - // Clean - var cleanedState = { - data: State.data, - title: passedState.title, - url: passedState.url - }; + // ---------------------------------------------------------------------- - // Fetch - var str = JSON.stringify(cleanedState); + // Update the URL if we have a duplicate + if ((History.emulated.pushState || History.bugs.safariPoll) && History.hasUrlDuplicate(newState)) { + newState.url = newState.hashedUrl; + } - // Return - return str; - }; + // ---------------------------------------------------------------------- - /** - * Get a State's ID - * @param {State} passedState - * @return {String} id - */ - History.getStateId = function(passedState){ - // Prepare - var State = History.normalizeState(passedState); + // Return + return newState; + }; - // Fetch - var id = State.id; + /** + * History.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} + */ + History.createStateObject = function(data, title, url) { + // Hashify + var State = { + 'data': data, + 'title': title, + 'url': url + }; - // Return - return id; - }; + // Expand the State + State = History.normalizeState(State); - /** - * History.getHashByState(State) - * Creates a Hash for the State Object - * @param {State} passedState - * @return {String} hash - */ - History.getHashByState = function(passedState){ - // Prepare - var hash, State = History.normalizeState(passedState); + // Return object + return State; + }; - // Fetch - hash = State.hash; + /** + * History.getStateById(id) + * Get a state by it's UID + * @param {String} id + */ + History.getStateById = function(id) { + // Prepare + id = String(id); - // Return - return hash; - }; + // Retrieve + var State = History.idToState[id] || History.store.idToState[id] || undefined; - /** - * History.extractId(url_or_hash) - * Get a State ID by it's URL or Hash - * @param {string} url_or_hash - * @return {string} id - */ - History.extractId = function ( url_or_hash ) { - // Prepare - var id; + // Return State + return State; + }; - // Extract - var parts,url; - parts = /(.*)\&_suid=([0-9]+)$/.exec(url_or_hash); - url = parts ? (parts[1]||url_or_hash) : url_or_hash; - id = parts ? String(parts[2]||'') : ''; + /** + * Get a State's String + * @param {State} passedState + */ + History.getStateString = function(passedState) { + // Prepare + var State = History.normalizeState(passedState); - // Return - return id||false; - }; + // Clean + var cleanedState = { + data: State.data, + title: passedState.title, + url: passedState.url + }; - /** - * History.extractState - * Get a State by it's URL or Hash - */ - History.extractState = function(url_or_hash,create){ - // Prepare - var State = null; - create = create||false; + // Fetch + var str = JSON.stringify(cleanedState); - // Fetch SUID - var id = History.extractId(url_or_hash); - if ( id ) { - State = History.getStateById(id); - } + // Return + return str; + }; - // Fetch SUID returned no State - if ( !State ) { - // Fetch URL - var url = History.getFullUrl(url_or_hash); + /** + * Get a State's ID + * @param {State} passedState + * @return {String} id + */ + History.getStateId = function(passedState) { + // Prepare + var State = History.normalizeState(passedState); - // Check URL - id = History.getIdByUrl(url)||false; - if ( id ) { - State = History.getStateById(id); - } - // Create State - else if ( create && /\//.test(url_or_hash) ) { - State = History.createStateObject(null,null,url.replace(/\&_suid.*/,'')); - } - } + // Fetch + var id = State.id; - // Return - return State; - }; + // Return + return id; + }; - /** - * History.getIdByUrl() - * Get a State ID by a State URL - */ - History.getIdByUrl = function(url){ - var id = History.urlToId[url]||false; - return id; - }; + /** + * History.getHashByState(State) + * Creates a Hash for the State Object + * @param {State} passedState + * @return {String} hash + */ + History.getHashByState = function(passedState) { + // Prepare + var hash, State = History.normalizeState(passedState); - /** - * History.getLastSavedState() - * Get an object containing the data, title and url of the current state - * @return {Object} State - */ - History.getLastSavedState = function(){ - return History.savedStates[History.savedStates.length-1]||undefined; - }; + // Fetch + hash = State.hash; - /** - * History.getLastStoredState() - * Get an object containing the data, title and url of the current state - * @return {Object} State - */ - History.getLastStoredState = function(){ - return History.storedStates[History.storedStates.length-1]||undefined; - }; + // Return + return hash; + }; - /** - * History.hasUrlDuplicate - * Checks if a Url will have a url conflict - * @param {Object} newState - * @return {Boolean} hasDuplicate - */ - History.hasUrlDuplicate = function(newState) { - // Prepare - var hasDuplicate = false; + /** + * History.extractId(url_or_hash) + * Get a State ID by it's URL or Hash + * @param {string} url_or_hash + * @return {string} id + */ + History.extractId = function (url_or_hash) { + // Prepare + var id; - // Fetch - var oldState = History.extractState(newState.url); + // Extract + var parts,url; + parts = /(.*)\&_suid=([0-9]+)$/.exec(url_or_hash); + url = parts ? (parts[1] || url_or_hash) : url_or_hash; + id = parts ? String(parts[2] || '') : ''; - // Check - hasDuplicate = oldState && oldState.id !== newState.id; + // Return + return id || false; + }; - // Return - return hasDuplicate; - }; + /** + * History.isTraditionalAnchor + * Checks to see if the url is a traditional anchor or not + * @param {String} url_or_hash + * @return {Boolean} + */ + History.isTraditionalAnchor = function(url_or_hash) { + // Check + var isTraditional = !(/[\/\?\.]/.test(url_or_hash)); - /** - * History.storeState - * Store a State - * @param {Object} newState - * @return {Object} newState - */ - History.storeState = function(newState){ - // Store the State - History.urlToId[newState.url] = newState.id; + // Return + return isTraditional; + }; - // Push the State - History.storedStates.push(History.cloneObject(newState)); + /** + * History.extractState + * Get a State by it's URL or Hash + * @param {String} url_or_hash + * @return {State|null} + */ + History.extractState = function(url_or_hash, create) { + // Prepare + var State = null; + create = create || false; - // Return newState - return newState; - }; + // Fetch SUID + var id = History.extractId(url_or_hash); + if (id) { + State = History.getStateById(id); + } - /** - * History.isLastSavedState(newState) - * Tests to see if the state is the last state - * @param {Object} newState - * @return {boolean} isLast - */ - History.isLastSavedState = function(newState){ - // Prepare - var isLast = false; + // Fetch SUID returned no State + if (!State) { + // Fetch URL + var url = History.getFullUrl(url_or_hash); - // Check - if ( History.savedStates.length ) { - var - newId = newState.id, - oldState = History.getLastSavedState(), - oldId = oldState.id; + // Check URL + id = History.getIdByUrl(url) || false; + if (id) { + State = History.getStateById(id); + } - // Check - isLast = (newId === oldId); - } + // Create State + if (!State && create && !History.isTraditionalAnchor(url_or_hash)) { + State = History.createStateObject(null, null, url); + } + } - // Return - return isLast; - }; + // Return + return State; + }; - /** - * History.saveState - * Push a State - * @param {Object} newState - * @return {boolean} changed - */ - History.saveState = function(newState){ - // Check Hash - if ( History.isLastSavedState(newState) ) { - return false; - } + /** + * History.getIdByUrl() + * Get a State ID by a State URL + */ + History.getIdByUrl = function(url) { + // Fetch + var id = History.urlToId[url] || History.store.urlToId[url] || undefined; - // Push the State - History.savedStates.push(History.cloneObject(newState)); + // Return + return id; + }; - // Return true - return true; - }; + /** + * History.getLastSavedState() + * Get an object containing the data, title and url of the current state + * @return {Object} State + */ + History.getLastSavedState = function() { + return History.savedStates[History.savedStates.length - 1] || undefined; + }; - /** - * History.getStateByIndex() - * Gets a state by the index - * @param {integer} index - * @return {Object} - */ - History.getStateByIndex = function(index){ - // Prepare - var State = null; + /** + * History.getLastStoredState() + * Get an object containing the data, title and url of the current state + * @return {Object} State + */ + History.getLastStoredState = function() { + return History.storedStates[History.storedStates.length - 1] || undefined; + }; - // Handle - if ( typeof index === 'undefined' ) { - // Get the last inserted - State = History.savedStates[History.savedStates.length-1]; - } - else if ( index < 0 ) { - // Get from the end - State = History.savedStates[History.savedStates.length+index]; - } - else { - // Get from the beginning - State = History.savedStates[index]; - } + /** + * History.hasUrlDuplicate + * Checks if a Url will have a url conflict + * @param {Object} newState + * @return {Boolean} hasDuplicate + */ + History.hasUrlDuplicate = function(newState) { + // Prepare + var hasDuplicate = false; - // Return State - return State; - }; + // Fetch + var oldState = History.extractState(newState.url); - // ---------------------------------------------------------------------- - // Hash Helpers + // Check + hasDuplicate = oldState && oldState.id !== newState.id; - /** - * History.getHash() - * Gets the current document hash - * @return {string} - */ - History.getHash = function(){ - var hash = History.unescapeHash(document.location.hash); - return hash; - }; + // Return + return hasDuplicate; + }; - /** - * History.unescapeHash() - * normalize and Unescape a Hash - * @return {string} - */ - History.unescapeHash = function(hash){ - var result = History.normalizeHash(hash); + /** + * History.storeState + * Store a State + * @param {Object} newState + * @return {Object} newState + */ + History.storeState = function(newState) { + // Store the State + History.urlToId[newState.url] = newState.id; - // Unescape hash - if ( /\%[^2][^5]/.test(result) ) { - result = window.unescape(result); - } + // Push the State + History.storedStates.push(History.cloneObject(newState)); - // Return result - return result; - }; + // Return newState + return newState; + }; - /** - * History.normalizeHash() - * normalize a hash across browsers - * @return {string} - */ - History.normalizeHash = function(hash){ - var result = hash.replace(/[^#]*#/,'').replace(/#.*/, ''); + /** + * History.isLastSavedState(newState) + * Tests to see if the state is the last state + * @param {Object} newState + * @return {boolean} isLast + */ + History.isLastSavedState = function(newState) { + // Prepare + var isLast = false; - // Return result - return result; - }; + // Check + if (History.savedStates.length) { + var + newId = newState.id, + oldState = History.getLastSavedState(), + oldId = oldState.id; - /** - * History.setHash(hash) - * Sets the document hash - * @param {string} hash - * @return {History} - */ - History.setHash = function(hash,queue){ - // Handle Queueing - if ( queue !== false && History.busy() ) { - // Wait + Push to Queue - //History.debug('History.setHash: we must wait', arguments); - History.pushQueue({ - scope: History, - callback: History.setHash, - args: arguments, - queue: queue - }); - return false; - } + // Check + isLast = (newId === oldId); + } - // Log - //History.debug('History.setHash: called',hash); + // Return + return isLast; + }; - // Prepare - var adjustedHash = History.escapeHash(hash); + /** + * History.saveState + * Push a State + * @param {Object} newState + * @return {boolean} changed + */ + History.saveState = function(newState) { + // Check Hash + if (History.isLastSavedState(newState)) { + return false; + } - // Make Busy + Continue - History.busy(true); + // Push the State + History.savedStates.push(History.cloneObject(newState)); - // Check if hash is a state - var State = History.extractState(hash,true); - if ( State && !History.emulated.pushState ) { - // Hash is a state so skip the setHash - //History.debug('History.setHash: Hash is a state so skipping the hash set with a direct pushState call',arguments); + // Return true + return true; + }; - // PushState - History.pushState(State.data,State.title,State.url,false); - } - else if ( document.location.hash !== adjustedHash ) { - // Hash is a proper hash, so apply it + /** + * History.getStateByIndex() + * Gets a state by the index + * @param {integer} index + * @return {Object} + */ + History.getStateByIndex = function(index) { + // Prepare + var State = null; - // Handle browser bugs - if ( History.bugs.setHash ) { - // Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249 + // Handle + if (typeof index === 'undefined') { + // Get the last inserted + State = History.savedStates[History.savedStates.length - 1]; + } + else if (index < 0) { + // Get from the end + State = History.savedStates[History.savedStates.length + index]; + } + else { + // Get from the beginning + State = History.savedStates[index]; + } - // Fetch the base page - var pageUrl = History.getPageUrl(); + // Return State + return State; + }; - // Safari hash apply - History.pushState(null,null,pageUrl+'#'+adjustedHash,false); - } - else { - // Normal hash apply - document.location.hash = adjustedHash; - } - } + // ---------------------------------------------------------------------- + // Hash Helpers - // Chain - return History; - }; + /** + * History.getHash() + * Gets the current document hash + * @return {string} + */ + History.getHash = function() { + var hash = History.unescapeHash(document.location.hash); + return hash; + }; - /** - * History.escape() - * normalize and Escape a Hash - * @return {string} - */ - History.escapeHash = function(hash){ - var result = History.normalizeHash(hash); + /** + * History.unescapeString() + * Unescape a string + * @param {String} str + * @return {string} + */ + History.unescapeString = function(str) { + // Prepare + var result = str; - // Escape hash - result = window.escape(result); + // Unescape hash + var tmp; + while (true) { + tmp = window.unescape(result); + if (tmp === result) { + break; + } + result = tmp; + } - // IE6 Escape Bug - if ( !History.bugs.hashEscape ) { - // Restore common parts - result = result - .replace('%21','!') - .replace('%26','&') - .replace('%3D','=') - .replace('%3F','?'); - } + // Return result + return result; + }; - // Return result - return result; - }; + /** + * History.unescapeHash() + * normalize and Unescape a Hash + * @param {String} hash + * @return {string} + */ + History.unescapeHash = function(hash) { + // Prepare + var result = History.normalizeHash(hash); - /** - * History.getHashByUrl(url) - * Extracts the Hash from a URL - * @param {string} url - * @return {string} url - */ - History.getHashByUrl = function(url){ - // Extract the hash - var hash = String(url) - .replace(/([^#]*)#?([^#]*)#?(.*)/, '$2') - ; + // Unescape hash + result = History.unescapeString(result); - // Unescape hash - hash = History.unescapeHash(hash); + // Return result + return result; + }; - // Return hash - return hash; - }; + /** + * History.normalizeHash() + * normalize a hash across browsers + * @return {string} + */ + History.normalizeHash = function(hash) { + var result = hash.replace(/[^#]*#/, '').replace(/#.*/, ''); - /** - * History.isTraditionalAnchor(url) - * Checks to see if the url is a traditional anchor - * @param {string} url - * @return {boolean} - */ - History.isTraditionalAnchor = function(url){ - var - hash = History.getHashByUrl(url), - el = document.getElementById(hash), - isTraditionalAnchor = typeof el !== 'undefined'; + // Return result + return result; + }; - // Return isTraditionalAnchor - return isTraditionalAnchor; - }; + /** + * History.setHash(hash) + * Sets the document hash + * @param {string} hash + * @return {History} + */ + History.setHash = function(hash, queue) { + // Handle Queueing + if (queue !== false && History.busy()) { + // Wait + Push to Queue + //History.debug('History.setHash: we must wait', arguments); + History.pushQueue({ + scope: History, + callback: History.setHash, + args: arguments, + queue: queue + }); + return false; + } - /** - * History.setTitle(title) - * Applies the title to the document - * @param {State} newState - * @return {Boolean} - */ - History.setTitle = function(newState){ - // Prepare - var title = newState.title; + // Log + //History.debug('History.setHash: called',hash); - // Initial - if ( !title ) { - var firstState = History.getStateByIndex(0); - if ( firstState && firstState.url === newState.url ) { - title = firstState.title||History.options.initialTitle; - } - } + // Prepare + var adjustedHash = History.escapeHash(hash); - // Apply - try { - document.getElementsByTagName('title')[0].innerHTML = title.replace('<','&lt;').replace('>','&gt;').replace(' & ',' &amp; '); - } - catch ( Exception ) { } - document.title = title; + // Make Busy + Continue + History.busy(true); - // Chain - return History; - }; + // Check if hash is a state + var State = History.extractState(hash, true); + if (State && !History.emulated.pushState) { + // Hash is a state so skip the setHash + //History.debug('History.setHash: Hash is a state so skipping the hash set with a direct pushState call',arguments); - // ---------------------------------------------------------------------- - // Queueing + // PushState + History.pushState(State.data, State.title, State.url, false); + } + else if (document.location.hash !== adjustedHash) { + // Hash is a proper hash, so apply it - /** - * History.queues - * The list of queues to use - * First In, First Out - */ - History.queues = []; + // Handle browser bugs + if (History.bugs.setHash) { + // Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249 - /** - * History.busy(value) - * @param {boolean} value [optional] - * @return {boolean} busy - */ - History.busy = function(value){ - // Apply - if ( typeof value !== 'undefined' ) { - //History.debug('History.busy: changing ['+(History.busy.flag||false)+'] to ['+(value||false)+']', History.queues.length); - History.busy.flag = value; - } - // Default - else if ( typeof History.busy.flag === 'undefined' ) { - History.busy.flag = false; - } + // Fetch the base page + var pageUrl = History.getPageUrl(); - // Queue - if ( !History.busy.flag ) { - // Execute the next item in the queue - clearTimeout(History.busy.timeout); - var fireNext = function(){ - if ( History.busy.flag ) return; - for ( var i=History.queues.length-1; i >= 0; --i ) { - var queue = History.queues[i]; - if ( queue.length === 0 ) continue; - var item = queue.shift(); - History.fireQueueItem(item); - History.busy.timeout = setTimeout(fireNext,History.options.busyDelay); - } - }; - History.busy.timeout = setTimeout(fireNext,History.options.busyDelay); - } + // Safari hash apply + History.pushState(null, null, pageUrl + '#' + adjustedHash, false); + } + else { + // Normal hash apply + document.location.hash = adjustedHash; + } + } - // Return - return History.busy.flag; - }; + // Chain + return History; + }; - /** - * History.fireQueueItem(item) - * Fire a Queue Item - * @param {Object} item - * @return {Mixed} result - */ - History.fireQueueItem = function(item){ - return item.callback.apply(item.scope||History,item.args||[]); - }; + /** + * History.escape() + * normalize and Escape a Hash + * @return {string} + */ + History.escapeHash = function(hash) { + var result = History.normalizeHash(hash); - /** - * History.pushQueue(callback,args) - * Add an item to the queue - * @param {Object} item [scope,callback,args,queue] - */ - History.pushQueue = function(item){ - // Prepare the queue - History.queues[item.queue||0] = History.queues[item.queue||0]||[]; + // Escape hash + result = window.escape(result); - // Add to the queue - History.queues[item.queue||0].push(item); + // IE6 Escape Bug + if (!History.bugs.hashEscape) { + // Restore common parts + result = result + .replace(/\%21/g, '!') + .replace(/\%26/g, '&') + .replace(/\%3D/g, '=') + .replace(/\%3F/g, '?'); + } - // Chain - return History; - }; + // Return result + return result; + }; - /** - * History.queue (item,queue), (func,queue), (func), (item) - * Either firs the item now if not busy, or adds it to the queue - */ - History.queue = function(item,queue){ - // Prepare - if ( typeof item === 'function' ) { - item = { - callback: item - }; - } - if ( typeof queue !== 'undefined' ) { - item.queue = queue; - } + /** + * History.getHashByUrl(url) + * Extracts the Hash from a URL + * @param {string} url + * @return {string} url + */ + History.getHashByUrl = function(url) { + // Extract the hash + var hash = String(url) + .replace(/([^#]*)#?([^#]*)#?(.*)/, '$2') + ; - // Handle - if ( History.busy() ) { - History.pushQueue(item); - } else { - History.fireQueueItem(item); - } + // Unescape hash + hash = History.unescapeHash(hash); - // Chain - return History; - }; + // Return hash + return hash; + }; - /** - * History.clearQueue() - * Clears the Queue - */ - History.clearQueue = function(){ - History.busy.flag = false; - History.queues = []; - return History; - }; + /** + * History.setTitle(title) + * Applies the title to the document + * @param {State} newState + * @return {Boolean} + */ + History.setTitle = function(newState) { + // Prepare + var title = newState.title; + // Initial + if (!title) { + var firstState = History.getStateByIndex(0); + if (firstState && firstState.url === newState.url) { + title = firstState.title || History.options.initialTitle; + } + } - // ---------------------------------------------------------------------- - // IE Bug Fix + // Apply + try { + document.getElementsByTagName('title')[0].innerHTML = title.replace('<', '&lt;').replace('>', '&gt;').replace(' & ', ' &amp; '); + } + catch (Exception) { + } + document.title = title; - /** - * History.stateChanged - * States whether or not the state has changed since the last double check was initialised - */ - History.stateChanged = false; + // Chain + return History; + }; - /** - * History.doubleChecker - * Contains the timeout used for the double checks - */ - History.doubleChecker = false; + // ---------------------------------------------------------------------- + // Queueing - /** - * History.doubleCheckComplete() - * Complete a double check - * @return {History} - */ - History.doubleCheckComplete = function(){ - // Update - History.stateChanged = true; + /** + * History.queues + * The list of queues to use + * First In, First Out + */ + History.queues = []; - // Clear - History.doubleCheckClear(); + /** + * History.busy(value) + * @param {boolean} value [optional] + * @return {boolean} busy + */ + History.busy = function(value) { + // Apply + if (typeof value !== 'undefined') { + //History.debug('History.busy: changing ['+(History.busy.flag||false)+'] to ['+(value||false)+']', History.queues.length); + History.busy.flag = value; + } + // Default + else if (typeof History.busy.flag === 'undefined') { + History.busy.flag = false; + } - // Chain - return History; - }; + // Queue + if (!History.busy.flag) { + // Execute the next item in the queue + clearTimeout(History.busy.timeout); + var fireNext = function() { + if (History.busy.flag) return; + for (var i = History.queues.length - 1; i >= 0; --i) { + var queue = History.queues[i]; + if (queue.length === 0) continue; + var item = queue.shift(); + History.fireQueueItem(item); + History.busy.timeout = setTimeout(fireNext, History.options.busyDelay); + } + }; + History.busy.timeout = setTimeout(fireNext, History.options.busyDelay); + } - /** - * History.doubleCheckClear() - * Clear a double check - * @return {History} - */ - History.doubleCheckClear = function(){ - // Clear - if ( History.doubleChecker ) { - clearTimeout(History.doubleChecker); - History.doubleChecker = false; - } + // Return + return History.busy.flag; + }; - // Chain - return History; - }; + /** + * History.fireQueueItem(item) + * Fire a Queue Item + * @param {Object} item + * @return {Mixed} result + */ + History.fireQueueItem = function(item) { + return item.callback.apply(item.scope || History, item.args || []); + }; - /** - * History.doubleCheck() - * Create a double check - * @return {History} - */ - History.doubleCheck = function(tryAgain){ - // Reset - History.stateChanged = false; - History.doubleCheckClear(); + /** + * History.pushQueue(callback,args) + * Add an item to the queue + * @param {Object} item [scope,callback,args,queue] + */ + History.pushQueue = function(item) { + // Prepare the queue + History.queues[item.queue || 0] = History.queues[item.queue || 0] || []; - // 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 ( History.bugs.ieDoubleCheck ) { - // Apply Check - History.doubleChecker = setTimeout( - function(){ - History.doubleCheckClear(); - if ( !History.stateChanged ) { - //History.debug('History.doubleCheck: State has not yet changed, trying again', arguments); - // Re-Attempt - tryAgain(); - } - return true; - }, - History.options.doubleCheckInterval - ); - } + // Add to the queue + History.queues[item.queue || 0].push(item); - // Chain - return History; - }; + // Chain + return History; + }; - // ---------------------------------------------------------------------- - // Safari Bug Fix + /** + * History.queue (item,queue), (func,queue), (func), (item) + * Either firs the item now if not busy, or adds it to the queue + */ + History.queue = function(item, queue) { + // Prepare + if (typeof item === 'function') { + item = { + callback: item + }; + } + if (typeof queue !== 'undefined') { + item.queue = queue; + } - /** - * History.safariStatePoll() - * Poll the current state - * @return {History} - */ - History.safariStatePoll = function(){ - // Poll the URL + // Handle + if (History.busy()) { + History.pushQueue(item); + } else { + History.fireQueueItem(item); + } - // Get the Last State which has the new URL - var - urlState = History.extractState(document.location.href), - newState; + // Chain + return History; + }; - // Check for a difference - if ( !History.isLastSavedState(urlState) ) { - newState = urlState; - } - else { - return; - } + /** + * History.clearQueue() + * Clears the Queue + */ + History.clearQueue = function() { + History.busy.flag = false; + History.queues = []; + return History; + }; - // Check if we have a state with that url - // If not create it - if ( !newState ) { - //History.debug('History.safariStatePoll: new'); - newState = History.createStateObject(); - } - // Apply the New State - //History.debug('History.safariStatePoll: trigger'); - History.Adapter.trigger(window,'popstate'); + // ---------------------------------------------------------------------- + // IE Bug Fix - // Chain - return History; - }; + /** + * History.stateChanged + * States whether or not the state has changed since the last double check was initialised + */ + History.stateChanged = false; - // ---------------------------------------------------------------------- - // State Aliases + /** + * History.doubleChecker + * Contains the timeout used for the double checks + */ + History.doubleChecker = false; - /** - * History.back(queue) - * Send the browser history back one item - * @param {Integer} queue [optional] - */ - History.back = function(queue){ - //History.debug('History.back: called', arguments); + /** + * History.doubleCheckComplete() + * Complete a double check + * @return {History} + */ + History.doubleCheckComplete = function() { + // Update + History.stateChanged = true; - // Handle Queueing - if ( queue !== false && History.busy() ) { - // Wait + Push to Queue - //History.debug('History.back: we must wait', arguments); - History.pushQueue({ - scope: History, - callback: History.back, - args: arguments, - queue: queue - }); - return false; - } + // Clear + History.doubleCheckClear(); - // Make Busy + Continue - History.busy(true); + // Chain + return History; + }; - // Fix certain browser bugs that prevent the state from changing - History.doubleCheck(function(){ - History.back(false); - }); + /** + * History.doubleCheckClear() + * Clear a double check + * @return {History} + */ + History.doubleCheckClear = function() { + // Clear + if (History.doubleChecker) { + clearTimeout(History.doubleChecker); + History.doubleChecker = false; + } - // Go back - history.go(-1); + // Chain + return History; + }; - // End back closure - return true; - }; + /** + * History.doubleCheck() + * Create a double check + * @return {History} + */ + History.doubleCheck = function(tryAgain) { + // Reset + History.stateChanged = false; + History.doubleCheckClear(); - /** - * History.forward(queue) - * Send the browser history forward one item - * @param {Integer} queue [optional] - */ - History.forward = function(queue){ - //History.debug('History.forward: called', arguments); + // 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 (History.bugs.ieDoubleCheck) { + // Apply Check + History.doubleChecker = setTimeout( + function() { + History.doubleCheckClear(); + if (!History.stateChanged) { + //History.debug('History.doubleCheck: State has not yet changed, trying again', arguments); + // Re-Attempt + tryAgain(); + } + return true; + }, + History.options.doubleCheckInterval + ); + } - // Handle Queueing - if ( queue !== false && History.busy() ) { - // Wait + Push to Queue - //History.debug('History.forward: we must wait', arguments); - History.pushQueue({ - scope: History, - callback: History.forward, - args: arguments, - queue: queue - }); - return false; - } + // Chain + return History; + }; - // Make Busy + Continue - History.busy(true); + // ---------------------------------------------------------------------- + // Safari Bug Fix - // Fix certain browser bugs that prevent the state from changing - History.doubleCheck(function(){ - History.forward(false); - }); + /** + * History.safariStatePoll() + * Poll the current state + * @return {History} + */ + History.safariStatePoll = function() { + // Poll the URL - // Go forward - history.go(1); + // Get the Last State which has the new URL + var + urlState = History.extractState(document.location.href), + newState; - // End forward closure - return true; - }; + // Check for a difference + if (!History.isLastSavedState(urlState)) { + newState = urlState; + } + else { + return; + } - /** - * History.go(index,queue) - * Send the browser history back or forward index times - * @param {Integer} queue [optional] - */ - History.go = function(index,queue){ - //History.debug('History.go: called', arguments); + // Check if we have a state with that url + // If not create it + if (!newState) { + //History.debug('History.safariStatePoll: new'); + newState = History.createStateObject(); + } - // Prepare - var i; + // Apply the New State + //History.debug('History.safariStatePoll: trigger'); + History.Adapter.trigger(window, 'popstate'); - // Handle - if ( index > 0 ) { - // Forward - for ( i=1; i<=index; ++i ) { - History.forward(queue); - } - } - else if ( index < 0 ) { - // Backward - for ( i=-1; i>=index; --i ) { - History.back(queue); - } - } - else { - throw new Error('History.go: History.go requires a positive or negative integer passed.'); - } + // Chain + return History; + }; - // Chain - return History; - }; + // ---------------------------------------------------------------------- + // State Aliases + /** + * History.back(queue) + * Send the browser history back one item + * @param {Integer} queue [optional] + */ + History.back = function(queue) { + //History.debug('History.back: called', arguments); - // ---------------------------------------------------------------------- - // HTML5 State Support + // Handle Queueing + if (queue !== false && History.busy()) { + // Wait + Push to Queue + //History.debug('History.back: we must wait', arguments); + History.pushQueue({ + scope: History, + callback: History.back, + args: arguments, + queue: queue + }); + return false; + } - if ( History.emulated.pushState ) { - /* - * Provide Skeleton for HTML4 Browsers - */ + // Make Busy + Continue + History.busy(true); - // Prepare - var emptyFunction = function(){}; - History.pushState = History.pushState||emptyFunction; - History.replaceState = History.replaceState||emptyFunction; - } - else { - /* - * Use native HTML5 History API Implementation - */ + // Fix certain browser bugs that prevent the state from changing + History.doubleCheck(function() { + History.back(false); + }); - /** - * History.onPopState(event,extra) - * Refresh the Current State - */ - History.onPopState = function(event){ - // Reset the double check - History.doubleCheckComplete(); + // Go back + history.go(-1); - // Check for a Hash, and handle apporiatly - var currentHash = History.getHash(); - if ( currentHash ) { - // Expand Hash - var currentState = History.extractState(currentHash||document.location.href,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); - } + // End back closure + return true; + }; - // We don't care for hashes - History.expectedStateId = false; - return false; - } + /** + * History.forward(queue) + * Send the browser history forward one item + * @param {Integer} queue [optional] + */ + History.forward = function(queue) { + //History.debug('History.forward: called', arguments); - // Prepare - var newState = null; + // Handle Queueing + if (queue !== false && History.busy()) { + // Wait + Push to Queue + //History.debug('History.forward: we must wait', arguments); + History.pushQueue({ + scope: History, + callback: History.forward, + args: arguments, + queue: queue + }); + return false; + } - // Prepare - event = event||{}; - if ( typeof event.state === 'undefined' ) { - // jQuery - if ( typeof event.originalEvent !== 'undefined' && typeof event.originalEvent.state !== 'undefined' ) { - event.state = event.originalEvent.state||false; - } - // MooTools - else if ( typeof event.event !== 'undefined' && typeof event.event.state !== 'undefined' ) { - event.state = event.event.state||false; - } - } + // Make Busy + Continue + History.busy(true); - // Ensure - event.state = (event.state||false); + // Fix certain browser bugs that prevent the state from changing + History.doubleCheck(function() { + History.forward(false); + }); - // Fetch State - if ( event.state ) { - // Vanilla: Back/forward button was used - newState = History.getStateById(event.state); - } - 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(document.location.href); - } + // Go forward + history.go(1); - // Clean - History.expectedStateId = false; + // End forward closure + return true; + }; - // 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; - } + /** + * History.go(index,queue) + * Send the browser history back or forward index times + * @param {Integer} queue [optional] + */ + History.go = function(index, queue) { + //History.debug('History.go: called', arguments); - // Store the State - History.storeState(newState); - History.saveState(newState); + // Prepare + var i; - // Force update of the title - History.setTitle(newState); + // Handle + if (index > 0) { + // Forward + for (i = 1; i <= index; ++i) { + History.forward(queue); + } + } + else if (index < 0) { + // Backward + for (i = -1; i >= index; --i) { + History.back(queue); + } + } + else { + throw new Error('History.go: History.go requires a positive or negative integer passed.'); + } - // Fire Our Event - History.Adapter.trigger(window,'statechange'); - History.busy(false); + // Chain + return History; + }; - // Return true - return true; - }; - History.Adapter.bind(window,'popstate',History.onPopState); - /** - * 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); + // ---------------------------------------------------------------------- + // Initialise - // Check the State - if ( History.getHashByUrl(url) && History.emulated.pushState ) { - throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).'); - } + /** + * Create the initial State + */ + History.saveState(History.storeState(History.extractState(document.location.href, true))); - // 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; - } + /** + * Bind for Saving Store + */ + if (amplify) { + History.onUnload = function() { + // Prepare + var + currentStore = amplify.store('History.store') || {}, + item; - // Make Busy + Continue - History.busy(true); + // Ensure + currentStore.idToState = currentStore.idToState || {}; + currentStore.urlToId = currentStore.urlToId || {}; + currentStore.stateToId = currentStore.stateToId || {}; - // Create the newState - var newState = History.createStateObject(data,title,url); + // 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]; + } - // 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; + // Update + History.store = currentStore; - // Push the newState - var pushUrl = - (History.bugs.safariPoll && History.hasUrlDuplicate(newState)) - ? newState.hashedUrl - : newState.url; - history.pushState(newState.id,newState.title,pushUrl); + // Store + amplify.store('History.store', currentStore); + }; + // For Internet Explorer + setInterval(History.onUnload, History.options.storeInterval); + // For Other Browsers + History.Adapter.bind(window, 'beforeunload', History.onUnload); + History.Adapter.bind(window, 'unload', History.onUnload); + // Both are enabled for consistency + } - // Fire HTML5 Event - History.Adapter.trigger(window,'popstate'); - } - // End pushState closure - return true; - }; + // ---------------------------------------------------------------------- + // HTML5 State Support - /** - * 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); + if (History.emulated.pushState) { + /* + * Provide Skeleton for HTML4 Browsers + */ - // Check the State - if ( History.getHashByUrl(url) && History.emulated.pushState ) { - throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).'); - } + // Prepare + var emptyFunction = function() { + }; + History.pushState = History.pushState || emptyFunction; + History.replaceState = History.replaceState || emptyFunction; + } + else { + /* + * Use native HTML5 History API Implementation + */ - // 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; - } + /** + * History.onPopState(event,extra) + * Refresh the Current State + */ + History.onPopState = function(event) { + // Reset the double check + History.doubleCheckComplete(); - // Make Busy + Continue - History.busy(true); + // Check for a Hash, and handle apporiatly + var currentHash = History.getHash(); + if (currentHash) { + // Expand Hash + var currentState = History.extractState(currentHash || document.location.href, 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); + } - // Create the newState - var newState = History.createStateObject(data,title,url); + // We don't care for hashes + History.expectedStateId = false; + return false; + } - // 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; + // Prepare + var newState = false; - // Push the newState - var pushUrl = - (History.bugs.safariPoll && History.hasUrlDuplicate(newState)) - ? newState.hashedUrl - : newState.url; - history.replaceState(newState.id,newState.title,pushUrl); + // Prepare + event = event || {}; + if (typeof event.state === 'undefined') { + // jQuery + if (typeof event.originalEvent !== 'undefined' && typeof event.originalEvent.state !== 'undefined') { + event.state = event.originalEvent.state || false; + } + // MooTools + else if (typeof event.event !== 'undefined' && typeof event.event.state !== 'undefined') { + event.state = event.event.state || false; + } + } - // Fire HTML5 Event - History.Adapter.trigger(window,'popstate'); - } + // Ensure + event.state = (event.state || false); - // End replaceState closure - return true; - }; + // Fetch State + if (event.state) { + // Vanilla: Back/forward button was used + newState = History.getStateById(event.state); + } + 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(document.location.href); + } - /** - * Create the initial State - */ - History.saveState(History.storeState(History.createStateObject({},'',document.location.href))); + // The State did not exist in our store + if (!newState) { + // Regenerate the State + newState = History.createStateObject(null, null, document.location.href); + } - /** - * Setup Safari Fix - */ - if ( History.bugs.safariPoll ) { - setInterval(History.safariStatePoll, History.options.safariPollInterval); - } + // Clean + History.expectedStateId = false; - /** - * Ensure Cross Browser Compatibility - */ - if ( navigator.vendor === 'Apple Computer, Inc.' || (navigator.appCodeName||'') === 'Mozilla' ) { - /** - * Fix Safari HashChange Issue - */ + // 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; + } - // Setup Alias - History.Adapter.bind(window,'hashchange',function(){ - History.Adapter.trigger(window,'popstate'); - }); + // Store the State + History.storeState(newState); + History.saveState(newState); - // Initialise Alias - if ( History.getHash() ) { - History.Adapter.onDomLoad(function(){ - History.Adapter.trigger(window,'hashchange'); - }); - } - } + // Force update of the title + History.setTitle(newState); - } // !History.emulated.pushState + // Fire Our Event + History.Adapter.trigger(window, 'statechange'); + History.busy(false); - }; // History.initCore + // Return true + return true; + }; + History.Adapter.bind(window, 'popstate', History.onPopState); - // Try and Initialise History - History.init(); + /** + * 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); + + // Check the State + if (History.getHashByUrl(url) && History.emulated.pushState) { + throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).'); + } + + // 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; + } + + // Make Busy + Continue + History.busy(true); + + // Create the newState + var newState = History.createStateObject(data, title, url); + + // 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; + + // Push the newState + history.pushState(newState.id, newState.title, newState.url); + + // Fire HTML5 Event + History.Adapter.trigger(window, 'popstate'); + } + + // End pushState closure + return true; + }; + + /** + * 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); + + // Check the State + if (History.getHashByUrl(url) && History.emulated.pushState) { + throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).'); + } + + // 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; + } + + // Make Busy + Continue + History.busy(true); + + // Create the newState + var newState = History.createStateObject(data, title, url); + + // 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; + + // Push the newState + history.replaceState(newState.id, newState.title, newState.url); + + // Fire HTML5 Event + History.Adapter.trigger(window, 'popstate'); + } + + // End replaceState closure + return true; + }; + + // 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) { + setInterval(History.safariStatePoll, History.options.safariPollInterval); + } + + /** + * Ensure Cross Browser Compatibility + */ + if (navigator.vendor === 'Apple Computer, Inc.' || (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 and Initialise History + History.init(); })(window);