extjs.history.js 88 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305
  1. /*
  2. json2.js
  3. 2012-10-08
  4. Public Domain.
  5. NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
  6. See http://www.JSON.org/js.html
  7. This code should be minified before deployment.
  8. See http://javascript.crockford.com/jsmin.html
  9. USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
  10. NOT CONTROL.
  11. This file creates a global JSON object containing two methods: stringify
  12. and parse.
  13. JSON.stringify(value, replacer, space)
  14. value any JavaScript value, usually an object or array.
  15. replacer an optional parameter that determines how object
  16. values are stringified for objects. It can be a
  17. function or an array of strings.
  18. space an optional parameter that specifies the indentation
  19. of nested structures. If it is omitted, the text will
  20. be packed without extra whitespace. If it is a number,
  21. it will specify the number of spaces to indent at each
  22. level. If it is a string (such as '\t' or ' '),
  23. it contains the characters used to indent at each level.
  24. This method produces a JSON text from a JavaScript value.
  25. When an object value is found, if the object contains a toJSON
  26. method, its toJSON method will be called and the result will be
  27. stringified. A toJSON method does not serialize: it returns the
  28. value represented by the name/value pair that should be serialized,
  29. or undefined if nothing should be serialized. The toJSON method
  30. will be passed the key associated with the value, and this will be
  31. bound to the value
  32. For example, this would serialize Dates as ISO strings.
  33. Date.prototype.toJSON = function (key) {
  34. function f(n) {
  35. // Format integers to have at least two digits.
  36. return n < 10 ? '0' + n : n;
  37. }
  38. return this.getUTCFullYear() + '-' +
  39. f(this.getUTCMonth() + 1) + '-' +
  40. f(this.getUTCDate()) + 'T' +
  41. f(this.getUTCHours()) + ':' +
  42. f(this.getUTCMinutes()) + ':' +
  43. f(this.getUTCSeconds()) + 'Z';
  44. };
  45. You can provide an optional replacer method. It will be passed the
  46. key and value of each member, with this bound to the containing
  47. object. The value that is returned from your method will be
  48. serialized. If your method returns undefined, then the member will
  49. be excluded from the serialization.
  50. If the replacer parameter is an array of strings, then it will be
  51. used to select the members to be serialized. It filters the results
  52. such that only members with keys listed in the replacer array are
  53. stringified.
  54. Values that do not have JSON representations, such as undefined or
  55. functions, will not be serialized. Such values in objects will be
  56. dropped; in arrays they will be replaced with null. You can use
  57. a replacer function to replace those with JSON values.
  58. JSON.stringify(undefined) returns undefined.
  59. The optional space parameter produces a stringification of the
  60. value that is filled with line breaks and indentation to make it
  61. easier to read.
  62. If the space parameter is a non-empty string, then that string will
  63. be used for indentation. If the space parameter is a number, then
  64. the indentation will be that many spaces.
  65. Example:
  66. text = JSON.stringify(['e', {pluribus: 'unum'}]);
  67. // text is '["e",{"pluribus":"unum"}]'
  68. text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
  69. // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
  70. text = JSON.stringify([new Date()], function (key, value) {
  71. return this[key] instanceof Date ?
  72. 'Date(' + this[key] + ')' : value;
  73. });
  74. // text is '["Date(---current time---)"]'
  75. JSON.parse(text, reviver)
  76. This method parses a JSON text to produce an object or array.
  77. It can throw a SyntaxError exception.
  78. The optional reviver parameter is a function that can filter and
  79. transform the results. It receives each of the keys and values,
  80. and its return value is used instead of the original value.
  81. If it returns what it received, then the structure is not modified.
  82. If it returns undefined then the member is deleted.
  83. Example:
  84. // Parse the text. Values that look like ISO date strings will
  85. // be converted to Date objects.
  86. myData = JSON.parse(text, function (key, value) {
  87. var a;
  88. if (typeof value === 'string') {
  89. a =
  90. /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
  91. if (a) {
  92. return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
  93. +a[5], +a[6]));
  94. }
  95. }
  96. return value;
  97. });
  98. myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
  99. var d;
  100. if (typeof value === 'string' &&
  101. value.slice(0, 5) === 'Date(' &&
  102. value.slice(-1) === ')') {
  103. d = new Date(value.slice(5, -1));
  104. if (d) {
  105. return d;
  106. }
  107. }
  108. return value;
  109. });
  110. This is a reference implementation. You are free to copy, modify, or
  111. redistribute.
  112. */
  113. /*jslint evil: true, regexp: true */
  114. /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
  115. call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
  116. getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
  117. lastIndex, length, parse, prototype, push, replace, slice, stringify,
  118. test, toJSON, toString, valueOf
  119. */
  120. // Create a JSON object only if one does not already exist. We create the
  121. // methods in a closure to avoid creating global variables.
  122. if (typeof JSON !== 'object') {
  123. JSON = {};
  124. }
  125. (function () {
  126. 'use strict';
  127. function f(n) {
  128. // Format integers to have at least two digits.
  129. return n < 10 ? '0' + n : n;
  130. }
  131. if (typeof Date.prototype.toJSON !== 'function') {
  132. Date.prototype.toJSON = function (key) {
  133. return isFinite(this.valueOf())
  134. ? this.getUTCFullYear() + '-' +
  135. f(this.getUTCMonth() + 1) + '-' +
  136. f(this.getUTCDate()) + 'T' +
  137. f(this.getUTCHours()) + ':' +
  138. f(this.getUTCMinutes()) + ':' +
  139. f(this.getUTCSeconds()) + 'Z'
  140. : null;
  141. };
  142. String.prototype.toJSON =
  143. Number.prototype.toJSON =
  144. Boolean.prototype.toJSON = function (key) {
  145. return this.valueOf();
  146. };
  147. }
  148. var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
  149. escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
  150. gap,
  151. indent,
  152. meta = { // table of character substitutions
  153. '\b': '\\b',
  154. '\t': '\\t',
  155. '\n': '\\n',
  156. '\f': '\\f',
  157. '\r': '\\r',
  158. '"' : '\\"',
  159. '\\': '\\\\'
  160. },
  161. rep;
  162. function quote(string) {
  163. // If the string contains no control characters, no quote characters, and no
  164. // backslash characters, then we can safely slap some quotes around it.
  165. // Otherwise we must also replace the offending characters with safe escape
  166. // sequences.
  167. escapable.lastIndex = 0;
  168. return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
  169. var c = meta[a];
  170. return typeof c === 'string'
  171. ? c
  172. : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
  173. }) + '"' : '"' + string + '"';
  174. }
  175. function str(key, holder) {
  176. // Produce a string from holder[key].
  177. var i, // The loop counter.
  178. k, // The member key.
  179. v, // The member value.
  180. length,
  181. mind = gap,
  182. partial,
  183. value = holder[key];
  184. // If the value has a toJSON method, call it to obtain a replacement value.
  185. if (value && typeof value === 'object' &&
  186. typeof value.toJSON === 'function') {
  187. value = value.toJSON(key);
  188. }
  189. // If we were called with a replacer function, then call the replacer to
  190. // obtain a replacement value.
  191. if (typeof rep === 'function') {
  192. value = rep.call(holder, key, value);
  193. }
  194. // What happens next depends on the value's type.
  195. switch (typeof value) {
  196. case 'string':
  197. return quote(value);
  198. case 'number':
  199. // JSON numbers must be finite. Encode non-finite numbers as null.
  200. return isFinite(value) ? String(value) : 'null';
  201. case 'boolean':
  202. case 'null':
  203. // If the value is a boolean or null, convert it to a string. Note:
  204. // typeof null does not produce 'null'. The case is included here in
  205. // the remote chance that this gets fixed someday.
  206. return String(value);
  207. // If the type is 'object', we might be dealing with an object or an array or
  208. // null.
  209. case 'object':
  210. // Due to a specification blunder in ECMAScript, typeof null is 'object',
  211. // so watch out for that case.
  212. if (!value) {
  213. return 'null';
  214. }
  215. // Make an array to hold the partial results of stringifying this object value.
  216. gap += indent;
  217. partial = [];
  218. // Is the value an array?
  219. if (Object.prototype.toString.apply(value) === '[object Array]') {
  220. // The value is an array. Stringify every element. Use null as a placeholder
  221. // for non-JSON values.
  222. length = value.length;
  223. for (i = 0; i < length; i += 1) {
  224. partial[i] = str(i, value) || 'null';
  225. }
  226. // Join all of the elements together, separated with commas, and wrap them in
  227. // brackets.
  228. v = partial.length === 0
  229. ? '[]'
  230. : gap
  231. ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
  232. : '[' + partial.join(',') + ']';
  233. gap = mind;
  234. return v;
  235. }
  236. // If the replacer is an array, use it to select the members to be stringified.
  237. if (rep && typeof rep === 'object') {
  238. length = rep.length;
  239. for (i = 0; i < length; i += 1) {
  240. if (typeof rep[i] === 'string') {
  241. k = rep[i];
  242. v = str(k, value);
  243. if (v) {
  244. partial.push(quote(k) + (gap ? ': ' : ':') + v);
  245. }
  246. }
  247. }
  248. } else {
  249. // Otherwise, iterate through all of the keys in the object.
  250. for (k in value) {
  251. if (Object.prototype.hasOwnProperty.call(value, k)) {
  252. v = str(k, value);
  253. if (v) {
  254. partial.push(quote(k) + (gap ? ': ' : ':') + v);
  255. }
  256. }
  257. }
  258. }
  259. // Join all of the member texts together, separated with commas,
  260. // and wrap them in braces.
  261. v = partial.length === 0
  262. ? '{}'
  263. : gap
  264. ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
  265. : '{' + partial.join(',') + '}';
  266. gap = mind;
  267. return v;
  268. }
  269. }
  270. // If the JSON object does not yet have a stringify method, give it one.
  271. if (typeof JSON.stringify !== 'function') {
  272. JSON.stringify = function (value, replacer, space) {
  273. // The stringify method takes a value and an optional replacer, and an optional
  274. // space parameter, and returns a JSON text. The replacer can be a function
  275. // that can replace values, or an array of strings that will select the keys.
  276. // A default replacer method can be provided. Use of the space parameter can
  277. // produce text that is more easily readable.
  278. var i;
  279. gap = '';
  280. indent = '';
  281. // If the space parameter is a number, make an indent string containing that
  282. // many spaces.
  283. if (typeof space === 'number') {
  284. for (i = 0; i < space; i += 1) {
  285. indent += ' ';
  286. }
  287. // If the space parameter is a string, it will be used as the indent string.
  288. } else if (typeof space === 'string') {
  289. indent = space;
  290. }
  291. // If there is a replacer, it must be a function or an array.
  292. // Otherwise, throw an error.
  293. rep = replacer;
  294. if (replacer && typeof replacer !== 'function' &&
  295. (typeof replacer !== 'object' ||
  296. typeof replacer.length !== 'number')) {
  297. throw new Error('JSON.stringify');
  298. }
  299. // Make a fake root object containing our value under the key of ''.
  300. // Return the result of stringifying the value.
  301. return str('', {'': value});
  302. };
  303. }
  304. // If the JSON object does not yet have a parse method, give it one.
  305. if (typeof JSON.parse !== 'function') {
  306. JSON.parse = function (text, reviver) {
  307. // The parse method takes a text and an optional reviver function, and returns
  308. // a JavaScript value if the text is a valid JSON text.
  309. var j;
  310. function walk(holder, key) {
  311. // The walk method is used to recursively walk the resulting structure so
  312. // that modifications can be made.
  313. var k, v, value = holder[key];
  314. if (value && typeof value === 'object') {
  315. for (k in value) {
  316. if (Object.prototype.hasOwnProperty.call(value, k)) {
  317. v = walk(value, k);
  318. if (v !== undefined) {
  319. value[k] = v;
  320. } else {
  321. delete value[k];
  322. }
  323. }
  324. }
  325. }
  326. return reviver.call(holder, key, value);
  327. }
  328. // Parsing happens in four stages. In the first stage, we replace certain
  329. // Unicode characters with escape sequences. JavaScript handles many characters
  330. // incorrectly, either silently deleting them, or treating them as line endings.
  331. text = String(text);
  332. cx.lastIndex = 0;
  333. if (cx.test(text)) {
  334. text = text.replace(cx, function (a) {
  335. return '\\u' +
  336. ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
  337. });
  338. }
  339. // In the second stage, we run the text against regular expressions that look
  340. // for non-JSON patterns. We are especially concerned with '()' and 'new'
  341. // because they can cause invocation, and '=' because it can cause mutation.
  342. // But just to be safe, we want to reject all unexpected forms.
  343. // We split the second stage into 4 regexp operations in order to work around
  344. // crippling inefficiencies in IE's and Safari's regexp engines. First we
  345. // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
  346. // replace all simple value tokens with ']' characters. Third, we delete all
  347. // open brackets that follow a colon or comma or that begin the text. Finally,
  348. // we look to see that the remaining characters are only whitespace or ']' or
  349. // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
  350. if (/^[\],:{}\s]*$/
  351. .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
  352. .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
  353. .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
  354. // In the third stage we use the eval function to compile the text into a
  355. // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
  356. // in JavaScript: it can begin a block or an object literal. We wrap the text
  357. // in parens to eliminate the ambiguity.
  358. j = eval('(' + text + ')');
  359. // In the optional fourth stage, we recursively walk the new structure, passing
  360. // each name/value pair to a reviver function for possible transformation.
  361. return typeof reviver === 'function'
  362. ? walk({'': j}, '')
  363. : j;
  364. }
  365. // If the text is not JSON parseable, then a SyntaxError is thrown.
  366. throw new SyntaxError('JSON.parse');
  367. };
  368. }
  369. }());/**
  370. * History.js ExtJS Adapter
  371. * @author Sean Adkinson <sean.adkinson@gmail.com>
  372. * @copyright 2012 Sean Adkinson <sean.adkinson@gmail.com>
  373. * @license New BSD License <http://creativecommons.org/licenses/BSD/>
  374. */
  375. // Closure
  376. (function(window,undefined){
  377. "use strict";
  378. // Localise Globals
  379. var
  380. History = window.History = window.History||{},
  381. Ext = window.Ext;
  382. window.JSON = {
  383. stringify: Ext.JSON.encode,
  384. parse: Ext.JSON.decode
  385. };
  386. // Check Existence
  387. if ( typeof History.Adapter !== 'undefined' ) {
  388. throw new Error('History.js Adapter has already been loaded...');
  389. }
  390. // Add the Adapter
  391. History.Adapter = {
  392. observables: {},
  393. /**
  394. * History.Adapter.bind(el,event,callback)
  395. * @param {Element|string} el
  396. * @param {string} event - custom and standard events
  397. * @param {function} callback
  398. * @param {Object} scope
  399. * @return {void}
  400. */
  401. bind: function(element,eventName,callback,scope){
  402. Ext.EventManager.addListener(element, eventName, callback, scope);
  403. //bind an observable to the element that will let us "trigger" events on it
  404. var id = Ext.id(element, 'history-'), observable = this.observables[id];
  405. if (!observable) {
  406. observable = Ext.create('Ext.util.Observable');
  407. this.observables[id] = observable;
  408. }
  409. observable.on(eventName, callback, scope);
  410. },
  411. /**
  412. * History.Adapter.trigger(el,event)
  413. * @param {Element|string} el
  414. * @param {string} event - custom and standard events
  415. * @param {Object=} extra - a object of extra event data (optional)
  416. * @return {void}
  417. */
  418. trigger: function(element,eventName,extra){
  419. var id = Ext.id(element, 'history-'), observable = this.observables[id];
  420. if (observable) {
  421. observable.fireEvent(eventName, extra);
  422. }
  423. },
  424. /**
  425. * History.Adapter.extractEventData(key,event,extra)
  426. * @param {string} key - key for the event data to extract
  427. * @param {string} event - custom and standard events
  428. * @param {Object=} extra - a object of extra event data (optional)
  429. * @return {mixed}
  430. */
  431. extractEventData: function(key,event,extra){
  432. var result = (event && event.browserEvent && event.browserEvent[key]) || (extra && extra[key]) || undefined;
  433. return result;
  434. },
  435. /**
  436. * History.Adapter.onDomLoad(callback)
  437. * @param {function} callback
  438. * @return {void}
  439. */
  440. onDomLoad: function(callback) {
  441. Ext.onReady(callback);
  442. }
  443. };
  444. // Try and Initialise History
  445. if ( typeof History.init !== 'undefined' ) {
  446. History.init();
  447. }
  448. })(window);/**
  449. * History.js HTML4 Support
  450. * Depends on the HTML5 Support
  451. * @author Benjamin Arthur Lupton <contact@balupton.com>
  452. * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
  453. * @license New BSD License <http://creativecommons.org/licenses/BSD/>
  454. */
  455. (function(window,undefined){
  456. "use strict";
  457. // ========================================================================
  458. // Initialise
  459. // Localise Globals
  460. var
  461. document = window.document, // Make sure we are using the correct document
  462. setTimeout = window.setTimeout||setTimeout,
  463. clearTimeout = window.clearTimeout||clearTimeout,
  464. setInterval = window.setInterval||setInterval,
  465. History = window.History = window.History||{}; // Public History Object
  466. // Check Existence
  467. if ( typeof History.initHtml4 !== 'undefined' ) {
  468. throw new Error('History.js HTML4 Support has already been loaded...');
  469. }
  470. // ========================================================================
  471. // Initialise HTML4 Support
  472. // Initialise HTML4 Support
  473. History.initHtml4 = function(){
  474. // Initialise
  475. if ( typeof History.initHtml4.initialized !== 'undefined' ) {
  476. // Already Loaded
  477. return false;
  478. }
  479. else {
  480. History.initHtml4.initialized = true;
  481. }
  482. // ====================================================================
  483. // Properties
  484. /**
  485. * History.enabled
  486. * Is History enabled?
  487. */
  488. History.enabled = true;
  489. // ====================================================================
  490. // Hash Storage
  491. /**
  492. * History.savedHashes
  493. * Store the hashes in an array
  494. */
  495. History.savedHashes = [];
  496. /**
  497. * History.isLastHash(newHash)
  498. * Checks if the hash is the last hash
  499. * @param {string} newHash
  500. * @return {boolean} true
  501. */
  502. History.isLastHash = function(newHash){
  503. // Prepare
  504. var oldHash = History.getHashByIndex(),
  505. isLast;
  506. // Check
  507. isLast = newHash === oldHash;
  508. // Return isLast
  509. return isLast;
  510. };
  511. /**
  512. * History.isHashEqual(newHash, oldHash)
  513. * Checks to see if two hashes are functionally equal
  514. * @param {string} newHash
  515. * @param {string} oldHash
  516. * @return {boolean} true
  517. */
  518. History.isHashEqual = function(newHash, oldHash){
  519. newHash = encodeURIComponent(newHash).replace(/%25/g, "%");
  520. oldHash = encodeURIComponent(oldHash).replace(/%25/g, "%");
  521. return newHash === oldHash;
  522. };
  523. /**
  524. * History.saveHash(newHash)
  525. * Push a Hash
  526. * @param {string} newHash
  527. * @return {boolean} true
  528. */
  529. History.saveHash = function(newHash){
  530. // Check Hash
  531. if ( History.isLastHash(newHash) ) {
  532. return false;
  533. }
  534. // Push the Hash
  535. History.savedHashes.push(newHash);
  536. // Return true
  537. return true;
  538. };
  539. /**
  540. * History.getHashByIndex()
  541. * Gets a hash by the index
  542. * @param {integer} index
  543. * @return {string}
  544. */
  545. History.getHashByIndex = function(index){
  546. // Prepare
  547. var hash = null;
  548. // Handle
  549. if ( typeof index === 'undefined' ) {
  550. // Get the last inserted
  551. hash = History.savedHashes[History.savedHashes.length-1];
  552. }
  553. else if ( index < 0 ) {
  554. // Get from the end
  555. hash = History.savedHashes[History.savedHashes.length+index];
  556. }
  557. else {
  558. // Get from the beginning
  559. hash = History.savedHashes[index];
  560. }
  561. // Return hash
  562. return hash;
  563. };
  564. // ====================================================================
  565. // Discarded States
  566. /**
  567. * History.discardedHashes
  568. * A hashed array of discarded hashes
  569. */
  570. History.discardedHashes = {};
  571. /**
  572. * History.discardedStates
  573. * A hashed array of discarded states
  574. */
  575. History.discardedStates = {};
  576. /**
  577. * History.discardState(State)
  578. * Discards the state by ignoring it through History
  579. * @param {object} State
  580. * @return {true}
  581. */
  582. History.discardState = function(discardedState,forwardState,backState){
  583. //History.debug('History.discardState', arguments);
  584. // Prepare
  585. var discardedStateHash = History.getHashByState(discardedState),
  586. discardObject;
  587. // Create Discard Object
  588. discardObject = {
  589. 'discardedState': discardedState,
  590. 'backState': backState,
  591. 'forwardState': forwardState
  592. };
  593. // Add to DiscardedStates
  594. History.discardedStates[discardedStateHash] = discardObject;
  595. // Return true
  596. return true;
  597. };
  598. /**
  599. * History.discardHash(hash)
  600. * Discards the hash by ignoring it through History
  601. * @param {string} hash
  602. * @return {true}
  603. */
  604. History.discardHash = function(discardedHash,forwardState,backState){
  605. //History.debug('History.discardState', arguments);
  606. // Create Discard Object
  607. var discardObject = {
  608. 'discardedHash': discardedHash,
  609. 'backState': backState,
  610. 'forwardState': forwardState
  611. };
  612. // Add to discardedHash
  613. History.discardedHashes[discardedHash] = discardObject;
  614. // Return true
  615. return true;
  616. };
  617. /**
  618. * History.discardedState(State)
  619. * Checks to see if the state is discarded
  620. * @param {object} State
  621. * @return {bool}
  622. */
  623. History.discardedState = function(State){
  624. // Prepare
  625. var StateHash = History.getHashByState(State),
  626. discarded;
  627. // Check
  628. discarded = History.discardedStates[StateHash]||false;
  629. // Return true
  630. return discarded;
  631. };
  632. /**
  633. * History.discardedHash(hash)
  634. * Checks to see if the state is discarded
  635. * @param {string} State
  636. * @return {bool}
  637. */
  638. History.discardedHash = function(hash){
  639. // Check
  640. var discarded = History.discardedHashes[hash]||false;
  641. // Return true
  642. return discarded;
  643. };
  644. /**
  645. * History.recycleState(State)
  646. * Allows a discarded state to be used again
  647. * @param {object} data
  648. * @param {string} title
  649. * @param {string} url
  650. * @return {true}
  651. */
  652. History.recycleState = function(State){
  653. //History.debug('History.recycleState', arguments);
  654. // Prepare
  655. var StateHash = History.getHashByState(State);
  656. // Remove from DiscardedStates
  657. if ( History.discardedState(State) ) {
  658. delete History.discardedStates[StateHash];
  659. }
  660. // Return true
  661. return true;
  662. };
  663. // ====================================================================
  664. // HTML4 HashChange Support
  665. if ( History.emulated.hashChange ) {
  666. /*
  667. * We must emulate the HTML4 HashChange Support by manually checking for hash changes
  668. */
  669. /**
  670. * History.hashChangeInit()
  671. * Init the HashChange Emulation
  672. */
  673. History.hashChangeInit = function(){
  674. // Define our Checker Function
  675. History.checkerFunction = null;
  676. // Define some variables that will help in our checker function
  677. var lastDocumentHash = '',
  678. iframeId, iframe,
  679. lastIframeHash, checkerRunning,
  680. startedWithHash = Boolean(History.getHash());
  681. // Handle depending on the browser
  682. if ( History.isInternetExplorer() ) {
  683. // IE6 and IE7
  684. // We need to use an iframe to emulate the back and forward buttons
  685. // Create iFrame
  686. iframeId = 'historyjs-iframe';
  687. iframe = document.createElement('iframe');
  688. // Adjust iFarme
  689. // IE 6 requires iframe to have a src on HTTPS pages, otherwise it will throw a
  690. // "This page contains both secure and nonsecure items" warning.
  691. iframe.setAttribute('id', iframeId);
  692. iframe.setAttribute('src', '#');
  693. iframe.style.display = 'none';
  694. // Append iFrame
  695. document.body.appendChild(iframe);
  696. // Create initial history entry
  697. iframe.contentWindow.document.open();
  698. iframe.contentWindow.document.close();
  699. // Define some variables that will help in our checker function
  700. lastIframeHash = '';
  701. checkerRunning = false;
  702. // Define the checker function
  703. History.checkerFunction = function(){
  704. // Check Running
  705. if ( checkerRunning ) {
  706. return false;
  707. }
  708. // Update Running
  709. checkerRunning = true;
  710. // Fetch
  711. var
  712. documentHash = History.getHash(),
  713. iframeHash = History.getHash(iframe.contentWindow.document);
  714. // The Document Hash has changed (application caused)
  715. if ( documentHash !== lastDocumentHash ) {
  716. // Equalise
  717. lastDocumentHash = documentHash;
  718. // Create a history entry in the iframe
  719. if ( iframeHash !== documentHash ) {
  720. //History.debug('hashchange.checker: iframe hash change', 'documentHash (new):', documentHash, 'iframeHash (old):', iframeHash);
  721. // Equalise
  722. lastIframeHash = iframeHash = documentHash;
  723. // Create History Entry
  724. iframe.contentWindow.document.open();
  725. iframe.contentWindow.document.close();
  726. // Update the iframe's hash
  727. iframe.contentWindow.document.location.hash = History.escapeHash(documentHash);
  728. }
  729. // Trigger Hashchange Event
  730. History.Adapter.trigger(window,'hashchange');
  731. }
  732. // The iFrame Hash has changed (back button caused)
  733. else if ( iframeHash !== lastIframeHash ) {
  734. //History.debug('hashchange.checker: iframe hash out of sync', 'iframeHash (new):', iframeHash, 'documentHash (old):', documentHash);
  735. // Equalise
  736. lastIframeHash = iframeHash;
  737. // If there is no iframe hash that means we're at the original
  738. // iframe state.
  739. // And if there was a hash on the original request, the original
  740. // iframe state was replaced instantly, so skip this state and take
  741. // the user back to where they came from.
  742. if (startedWithHash && iframeHash === '') {
  743. History.back();
  744. }
  745. else {
  746. // Update the Hash
  747. History.setHash(iframeHash,false);
  748. }
  749. }
  750. // Reset Running
  751. checkerRunning = false;
  752. // Return true
  753. return true;
  754. };
  755. }
  756. else {
  757. // We are not IE
  758. // Firefox 1 or 2, Opera
  759. // Define the checker function
  760. History.checkerFunction = function(){
  761. // Prepare
  762. var documentHash = History.getHash()||'';
  763. // The Document Hash has changed (application caused)
  764. if ( documentHash !== lastDocumentHash ) {
  765. // Equalise
  766. lastDocumentHash = documentHash;
  767. // Trigger Hashchange Event
  768. History.Adapter.trigger(window,'hashchange');
  769. }
  770. // Return true
  771. return true;
  772. };
  773. }
  774. // Apply the checker function
  775. History.intervalList.push(setInterval(History.checkerFunction, History.options.hashChangeInterval));
  776. // Done
  777. return true;
  778. }; // History.hashChangeInit
  779. // Bind hashChangeInit
  780. History.Adapter.onDomLoad(History.hashChangeInit);
  781. } // History.emulated.hashChange
  782. // ====================================================================
  783. // HTML5 State Support
  784. // Non-Native pushState Implementation
  785. if ( History.emulated.pushState ) {
  786. /*
  787. * We must emulate the HTML5 State Management by using HTML4 HashChange
  788. */
  789. /**
  790. * History.onHashChange(event)
  791. * Trigger HTML5's window.onpopstate via HTML4 HashChange Support
  792. */
  793. History.onHashChange = function(event){
  794. //History.debug('History.onHashChange', arguments);
  795. // Prepare
  796. var currentUrl = ((event && event.newURL) || History.getLocationHref()),
  797. currentHash = History.getHashByUrl(currentUrl),
  798. currentState = null,
  799. currentStateHash = null,
  800. currentStateHashExits = null,
  801. discardObject;
  802. // Check if we are the same state
  803. if ( History.isLastHash(currentHash) ) {
  804. // There has been no change (just the page's hash has finally propagated)
  805. //History.debug('History.onHashChange: no change');
  806. History.busy(false);
  807. return false;
  808. }
  809. // Reset the double check
  810. History.doubleCheckComplete();
  811. // Store our location for use in detecting back/forward direction
  812. History.saveHash(currentHash);
  813. // Expand Hash
  814. if ( currentHash && History.isTraditionalAnchor(currentHash) ) {
  815. //History.debug('History.onHashChange: traditional anchor', currentHash);
  816. // Traditional Anchor Hash
  817. History.Adapter.trigger(window,'anchorchange');
  818. History.busy(false);
  819. return false;
  820. }
  821. // Create State
  822. currentState = History.extractState(History.getFullUrl(currentHash||History.getLocationHref()),true);
  823. // Check if we are the same state
  824. if ( History.isLastSavedState(currentState) ) {
  825. //History.debug('History.onHashChange: no change');
  826. // There has been no change (just the page's hash has finally propagated)
  827. History.busy(false);
  828. return false;
  829. }
  830. // Create the state Hash
  831. currentStateHash = History.getHashByState(currentState);
  832. // Check if we are DiscardedState
  833. discardObject = History.discardedState(currentState);
  834. if ( discardObject ) {
  835. // Ignore this state as it has been discarded and go back to the state before it
  836. if ( History.getHashByIndex(-2) === History.getHashByState(discardObject.forwardState) ) {
  837. // We are going backwards
  838. //History.debug('History.onHashChange: go backwards');
  839. History.back(false);
  840. } else {
  841. // We are going forwards
  842. //History.debug('History.onHashChange: go forwards');
  843. History.forward(false);
  844. }
  845. return false;
  846. }
  847. // Push the new HTML5 State
  848. //History.debug('History.onHashChange: success hashchange');
  849. History.pushState(currentState.data,currentState.title,encodeURI(currentState.url),false);
  850. // End onHashChange closure
  851. return true;
  852. };
  853. History.Adapter.bind(window,'hashchange',History.onHashChange);
  854. /**
  855. * History.pushState(data,title,url)
  856. * Add a new State to the history object, become it, and trigger onpopstate
  857. * We have to trigger for HTML4 compatibility
  858. * @param {object} data
  859. * @param {string} title
  860. * @param {string} url
  861. * @return {true}
  862. */
  863. History.pushState = function(data,title,url,queue){
  864. //History.debug('History.pushState: called', arguments);
  865. // We assume that the URL passed in is URI-encoded, but this makes
  866. // sure that it's fully URI encoded; any '%'s that are encoded are
  867. // converted back into '%'s
  868. url = encodeURI(url).replace(/%25/g, "%");
  869. // Check the State
  870. if ( History.getHashByUrl(url) ) {
  871. throw new Error('History.js does not support states with fragment-identifiers (hashes/anchors).');
  872. }
  873. // Handle Queueing
  874. if ( queue !== false && History.busy() ) {
  875. // Wait + Push to Queue
  876. //History.debug('History.pushState: we must wait', arguments);
  877. History.pushQueue({
  878. scope: History,
  879. callback: History.pushState,
  880. args: arguments,
  881. queue: queue
  882. });
  883. return false;
  884. }
  885. // Make Busy
  886. History.busy(true);
  887. // Fetch the State Object
  888. var newState = History.createStateObject(data,title,url),
  889. newStateHash = History.getHashByState(newState),
  890. oldState = History.getState(false),
  891. oldStateHash = History.getHashByState(oldState),
  892. html4Hash = History.getHash(),
  893. wasExpected = History.expectedStateId == newState.id;
  894. // Store the newState
  895. History.storeState(newState);
  896. History.expectedStateId = newState.id;
  897. // Recycle the State
  898. History.recycleState(newState);
  899. // Force update of the title
  900. History.setTitle(newState);
  901. // Check if we are the same State
  902. if ( newStateHash === oldStateHash ) {
  903. //History.debug('History.pushState: no change', newStateHash);
  904. History.busy(false);
  905. return false;
  906. }
  907. // Update HTML5 State
  908. History.saveState(newState);
  909. // Fire HTML5 Event
  910. if(!wasExpected)
  911. History.Adapter.trigger(window,'statechange');
  912. // Update HTML4 Hash
  913. if ( !History.isHashEqual(newStateHash, html4Hash) && !History.isHashEqual(newStateHash, History.getShortUrl(History.getLocationHref())) ) {
  914. History.setHash(newStateHash,false);
  915. }
  916. History.busy(false);
  917. // End pushState closure
  918. return true;
  919. };
  920. /**
  921. * History.replaceState(data,title,url)
  922. * Replace the State and trigger onpopstate
  923. * We have to trigger for HTML4 compatibility
  924. * @param {object} data
  925. * @param {string} title
  926. * @param {string} url
  927. * @return {true}
  928. */
  929. History.replaceState = function(data,title,url,queue){
  930. //History.debug('History.replaceState: called', arguments);
  931. // We assume that the URL passed in is URI-encoded, but this makes
  932. // sure that it's fully URI encoded; any '%'s that are encoded are
  933. // converted back into '%'s
  934. url = encodeURI(url).replace(/%25/g, "%");
  935. // Check the State
  936. if ( History.getHashByUrl(url) ) {
  937. throw new Error('History.js does not support states with fragment-identifiers (hashes/anchors).');
  938. }
  939. // Handle Queueing
  940. if ( queue !== false && History.busy() ) {
  941. // Wait + Push to Queue
  942. //History.debug('History.replaceState: we must wait', arguments);
  943. History.pushQueue({
  944. scope: History,
  945. callback: History.replaceState,
  946. args: arguments,
  947. queue: queue
  948. });
  949. return false;
  950. }
  951. // Make Busy
  952. History.busy(true);
  953. // Fetch the State Objects
  954. var newState = History.createStateObject(data,title,url),
  955. newStateHash = History.getHashByState(newState),
  956. oldState = History.getState(false),
  957. oldStateHash = History.getHashByState(oldState),
  958. previousState = History.getStateByIndex(-2);
  959. // Discard Old State
  960. History.discardState(oldState,newState,previousState);
  961. // If the url hasn't changed, just store and save the state
  962. // and fire a statechange event to be consistent with the
  963. // html 5 api
  964. if ( newStateHash === oldStateHash ) {
  965. // Store the newState
  966. History.storeState(newState);
  967. History.expectedStateId = newState.id;
  968. // Recycle the State
  969. History.recycleState(newState);
  970. // Force update of the title
  971. History.setTitle(newState);
  972. // Update HTML5 State
  973. History.saveState(newState);
  974. // Fire HTML5 Event
  975. //History.debug('History.pushState: trigger popstate');
  976. History.Adapter.trigger(window,'statechange');
  977. History.busy(false);
  978. }
  979. else {
  980. // Alias to PushState
  981. History.pushState(newState.data,newState.title,newState.url,false);
  982. }
  983. // End replaceState closure
  984. return true;
  985. };
  986. } // History.emulated.pushState
  987. // ====================================================================
  988. // Initialise
  989. // Non-Native pushState Implementation
  990. if ( History.emulated.pushState ) {
  991. /**
  992. * Ensure initial state is handled correctly
  993. */
  994. if ( History.getHash() && !History.emulated.hashChange ) {
  995. History.Adapter.onDomLoad(function(){
  996. History.Adapter.trigger(window,'hashchange');
  997. });
  998. }
  999. } // History.emulated.pushState
  1000. }; // History.initHtml4
  1001. // Try to Initialise History
  1002. if ( typeof History.init !== 'undefined' ) {
  1003. History.init();
  1004. }
  1005. })(window);
  1006. /**
  1007. * History.js Core
  1008. * @author Benjamin Arthur Lupton <contact@balupton.com>
  1009. * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
  1010. * @license New BSD License <http://creativecommons.org/licenses/BSD/>
  1011. */
  1012. (function(window,undefined){
  1013. "use strict";
  1014. // ========================================================================
  1015. // Initialise
  1016. // Localise Globals
  1017. var
  1018. console = window.console||undefined, // Prevent a JSLint complain
  1019. document = window.document, // Make sure we are using the correct document
  1020. navigator = window.navigator, // Make sure we are using the correct navigator
  1021. sessionStorage = window.sessionStorage||false, // sessionStorage
  1022. setTimeout = window.setTimeout,
  1023. clearTimeout = window.clearTimeout,
  1024. setInterval = window.setInterval,
  1025. clearInterval = window.clearInterval,
  1026. JSON = window.JSON,
  1027. alert = window.alert,
  1028. History = window.History = window.History||{}, // Public History Object
  1029. history = window.history; // Old History Object
  1030. try {
  1031. sessionStorage.setItem('TEST', '1');
  1032. sessionStorage.removeItem('TEST');
  1033. } catch(e) {
  1034. sessionStorage = false;
  1035. }
  1036. // MooTools Compatibility
  1037. JSON.stringify = JSON.stringify||JSON.encode;
  1038. JSON.parse = JSON.parse||JSON.decode;
  1039. // Check Existence
  1040. if ( typeof History.init !== 'undefined' ) {
  1041. throw new Error('History.js Core has already been loaded...');
  1042. }
  1043. // Initialise History
  1044. History.init = function(options){
  1045. // Check Load Status of Adapter
  1046. if ( typeof History.Adapter === 'undefined' ) {
  1047. return false;
  1048. }
  1049. // Check Load Status of Core
  1050. if ( typeof History.initCore !== 'undefined' ) {
  1051. History.initCore();
  1052. }
  1053. // Check Load Status of HTML4 Support
  1054. if ( typeof History.initHtml4 !== 'undefined' ) {
  1055. History.initHtml4();
  1056. }
  1057. // Return true
  1058. return true;
  1059. };
  1060. // ========================================================================
  1061. // Initialise Core
  1062. // Initialise Core
  1063. History.initCore = function(options){
  1064. // Initialise
  1065. if ( typeof History.initCore.initialized !== 'undefined' ) {
  1066. // Already Loaded
  1067. return false;
  1068. }
  1069. else {
  1070. History.initCore.initialized = true;
  1071. }
  1072. // ====================================================================
  1073. // Options
  1074. /**
  1075. * History.options
  1076. * Configurable options
  1077. */
  1078. History.options = History.options||{};
  1079. /**
  1080. * History.options.hashChangeInterval
  1081. * How long should the interval be before hashchange checks
  1082. */
  1083. History.options.hashChangeInterval = History.options.hashChangeInterval || 100;
  1084. /**
  1085. * History.options.safariPollInterval
  1086. * How long should the interval be before safari poll checks
  1087. */
  1088. History.options.safariPollInterval = History.options.safariPollInterval || 500;
  1089. /**
  1090. * History.options.doubleCheckInterval
  1091. * How long should the interval be before we perform a double check
  1092. */
  1093. History.options.doubleCheckInterval = History.options.doubleCheckInterval || 500;
  1094. /**
  1095. * History.options.disableSuid
  1096. * Force History not to append suid
  1097. */
  1098. History.options.disableSuid = History.options.disableSuid || false;
  1099. /**
  1100. * History.options.storeInterval
  1101. * How long should we wait between store calls
  1102. */
  1103. History.options.storeInterval = History.options.storeInterval || 1000;
  1104. /**
  1105. * History.options.busyDelay
  1106. * How long should we wait between busy events
  1107. */
  1108. History.options.busyDelay = History.options.busyDelay || 250;
  1109. /**
  1110. * History.options.debug
  1111. * If true will enable debug messages to be logged
  1112. */
  1113. History.options.debug = History.options.debug || false;
  1114. /**
  1115. * History.options.initialTitle
  1116. * What is the title of the initial state
  1117. */
  1118. History.options.initialTitle = History.options.initialTitle || document.title;
  1119. /**
  1120. * History.options.html4Mode
  1121. * If true, will force HTMl4 mode (hashtags)
  1122. */
  1123. History.options.html4Mode = History.options.html4Mode || false;
  1124. /**
  1125. * History.options.delayInit
  1126. * Want to override default options and call init manually.
  1127. */
  1128. History.options.delayInit = History.options.delayInit || false;
  1129. // ====================================================================
  1130. // Interval record
  1131. /**
  1132. * History.intervalList
  1133. * List of intervals set, to be cleared when document is unloaded.
  1134. */
  1135. History.intervalList = [];
  1136. /**
  1137. * History.clearAllIntervals
  1138. * Clears all setInterval instances.
  1139. */
  1140. History.clearAllIntervals = function(){
  1141. var i, il = History.intervalList;
  1142. if (typeof il !== "undefined" && il !== null) {
  1143. for (i = 0; i < il.length; i++) {
  1144. clearInterval(il[i]);
  1145. }
  1146. History.intervalList = null;
  1147. }
  1148. };
  1149. // ====================================================================
  1150. // Debug
  1151. /**
  1152. * History.debug(message,...)
  1153. * Logs the passed arguments if debug enabled
  1154. */
  1155. History.debug = function(){
  1156. if ( (History.options.debug||false) ) {
  1157. History.log.apply(History,arguments);
  1158. }
  1159. };
  1160. /**
  1161. * History.log(message,...)
  1162. * Logs the passed arguments
  1163. */
  1164. History.log = function(){
  1165. // Prepare
  1166. var
  1167. consoleExists = !(typeof console === 'undefined' || typeof console.log === 'undefined' || typeof console.log.apply === 'undefined'),
  1168. textarea = document.getElementById('log'),
  1169. message,
  1170. i,n,
  1171. args,arg
  1172. ;
  1173. // Write to Console
  1174. if ( consoleExists ) {
  1175. args = Array.prototype.slice.call(arguments);
  1176. message = args.shift();
  1177. if ( typeof console.debug !== 'undefined' ) {
  1178. console.debug.apply(console,[message,args]);
  1179. }
  1180. else {
  1181. console.log.apply(console,[message,args]);
  1182. }
  1183. }
  1184. else {
  1185. message = ("\n"+arguments[0]+"\n");
  1186. }
  1187. // Write to log
  1188. for ( i=1,n=arguments.length; i<n; ++i ) {
  1189. arg = arguments[i];
  1190. if ( typeof arg === 'object' && typeof JSON !== 'undefined' ) {
  1191. try {
  1192. arg = JSON.stringify(arg);
  1193. }
  1194. catch ( Exception ) {
  1195. // Recursive Object
  1196. }
  1197. }
  1198. message += "\n"+arg+"\n";
  1199. }
  1200. // Textarea
  1201. if ( textarea ) {
  1202. textarea.value += message+"\n-----\n";
  1203. textarea.scrollTop = textarea.scrollHeight - textarea.clientHeight;
  1204. }
  1205. // No Textarea, No Console
  1206. else if ( !consoleExists ) {
  1207. alert(message);
  1208. }
  1209. // Return true
  1210. return true;
  1211. };
  1212. // ====================================================================
  1213. // Emulated Status
  1214. /**
  1215. * History.getInternetExplorerMajorVersion()
  1216. * Get's the major version of Internet Explorer
  1217. * @return {integer}
  1218. * @license Public Domain
  1219. * @author Benjamin Arthur Lupton <contact@balupton.com>
  1220. * @author James Padolsey <https://gist.github.com/527683>
  1221. */
  1222. History.getInternetExplorerMajorVersion = function(){
  1223. var result = History.getInternetExplorerMajorVersion.cached =
  1224. (typeof History.getInternetExplorerMajorVersion.cached !== 'undefined')
  1225. ? History.getInternetExplorerMajorVersion.cached
  1226. : (function(){
  1227. var v = 3,
  1228. div = document.createElement('div'),
  1229. all = div.getElementsByTagName('i');
  1230. while ( (div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->') && all[0] ) {}
  1231. return (v > 4) ? v : false;
  1232. })()
  1233. ;
  1234. return result;
  1235. };
  1236. /**
  1237. * History.isInternetExplorer()
  1238. * Are we using Internet Explorer?
  1239. * @return {boolean}
  1240. * @license Public Domain
  1241. * @author Benjamin Arthur Lupton <contact@balupton.com>
  1242. */
  1243. History.isInternetExplorer = function(){
  1244. var result =
  1245. History.isInternetExplorer.cached =
  1246. (typeof History.isInternetExplorer.cached !== 'undefined')
  1247. ? History.isInternetExplorer.cached
  1248. : Boolean(History.getInternetExplorerMajorVersion())
  1249. ;
  1250. return result;
  1251. };
  1252. /**
  1253. * History.emulated
  1254. * Which features require emulating?
  1255. */
  1256. if (History.options.html4Mode) {
  1257. History.emulated = {
  1258. pushState : true,
  1259. hashChange: true
  1260. };
  1261. }
  1262. else {
  1263. History.emulated = {
  1264. pushState: !Boolean(
  1265. window.history && window.history.pushState && window.history.replaceState
  1266. && !(
  1267. (/ 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) */
  1268. || (/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 */
  1269. )
  1270. ),
  1271. hashChange: Boolean(
  1272. !(('onhashchange' in window) || ('onhashchange' in document))
  1273. ||
  1274. (History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8)
  1275. )
  1276. };
  1277. }
  1278. /**
  1279. * History.enabled
  1280. * Is History enabled?
  1281. */
  1282. History.enabled = !History.emulated.pushState;
  1283. /**
  1284. * History.bugs
  1285. * Which bugs are present
  1286. */
  1287. History.bugs = {
  1288. /**
  1289. * Safari 5 and Safari iOS 4 fail to return to the correct state once a hash is replaced by a `replaceState` call
  1290. * https://bugs.webkit.org/show_bug.cgi?id=56249
  1291. */
  1292. setHash: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
  1293. /**
  1294. * Safari 5 and Safari iOS 4 sometimes fail to apply the state change under busy conditions
  1295. * https://bugs.webkit.org/show_bug.cgi?id=42940
  1296. */
  1297. safariPoll: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
  1298. /**
  1299. * MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function)
  1300. */
  1301. ieDoubleCheck: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8),
  1302. /**
  1303. * MSIE 6 requires the entire hash to be encoded for the hashes to trigger the onHashChange event
  1304. */
  1305. hashEscape: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 7)
  1306. };
  1307. /**
  1308. * History.isEmptyObject(obj)
  1309. * Checks to see if the Object is Empty
  1310. * @param {Object} obj
  1311. * @return {boolean}
  1312. */
  1313. History.isEmptyObject = function(obj) {
  1314. for ( var name in obj ) {
  1315. if ( obj.hasOwnProperty(name) ) {
  1316. return false;
  1317. }
  1318. }
  1319. return true;
  1320. };
  1321. /**
  1322. * History.cloneObject(obj)
  1323. * Clones a object and eliminate all references to the original contexts
  1324. * @param {Object} obj
  1325. * @return {Object}
  1326. */
  1327. History.cloneObject = function(obj) {
  1328. var hash,newObj;
  1329. if ( obj ) {
  1330. hash = JSON.stringify(obj);
  1331. newObj = JSON.parse(hash);
  1332. }
  1333. else {
  1334. newObj = {};
  1335. }
  1336. return newObj;
  1337. };
  1338. // ====================================================================
  1339. // URL Helpers
  1340. /**
  1341. * History.getRootUrl()
  1342. * Turns "http://mysite.com/dir/page.html?asd" into "http://mysite.com"
  1343. * @return {String} rootUrl
  1344. */
  1345. History.getRootUrl = function(){
  1346. // Create
  1347. var rootUrl = document.location.protocol+'//'+(document.location.hostname||document.location.host);
  1348. if ( document.location.port||false ) {
  1349. rootUrl += ':'+document.location.port;
  1350. }
  1351. rootUrl += '/';
  1352. // Return
  1353. return rootUrl;
  1354. };
  1355. /**
  1356. * History.getBaseHref()
  1357. * Fetches the `href` attribute of the `<base href="...">` element if it exists
  1358. * @return {String} baseHref
  1359. */
  1360. History.getBaseHref = function(){
  1361. // Create
  1362. var
  1363. baseElements = document.getElementsByTagName('base'),
  1364. baseElement = null,
  1365. baseHref = '';
  1366. // Test for Base Element
  1367. if ( baseElements.length === 1 ) {
  1368. // Prepare for Base Element
  1369. baseElement = baseElements[0];
  1370. baseHref = baseElement.href.replace(/[^\/]+$/,'');
  1371. }
  1372. // Adjust trailing slash
  1373. baseHref = baseHref.replace(/\/+$/,'');
  1374. if ( baseHref ) baseHref += '/';
  1375. // Return
  1376. return baseHref;
  1377. };
  1378. /**
  1379. * History.getBaseUrl()
  1380. * Fetches the baseHref or basePageUrl or rootUrl (whichever one exists first)
  1381. * @return {String} baseUrl
  1382. */
  1383. History.getBaseUrl = function(){
  1384. // Create
  1385. var baseUrl = History.getBaseHref()||History.getBasePageUrl()||History.getRootUrl();
  1386. // Return
  1387. return baseUrl;
  1388. };
  1389. /**
  1390. * History.getPageUrl()
  1391. * Fetches the URL of the current page
  1392. * @return {String} pageUrl
  1393. */
  1394. History.getPageUrl = function(){
  1395. // Fetch
  1396. var
  1397. State = History.getState(false,false),
  1398. stateUrl = (State||{}).url||History.getLocationHref(),
  1399. pageUrl;
  1400. // Create
  1401. pageUrl = stateUrl.replace(/\/+$/,'').replace(/[^\/]+$/,function(part,index,string){
  1402. return (/\./).test(part) ? part : part+'/';
  1403. });
  1404. // Return
  1405. return pageUrl;
  1406. };
  1407. /**
  1408. * History.getBasePageUrl()
  1409. * Fetches the Url of the directory of the current page
  1410. * @return {String} basePageUrl
  1411. */
  1412. History.getBasePageUrl = function(){
  1413. // Create
  1414. var basePageUrl = (History.getLocationHref()).replace(/[#\?].*/,'').replace(/[^\/]+$/,function(part,index,string){
  1415. return (/[^\/]$/).test(part) ? '' : part;
  1416. }).replace(/\/+$/,'')+'/';
  1417. // Return
  1418. return basePageUrl;
  1419. };
  1420. /**
  1421. * History.getFullUrl(url)
  1422. * Ensures that we have an absolute URL and not a relative URL
  1423. * @param {string} url
  1424. * @param {Boolean} allowBaseHref
  1425. * @return {string} fullUrl
  1426. */
  1427. History.getFullUrl = function(url,allowBaseHref){
  1428. // Prepare
  1429. var fullUrl = url, firstChar = url.substring(0,1);
  1430. allowBaseHref = (typeof allowBaseHref === 'undefined') ? true : allowBaseHref;
  1431. // Check
  1432. if ( /[a-z]+\:\/\//.test(url) ) {
  1433. // Full URL
  1434. }
  1435. else if ( firstChar === '/' ) {
  1436. // Root URL
  1437. fullUrl = History.getRootUrl()+url.replace(/^\/+/,'');
  1438. }
  1439. else if ( firstChar === '#' ) {
  1440. // Anchor URL
  1441. fullUrl = History.getPageUrl().replace(/#.*/,'')+url;
  1442. }
  1443. else if ( firstChar === '?' ) {
  1444. // Query URL
  1445. fullUrl = History.getPageUrl().replace(/[\?#].*/,'')+url;
  1446. }
  1447. else {
  1448. // Relative URL
  1449. if ( allowBaseHref ) {
  1450. fullUrl = History.getBaseUrl()+url.replace(/^(\.\/)+/,'');
  1451. } else {
  1452. fullUrl = History.getBasePageUrl()+url.replace(/^(\.\/)+/,'');
  1453. }
  1454. // We have an if condition above as we do not want hashes
  1455. // which are relative to the baseHref in our URLs
  1456. // as if the baseHref changes, then all our bookmarks
  1457. // would now point to different locations
  1458. // whereas the basePageUrl will always stay the same
  1459. }
  1460. // Return
  1461. return fullUrl.replace(/\#$/,'');
  1462. };
  1463. /**
  1464. * History.getShortUrl(url)
  1465. * Ensures that we have a relative URL and not a absolute URL
  1466. * @param {string} url
  1467. * @return {string} url
  1468. */
  1469. History.getShortUrl = function(url){
  1470. // Prepare
  1471. var shortUrl = url, baseUrl = History.getBaseUrl(), rootUrl = History.getRootUrl();
  1472. // Trim baseUrl
  1473. if ( History.emulated.pushState ) {
  1474. // We are in a if statement as when pushState is not emulated
  1475. // The actual url these short urls are relative to can change
  1476. // So within the same session, we the url may end up somewhere different
  1477. shortUrl = shortUrl.replace(baseUrl,'');
  1478. }
  1479. // Trim rootUrl
  1480. shortUrl = shortUrl.replace(rootUrl,'/');
  1481. // Ensure we can still detect it as a state
  1482. if ( History.isTraditionalAnchor(shortUrl) ) {
  1483. shortUrl = './'+shortUrl;
  1484. }
  1485. // Clean It
  1486. shortUrl = shortUrl.replace(/^(\.\/)+/g,'./').replace(/\#$/,'');
  1487. // Return
  1488. return shortUrl;
  1489. };
  1490. /**
  1491. * History.getLocationHref(document)
  1492. * Returns a normalized version of document.location.href
  1493. * accounting for browser inconsistencies, etc.
  1494. *
  1495. * This URL will be URI-encoded and will include the hash
  1496. *
  1497. * @param {object} document
  1498. * @return {string} url
  1499. */
  1500. History.getLocationHref = function(doc) {
  1501. doc = doc || document;
  1502. // most of the time, this will be true
  1503. if (doc.URL === doc.location.href)
  1504. return doc.location.href;
  1505. // some versions of webkit URI-decode document.location.href
  1506. // but they leave document.URL in an encoded state
  1507. if (doc.location.href === decodeURIComponent(doc.URL))
  1508. return doc.URL;
  1509. // FF 3.6 only updates document.URL when a page is reloaded
  1510. // document.location.href is updated correctly
  1511. if (doc.location.hash && decodeURIComponent(doc.location.href.replace(/^[^#]+/, "")) === doc.location.hash)
  1512. return doc.location.href;
  1513. if (doc.URL.indexOf('#') == -1 && doc.location.href.indexOf('#') != -1)
  1514. return doc.location.href;
  1515. return doc.URL || doc.location.href;
  1516. };
  1517. // ====================================================================
  1518. // State Storage
  1519. /**
  1520. * History.store
  1521. * The store for all session specific data
  1522. */
  1523. History.store = {};
  1524. /**
  1525. * History.idToState
  1526. * 1-1: State ID to State Object
  1527. */
  1528. History.idToState = History.idToState||{};
  1529. /**
  1530. * History.stateToId
  1531. * 1-1: State String to State ID
  1532. */
  1533. History.stateToId = History.stateToId||{};
  1534. /**
  1535. * History.urlToId
  1536. * 1-1: State URL to State ID
  1537. */
  1538. History.urlToId = History.urlToId||{};
  1539. /**
  1540. * History.storedStates
  1541. * Store the states in an array
  1542. */
  1543. History.storedStates = History.storedStates||[];
  1544. /**
  1545. * History.savedStates
  1546. * Saved the states in an array
  1547. */
  1548. History.savedStates = History.savedStates||[];
  1549. /**
  1550. * History.noramlizeStore()
  1551. * Noramlize the store by adding necessary values
  1552. */
  1553. History.normalizeStore = function(){
  1554. History.store.idToState = History.store.idToState||{};
  1555. History.store.urlToId = History.store.urlToId||{};
  1556. History.store.stateToId = History.store.stateToId||{};
  1557. };
  1558. /**
  1559. * History.getState()
  1560. * Get an object containing the data, title and url of the current state
  1561. * @param {Boolean} friendly
  1562. * @param {Boolean} create
  1563. * @return {Object} State
  1564. */
  1565. History.getState = function(friendly,create){
  1566. // Prepare
  1567. if ( typeof friendly === 'undefined' ) { friendly = true; }
  1568. if ( typeof create === 'undefined' ) { create = true; }
  1569. // Fetch
  1570. var State = History.getLastSavedState();
  1571. // Create
  1572. if ( !State && create ) {
  1573. State = History.createStateObject();
  1574. }
  1575. // Adjust
  1576. if ( friendly ) {
  1577. State = History.cloneObject(State);
  1578. State.url = State.cleanUrl||State.url;
  1579. }
  1580. // Return
  1581. return State;
  1582. };
  1583. /**
  1584. * History.getIdByState(State)
  1585. * Gets a ID for a State
  1586. * @param {State} newState
  1587. * @return {String} id
  1588. */
  1589. History.getIdByState = function(newState){
  1590. // Fetch ID
  1591. var id = History.extractId(newState.url),
  1592. str;
  1593. if ( !id ) {
  1594. // Find ID via State String
  1595. str = History.getStateString(newState);
  1596. if ( typeof History.stateToId[str] !== 'undefined' ) {
  1597. id = History.stateToId[str];
  1598. }
  1599. else if ( typeof History.store.stateToId[str] !== 'undefined' ) {
  1600. id = History.store.stateToId[str];
  1601. }
  1602. else {
  1603. // Generate a new ID
  1604. while ( true ) {
  1605. id = (new Date()).getTime() + String(Math.random()).replace(/\D/g,'');
  1606. if ( typeof History.idToState[id] === 'undefined' && typeof History.store.idToState[id] === 'undefined' ) {
  1607. break;
  1608. }
  1609. }
  1610. // Apply the new State to the ID
  1611. History.stateToId[str] = id;
  1612. History.idToState[id] = newState;
  1613. }
  1614. }
  1615. // Return ID
  1616. return id;
  1617. };
  1618. /**
  1619. * History.normalizeState(State)
  1620. * Expands a State Object
  1621. * @param {object} State
  1622. * @return {object}
  1623. */
  1624. History.normalizeState = function(oldState){
  1625. // Variables
  1626. var newState, dataNotEmpty;
  1627. // Prepare
  1628. if ( !oldState || (typeof oldState !== 'object') ) {
  1629. oldState = {};
  1630. }
  1631. // Check
  1632. if ( typeof oldState.normalized !== 'undefined' ) {
  1633. return oldState;
  1634. }
  1635. // Adjust
  1636. if ( !oldState.data || (typeof oldState.data !== 'object') ) {
  1637. oldState.data = {};
  1638. }
  1639. // ----------------------------------------------------------------
  1640. // Create
  1641. newState = {};
  1642. newState.normalized = true;
  1643. newState.title = oldState.title||'';
  1644. newState.url = History.getFullUrl(oldState.url?oldState.url:(History.getLocationHref()));
  1645. newState.hash = History.getShortUrl(newState.url);
  1646. newState.data = History.cloneObject(oldState.data);
  1647. // Fetch ID
  1648. newState.id = History.getIdByState(newState);
  1649. // ----------------------------------------------------------------
  1650. // Clean the URL
  1651. newState.cleanUrl = newState.url.replace(/\??\&_suid.*/,'');
  1652. newState.url = newState.cleanUrl;
  1653. // Check to see if we have more than just a url
  1654. dataNotEmpty = !History.isEmptyObject(newState.data);
  1655. // Apply
  1656. if ( (newState.title || dataNotEmpty) && History.options.disableSuid !== true ) {
  1657. // Add ID to Hash
  1658. newState.hash = History.getShortUrl(newState.url).replace(/\??\&_suid.*/,'');
  1659. if ( !/\?/.test(newState.hash) ) {
  1660. newState.hash += '?';
  1661. }
  1662. newState.hash += '&_suid='+newState.id;
  1663. }
  1664. // Create the Hashed URL
  1665. newState.hashedUrl = History.getFullUrl(newState.hash);
  1666. // ----------------------------------------------------------------
  1667. // Update the URL if we have a duplicate
  1668. if ( (History.emulated.pushState || History.bugs.safariPoll) && History.hasUrlDuplicate(newState) ) {
  1669. newState.url = newState.hashedUrl;
  1670. }
  1671. // ----------------------------------------------------------------
  1672. // Return
  1673. return newState;
  1674. };
  1675. /**
  1676. * History.createStateObject(data,title,url)
  1677. * Creates a object based on the data, title and url state params
  1678. * @param {object} data
  1679. * @param {string} title
  1680. * @param {string} url
  1681. * @return {object}
  1682. */
  1683. History.createStateObject = function(data,title,url){
  1684. // Hashify
  1685. var State = {
  1686. 'data': data,
  1687. 'title': title,
  1688. 'url': url
  1689. };
  1690. // Expand the State
  1691. State = History.normalizeState(State);
  1692. // Return object
  1693. return State;
  1694. };
  1695. /**
  1696. * History.getStateById(id)
  1697. * Get a state by it's UID
  1698. * @param {String} id
  1699. */
  1700. History.getStateById = function(id){
  1701. // Prepare
  1702. id = String(id);
  1703. // Retrieve
  1704. var State = History.idToState[id] || History.store.idToState[id] || undefined;
  1705. // Return State
  1706. return State;
  1707. };
  1708. /**
  1709. * Get a State's String
  1710. * @param {State} passedState
  1711. */
  1712. History.getStateString = function(passedState){
  1713. // Prepare
  1714. var State, cleanedState, str;
  1715. // Fetch
  1716. State = History.normalizeState(passedState);
  1717. // Clean
  1718. cleanedState = {
  1719. data: State.data,
  1720. title: passedState.title,
  1721. url: passedState.url
  1722. };
  1723. // Fetch
  1724. str = JSON.stringify(cleanedState);
  1725. // Return
  1726. return str;
  1727. };
  1728. /**
  1729. * Get a State's ID
  1730. * @param {State} passedState
  1731. * @return {String} id
  1732. */
  1733. History.getStateId = function(passedState){
  1734. // Prepare
  1735. var State, id;
  1736. // Fetch
  1737. State = History.normalizeState(passedState);
  1738. // Fetch
  1739. id = State.id;
  1740. // Return
  1741. return id;
  1742. };
  1743. /**
  1744. * History.getHashByState(State)
  1745. * Creates a Hash for the State Object
  1746. * @param {State} passedState
  1747. * @return {String} hash
  1748. */
  1749. History.getHashByState = function(passedState){
  1750. // Prepare
  1751. var State, hash;
  1752. // Fetch
  1753. State = History.normalizeState(passedState);
  1754. // Hash
  1755. hash = State.hash;
  1756. // Return
  1757. return hash;
  1758. };
  1759. /**
  1760. * History.extractId(url_or_hash)
  1761. * Get a State ID by it's URL or Hash
  1762. * @param {string} url_or_hash
  1763. * @return {string} id
  1764. */
  1765. History.extractId = function ( url_or_hash ) {
  1766. // Prepare
  1767. var id,parts,url, tmp;
  1768. // Extract
  1769. // If the URL has a #, use the id from before the #
  1770. if (url_or_hash.indexOf('#') != -1)
  1771. {
  1772. tmp = url_or_hash.split("#")[0];
  1773. }
  1774. else
  1775. {
  1776. tmp = url_or_hash;
  1777. }
  1778. parts = /(.*)\&_suid=([0-9]+)$/.exec(tmp);
  1779. url = parts ? (parts[1]||url_or_hash) : url_or_hash;
  1780. id = parts ? String(parts[2]||'') : '';
  1781. // Return
  1782. return id||false;
  1783. };
  1784. /**
  1785. * History.isTraditionalAnchor
  1786. * Checks to see if the url is a traditional anchor or not
  1787. * @param {String} url_or_hash
  1788. * @return {Boolean}
  1789. */
  1790. History.isTraditionalAnchor = function(url_or_hash){
  1791. // Check
  1792. var isTraditional = !(/[\/\?\.]/.test(url_or_hash));
  1793. // Return
  1794. return isTraditional;
  1795. };
  1796. /**
  1797. * History.extractState
  1798. * Get a State by it's URL or Hash
  1799. * @param {String} url_or_hash
  1800. * @return {State|null}
  1801. */
  1802. History.extractState = function(url_or_hash,create){
  1803. // Prepare
  1804. var State = null, id, url;
  1805. create = create||false;
  1806. // Fetch SUID
  1807. id = History.extractId(url_or_hash);
  1808. if ( id ) {
  1809. State = History.getStateById(id);
  1810. }
  1811. // Fetch SUID returned no State
  1812. if ( !State ) {
  1813. // Fetch URL
  1814. url = History.getFullUrl(url_or_hash);
  1815. // Check URL
  1816. id = History.getIdByUrl(url)||false;
  1817. if ( id ) {
  1818. State = History.getStateById(id);
  1819. }
  1820. // Create State
  1821. if ( !State && create && !History.isTraditionalAnchor(url_or_hash) ) {
  1822. State = History.createStateObject(null,null,url);
  1823. }
  1824. }
  1825. // Return
  1826. return State;
  1827. };
  1828. /**
  1829. * History.getIdByUrl()
  1830. * Get a State ID by a State URL
  1831. */
  1832. History.getIdByUrl = function(url){
  1833. // Fetch
  1834. var id = History.urlToId[url] || History.store.urlToId[url] || undefined;
  1835. // Return
  1836. return id;
  1837. };
  1838. /**
  1839. * History.getLastSavedState()
  1840. * Get an object containing the data, title and url of the current state
  1841. * @return {Object} State
  1842. */
  1843. History.getLastSavedState = function(){
  1844. return History.savedStates[History.savedStates.length-1]||undefined;
  1845. };
  1846. /**
  1847. * History.getLastStoredState()
  1848. * Get an object containing the data, title and url of the current state
  1849. * @return {Object} State
  1850. */
  1851. History.getLastStoredState = function(){
  1852. return History.storedStates[History.storedStates.length-1]||undefined;
  1853. };
  1854. /**
  1855. * History.hasUrlDuplicate
  1856. * Checks if a Url will have a url conflict
  1857. * @param {Object} newState
  1858. * @return {Boolean} hasDuplicate
  1859. */
  1860. History.hasUrlDuplicate = function(newState) {
  1861. // Prepare
  1862. var hasDuplicate = false,
  1863. oldState;
  1864. // Fetch
  1865. oldState = History.extractState(newState.url);
  1866. // Check
  1867. hasDuplicate = oldState && oldState.id !== newState.id;
  1868. // Return
  1869. return hasDuplicate;
  1870. };
  1871. /**
  1872. * History.storeState
  1873. * Store a State
  1874. * @param {Object} newState
  1875. * @return {Object} newState
  1876. */
  1877. History.storeState = function(newState){
  1878. // Store the State
  1879. History.urlToId[newState.url] = newState.id;
  1880. // Push the State
  1881. History.storedStates.push(History.cloneObject(newState));
  1882. // Return newState
  1883. return newState;
  1884. };
  1885. /**
  1886. * History.isLastSavedState(newState)
  1887. * Tests to see if the state is the last state
  1888. * @param {Object} newState
  1889. * @return {boolean} isLast
  1890. */
  1891. History.isLastSavedState = function(newState){
  1892. // Prepare
  1893. var isLast = false,
  1894. newId, oldState, oldId;
  1895. // Check
  1896. if ( History.savedStates.length ) {
  1897. newId = newState.id;
  1898. oldState = History.getLastSavedState();
  1899. oldId = oldState.id;
  1900. // Check
  1901. isLast = (newId === oldId);
  1902. }
  1903. // Return
  1904. return isLast;
  1905. };
  1906. /**
  1907. * History.saveState
  1908. * Push a State
  1909. * @param {Object} newState
  1910. * @return {boolean} changed
  1911. */
  1912. History.saveState = function(newState){
  1913. // Check Hash
  1914. if ( History.isLastSavedState(newState) ) {
  1915. return false;
  1916. }
  1917. // Push the State
  1918. History.savedStates.push(History.cloneObject(newState));
  1919. // Return true
  1920. return true;
  1921. };
  1922. /**
  1923. * History.getStateByIndex()
  1924. * Gets a state by the index
  1925. * @param {integer} index
  1926. * @return {Object}
  1927. */
  1928. History.getStateByIndex = function(index){
  1929. // Prepare
  1930. var State = null;
  1931. // Handle
  1932. if ( typeof index === 'undefined' ) {
  1933. // Get the last inserted
  1934. State = History.savedStates[History.savedStates.length-1];
  1935. }
  1936. else if ( index < 0 ) {
  1937. // Get from the end
  1938. State = History.savedStates[History.savedStates.length+index];
  1939. }
  1940. else {
  1941. // Get from the beginning
  1942. State = History.savedStates[index];
  1943. }
  1944. // Return State
  1945. return State;
  1946. };
  1947. /**
  1948. * History.getCurrentIndex()
  1949. * Gets the current index
  1950. * @return (integer)
  1951. */
  1952. History.getCurrentIndex = function(){
  1953. // Prepare
  1954. var index = null;
  1955. // No states saved
  1956. if(History.savedStates.length < 1) {
  1957. index = 0;
  1958. }
  1959. else {
  1960. index = History.savedStates.length-1;
  1961. }
  1962. return index;
  1963. };
  1964. // ====================================================================
  1965. // Hash Helpers
  1966. /**
  1967. * History.getHash()
  1968. * @param {Location=} location
  1969. * Gets the current document hash
  1970. * Note: unlike location.hash, this is guaranteed to return the escaped hash in all browsers
  1971. * @return {string}
  1972. */
  1973. History.getHash = function(doc){
  1974. var url = History.getLocationHref(doc),
  1975. hash;
  1976. hash = History.getHashByUrl(url);
  1977. return hash;
  1978. };
  1979. /**
  1980. * History.unescapeHash()
  1981. * normalize and Unescape a Hash
  1982. * @param {String} hash
  1983. * @return {string}
  1984. */
  1985. History.unescapeHash = function(hash){
  1986. // Prepare
  1987. var result = History.normalizeHash(hash);
  1988. // Unescape hash
  1989. result = decodeURIComponent(result);
  1990. // Return result
  1991. return result;
  1992. };
  1993. /**
  1994. * History.normalizeHash()
  1995. * normalize a hash across browsers
  1996. * @return {string}
  1997. */
  1998. History.normalizeHash = function(hash){
  1999. // Prepare
  2000. var result = hash.replace(/[^#]*#/,'').replace(/#.*/, '');
  2001. // Return result
  2002. return result;
  2003. };
  2004. /**
  2005. * History.setHash(hash)
  2006. * Sets the document hash
  2007. * @param {string} hash
  2008. * @return {History}
  2009. */
  2010. History.setHash = function(hash,queue){
  2011. // Prepare
  2012. var State, pageUrl;
  2013. // Handle Queueing
  2014. if ( queue !== false && History.busy() ) {
  2015. // Wait + Push to Queue
  2016. //History.debug('History.setHash: we must wait', arguments);
  2017. History.pushQueue({
  2018. scope: History,
  2019. callback: History.setHash,
  2020. args: arguments,
  2021. queue: queue
  2022. });
  2023. return false;
  2024. }
  2025. // Log
  2026. //History.debug('History.setHash: called',hash);
  2027. // Make Busy + Continue
  2028. History.busy(true);
  2029. // Check if hash is a state
  2030. State = History.extractState(hash,true);
  2031. if ( State && !History.emulated.pushState ) {
  2032. // Hash is a state so skip the setHash
  2033. //History.debug('History.setHash: Hash is a state so skipping the hash set with a direct pushState call',arguments);
  2034. // PushState
  2035. History.pushState(State.data,State.title,State.url,false);
  2036. }
  2037. else if ( History.getHash() !== hash ) {
  2038. // Hash is a proper hash, so apply it
  2039. // Handle browser bugs
  2040. if ( History.bugs.setHash ) {
  2041. // Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249
  2042. // Fetch the base page
  2043. pageUrl = History.getPageUrl();
  2044. // Safari hash apply
  2045. History.pushState(null,null,pageUrl+'#'+hash,false);
  2046. }
  2047. else {
  2048. // Normal hash apply
  2049. document.location.hash = hash;
  2050. }
  2051. }
  2052. // Chain
  2053. return History;
  2054. };
  2055. /**
  2056. * History.escape()
  2057. * normalize and Escape a Hash
  2058. * @return {string}
  2059. */
  2060. History.escapeHash = function(hash){
  2061. // Prepare
  2062. var result = History.normalizeHash(hash);
  2063. // Escape hash
  2064. result = window.encodeURIComponent(result);
  2065. // IE6 Escape Bug
  2066. if ( !History.bugs.hashEscape ) {
  2067. // Restore common parts
  2068. result = result
  2069. .replace(/\%21/g,'!')
  2070. .replace(/\%26/g,'&')
  2071. .replace(/\%3D/g,'=')
  2072. .replace(/\%3F/g,'?');
  2073. }
  2074. // Return result
  2075. return result;
  2076. };
  2077. /**
  2078. * History.getHashByUrl(url)
  2079. * Extracts the Hash from a URL
  2080. * @param {string} url
  2081. * @return {string} url
  2082. */
  2083. History.getHashByUrl = function(url){
  2084. // Extract the hash
  2085. var hash = String(url)
  2086. .replace(/([^#]*)#?([^#]*)#?(.*)/, '$2')
  2087. ;
  2088. // Unescape hash
  2089. hash = History.unescapeHash(hash);
  2090. // Return hash
  2091. return hash;
  2092. };
  2093. /**
  2094. * History.setTitle(title)
  2095. * Applies the title to the document
  2096. * @param {State} newState
  2097. * @return {Boolean}
  2098. */
  2099. History.setTitle = function(newState){
  2100. // Prepare
  2101. var title = newState.title,
  2102. firstState;
  2103. // Initial
  2104. if ( !title ) {
  2105. firstState = History.getStateByIndex(0);
  2106. if ( firstState && firstState.url === newState.url ) {
  2107. title = firstState.title||History.options.initialTitle;
  2108. }
  2109. }
  2110. // Apply
  2111. try {
  2112. document.getElementsByTagName('title')[0].innerHTML = title.replace('<','&lt;').replace('>','&gt;').replace(' & ',' &amp; ');
  2113. }
  2114. catch ( Exception ) { }
  2115. document.title = title;
  2116. // Chain
  2117. return History;
  2118. };
  2119. // ====================================================================
  2120. // Queueing
  2121. /**
  2122. * History.queues
  2123. * The list of queues to use
  2124. * First In, First Out
  2125. */
  2126. History.queues = [];
  2127. /**
  2128. * History.busy(value)
  2129. * @param {boolean} value [optional]
  2130. * @return {boolean} busy
  2131. */
  2132. History.busy = function(value){
  2133. // Apply
  2134. if ( typeof value !== 'undefined' ) {
  2135. //History.debug('History.busy: changing ['+(History.busy.flag||false)+'] to ['+(value||false)+']', History.queues.length);
  2136. History.busy.flag = value;
  2137. }
  2138. // Default
  2139. else if ( typeof History.busy.flag === 'undefined' ) {
  2140. History.busy.flag = false;
  2141. }
  2142. // Queue
  2143. if ( !History.busy.flag ) {
  2144. // Execute the next item in the queue
  2145. clearTimeout(History.busy.timeout);
  2146. var fireNext = function(){
  2147. var i, queue, item;
  2148. if ( History.busy.flag ) return;
  2149. for ( i=History.queues.length-1; i >= 0; --i ) {
  2150. queue = History.queues[i];
  2151. if ( queue.length === 0 ) continue;
  2152. item = queue.shift();
  2153. History.fireQueueItem(item);
  2154. History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
  2155. }
  2156. };
  2157. History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
  2158. }
  2159. // Return
  2160. return History.busy.flag;
  2161. };
  2162. /**
  2163. * History.busy.flag
  2164. */
  2165. History.busy.flag = false;
  2166. /**
  2167. * History.fireQueueItem(item)
  2168. * Fire a Queue Item
  2169. * @param {Object} item
  2170. * @return {Mixed} result
  2171. */
  2172. History.fireQueueItem = function(item){
  2173. return item.callback.apply(item.scope||History,item.args||[]);
  2174. };
  2175. /**
  2176. * History.pushQueue(callback,args)
  2177. * Add an item to the queue
  2178. * @param {Object} item [scope,callback,args,queue]
  2179. */
  2180. History.pushQueue = function(item){
  2181. // Prepare the queue
  2182. History.queues[item.queue||0] = History.queues[item.queue||0]||[];
  2183. // Add to the queue
  2184. History.queues[item.queue||0].push(item);
  2185. // Chain
  2186. return History;
  2187. };
  2188. /**
  2189. * History.queue (item,queue), (func,queue), (func), (item)
  2190. * Either firs the item now if not busy, or adds it to the queue
  2191. */
  2192. History.queue = function(item,queue){
  2193. // Prepare
  2194. if ( typeof item === 'function' ) {
  2195. item = {
  2196. callback: item
  2197. };
  2198. }
  2199. if ( typeof queue !== 'undefined' ) {
  2200. item.queue = queue;
  2201. }
  2202. // Handle
  2203. if ( History.busy() ) {
  2204. History.pushQueue(item);
  2205. } else {
  2206. History.fireQueueItem(item);
  2207. }
  2208. // Chain
  2209. return History;
  2210. };
  2211. /**
  2212. * History.clearQueue()
  2213. * Clears the Queue
  2214. */
  2215. History.clearQueue = function(){
  2216. History.busy.flag = false;
  2217. History.queues = [];
  2218. return History;
  2219. };
  2220. // ====================================================================
  2221. // IE Bug Fix
  2222. /**
  2223. * History.stateChanged
  2224. * States whether or not the state has changed since the last double check was initialised
  2225. */
  2226. History.stateChanged = false;
  2227. /**
  2228. * History.doubleChecker
  2229. * Contains the timeout used for the double checks
  2230. */
  2231. History.doubleChecker = false;
  2232. /**
  2233. * History.doubleCheckComplete()
  2234. * Complete a double check
  2235. * @return {History}
  2236. */
  2237. History.doubleCheckComplete = function(){
  2238. // Update
  2239. History.stateChanged = true;
  2240. // Clear
  2241. History.doubleCheckClear();
  2242. // Chain
  2243. return History;
  2244. };
  2245. /**
  2246. * History.doubleCheckClear()
  2247. * Clear a double check
  2248. * @return {History}
  2249. */
  2250. History.doubleCheckClear = function(){
  2251. // Clear
  2252. if ( History.doubleChecker ) {
  2253. clearTimeout(History.doubleChecker);
  2254. History.doubleChecker = false;
  2255. }
  2256. // Chain
  2257. return History;
  2258. };
  2259. /**
  2260. * History.doubleCheck()
  2261. * Create a double check
  2262. * @return {History}
  2263. */
  2264. History.doubleCheck = function(tryAgain){
  2265. // Reset
  2266. History.stateChanged = false;
  2267. History.doubleCheckClear();
  2268. // Fix IE6,IE7 bug where calling history.back or history.forward does not actually change the hash (whereas doing it manually does)
  2269. // Fix Safari 5 bug where sometimes the state does not change: https://bugs.webkit.org/show_bug.cgi?id=42940
  2270. if ( History.bugs.ieDoubleCheck ) {
  2271. // Apply Check
  2272. History.doubleChecker = setTimeout(
  2273. function(){
  2274. History.doubleCheckClear();
  2275. if ( !History.stateChanged ) {
  2276. //History.debug('History.doubleCheck: State has not yet changed, trying again', arguments);
  2277. // Re-Attempt
  2278. tryAgain();
  2279. }
  2280. return true;
  2281. },
  2282. History.options.doubleCheckInterval
  2283. );
  2284. }
  2285. // Chain
  2286. return History;
  2287. };
  2288. // ====================================================================
  2289. // Safari Bug Fix
  2290. /**
  2291. * History.safariStatePoll()
  2292. * Poll the current state
  2293. * @return {History}
  2294. */
  2295. History.safariStatePoll = function(){
  2296. // Poll the URL
  2297. // Get the Last State which has the new URL
  2298. var
  2299. urlState = History.extractState(History.getLocationHref()),
  2300. newState;
  2301. // Check for a difference
  2302. if ( !History.isLastSavedState(urlState) ) {
  2303. newState = urlState;
  2304. }
  2305. else {
  2306. return;
  2307. }
  2308. // Check if we have a state with that url
  2309. // If not create it
  2310. if ( !newState ) {
  2311. //History.debug('History.safariStatePoll: new');
  2312. newState = History.createStateObject();
  2313. }
  2314. // Apply the New State
  2315. //History.debug('History.safariStatePoll: trigger');
  2316. History.Adapter.trigger(window,'popstate');
  2317. // Chain
  2318. return History;
  2319. };
  2320. // ====================================================================
  2321. // State Aliases
  2322. /**
  2323. * History.back(queue)
  2324. * Send the browser history back one item
  2325. * @param {Integer} queue [optional]
  2326. */
  2327. History.back = function(queue){
  2328. //History.debug('History.back: called', arguments);
  2329. // Handle Queueing
  2330. if ( queue !== false && History.busy() ) {
  2331. // Wait + Push to Queue
  2332. //History.debug('History.back: we must wait', arguments);
  2333. History.pushQueue({
  2334. scope: History,
  2335. callback: History.back,
  2336. args: arguments,
  2337. queue: queue
  2338. });
  2339. return false;
  2340. }
  2341. // Make Busy + Continue
  2342. History.busy(true);
  2343. // Fix certain browser bugs that prevent the state from changing
  2344. History.doubleCheck(function(){
  2345. History.back(false);
  2346. });
  2347. // Go back
  2348. history.go(-1);
  2349. // End back closure
  2350. return true;
  2351. };
  2352. /**
  2353. * History.forward(queue)
  2354. * Send the browser history forward one item
  2355. * @param {Integer} queue [optional]
  2356. */
  2357. History.forward = function(queue){
  2358. //History.debug('History.forward: called', arguments);
  2359. // Handle Queueing
  2360. if ( queue !== false && History.busy() ) {
  2361. // Wait + Push to Queue
  2362. //History.debug('History.forward: we must wait', arguments);
  2363. History.pushQueue({
  2364. scope: History,
  2365. callback: History.forward,
  2366. args: arguments,
  2367. queue: queue
  2368. });
  2369. return false;
  2370. }
  2371. // Make Busy + Continue
  2372. History.busy(true);
  2373. // Fix certain browser bugs that prevent the state from changing
  2374. History.doubleCheck(function(){
  2375. History.forward(false);
  2376. });
  2377. // Go forward
  2378. history.go(1);
  2379. // End forward closure
  2380. return true;
  2381. };
  2382. /**
  2383. * History.go(index,queue)
  2384. * Send the browser history back or forward index times
  2385. * @param {Integer} queue [optional]
  2386. */
  2387. History.go = function(index,queue){
  2388. //History.debug('History.go: called', arguments);
  2389. // Prepare
  2390. var i;
  2391. // Handle
  2392. if ( index > 0 ) {
  2393. // Forward
  2394. for ( i=1; i<=index; ++i ) {
  2395. History.forward(queue);
  2396. }
  2397. }
  2398. else if ( index < 0 ) {
  2399. // Backward
  2400. for ( i=-1; i>=index; --i ) {
  2401. History.back(queue);
  2402. }
  2403. }
  2404. else {
  2405. throw new Error('History.go: History.go requires a positive or negative integer passed.');
  2406. }
  2407. // Chain
  2408. return History;
  2409. };
  2410. // ====================================================================
  2411. // HTML5 State Support
  2412. // Non-Native pushState Implementation
  2413. if ( History.emulated.pushState ) {
  2414. /*
  2415. * Provide Skeleton for HTML4 Browsers
  2416. */
  2417. // Prepare
  2418. var emptyFunction = function(){};
  2419. History.pushState = History.pushState||emptyFunction;
  2420. History.replaceState = History.replaceState||emptyFunction;
  2421. } // History.emulated.pushState
  2422. // Native pushState Implementation
  2423. else {
  2424. /*
  2425. * Use native HTML5 History API Implementation
  2426. */
  2427. /**
  2428. * History.onPopState(event,extra)
  2429. * Refresh the Current State
  2430. */
  2431. History.onPopState = function(event,extra){
  2432. // Prepare
  2433. var stateId = false, newState = false, currentHash, currentState;
  2434. // Reset the double check
  2435. History.doubleCheckComplete();
  2436. // Check for a Hash, and handle apporiatly
  2437. currentHash = History.getHash();
  2438. if ( currentHash ) {
  2439. // Expand Hash
  2440. currentState = History.extractState(currentHash||History.getLocationHref(),true);
  2441. if ( currentState ) {
  2442. // We were able to parse it, it must be a State!
  2443. // Let's forward to replaceState
  2444. //History.debug('History.onPopState: state anchor', currentHash, currentState);
  2445. History.replaceState(currentState.data, currentState.title, currentState.url, false);
  2446. }
  2447. else {
  2448. // Traditional Anchor
  2449. //History.debug('History.onPopState: traditional anchor', currentHash);
  2450. History.Adapter.trigger(window,'anchorchange');
  2451. History.busy(false);
  2452. }
  2453. // We don't care for hashes
  2454. History.expectedStateId = false;
  2455. return false;
  2456. }
  2457. // Ensure
  2458. stateId = History.Adapter.extractEventData('state',event,extra) || false;
  2459. // Fetch State
  2460. if ( stateId ) {
  2461. // Vanilla: Back/forward button was used
  2462. newState = History.getStateById(stateId);
  2463. }
  2464. else if ( History.expectedStateId ) {
  2465. // Vanilla: A new state was pushed, and popstate was called manually
  2466. newState = History.getStateById(History.expectedStateId);
  2467. }
  2468. else {
  2469. // Initial State
  2470. newState = History.extractState(History.getLocationHref());
  2471. }
  2472. // The State did not exist in our store
  2473. if ( !newState ) {
  2474. // Regenerate the State
  2475. newState = History.createStateObject(null,null,History.getLocationHref());
  2476. }
  2477. // Clean
  2478. History.expectedStateId = false;
  2479. // Check if we are the same state
  2480. if ( History.isLastSavedState(newState) ) {
  2481. // There has been no change (just the page's hash has finally propagated)
  2482. //History.debug('History.onPopState: no change', newState, History.savedStates);
  2483. History.busy(false);
  2484. return false;
  2485. }
  2486. // Store the State
  2487. History.storeState(newState);
  2488. History.saveState(newState);
  2489. // Force update of the title
  2490. History.setTitle(newState);
  2491. // Fire Our Event
  2492. History.Adapter.trigger(window,'statechange');
  2493. History.busy(false);
  2494. // Return true
  2495. return true;
  2496. };
  2497. History.Adapter.bind(window,'popstate',History.onPopState);
  2498. /**
  2499. * History.pushState(data,title,url)
  2500. * Add a new State to the history object, become it, and trigger onpopstate
  2501. * We have to trigger for HTML4 compatibility
  2502. * @param {object} data
  2503. * @param {string} title
  2504. * @param {string} url
  2505. * @return {true}
  2506. */
  2507. History.pushState = function(data,title,url,queue){
  2508. //History.debug('History.pushState: called', arguments);
  2509. // Check the State
  2510. if ( History.getHashByUrl(url) && History.emulated.pushState ) {
  2511. throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
  2512. }
  2513. // Handle Queueing
  2514. if ( queue !== false && History.busy() ) {
  2515. // Wait + Push to Queue
  2516. //History.debug('History.pushState: we must wait', arguments);
  2517. History.pushQueue({
  2518. scope: History,
  2519. callback: History.pushState,
  2520. args: arguments,
  2521. queue: queue
  2522. });
  2523. return false;
  2524. }
  2525. // Make Busy + Continue
  2526. History.busy(true);
  2527. // Create the newState
  2528. var newState = History.createStateObject(data,title,url);
  2529. // Check it
  2530. if ( History.isLastSavedState(newState) ) {
  2531. // Won't be a change
  2532. History.busy(false);
  2533. }
  2534. else {
  2535. // Store the newState
  2536. History.storeState(newState);
  2537. History.expectedStateId = newState.id;
  2538. // Push the newState
  2539. history.pushState(newState.id,newState.title,newState.url);
  2540. // Fire HTML5 Event
  2541. History.Adapter.trigger(window,'popstate');
  2542. }
  2543. // End pushState closure
  2544. return true;
  2545. };
  2546. /**
  2547. * History.replaceState(data,title,url)
  2548. * Replace the State and trigger onpopstate
  2549. * We have to trigger for HTML4 compatibility
  2550. * @param {object} data
  2551. * @param {string} title
  2552. * @param {string} url
  2553. * @return {true}
  2554. */
  2555. History.replaceState = function(data,title,url,queue){
  2556. //History.debug('History.replaceState: called', arguments);
  2557. // Check the State
  2558. if ( History.getHashByUrl(url) && History.emulated.pushState ) {
  2559. throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
  2560. }
  2561. // Handle Queueing
  2562. if ( queue !== false && History.busy() ) {
  2563. // Wait + Push to Queue
  2564. //History.debug('History.replaceState: we must wait', arguments);
  2565. History.pushQueue({
  2566. scope: History,
  2567. callback: History.replaceState,
  2568. args: arguments,
  2569. queue: queue
  2570. });
  2571. return false;
  2572. }
  2573. // Make Busy + Continue
  2574. History.busy(true);
  2575. // Create the newState
  2576. var newState = History.createStateObject(data,title,url);
  2577. // Check it
  2578. if ( History.isLastSavedState(newState) ) {
  2579. // Won't be a change
  2580. History.busy(false);
  2581. }
  2582. else {
  2583. // Store the newState
  2584. History.storeState(newState);
  2585. History.expectedStateId = newState.id;
  2586. // Push the newState
  2587. history.replaceState(newState.id,newState.title,newState.url);
  2588. // Fire HTML5 Event
  2589. History.Adapter.trigger(window,'popstate');
  2590. }
  2591. // End replaceState closure
  2592. return true;
  2593. };
  2594. } // !History.emulated.pushState
  2595. // ====================================================================
  2596. // Initialise
  2597. /**
  2598. * Load the Store
  2599. */
  2600. if ( sessionStorage ) {
  2601. // Fetch
  2602. try {
  2603. History.store = JSON.parse(sessionStorage.getItem('History.store'))||{};
  2604. }
  2605. catch ( err ) {
  2606. History.store = {};
  2607. }
  2608. // Normalize
  2609. History.normalizeStore();
  2610. }
  2611. else {
  2612. // Default Load
  2613. History.store = {};
  2614. History.normalizeStore();
  2615. }
  2616. /**
  2617. * Clear Intervals on exit to prevent memory leaks
  2618. */
  2619. History.Adapter.bind(window,"unload",History.clearAllIntervals);
  2620. /**
  2621. * Create the initial State
  2622. */
  2623. History.saveState(History.storeState(History.extractState(History.getLocationHref(),true)));
  2624. /**
  2625. * Bind for Saving Store
  2626. */
  2627. if ( sessionStorage ) {
  2628. // When the page is closed
  2629. History.onUnload = function(){
  2630. // Prepare
  2631. var currentStore, item, currentStoreString;
  2632. // Fetch
  2633. try {
  2634. currentStore = JSON.parse(sessionStorage.getItem('History.store'))||{};
  2635. }
  2636. catch ( err ) {
  2637. currentStore = {};
  2638. }
  2639. // Ensure
  2640. currentStore.idToState = currentStore.idToState || {};
  2641. currentStore.urlToId = currentStore.urlToId || {};
  2642. currentStore.stateToId = currentStore.stateToId || {};
  2643. // Sync
  2644. for ( item in History.idToState ) {
  2645. if ( !History.idToState.hasOwnProperty(item) ) {
  2646. continue;
  2647. }
  2648. currentStore.idToState[item] = History.idToState[item];
  2649. }
  2650. for ( item in History.urlToId ) {
  2651. if ( !History.urlToId.hasOwnProperty(item) ) {
  2652. continue;
  2653. }
  2654. currentStore.urlToId[item] = History.urlToId[item];
  2655. }
  2656. for ( item in History.stateToId ) {
  2657. if ( !History.stateToId.hasOwnProperty(item) ) {
  2658. continue;
  2659. }
  2660. currentStore.stateToId[item] = History.stateToId[item];
  2661. }
  2662. // Update
  2663. History.store = currentStore;
  2664. History.normalizeStore();
  2665. // In Safari, going into Private Browsing mode causes the
  2666. // Session Storage object to still exist but if you try and use
  2667. // or set any property/function of it it throws the exception
  2668. // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to
  2669. // add something to storage that exceeded the quota." infinitely
  2670. // every second.
  2671. currentStoreString = JSON.stringify(currentStore);
  2672. try {
  2673. // Store
  2674. sessionStorage.setItem('History.store', currentStoreString);
  2675. }
  2676. catch (e) {
  2677. if (e.code === DOMException.QUOTA_EXCEEDED_ERR) {
  2678. if (sessionStorage.length) {
  2679. // Workaround for a bug seen on iPads. Sometimes the quota exceeded error comes up and simply
  2680. // removing/resetting the storage can work.
  2681. sessionStorage.removeItem('History.store');
  2682. sessionStorage.setItem('History.store', currentStoreString);
  2683. } else {
  2684. // Otherwise, we're probably private browsing in Safari, so we'll ignore the exception.
  2685. }
  2686. } else {
  2687. throw e;
  2688. }
  2689. }
  2690. };
  2691. // For Internet Explorer
  2692. History.intervalList.push(setInterval(History.onUnload,History.options.storeInterval));
  2693. // For Other Browsers
  2694. History.Adapter.bind(window,'beforeunload',History.onUnload);
  2695. History.Adapter.bind(window,'unload',History.onUnload);
  2696. // Both are enabled for consistency
  2697. }
  2698. // Non-Native pushState Implementation
  2699. if ( !History.emulated.pushState ) {
  2700. // Be aware, the following is only for native pushState implementations
  2701. // If you are wanting to include something for all browsers
  2702. // Then include it above this if block
  2703. /**
  2704. * Setup Safari Fix
  2705. */
  2706. if ( History.bugs.safariPoll ) {
  2707. History.intervalList.push(setInterval(History.safariStatePoll, History.options.safariPollInterval));
  2708. }
  2709. /**
  2710. * Ensure Cross Browser Compatibility
  2711. */
  2712. if ( navigator.vendor === 'Apple Computer, Inc.' || (navigator.appCodeName||'') === 'Mozilla' ) {
  2713. /**
  2714. * Fix Safari HashChange Issue
  2715. */
  2716. // Setup Alias
  2717. History.Adapter.bind(window,'hashchange',function(){
  2718. History.Adapter.trigger(window,'popstate');
  2719. });
  2720. // Initialise Alias
  2721. if ( History.getHash() ) {
  2722. History.Adapter.onDomLoad(function(){
  2723. History.Adapter.trigger(window,'hashchange');
  2724. });
  2725. }
  2726. }
  2727. } // !History.emulated.pushState
  2728. }; // History.initCore
  2729. // Try to Initialise History
  2730. if (!History.options || !History.options.delayInit) {
  2731. History.init();
  2732. }
  2733. })(window);