native.history.js
1974 lines
| 48.5 KiB
| application/javascript
|
JavascriptLexer
r3038 | /** | |||
* History.js Native Adapter | ||||
* @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/> | ||||
*/ | ||||
// Closure | ||||
(function(window,undefined){ | ||||
"use strict"; | ||||
// Localise Globals | ||||
var History = window.History = window.History||{}; | ||||
// Check Existence | ||||
if ( typeof History.Adapter !== 'undefined' ) { | ||||
throw new Error('History.js Adapter has already been loaded...'); | ||||
} | ||||
// Add the Adapter | ||||
History.Adapter = { | ||||
/** | ||||
* History.Adapter.handlers[uid][eventName] = Array | ||||
*/ | ||||
handlers: {}, | ||||
/** | ||||
* History.Adapter._uid | ||||
* The current element unique identifier | ||||
*/ | ||||
_uid: 1, | ||||
/** | ||||
* History.Adapter.uid(element) | ||||
* @param {Element} element | ||||
* @return {String} uid | ||||
*/ | ||||
uid: function(element){ | ||||
return element._uid || (element._uid = History.Adapter._uid++); | ||||
}, | ||||
/** | ||||
* History.Adapter.bind(el,event,callback) | ||||
* @param {Element} element | ||||
* @param {String} eventName - custom and standard events | ||||
* @param {Function} callback | ||||
* @return | ||||
*/ | ||||
bind: function(element,eventName,callback){ | ||||
// Prepare | ||||
var uid = History.Adapter.uid(element); | ||||
// Apply Listener | ||||
History.Adapter.handlers[uid] = History.Adapter.handlers[uid] || {}; | ||||
History.Adapter.handlers[uid][eventName] = History.Adapter.handlers[uid][eventName] || []; | ||||
History.Adapter.handlers[uid][eventName].push(callback); | ||||
// Bind Global Listener | ||||
element['on'+eventName] = (function(element,eventName){ | ||||
return function(event){ | ||||
History.Adapter.trigger(element,eventName,event); | ||||
}; | ||||
})(element,eventName); | ||||
}, | ||||
/** | ||||
* History.Adapter.trigger(el,event) | ||||
* @param {Element} element | ||||
* @param {String} eventName - custom and standard events | ||||
* @param {Object} event - a object of event data | ||||
* @return | ||||
*/ | ||||
trigger: function(element,eventName,event){ | ||||
// Prepare | ||||
event = event || {}; | ||||
var uid = History.Adapter.uid(element), | ||||
i,n; | ||||
// Apply Listener | ||||
History.Adapter.handlers[uid] = History.Adapter.handlers[uid] || {}; | ||||
History.Adapter.handlers[uid][eventName] = History.Adapter.handlers[uid][eventName] || []; | ||||
// Fire Listeners | ||||
for ( i=0,n=History.Adapter.handlers[uid][eventName].length; i<n; ++i ) { | ||||
History.Adapter.handlers[uid][eventName][i].apply(this,[event]); | ||||
} | ||||
}, | ||||
/** | ||||
* History.Adapter.extractEventData(key,event,extra) | ||||
* @param {String} key - key for the event data to extract | ||||
* @param {String} event - custom and standard events | ||||
* @return {mixed} | ||||
*/ | ||||
extractEventData: function(key,event){ | ||||
var result = (event && event[key]) || undefined; | ||||
return result; | ||||
}, | ||||
/** | ||||
* History.Adapter.onDomLoad(callback) | ||||
* @param {Function} callback | ||||
* @return | ||||
*/ | ||||
onDomLoad: function(callback) { | ||||
var timeout = window.setTimeout(function(){ | ||||
callback(); | ||||
},2000); | ||||
window.onload = function(){ | ||||
clearTimeout(timeout); | ||||
callback(); | ||||
}; | ||||
} | ||||
}; | ||||
// Try and Initialise History | ||||
if ( typeof History.init !== 'undefined' ) { | ||||
History.init(); | ||||
} | ||||
})(window); | ||||
/** | ||||
* History.js Core | ||||
* @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"; | ||||
// -------------------------------------------------------------------------- | ||||
// 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 | ||||
amplify = window.amplify||false, // Amplify.js | ||||
setTimeout = window.setTimeout, | ||||
clearTimeout = window.clearTimeout, | ||||
setInterval = window.setInterval, | ||||
clearInterval = window.clearInterval, | ||||
JSON = window.JSON, | ||||
History = window.History = window.History||{}, // Public History Object | ||||
history = window.history; // Old History Object | ||||
// MooTools Compatibility | ||||
JSON.stringify = JSON.stringify||JSON.encode; | ||||
JSON.parse = JSON.parse||JSON.decode; | ||||
// Check Existence | ||||
if ( typeof History.init !== 'undefined' ) { | ||||
throw new Error('History.js Core has already been loaded...'); | ||||
} | ||||
// Initialise History | ||||
History.init = function(){ | ||||
// Check Load Status of Adapter | ||||
if ( typeof History.Adapter === 'undefined' ) { | ||||
return false; | ||||
} | ||||
// Check Load Status of Core | ||||
if ( typeof History.initCore !== 'undefined' ) { | ||||
History.initCore(); | ||||
} | ||||
// Check Load Status of HTML4 Support | ||||
if ( typeof History.initHtml4 !== 'undefined' ) { | ||||
History.initHtml4(); | ||||
} | ||||
// Return true | ||||
return true; | ||||
}; | ||||
// -------------------------------------------------------------------------- | ||||
// Initialise Core | ||||
// Initialise Core | ||||
History.initCore = function(){ | ||||
// Initialise | ||||
if ( typeof History.initCore.initialized !== 'undefined' ) { | ||||
// Already Loaded | ||||
return false; | ||||
} | ||||
else { | ||||
History.initCore.initialized = true; | ||||
} | ||||
// ---------------------------------------------------------------------- | ||||
// Options | ||||
/** | ||||
* History.options | ||||
* Configurable options | ||||
*/ | ||||
History.options = History.options||{}; | ||||
/** | ||||
* History.options.hashChangeInterval | ||||
* How long should the interval be before hashchange checks | ||||
*/ | ||||
History.options.hashChangeInterval = History.options.hashChangeInterval || 100; | ||||
/** | ||||
* History.options.safariPollInterval | ||||
* How long should the interval be before safari poll checks | ||||
*/ | ||||
History.options.safariPollInterval = History.options.safariPollInterval || 500; | ||||
/** | ||||
* History.options.doubleCheckInterval | ||||
* How long should the interval be before we perform a double check | ||||
*/ | ||||
History.options.doubleCheckInterval = History.options.doubleCheckInterval || 500; | ||||
/** | ||||
* History.options.storeInterval | ||||
* How long should we wait between store calls | ||||
*/ | ||||
History.options.storeInterval = History.options.storeInterval || 1000; | ||||
/** | ||||
* 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; | ||||
/** | ||||
* History.options.initialTitle | ||||
* What is the title of the initial state | ||||
*/ | ||||
History.options.initialTitle = History.options.initialTitle || document.title; | ||||
// ---------------------------------------------------------------------- | ||||
// Interval record | ||||
/** | ||||
* History.intervalList | ||||
* List of intervals set, to be cleared when document is unloaded. | ||||
*/ | ||||
History.intervalList = []; | ||||
/** | ||||
* History.clearAllIntervals | ||||
* Clears all setInterval instances. | ||||
*/ | ||||
History.clearAllIntervals = function(){ | ||||
var i, il = History.intervalList; | ||||
if (typeof il !== "undefined" && il !== null) { | ||||
for (i = 0; i < il.length; i++) { | ||||
clearInterval(il[i]); | ||||
} | ||||
History.intervalList = null; | ||||
} | ||||
}; | ||||
History.Adapter.bind(window,"beforeunload",History.clearAllIntervals); | ||||
History.Adapter.bind(window,"unload",History.clearAllIntervals); | ||||
// ---------------------------------------------------------------------- | ||||
// Debug | ||||
/** | ||||
* 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 | ||||
; | ||||
// 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"); | ||||
} | ||||
// 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"; | ||||
} | ||||
// Textarea | ||||
if ( textarea ) { | ||||
textarea.value += message+"\n-----\n"; | ||||
textarea.scrollTop = textarea.scrollHeight - textarea.clientHeight; | ||||
} | ||||
// No Textarea, No Console | ||||
else if ( !consoleExists ) { | ||||
alert(message); | ||||
} | ||||
// Return true | ||||
return true; | ||||
}; | ||||
// ---------------------------------------------------------------------- | ||||
// Emulated Status | ||||
/** | ||||
* History.getInternetExplorerMajorVersion() | ||||
r4089 | * Gets the major version of Internet Explorer | |||
r3038 | * @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.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; | ||||
}; | ||||
/** | ||||
* 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) | ||||
) | ||||
}; | ||||
/** | ||||
* History.enabled | ||||
* Is History enabled? | ||||
*/ | ||||
History.enabled = !History.emulated.pushState; | ||||
/** | ||||
* 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)), | ||||
/** | ||||
* 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)), | ||||
/** | ||||
* 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), | ||||
/** | ||||
* 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.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; | ||||
}; | ||||
/** | ||||
* 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; | ||||
}; | ||||
// ---------------------------------------------------------------------- | ||||
// URL Helpers | ||||
/** | ||||
* 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 += '/'; | ||||
// Return | ||||
return rootUrl; | ||||
}; | ||||
/** | ||||
* 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 = ''; | ||||
// Test for Base Element | ||||
if ( baseElements.length === 1 ) { | ||||
// Prepare for Base Element | ||||
baseElement = baseElements[0]; | ||||
baseHref = baseElement.href.replace(/[^\/]+$/,''); | ||||
} | ||||
// Adjust trailing slash | ||||
baseHref = baseHref.replace(/\/+$/,''); | ||||
if ( baseHref ) baseHref += '/'; | ||||
// Return | ||||
return 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(); | ||||
// Return | ||||
return baseUrl; | ||||
}; | ||||
/** | ||||
* 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.URL||document.location.href; | ||||
// Create | ||||
var pageUrl = stateUrl.replace(/\/+$/,'').replace(/[^\/]+$/,function(part,index,string){ | ||||
return (/\./).test(part) ? part : part+'/'; | ||||
}); | ||||
// Return | ||||
return pageUrl; | ||||
}; | ||||
/** | ||||
* History.getBasePageUrl() | ||||
* Fetches the Url of the directory of the current page | ||||
* @return {String} basePageUrl | ||||
*/ | ||||
History.getBasePageUrl = function(){ | ||||
// Create | ||||
var basePageUrl = (document.URL||document.location.href).replace(/[#\?].*/,'').replace(/[^\/]+$/,function(part,index,string){ | ||||
return (/[^\/]$/).test(part) ? '' : part; | ||||
}).replace(/\/+$/,'')+'/'; | ||||
// Return | ||||
return basePageUrl; | ||||
}; | ||||
/** | ||||
* 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; | ||||
// 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 fullUrl.replace(/\#$/,''); | ||||
}; | ||||
/** | ||||
* 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(); | ||||
// 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,''); | ||||
} | ||||
// Trim rootUrl | ||||
shortUrl = shortUrl.replace(rootUrl,'/'); | ||||
// Ensure we can still detect it as a state | ||||
if ( History.isTraditionalAnchor(shortUrl) ) { | ||||
shortUrl = './'+shortUrl; | ||||
} | ||||
// Clean It | ||||
shortUrl = shortUrl.replace(/^(\.\/)+/g,'./').replace(/\#$/,''); | ||||
// Return | ||||
return shortUrl; | ||||
}; | ||||
// ---------------------------------------------------------------------- | ||||
// State Storage | ||||
/** | ||||
* 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||{}; | ||||
/** | ||||
* History.idToState | ||||
* 1-1: State ID to State Object | ||||
*/ | ||||
History.idToState = History.idToState||{}; | ||||
/** | ||||
* History.stateToId | ||||
* 1-1: State String to State ID | ||||
*/ | ||||
History.stateToId = History.stateToId||{}; | ||||
/** | ||||
* History.urlToId | ||||
* 1-1: State URL to State ID | ||||
*/ | ||||
History.urlToId = History.urlToId||{}; | ||||
/** | ||||
* History.storedStates | ||||
* Store the states in an array | ||||
*/ | ||||
History.storedStates = History.storedStates||[]; | ||||
/** | ||||
* History.savedStates | ||||
* Saved the states in an array | ||||
*/ | ||||
History.savedStates = History.savedStates||[]; | ||||
/** | ||||
* 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; } | ||||
// Fetch | ||||
var State = History.getLastSavedState(); | ||||
// Create | ||||
if ( !State && create ) { | ||||
State = History.createStateObject(); | ||||
} | ||||
// Adjust | ||||
if ( friendly ) { | ||||
State = History.cloneObject(State); | ||||
State.url = State.cleanUrl||State.url; | ||||
} | ||||
// Return | ||||
return State; | ||||
}; | ||||
/** | ||||
* History.getIdByState(State) | ||||
* Gets a ID for a State | ||||
* @param {State} newState | ||||
* @return {String} id | ||||
*/ | ||||
History.getIdByState = function(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; | ||||
} | ||||
} | ||||
// Return ID | ||||
return id; | ||||
}; | ||||
/** | ||||
* History.normalizeState(State) | ||||
* Expands a State Object | ||||
* @param {object} State | ||||
* @return {object} | ||||
*/ | ||||
History.normalizeState = function(oldState){ | ||||
// Prepare | ||||
if ( !oldState || (typeof oldState !== 'object') ) { | ||||
oldState = {}; | ||||
} | ||||
// Check | ||||
if ( typeof oldState.normalized !== 'undefined' ) { | ||||
return oldState; | ||||
} | ||||
// Adjust | ||||
if ( !oldState.data || (typeof oldState.data !== 'object') ) { | ||||
oldState.data = {}; | ||||
} | ||||
// ---------------------------------------------------------------------- | ||||
// Create | ||||
var newState = {}; | ||||
newState.normalized = true; | ||||
newState.title = oldState.title||''; | ||||
newState.url = History.getFullUrl(oldState.url?decodeURIComponent(oldState.url):(document.URL||document.location.href)); | ||||
newState.hash = History.getShortUrl(newState.url); | ||||
newState.data = History.cloneObject(oldState.data); | ||||
// Fetch ID | ||||
newState.id = History.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 | ||||
var dataNotEmpty = !History.isEmptyObject(newState.data); | ||||
// 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; | ||||
} | ||||
// Create the Hashed URL | ||||
newState.hashedUrl = History.getFullUrl(newState.hash); | ||||
// ---------------------------------------------------------------------- | ||||
// Update the URL if we have a duplicate | ||||
if ( (History.emulated.pushState || History.bugs.safariPoll) && History.hasUrlDuplicate(newState) ) { | ||||
newState.url = newState.hashedUrl; | ||||
} | ||||
// ---------------------------------------------------------------------- | ||||
// Return | ||||
return 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': encodeURIComponent(url||"") | ||||
}; | ||||
// Expand the State | ||||
State = History.normalizeState(State); | ||||
// Return object | ||||
return State; | ||||
}; | ||||
/** | ||||
* History.getStateById(id) | ||||
* Get a state by it's UID | ||||
* @param {String} id | ||||
*/ | ||||
History.getStateById = function(id){ | ||||
// Prepare | ||||
id = String(id); | ||||
// Retrieve | ||||
var State = History.idToState[id] || History.store.idToState[id] || undefined; | ||||
// Return State | ||||
return State; | ||||
}; | ||||
/** | ||||
* Get a State's String | ||||
* @param {State} passedState | ||||
*/ | ||||
History.getStateString = function(passedState){ | ||||
// Prepare | ||||
var State = History.normalizeState(passedState); | ||||
// Clean | ||||
var cleanedState = { | ||||
data: State.data, | ||||
title: passedState.title, | ||||
url: passedState.url | ||||
}; | ||||
// Fetch | ||||
var str = JSON.stringify(cleanedState); | ||||
// Return | ||||
return str; | ||||
}; | ||||
/** | ||||
* Get a State's ID | ||||
* @param {State} passedState | ||||
* @return {String} id | ||||
*/ | ||||
History.getStateId = function(passedState){ | ||||
// Prepare | ||||
var State = History.normalizeState(passedState); | ||||
// Fetch | ||||
var id = State.id; | ||||
// Return | ||||
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); | ||||
// Fetch | ||||
hash = State.hash; | ||||
// Return | ||||
return hash; | ||||
}; | ||||
/** | ||||
* 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; | ||||
// 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]||'') : ''; | ||||
// Return | ||||
return id||false; | ||||
}; | ||||
/** | ||||
* 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)); | ||||
// Return | ||||
return isTraditional; | ||||
}; | ||||
/** | ||||
* 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; | ||||
// Fetch SUID | ||||
var id = History.extractId(url_or_hash); | ||||
if ( id ) { | ||||
State = History.getStateById(id); | ||||
} | ||||
// Fetch SUID returned no State | ||||
if ( !State ) { | ||||
// Fetch URL | ||||
var url = History.getFullUrl(url_or_hash); | ||||
// Check URL | ||||
id = History.getIdByUrl(url)||false; | ||||
if ( id ) { | ||||
State = History.getStateById(id); | ||||
} | ||||
// Create State | ||||
if ( !State && create && !History.isTraditionalAnchor(url_or_hash) ) { | ||||
State = History.createStateObject(null,null,url); | ||||
} | ||||
} | ||||
// Return | ||||
return State; | ||||
}; | ||||
/** | ||||
* 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; | ||||
// Return | ||||
return id; | ||||
}; | ||||
/** | ||||
* 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.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; | ||||
}; | ||||
/** | ||||
* 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; | ||||
// Fetch | ||||
var oldState = History.extractState(newState.url); | ||||
// Check | ||||
hasDuplicate = oldState && oldState.id !== newState.id; | ||||
// Return | ||||
return hasDuplicate; | ||||
}; | ||||
/** | ||||
* History.storeState | ||||
* Store a State | ||||
* @param {Object} newState | ||||
* @return {Object} newState | ||||
*/ | ||||
History.storeState = function(newState){ | ||||
// Store the State | ||||
History.urlToId[newState.url] = newState.id; | ||||
// Push the State | ||||
History.storedStates.push(History.cloneObject(newState)); | ||||
// Return newState | ||||
return newState; | ||||
}; | ||||
/** | ||||
* 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; | ||||
// Check | ||||
if ( History.savedStates.length ) { | ||||
var | ||||
newId = newState.id, | ||||
oldState = History.getLastSavedState(), | ||||
oldId = oldState.id; | ||||
// Check | ||||
isLast = (newId === oldId); | ||||
} | ||||
// Return | ||||
return isLast; | ||||
}; | ||||
/** | ||||
* History.saveState | ||||
* Push a State | ||||
* @param {Object} newState | ||||
* @return {boolean} changed | ||||
*/ | ||||
History.saveState = function(newState){ | ||||
// Check Hash | ||||
if ( History.isLastSavedState(newState) ) { | ||||
return false; | ||||
} | ||||
// Push the State | ||||
History.savedStates.push(History.cloneObject(newState)); | ||||
// Return true | ||||
return true; | ||||
}; | ||||
/** | ||||
* History.getStateByIndex() | ||||
* Gets a state by the index | ||||
* @param {integer} index | ||||
* @return {Object} | ||||
*/ | ||||
History.getStateByIndex = function(index){ | ||||
// Prepare | ||||
var State = null; | ||||
// 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]; | ||||
} | ||||
// Return State | ||||
return State; | ||||
}; | ||||
// ---------------------------------------------------------------------- | ||||
// Hash Helpers | ||||
/** | ||||
* History.escapeString() | ||||
* Escape a string | ||||
* @param {String} str | ||||
* @return {string} | ||||
*/ | ||||
History.escapeString = function(str){ | ||||
return encodeURI(url).replace(/%25/g, "%", "g"); | ||||
}; | ||||
/** | ||||
* History.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} | ||||
*/ | ||||
History.getHash = function(location){ | ||||
if ( !location ) location = document.location; | ||||
var href = location.href.replace( /^[^#]*/, "" ); | ||||
return href.substr(1); | ||||
}; | ||||
/** | ||||
* History.unescapeHash() | ||||
* normalize and Unescape a Hash | ||||
* @param {String} hash | ||||
* @return {string} | ||||
*/ | ||||
History.unescapeHash = function(hash){ | ||||
// Prepare | ||||
var result = History.normalizeHash(hash); | ||||
// Unescape hash | ||||
result = decodeURIComponent(result); | ||||
// Return result | ||||
return result; | ||||
}; | ||||
/** | ||||
* History.normalizeHash() | ||||
* normalize a hash across browsers | ||||
* @return {string} | ||||
*/ | ||||
History.normalizeHash = function(hash){ | ||||
var result = hash.replace(/[^#]*#/,'').replace(/#.*/, ''); | ||||
// Return result | ||||
return result; | ||||
}; | ||||
/** | ||||
* 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; | ||||
} | ||||
// Log | ||||
//History.debug('History.setHash: called',hash); | ||||
// Make Busy + Continue | ||||
History.busy(true); | ||||
// 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); | ||||
// PushState | ||||
History.pushState(State.data,State.title,State.url,false); | ||||
} | ||||
else if ( History.getHash() !== hash ) { | ||||
// Hash is a proper hash, so apply it | ||||
// Handle browser bugs | ||||
if ( History.bugs.setHash ) { | ||||
// Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249 | ||||
// Fetch the base page | ||||
var pageUrl = History.getPageUrl(); | ||||
// Safari hash apply | ||||
History.pushState(null,null,pageUrl+'#'+hash,false); | ||||
} | ||||
else { | ||||
// Normal hash apply | ||||
document.location.hash = hash; | ||||
} | ||||
} | ||||
// Chain | ||||
return History; | ||||
}; | ||||
/** | ||||
* History.escape() | ||||
* normalize and Escape a Hash | ||||
* @return {string} | ||||
*/ | ||||
History.escapeHash = function(hash){ | ||||
var result = History.normalizeHash(hash); | ||||
// Escape hash | ||||
result = window.encodeURIComponent(result); | ||||
// IE6 Escape Bug | ||||
if ( !History.bugs.hashEscape ) { | ||||
// Restore common parts | ||||
result = result | ||||
.replace(/\%21/g,'!') | ||||
.replace(/\%26/g,'&') | ||||
.replace(/\%3D/g,'=') | ||||
.replace(/\%3F/g,'?'); | ||||
} | ||||
// Return result | ||||
return result; | ||||
}; | ||||
/** | ||||
* 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 | ||||
hash = History.unescapeHash(hash); | ||||
// Return hash | ||||
return hash; | ||||
}; | ||||
/** | ||||
* 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; | ||||
} | ||||
} | ||||
// Apply | ||||
try { | ||||
document.getElementsByTagName('title')[0].innerHTML = title.replace('<','<').replace('>','>').replace(' & ',' & '); | ||||
} | ||||
catch ( Exception ) { } | ||||
document.title = title; | ||||
// Chain | ||||
return History; | ||||
}; | ||||
// ---------------------------------------------------------------------- | ||||
// Queueing | ||||
/** | ||||
* History.queues | ||||
* The list of queues to use | ||||
* First In, First Out | ||||
*/ | ||||
History.queues = []; | ||||
/** | ||||
* 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; | ||||
} | ||||
// 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); | ||||
} | ||||
// Return | ||||
return History.busy.flag; | ||||
}; | ||||
/** | ||||
* 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.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]||[]; | ||||
// Add to the queue | ||||
History.queues[item.queue||0].push(item); | ||||
// Chain | ||||
return History; | ||||
}; | ||||
/** | ||||
* 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; | ||||
} | ||||
// Handle | ||||
if ( History.busy() ) { | ||||
History.pushQueue(item); | ||||
} else { | ||||
History.fireQueueItem(item); | ||||
} | ||||
// Chain | ||||
return History; | ||||
}; | ||||
/** | ||||
* History.clearQueue() | ||||
* Clears the Queue | ||||
*/ | ||||
History.clearQueue = function(){ | ||||
History.busy.flag = false; | ||||
History.queues = []; | ||||
return History; | ||||
}; | ||||
// ---------------------------------------------------------------------- | ||||
// IE Bug Fix | ||||
/** | ||||
* History.stateChanged | ||||
* States whether or not the state has changed since the last double check was initialised | ||||
*/ | ||||
History.stateChanged = false; | ||||
/** | ||||
* History.doubleChecker | ||||
* Contains the timeout used for the double checks | ||||
*/ | ||||
History.doubleChecker = false; | ||||
/** | ||||
* History.doubleCheckComplete() | ||||
* Complete a double check | ||||
* @return {History} | ||||
*/ | ||||
History.doubleCheckComplete = function(){ | ||||
// Update | ||||
History.stateChanged = true; | ||||
// Clear | ||||
History.doubleCheckClear(); | ||||
// Chain | ||||
return History; | ||||
}; | ||||
/** | ||||
* History.doubleCheckClear() | ||||
* Clear a double check | ||||
* @return {History} | ||||
*/ | ||||
History.doubleCheckClear = function(){ | ||||
// Clear | ||||
if ( History.doubleChecker ) { | ||||
clearTimeout(History.doubleChecker); | ||||
History.doubleChecker = false; | ||||
} | ||||
// Chain | ||||
return History; | ||||
}; | ||||
/** | ||||
* History.doubleCheck() | ||||
* Create a double check | ||||
* @return {History} | ||||
*/ | ||||
History.doubleCheck = function(tryAgain){ | ||||
// Reset | ||||
History.stateChanged = false; | ||||
History.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 ( 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 | ||||
); | ||||
} | ||||
// Chain | ||||
return History; | ||||
}; | ||||
// ---------------------------------------------------------------------- | ||||
// Safari Bug Fix | ||||
/** | ||||
* History.safariStatePoll() | ||||
* Poll the current state | ||||
* @return {History} | ||||
*/ | ||||
History.safariStatePoll = function(){ | ||||
// Poll the URL | ||||
// Get the Last State which has the new URL | ||||
var | ||||
urlState = History.extractState(document.URL||document.location.href), | ||||
newState; | ||||
// Check for a difference | ||||
if ( !History.isLastSavedState(urlState) ) { | ||||
newState = urlState; | ||||
} | ||||
else { | ||||
return; | ||||
} | ||||
// 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'); | ||||
// 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); | ||||
// 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; | ||||
} | ||||
// Make Busy + Continue | ||||
History.busy(true); | ||||
// Fix certain browser bugs that prevent the state from changing | ||||
History.doubleCheck(function(){ | ||||
History.back(false); | ||||
}); | ||||
// Go back | ||||
history.go(-1); | ||||
// End back closure | ||||
return true; | ||||
}; | ||||
/** | ||||
* History.forward(queue) | ||||
* Send the browser history forward one item | ||||
* @param {Integer} queue [optional] | ||||
*/ | ||||
History.forward = function(queue){ | ||||
//History.debug('History.forward: called', arguments); | ||||
// 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; | ||||
} | ||||
// Make Busy + Continue | ||||
History.busy(true); | ||||
// Fix certain browser bugs that prevent the state from changing | ||||
History.doubleCheck(function(){ | ||||
History.forward(false); | ||||
}); | ||||
// Go forward | ||||
history.go(1); | ||||
// End forward closure | ||||
return true; | ||||
}; | ||||
/** | ||||
* 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); | ||||
// Prepare | ||||
var i; | ||||
// 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; | ||||
}; | ||||
// ---------------------------------------------------------------------- | ||||
// Initialise | ||||
/** | ||||
* Create the initial State | ||||
*/ | ||||
History.saveState(History.storeState(History.extractState(document.URL||document.location.href,true))); | ||||
/** | ||||
* Bind for Saving Store | ||||
*/ | ||||
if ( amplify ) { | ||||
History.onUnload = function(){ | ||||
// Prepare | ||||
var | ||||
currentStore = amplify.store('History.store')||{}, | ||||
item; | ||||
// 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; | ||||
// Store | ||||
amplify.store('History.store',currentStore); | ||||
}; | ||||
// For Internet Explorer | ||||
History.intervalList.push(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 | ||||
} | ||||
// ---------------------------------------------------------------------- | ||||
// HTML5 State Support | ||||
if ( History.emulated.pushState ) { | ||||
/* | ||||
* Provide Skeleton for HTML4 Browsers | ||||
*/ | ||||
// Prepare | ||||
var emptyFunction = function(){}; | ||||
History.pushState = History.pushState||emptyFunction; | ||||
History.replaceState = History.replaceState||emptyFunction; | ||||
} | ||||
else { | ||||
/* | ||||
* Use native HTML5 History API Implementation | ||||
*/ | ||||
/** | ||||
* History.onPopState(event,extra) | ||||
* Refresh the Current State | ||||
*/ | ||||
History.onPopState = function(event){ | ||||
// Reset the double check | ||||
History.doubleCheckComplete(); | ||||
// Check for a Hash, and handle apporiatly | ||||
var currentHash = History.getHash(); | ||||
if ( currentHash ) { | ||||
// Expand Hash | ||||
var currentState = History.extractState(currentHash||document.URL||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); | ||||
} | ||||
// We don't care for hashes | ||||
History.expectedStateId = false; | ||||
return false; | ||||
} | ||||
// Prepare | ||||
var newState = 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; | ||||
} | ||||
// Ensure | ||||
event.state = (event.state||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.URL||document.location.href); | ||||
} | ||||
// The State did not exist in our store | ||||
if ( !newState ) { | ||||
// Regenerate the State | ||||
newState = History.createStateObject(null,null,document.URL||document.location.href); | ||||
} | ||||
// Clean | ||||
History.expectedStateId = false; | ||||
// 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; | ||||
} | ||||
// Store the State | ||||
History.storeState(newState); | ||||
History.saveState(newState); | ||||
// Force update of the title | ||||
History.setTitle(newState); | ||||
// Fire Our Event | ||||
History.Adapter.trigger(window,'statechange'); | ||||
History.busy(false); | ||||
// 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); | ||||
// 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 ) { | ||||
History.intervalList.push(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(); | ||||
r4089 | })(window); | |||