native.history.js 88 KB

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