jquery.history.js 87 KB

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