mootools.history.js 87 KB

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