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