right.history.js 87 KB

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