o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1357543300.266218: @value"ß5{I" class:EFI"BundledAsset;FI"logical_path;FI"application.js;TI" pathname;FI"7$root/app/assets/javascripts/application.js.coffee;TI"content_type;FI"application/javascript;FI" mtime;FI"2013-01-07T10:48:19+04:00;FI" length;Fi'4I" digest;F"%a98e47eb93026a340a766faf55214702I" source;FI"'4/** * History.js Core * @author Benjamin Arthur Lupton * @copyright 2010-2011 Benjamin Arthur Lupton * @license New BSD License */ (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 sessionStorage = window.sessionStorage||false, // sessionStorage setTimeout = window.setTimeout, clearTimeout = window.clearTimeout, setInterval = window.setInterval, clearInterval = window.clearInterval, JSON = window.JSON, alert = window.alert, 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; } }; // ==================================================================== // 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, args,arg ; // Write to Console if ( consoleExists ) { 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 * @author James Padolsey */ 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 = '') && 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 */ 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 and eliminate all references to the original contexts * @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 `` 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.location.href, pageUrl; // Create 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.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; // // shortUrl = '/'+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 = {}; /** * 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.noramlizeStore() * Noramlize the store by adding necessary values */ History.normalizeStore = function(){ History.store.idToState = History.store.idToState||{}; History.store.urlToId = History.store.urlToId||{}; History.store.stateToId = History.store.stateToId||{}; }; /** * 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), str; if ( !id ) { // Find ID via State String 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 = (new Date()).getTime() + String(Math.random()).replace(/\D/g,''); 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){ // Variables var newState, dataNotEmpty; // 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 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); // 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 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': 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, cleanedState, str; // Fetch State = History.normalizeState(passedState); // Clean cleanedState = { data: State.data, title: passedState.title, url: passedState.url }; // Fetch 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, id; // Fetch State = History.normalizeState(passedState); // Fetch 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 State, hash; // Fetch State = History.normalizeState(passedState); // Hash 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,parts,url; // Extract 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, id, url; create = create||false; // Fetch SUID id = History.extractId(url_or_hash); if ( id ) { State = History.getStateById(id); } // Fetch SUID returned no State if ( !State ) { // Fetch URL 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, oldState; // Fetch 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, newId, oldState, oldId; // Check if ( History.savedStates.length ) { 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.getHash() * Gets the current document hash * @return {string} */ History.getHash = function(){ var hash = History.unescapeHash(document.location.hash); return hash; }; /** * History.unescapeString() * Unescape a string * @param {String} str * @return {string} */ History.unescapeString = function(str){ // Prepare var result = str, tmp; // Unescape hash while ( true ) { tmp = window.decodeURI(result); if ( tmp === result ) { break; } result = tmp; } // 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); // Unescape hash result = History.unescapeString(result); // Return result return result; }; /** * History.normalizeHash() * normalize a hash across browsers * @return {string} */ History.normalizeHash = function(hash){ // Prepare 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){ // Prepare var adjustedHash, State, pageUrl; // 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); // Prepare adjustedHash = History.escapeHash(hash); // Make Busy + Continue History.busy(true); // Check if hash is a state 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 ( document.location.hash !== adjustedHash ) { // 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 pageUrl = History.getPageUrl(); // Safari hash apply History.pushState(null,null,pageUrl+'#'+adjustedHash,false); } else { // Normal hash apply document.location.hash = adjustedHash; } } // Chain return History; }; /** * History.escape() * normalize and Escape a Hash * @return {string} */ History.escapeHash = function(hash){ // Prepare var result = History.normalizeHash(hash); // Escape hash result = window.encodeURI(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, firstState; // Initial if ( !title ) { 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(){ var i, queue, item; if ( History.busy.flag ) return; for ( i=History.queues.length-1; i >= 0; --i ) { queue = History.queues[i]; if ( queue.length === 0 ) continue; 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.busy.flag */ History.busy.flag = false; /** * 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.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; }; // ==================================================================== // HTML5 State Support // Non-Native pushState Implementation if ( History.emulated.pushState ) { /* * Provide Skeleton for HTML4 Browsers */ // Prepare var emptyFunction = function(){}; History.pushState = History.pushState||emptyFunction; History.replaceState = History.replaceState||emptyFunction; } // History.emulated.pushState // Native pushState Implementation else { /* * Use native HTML5 History API Implementation */ /** * History.onPopState(event,extra) * Refresh the Current State */ History.onPopState = function(event,extra){ // Prepare var stateId = false, newState = false, currentHash, currentState; // Reset the double check History.doubleCheckComplete(); // Check for a Hash, and handle apporiatly currentHash = History.getHash(); if ( currentHash ) { // Expand Hash currentState = History.extractState(currentHash||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; } // Ensure stateId = History.Adapter.extractEventData('state',event,extra) || false; // Fetch State if ( stateId ) { // Vanilla: Back/forward button was used newState = History.getStateById(stateId); } else if ( History.expectedStateId ) { // Vanilla: A new state was pushed, and popstate was called manually newState = History.getStateById(History.expectedStateId); } else { // Initial State newState = History.extractState(document.location.href); } // The State did not exist in our store if ( !newState ) { // Regenerate the State newState = History.createStateObject(null,null,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; }; } // !History.emulated.pushState // ==================================================================== // Initialise /** * Load the Store */ if ( sessionStorage ) { // Fetch try { History.store = JSON.parse(sessionStorage.getItem('History.store'))||{}; } catch ( err ) { History.store = {}; } // Normalize History.normalizeStore(); } else { // Default Load History.store = {}; History.normalizeStore(); } /** * Clear Intervals on exit to prevent memory leaks */ History.Adapter.bind(window,"beforeunload",History.clearAllIntervals); History.Adapter.bind(window,"unload",History.clearAllIntervals); /** * Create the initial State */ History.saveState(History.storeState(History.extractState(document.location.href,true))); /** * Bind for Saving Store */ if ( sessionStorage ) { // When the page is closed History.onUnload = function(){ // Prepare var currentStore, item; // Fetch try { currentStore = JSON.parse(sessionStorage.getItem('History.store'))||{}; } catch ( err ) { currentStore = {}; } // Ensure currentStore.idToState = currentStore.idToState || {}; currentStore.urlToId = currentStore.urlToId || {}; currentStore.stateToId = currentStore.stateToId || {}; // Sync for ( item in History.idToState ) { if ( !History.idToState.hasOwnProperty(item) ) { continue; } currentStore.idToState[item] = History.idToState[item]; } for ( item in History.urlToId ) { if ( !History.urlToId.hasOwnProperty(item) ) { continue; } currentStore.urlToId[item] = History.urlToId[item]; } for ( item in History.stateToId ) { if ( !History.stateToId.hasOwnProperty(item) ) { continue; } currentStore.stateToId[item] = History.stateToId[item]; } // Update History.store = currentStore; History.normalizeStore(); // Store sessionStorage.setItem('History.store',JSON.stringify(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 } // Non-Native pushState Implementation if ( !History.emulated.pushState ) { // Be aware, the following is only for native pushState implementations // If you are wanting to include something for all browsers // Then include it above this if block /** * Setup Safari Fix */ if ( History.bugs.safariPoll ) { History.intervalList.push(setInterval(History.safariStatePoll, 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); /** * History.js HTML4 Support * Depends on the HTML5 Support * @author Benjamin Arthur Lupton * @copyright 2010-2011 Benjamin Arthur Lupton * @license New BSD License */ (function(window,undefined){ "use strict"; // ======================================================================== // Initialise // Localise Globals var document = window.document, // Make sure we are using the correct document setTimeout = window.setTimeout||setTimeout, clearTimeout = window.clearTimeout||clearTimeout, setInterval = window.setInterval||setInterval, History = window.History = window.History||{}; // Public History Object // Check Existence if ( typeof History.initHtml4 !== 'undefined' ) { throw new Error('History.js HTML4 Support has already been loaded...'); } // ======================================================================== // Initialise HTML4 Support // Initialise HTML4 Support History.initHtml4 = function(){ // Initialise if ( typeof History.initHtml4.initialized !== 'undefined' ) { // Already Loaded return false; } else { History.initHtml4.initialized = true; } // ==================================================================== // Properties /** * History.enabled * Is History enabled? */ History.enabled = true; // ==================================================================== // Hash Storage /** * History.savedHashes * Store the hashes in an array */ History.savedHashes = []; /** * History.isLastHash(newHash) * Checks if the hash is the last hash * @param {string} newHash * @return {boolean} true */ History.isLastHash = function(newHash){ // Prepare var oldHash = History.getHashByIndex(), isLast; // Check isLast = newHash === oldHash; // Return isLast return isLast; }; /** * History.saveHash(newHash) * Push a Hash * @param {string} newHash * @return {boolean} true */ History.saveHash = function(newHash){ // Check Hash if ( History.isLastHash(newHash) ) { return false; } // Push the Hash History.savedHashes.push(newHash); // Return true return true; }; /** * History.getHashByIndex() * Gets a hash by the index * @param {integer} index * @return {string} */ History.getHashByIndex = function(index){ // Prepare var hash = null; // Handle if ( typeof index === 'undefined' ) { // Get the last inserted hash = History.savedHashes[History.savedHashes.length-1]; } else if ( index < 0 ) { // Get from the end hash = History.savedHashes[History.savedHashes.length+index]; } else { // Get from the beginning hash = History.savedHashes[index]; } // Return hash return hash; }; // ==================================================================== // Discarded States /** * History.discardedHashes * A hashed array of discarded hashes */ History.discardedHashes = {}; /** * History.discardedStates * A hashed array of discarded states */ History.discardedStates = {}; /** * History.discardState(State) * Discards the state by ignoring it through History * @param {object} State * @return {true} */ History.discardState = function(discardedState,forwardState,backState){ //History.debug('History.discardState', arguments); // Prepare var discardedStateHash = History.getHashByState(discardedState), discardObject; // Create Discard Object discardObject = { 'discardedState': discardedState, 'backState': backState, 'forwardState': forwardState }; // Add to DiscardedStates History.discardedStates[discardedStateHash] = discardObject; // Return true return true; }; /** * History.discardHash(hash) * Discards the hash by ignoring it through History * @param {string} hash * @return {true} */ History.discardHash = function(discardedHash,forwardState,backState){ //History.debug('History.discardState', arguments); // Create Discard Object var discardObject = { 'discardedHash': discardedHash, 'backState': backState, 'forwardState': forwardState }; // Add to discardedHash History.discardedHashes[discardedHash] = discardObject; // Return true return true; }; /** * History.discardState(State) * Checks to see if the state is discarded * @param {object} State * @return {bool} */ History.discardedState = function(State){ // Prepare var StateHash = History.getHashByState(State), discarded; // Check discarded = History.discardedStates[StateHash]||false; // Return true return discarded; }; /** * History.discardedHash(hash) * Checks to see if the state is discarded * @param {string} State * @return {bool} */ History.discardedHash = function(hash){ // Check var discarded = History.discardedHashes[hash]||false; // Return true return discarded; }; /** * History.recycleState(State) * Allows a discarded state to be used again * @param {object} data * @param {string} title * @param {string} url * @return {true} */ History.recycleState = function(State){ //History.debug('History.recycleState', arguments); // Prepare var StateHash = History.getHashByState(State); // Remove from DiscardedStates if ( History.discardedState(State) ) { delete History.discardedStates[StateHash]; } // Return true return true; }; // ==================================================================== // HTML4 HashChange Support if ( History.emulated.hashChange ) { /* * We must emulate the HTML4 HashChange Support by manually checking for hash changes */ /** * History.hashChangeInit() * Init the HashChange Emulation */ History.hashChangeInit = function(){ // Define our Checker Function History.checkerFunction = null; // Define some variables that will help in our checker function var lastDocumentHash = '', iframeId, iframe, lastIframeHash, checkerRunning; // Handle depending on the browser if ( History.isInternetExplorer() ) { // IE6 and IE7 // We need to use an iframe to emulate the back and forward buttons // Create iFrame iframeId = 'historyjs-iframe'; iframe = document.createElement('iframe'); // Adjust iFarme iframe.setAttribute('id', iframeId); iframe.style.display = 'none'; // Append iFrame document.body.appendChild(iframe); // Create initial history entry iframe.contentWindow.document.open(); iframe.contentWindow.document.close(); // Define some variables that will help in our checker function lastIframeHash = ''; checkerRunning = false; // Define the checker function History.checkerFunction = function(){ // Check Running if ( checkerRunning ) { return false; } // Update Running checkerRunning = true; // Fetch var documentHash = History.getHash()||'', iframeHash = History.unescapeHash(iframe.contentWindow.document.location.hash)||''; // The Document Hash has changed (application caused) if ( documentHash !== lastDocumentHash ) { // Equalise lastDocumentHash = documentHash; // Create a history entry in the iframe if ( iframeHash !== documentHash ) { //History.debug('hashchange.checker: iframe hash change', 'documentHash (new):', documentHash, 'iframeHash (old):', iframeHash); // Equalise lastIframeHash = iframeHash = documentHash; // Create History Entry iframe.contentWindow.document.open(); iframe.contentWindow.document.close(); // Update the iframe's hash iframe.contentWindow.document.location.hash = History.escapeHash(documentHash); } // Trigger Hashchange Event History.Adapter.trigger(window,'hashchange'); } // The iFrame Hash has changed (back button caused) else if ( iframeHash !== lastIframeHash ) { //History.debug('hashchange.checker: iframe hash out of sync', 'iframeHash (new):', iframeHash, 'documentHash (old):', documentHash); // Equalise lastIframeHash = iframeHash; // Update the Hash History.setHash(iframeHash,false); } // Reset Running checkerRunning = false; // Return true return true; }; } else { // We are not IE // Firefox 1 or 2, Opera // Define the checker function History.checkerFunction = function(){ // Prepare var documentHash = History.getHash(); // The Document Hash has changed (application caused) if ( documentHash !== lastDocumentHash ) { // Equalise lastDocumentHash = documentHash; // Trigger Hashchange Event History.Adapter.trigger(window,'hashchange'); } // Return true return true; }; } // Apply the checker function History.intervalList.push(setInterval(History.checkerFunction, History.options.hashChangeInterval)); // Done return true; }; // History.hashChangeInit // Bind hashChangeInit History.Adapter.onDomLoad(History.hashChangeInit); } // History.emulated.hashChange // ==================================================================== // HTML5 State Support // Non-Native pushState Implementation if ( History.emulated.pushState ) { /* * We must emulate the HTML5 State Management by using HTML4 HashChange */ /** * History.onHashChange(event) * Trigger HTML5's window.onpopstate via HTML4 HashChange Support */ History.onHashChange = function(event){ //History.debug('History.onHashChange', arguments); // Prepare var currentUrl = ((event && event.newURL) || document.location.href), currentHash = History.getHashByUrl(currentUrl), currentState = null, currentStateHash = null, currentStateHashExits = null, discardObject; // Check if we are the same state if ( History.isLastHash(currentHash) ) { // There has been no change (just the page's hash has finally propagated) //History.debug('History.onHashChange: no change'); History.busy(false); return false; } // Reset the double check History.doubleCheckComplete(); // Store our location for use in detecting back/forward direction History.saveHash(currentHash); // Expand Hash if ( currentHash && History.isTraditionalAnchor(currentHash) ) { //History.debug('History.onHashChange: traditional anchor', currentHash); // Traditional Anchor Hash History.Adapter.trigger(window,'anchorchange'); History.busy(false); return false; } // Create State currentState = History.extractState(History.getFullUrl(currentHash||document.location.href,false),true); // Check if we are the same state if ( History.isLastSavedState(currentState) ) { //History.debug('History.onHashChange: no change'); // There has been no change (just the page's hash has finally propagated) History.busy(false); return false; } // Create the state Hash currentStateHash = History.getHashByState(currentState); // Check if we are DiscardedState discardObject = History.discardedState(currentState); if ( discardObject ) { // Ignore this state as it has been discarded and go back to the state before it if ( History.getHashByIndex(-2) === History.getHashByState(discardObject.forwardState) ) { // We are going backwards //History.debug('History.onHashChange: go backwards'); History.back(false); } else { // We are going forwards //History.debug('History.onHashChange: go forwards'); History.forward(false); } return false; } // Push the new HTML5 State //History.debug('History.onHashChange: success hashchange'); History.pushState(currentState.data,currentState.title,currentState.url,false); // End onHashChange closure return true; }; History.Adapter.bind(window,'hashchange',History.onHashChange); /** * 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) ) { 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 History.busy(true); // Fetch the State Object var newState = History.createStateObject(data,title,url), newStateHash = History.getHashByState(newState), oldState = History.getState(false), oldStateHash = History.getHashByState(oldState), html4Hash = History.getHash(); // Store the newState History.storeState(newState); History.expectedStateId = newState.id; // Recycle the State History.recycleState(newState); // Force update of the title // History.setTitle(newState); // Check if we are the same State if ( newStateHash === oldStateHash ) { //History.debug('History.pushState: no change', newStateHash); History.busy(false); return false; } // Update HTML4 Hash if ( newStateHash !== html4Hash && newStateHash !== History.getShortUrl(document.location.href) ) { //History.debug('History.pushState: update hash', newStateHash, html4Hash); History.setHash(newStateHash,false); return false; } // Update HTML5 State History.saveState(newState); // Fire HTML5 Event //History.debug('History.pushState: trigger popstate'); History.Adapter.trigger(window,'statechange'); History.busy(false); // 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) ) { 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 History.busy(true); // Fetch the State Objects var newState = History.createStateObject(data,title,url), oldState = History.getState(false), previousState = History.getStateByIndex(-2); // Discard Old State History.discardState(oldState,newState,previousState); // Alias to PushState History.pushState(newState.data,newState.title,newState.url,false); // End replaceState closure return true; }; } // History.emulated.pushState // ==================================================================== // Initialise // Non-Native pushState Implementation if ( History.emulated.pushState ) { /** * Ensure initial state is handled correctly */ if ( History.getHash() && !History.emulated.hashChange ) { History.Adapter.onDomLoad(function(){ History.Adapter.trigger(window,'hashchange'); }); } } // History.emulated.pushState }; // History.initHtml4 // Try and Initialise History if ( typeof History.init !== 'undefined' ) { History.init(); } })(window); /** * History.js jQuery Adapter * @author Benjamin Arthur Lupton * @copyright 2010-2011 Benjamin Arthur Lupton * @license New BSD License */ // Closure (function(window,undefined){ "use strict"; // Localise Globals var History = window.History = window.History||{}, jQuery = window.jQuery; // Check Existence if ( typeof History.Adapter !== 'undefined' ) { throw new Error('History.js Adapter has already been loaded...'); } // Add the Adapter History.Adapter = { /** * History.Adapter.bind(el,event,callback) * @param {Element|string} el * @param {string} event - custom and standard events * @param {function} callback * @return {void} */ bind: function(el,event,callback){ jQuery(el).bind(event,callback); }, /** * History.Adapter.trigger(el,event) * @param {Element|string} el * @param {string} event - custom and standard events * @param {Object=} extra - a object of extra event data (optional) * @return {void} */ trigger: function(el,event,extra){ jQuery(el).trigger(event,extra); }, /** * History.Adapter.extractEventData(key,event,extra) * @param {string} key - key for the event data to extract * @param {string} event - custom and standard events * @param {Object=} extra - a object of extra event data (optional) * @return {mixed} */ extractEventData: function(key,event,extra){ // jQuery Native then jQuery Custom var result = (event && event.originalEvent && event.originalEvent[key]) || (extra && extra[key]) || undefined; // Return return result; }, /** * History.Adapter.onDomLoad(callback) * @param {function} callback * @return {void} */ onDomLoad: function(callback) { jQuery(callback); } }; // Try and Initialise History if ( typeof History.init !== 'undefined' ) { History.init(); } })(window); (function() { var Wiselinks; String.prototype.ends_with = function(suffix) { return this.indexOf(suffix, this.length - suffix.length) !== -1; }; Wiselinks = (function() { function Wiselinks($target, options) { var self; this.$target = $target != null ? $target : $('body'); this.options = options != null ? options : {}; if (window.jQuery == null) { throw "Load jQuery to use Wiselinks"; } self = this; this.options = jQuery.extend(self._defaults(), this.options); if (self.enabled()) { this.assets_digest = $("meta[name='assets-digest']").attr("content"); if (History.emulated.pushState && this.options.html4 === true) { if (window.location.href.indexOf('#!') === -1 && window.location.pathname !== '/') { window.location.href = "" + window.location.protocol + "//" + window.location.host + "/#!" + window.location.pathname; } if (window.location.hash.indexOf('#!') !== -1) { self._call(window.location.hash.substring(2)); } } History.Adapter.bind(window, "statechange", function(event, data) { var state; if (!History.ready) { return false; } state = History.getState(); return self._call(state.url, state.data.target, state.data.render); }); $(document).on("submit", "form[data-push], form[data-replace]", function(event) { self._process_form($(this)); event.preventDefault(); return false; }); $(document).on("click", "a[data-push], a[data-replace]", function(event) { if (self._cross_origin_link(event.target) || self._non_standard_click(event)) { return true; } self._process_link($(this)); event.preventDefault(); return false; }); } } Wiselinks.prototype.enabled = function() { return !History.emulated.pushState || this.options.html4 === true; }; Wiselinks.prototype.load = function(url, target, render) { if (render == null) { render = 'template'; } History.ready = true; return History.pushState({ timestamp: new Date().getTime(), render: render, target: target }, document.title, url); }; Wiselinks.prototype.reload = function() { History.ready = true; return History.replaceState({ timestamp: new Date().getTime(), render: 'template' }, document.title, History.getState().url); }; Wiselinks.prototype._defaults = function() { return { html4: true }; }; Wiselinks.prototype._call = function(url, target, render) { var $document, $target, self; if (render == null) { render = 'template'; } self = this; $target = target != null ? $(target) : self.$target; $document = $(document).trigger('page:loading', [url, $target.selector, render]); return $.ajax({ url: url, headers: { 'X-Render': render }, complete: function(xhr, status) { return $document.trigger('page:complete', [xhr, status]); }, success: function(data, status, xhr) { if (self._assets_changed(xhr.getResponseHeader('X-Assets-Digest'))) { return window.location.reload(true); } else { self._set_title(xhr); $target.html(data); return $document.trigger('page:success', [data, status]); } }, error: function(xhr, status, error) { return $document.trigger('page:error', [status, error]); }, dataType: "html" }); }; Wiselinks.prototype._process_form = function($form) { var $disable, item, key, name, params, self, serialized, type, url, _i, _len, _ref; self = this; $disable = $form.find(':input[value=""]'); $disable.attr('disabled', true); params = {}; _ref = $form.serializeArray(); for (_i = 0, _len = _ref.length; _i < _len; _i++) { item = _ref[_i]; if (item.name !== 'utf8') { name = item.name.ends_with('[]') ? item.name.substr(0, item.name.length - 2) : item.name; if (params[name] != null) { params[name] = params[name] + ("," + item.value); } else { params[name] = item.value; } } } serialized = []; for (key in params) { serialized.push("" + key + "=" + params[key]); } serialized = serialized.join('&').replace(/%|!/g, ''); url = $form.attr("action"); if (serialized.length > 0) { url += "?" + serialized; } $disable.attr('disabled', false); type = $form.attr("data-push") === 'partial' ? 'partial' : 'template'; return self.load(url, $form.attr("data-target"), type); }; Wiselinks.prototype._process_link = function($link) { var self, type; self = this; type = $link.attr("data-push") === 'partial' ? 'partial' : 'template'; return self.load($link.attr("href"), $link.attr("data-target"), type); }; Wiselinks.prototype._cross_origin_link = function(link) { return (location.protocol !== link.protocol) || (location.host.split(':')[0] !== link.host.split(':')[0]); }; Wiselinks.prototype._non_standard_click = function(event) { return event.metaKey || event.ctrlKey || event.shiftKey || event.altKey; }; Wiselinks.prototype._assets_changed = function(digest) { return (this.assets_digest != null) && this.assets_digest !== digest; }; Wiselinks.prototype._set_title = function(xhr) { var value; value = xhr.getResponseHeader('X-Title'); if (value != null) { return document.title = decodeURI(value); } }; return Wiselinks; })(); window.Wiselinks = Wiselinks; }).call(this); (function() { }).call(this); ;FI"required_assets_digest;F"%c10dfd7eb03e27c8d3a511491e4bfe20I" _version;F"%ffd318ca8b794f42f5029101c2cf5d91