zepto.history.js 86 KB

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