wiselinks 0.3.0 → 0.3.2

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