alongslide 0.9.9 → 0.9.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3292 @@
1
+ /*
2
+ json2.js
3
+ 2012-10-08
4
+
5
+ Public Domain.
6
+
7
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
8
+
9
+ See http://www.JSON.org/js.html
10
+
11
+
12
+ This code should be minified before deployment.
13
+ See http://javascript.crockford.com/jsmin.html
14
+
15
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
16
+ NOT CONTROL.
17
+
18
+
19
+ This file creates a global JSON object containing two methods: stringify
20
+ and parse.
21
+
22
+ JSON.stringify(value, replacer, space)
23
+ value any JavaScript value, usually an object or array.
24
+
25
+ replacer an optional parameter that determines how object
26
+ values are stringified for objects. It can be a
27
+ function or an array of strings.
28
+
29
+ space an optional parameter that specifies the indentation
30
+ of nested structures. If it is omitted, the text will
31
+ be packed without extra whitespace. If it is a number,
32
+ it will specify the number of spaces to indent at each
33
+ level. If it is a string (such as '\t' or ' '),
34
+ it contains the characters used to indent at each level.
35
+
36
+ This method produces a JSON text from a JavaScript value.
37
+
38
+ When an object value is found, if the object contains a toJSON
39
+ method, its toJSON method will be called and the result will be
40
+ stringified. A toJSON method does not serialize: it returns the
41
+ value represented by the name/value pair that should be serialized,
42
+ or undefined if nothing should be serialized. The toJSON method
43
+ will be passed the key associated with the value, and this will be
44
+ bound to the value
45
+
46
+ For example, this would serialize Dates as ISO strings.
47
+
48
+ Date.prototype.toJSON = function (key) {
49
+ function f(n) {
50
+ // Format integers to have at least two digits.
51
+ return n < 10 ? '0' + n : n;
52
+ }
53
+
54
+ return this.getUTCFullYear() + '-' +
55
+ f(this.getUTCMonth() + 1) + '-' +
56
+ f(this.getUTCDate()) + 'T' +
57
+ f(this.getUTCHours()) + ':' +
58
+ f(this.getUTCMinutes()) + ':' +
59
+ f(this.getUTCSeconds()) + 'Z';
60
+ };
61
+
62
+ You can provide an optional replacer method. It will be passed the
63
+ key and value of each member, with this bound to the containing
64
+ object. The value that is returned from your method will be
65
+ serialized. If your method returns undefined, then the member will
66
+ be excluded from the serialization.
67
+
68
+ If the replacer parameter is an array of strings, then it will be
69
+ used to select the members to be serialized. It filters the results
70
+ such that only members with keys listed in the replacer array are
71
+ stringified.
72
+
73
+ Values that do not have JSON representations, such as undefined or
74
+ functions, will not be serialized. Such values in objects will be
75
+ dropped; in arrays they will be replaced with null. You can use
76
+ a replacer function to replace those with JSON values.
77
+ JSON.stringify(undefined) returns undefined.
78
+
79
+ The optional space parameter produces a stringification of the
80
+ value that is filled with line breaks and indentation to make it
81
+ easier to read.
82
+
83
+ If the space parameter is a non-empty string, then that string will
84
+ be used for indentation. If the space parameter is a number, then
85
+ the indentation will be that many spaces.
86
+
87
+ Example:
88
+
89
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
90
+ // text is '["e",{"pluribus":"unum"}]'
91
+
92
+
93
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
94
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
95
+
96
+ text = JSON.stringify([new Date()], function (key, value) {
97
+ return this[key] instanceof Date ?
98
+ 'Date(' + this[key] + ')' : value;
99
+ });
100
+ // text is '["Date(---current time---)"]'
101
+
102
+
103
+ JSON.parse(text, reviver)
104
+ This method parses a JSON text to produce an object or array.
105
+ It can throw a SyntaxError exception.
106
+
107
+ The optional reviver parameter is a function that can filter and
108
+ transform the results. It receives each of the keys and values,
109
+ and its return value is used instead of the original value.
110
+ If it returns what it received, then the structure is not modified.
111
+ If it returns undefined then the member is deleted.
112
+
113
+ Example:
114
+
115
+ // Parse the text. Values that look like ISO date strings will
116
+ // be converted to Date objects.
117
+
118
+ myData = JSON.parse(text, function (key, value) {
119
+ var a;
120
+ if (typeof value === 'string') {
121
+ a =
122
+ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
123
+ if (a) {
124
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
125
+ +a[5], +a[6]));
126
+ }
127
+ }
128
+ return value;
129
+ });
130
+
131
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
132
+ var d;
133
+ if (typeof value === 'string' &&
134
+ value.slice(0, 5) === 'Date(' &&
135
+ value.slice(-1) === ')') {
136
+ d = new Date(value.slice(5, -1));
137
+ if (d) {
138
+ return d;
139
+ }
140
+ }
141
+ return value;
142
+ });
143
+
144
+
145
+ This is a reference implementation. You are free to copy, modify, or
146
+ redistribute.
147
+ */
148
+
149
+ /*jslint evil: true, regexp: true */
150
+
151
+ /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
152
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
153
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
154
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
155
+ test, toJSON, toString, valueOf
156
+ */
157
+
158
+
159
+ // Create a JSON object only if one does not already exist. We create the
160
+ // methods in a closure to avoid creating global variables.
161
+
162
+ if (typeof JSON !== 'object') {
163
+ JSON = {};
164
+ }
165
+
166
+ (function () {
167
+ 'use strict';
168
+
169
+ function f(n) {
170
+ // Format integers to have at least two digits.
171
+ return n < 10 ? '0' + n : n;
172
+ }
173
+
174
+ if (typeof Date.prototype.toJSON !== 'function') {
175
+
176
+ Date.prototype.toJSON = function (key) {
177
+
178
+ return isFinite(this.valueOf())
179
+ ? this.getUTCFullYear() + '-' +
180
+ f(this.getUTCMonth() + 1) + '-' +
181
+ f(this.getUTCDate()) + 'T' +
182
+ f(this.getUTCHours()) + ':' +
183
+ f(this.getUTCMinutes()) + ':' +
184
+ f(this.getUTCSeconds()) + 'Z'
185
+ : null;
186
+ };
187
+
188
+ String.prototype.toJSON =
189
+ Number.prototype.toJSON =
190
+ Boolean.prototype.toJSON = function (key) {
191
+ return this.valueOf();
192
+ };
193
+ }
194
+
195
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
196
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
197
+ gap,
198
+ indent,
199
+ meta = { // table of character substitutions
200
+ '\b': '\\b',
201
+ '\t': '\\t',
202
+ '\n': '\\n',
203
+ '\f': '\\f',
204
+ '\r': '\\r',
205
+ '"' : '\\"',
206
+ '\\': '\\\\'
207
+ },
208
+ rep;
209
+
210
+
211
+ function quote(string) {
212
+
213
+ // If the string contains no control characters, no quote characters, and no
214
+ // backslash characters, then we can safely slap some quotes around it.
215
+ // Otherwise we must also replace the offending characters with safe escape
216
+ // sequences.
217
+
218
+ escapable.lastIndex = 0;
219
+ return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
220
+ var c = meta[a];
221
+ return typeof c === 'string'
222
+ ? c
223
+ : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
224
+ }) + '"' : '"' + string + '"';
225
+ }
226
+
227
+
228
+ function str(key, holder) {
229
+
230
+ // Produce a string from holder[key].
231
+
232
+ var i, // The loop counter.
233
+ k, // The member key.
234
+ v, // The member value.
235
+ length,
236
+ mind = gap,
237
+ partial,
238
+ value = holder[key];
239
+
240
+ // If the value has a toJSON method, call it to obtain a replacement value.
241
+
242
+ if (value && typeof value === 'object' &&
243
+ typeof value.toJSON === 'function') {
244
+ value = value.toJSON(key);
245
+ }
246
+
247
+ // If we were called with a replacer function, then call the replacer to
248
+ // obtain a replacement value.
249
+
250
+ if (typeof rep === 'function') {
251
+ value = rep.call(holder, key, value);
252
+ }
253
+
254
+ // What happens next depends on the value's type.
255
+
256
+ switch (typeof value) {
257
+ case 'string':
258
+ return quote(value);
259
+
260
+ case 'number':
261
+
262
+ // JSON numbers must be finite. Encode non-finite numbers as null.
263
+
264
+ return isFinite(value) ? String(value) : 'null';
265
+
266
+ case 'boolean':
267
+ case 'null':
268
+
269
+ // If the value is a boolean or null, convert it to a string. Note:
270
+ // typeof null does not produce 'null'. The case is included here in
271
+ // the remote chance that this gets fixed someday.
272
+
273
+ return String(value);
274
+
275
+ // If the type is 'object', we might be dealing with an object or an array or
276
+ // null.
277
+
278
+ case 'object':
279
+
280
+ // Due to a specification blunder in ECMAScript, typeof null is 'object',
281
+ // so watch out for that case.
282
+
283
+ if (!value) {
284
+ return 'null';
285
+ }
286
+
287
+ // Make an array to hold the partial results of stringifying this object value.
288
+
289
+ gap += indent;
290
+ partial = [];
291
+
292
+ // Is the value an array?
293
+
294
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
295
+
296
+ // The value is an array. Stringify every element. Use null as a placeholder
297
+ // for non-JSON values.
298
+
299
+ length = value.length;
300
+ for (i = 0; i < length; i += 1) {
301
+ partial[i] = str(i, value) || 'null';
302
+ }
303
+
304
+ // Join all of the elements together, separated with commas, and wrap them in
305
+ // brackets.
306
+
307
+ v = partial.length === 0
308
+ ? '[]'
309
+ : gap
310
+ ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
311
+ : '[' + partial.join(',') + ']';
312
+ gap = mind;
313
+ return v;
314
+ }
315
+
316
+ // If the replacer is an array, use it to select the members to be stringified.
317
+
318
+ if (rep && typeof rep === 'object') {
319
+ length = rep.length;
320
+ for (i = 0; i < length; i += 1) {
321
+ if (typeof rep[i] === 'string') {
322
+ k = rep[i];
323
+ v = str(k, value);
324
+ if (v) {
325
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
326
+ }
327
+ }
328
+ }
329
+ } else {
330
+
331
+ // Otherwise, iterate through all of the keys in the object.
332
+
333
+ for (k in value) {
334
+ if (Object.prototype.hasOwnProperty.call(value, k)) {
335
+ v = str(k, value);
336
+ if (v) {
337
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
338
+ }
339
+ }
340
+ }
341
+ }
342
+
343
+ // Join all of the member texts together, separated with commas,
344
+ // and wrap them in braces.
345
+
346
+ v = partial.length === 0
347
+ ? '{}'
348
+ : gap
349
+ ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
350
+ : '{' + partial.join(',') + '}';
351
+ gap = mind;
352
+ return v;
353
+ }
354
+ }
355
+
356
+ // If the JSON object does not yet have a stringify method, give it one.
357
+
358
+ if (typeof JSON.stringify !== 'function') {
359
+ JSON.stringify = function (value, replacer, space) {
360
+
361
+ // The stringify method takes a value and an optional replacer, and an optional
362
+ // space parameter, and returns a JSON text. The replacer can be a function
363
+ // that can replace values, or an array of strings that will select the keys.
364
+ // A default replacer method can be provided. Use of the space parameter can
365
+ // produce text that is more easily readable.
366
+
367
+ var i;
368
+ gap = '';
369
+ indent = '';
370
+
371
+ // If the space parameter is a number, make an indent string containing that
372
+ // many spaces.
373
+
374
+ if (typeof space === 'number') {
375
+ for (i = 0; i < space; i += 1) {
376
+ indent += ' ';
377
+ }
378
+
379
+ // If the space parameter is a string, it will be used as the indent string.
380
+
381
+ } else if (typeof space === 'string') {
382
+ indent = space;
383
+ }
384
+
385
+ // If there is a replacer, it must be a function or an array.
386
+ // Otherwise, throw an error.
387
+
388
+ rep = replacer;
389
+ if (replacer && typeof replacer !== 'function' &&
390
+ (typeof replacer !== 'object' ||
391
+ typeof replacer.length !== 'number')) {
392
+ throw new Error('JSON.stringify');
393
+ }
394
+
395
+ // Make a fake root object containing our value under the key of ''.
396
+ // Return the result of stringifying the value.
397
+
398
+ return str('', {'': value});
399
+ };
400
+ }
401
+
402
+
403
+ // If the JSON object does not yet have a parse method, give it one.
404
+
405
+ if (typeof JSON.parse !== 'function') {
406
+ JSON.parse = function (text, reviver) {
407
+
408
+ // The parse method takes a text and an optional reviver function, and returns
409
+ // a JavaScript value if the text is a valid JSON text.
410
+
411
+ var j;
412
+
413
+ function walk(holder, key) {
414
+
415
+ // The walk method is used to recursively walk the resulting structure so
416
+ // that modifications can be made.
417
+
418
+ var k, v, value = holder[key];
419
+ if (value && typeof value === 'object') {
420
+ for (k in value) {
421
+ if (Object.prototype.hasOwnProperty.call(value, k)) {
422
+ v = walk(value, k);
423
+ if (v !== undefined) {
424
+ value[k] = v;
425
+ } else {
426
+ delete value[k];
427
+ }
428
+ }
429
+ }
430
+ }
431
+ return reviver.call(holder, key, value);
432
+ }
433
+
434
+
435
+ // Parsing happens in four stages. In the first stage, we replace certain
436
+ // Unicode characters with escape sequences. JavaScript handles many characters
437
+ // incorrectly, either silently deleting them, or treating them as line endings.
438
+
439
+ text = String(text);
440
+ cx.lastIndex = 0;
441
+ if (cx.test(text)) {
442
+ text = text.replace(cx, function (a) {
443
+ return '\\u' +
444
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
445
+ });
446
+ }
447
+
448
+ // In the second stage, we run the text against regular expressions that look
449
+ // for non-JSON patterns. We are especially concerned with '()' and 'new'
450
+ // because they can cause invocation, and '=' because it can cause mutation.
451
+ // But just to be safe, we want to reject all unexpected forms.
452
+
453
+ // We split the second stage into 4 regexp operations in order to work around
454
+ // crippling inefficiencies in IE's and Safari's regexp engines. First we
455
+ // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
456
+ // replace all simple value tokens with ']' characters. Third, we delete all
457
+ // open brackets that follow a colon or comma or that begin the text. Finally,
458
+ // we look to see that the remaining characters are only whitespace or ']' or
459
+ // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
460
+
461
+ if (/^[\],:{}\s]*$/
462
+ .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
463
+ .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
464
+ .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
465
+
466
+ // In the third stage we use the eval function to compile the text into a
467
+ // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
468
+ // in JavaScript: it can begin a block or an object literal. We wrap the text
469
+ // in parens to eliminate the ambiguity.
470
+
471
+ j = eval('(' + text + ')');
472
+
473
+ // In the optional fourth stage, we recursively walk the new structure, passing
474
+ // each name/value pair to a reviver function for possible transformation.
475
+
476
+ return typeof reviver === 'function'
477
+ ? walk({'': j}, '')
478
+ : j;
479
+ }
480
+
481
+ // If the text is not JSON parseable, then a SyntaxError is thrown.
482
+
483
+ throw new SyntaxError('JSON.parse');
484
+ };
485
+ }
486
+ }());/**
487
+ * History.js jQuery Adapter
488
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
489
+ * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
490
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
491
+ */
492
+
493
+ // Closure
494
+ (function(window,undefined){
495
+ "use strict";
496
+
497
+ // Localise Globals
498
+ var
499
+ History = window.History = window.History||{},
500
+ jQuery = window.jQuery;
501
+
502
+ // Check Existence
503
+ if ( typeof History.Adapter !== 'undefined' ) {
504
+ throw new Error('History.js Adapter has already been loaded...');
505
+ }
506
+
507
+ // Add the Adapter
508
+ History.Adapter = {
509
+ /**
510
+ * History.Adapter.bind(el,event,callback)
511
+ * @param {Element|string} el
512
+ * @param {string} event - custom and standard events
513
+ * @param {function} callback
514
+ * @return {void}
515
+ */
516
+ bind: function(el,event,callback){
517
+ jQuery(el).bind(event,callback);
518
+ },
519
+
520
+ /**
521
+ * History.Adapter.trigger(el,event)
522
+ * @param {Element|string} el
523
+ * @param {string} event - custom and standard events
524
+ * @param {Object=} extra - a object of extra event data (optional)
525
+ * @return {void}
526
+ */
527
+ trigger: function(el,event,extra){
528
+ jQuery(el).trigger(event,extra);
529
+ },
530
+
531
+ /**
532
+ * History.Adapter.extractEventData(key,event,extra)
533
+ * @param {string} key - key for the event data to extract
534
+ * @param {string} event - custom and standard events
535
+ * @param {Object=} extra - a object of extra event data (optional)
536
+ * @return {mixed}
537
+ */
538
+ extractEventData: function(key,event,extra){
539
+ // jQuery Native then jQuery Custom
540
+ var result = (event && event.originalEvent && event.originalEvent[key]) || (extra && extra[key]) || undefined;
541
+
542
+ // Return
543
+ return result;
544
+ },
545
+
546
+ /**
547
+ * History.Adapter.onDomLoad(callback)
548
+ * @param {function} callback
549
+ * @return {void}
550
+ */
551
+ onDomLoad: function(callback) {
552
+ jQuery(callback);
553
+ }
554
+ };
555
+
556
+ // Try and Initialise History
557
+ if ( typeof History.init !== 'undefined' ) {
558
+ History.init();
559
+ }
560
+
561
+ })(window);
562
+
563
+ /**
564
+ * History.js HTML4 Support
565
+ * Depends on the HTML5 Support
566
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
567
+ * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
568
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
569
+ */
570
+
571
+ (function(window,undefined){
572
+ "use strict";
573
+
574
+ // ========================================================================
575
+ // Initialise
576
+
577
+ // Localise Globals
578
+ var
579
+ document = window.document, // Make sure we are using the correct document
580
+ setTimeout = window.setTimeout||setTimeout,
581
+ clearTimeout = window.clearTimeout||clearTimeout,
582
+ setInterval = window.setInterval||setInterval,
583
+ History = window.History = window.History||{}; // Public History Object
584
+
585
+ // Check Existence
586
+ if ( typeof History.initHtml4 !== 'undefined' ) {
587
+ throw new Error('History.js HTML4 Support has already been loaded...');
588
+ }
589
+
590
+
591
+ // ========================================================================
592
+ // Initialise HTML4 Support
593
+
594
+ // Initialise HTML4 Support
595
+ History.initHtml4 = function(){
596
+ // Initialise
597
+ if ( typeof History.initHtml4.initialized !== 'undefined' ) {
598
+ // Already Loaded
599
+ return false;
600
+ }
601
+ else {
602
+ History.initHtml4.initialized = true;
603
+ }
604
+
605
+
606
+ // ====================================================================
607
+ // Properties
608
+
609
+ /**
610
+ * History.enabled
611
+ * Is History enabled?
612
+ */
613
+ History.enabled = true;
614
+
615
+
616
+ // ====================================================================
617
+ // Hash Storage
618
+
619
+ /**
620
+ * History.savedHashes
621
+ * Store the hashes in an array
622
+ */
623
+ History.savedHashes = [];
624
+
625
+ /**
626
+ * History.isLastHash(newHash)
627
+ * Checks if the hash is the last hash
628
+ * @param {string} newHash
629
+ * @return {boolean} true
630
+ */
631
+ History.isLastHash = function(newHash){
632
+ // Prepare
633
+ var oldHash = History.getHashByIndex(),
634
+ isLast;
635
+
636
+ // Check
637
+ isLast = newHash === oldHash;
638
+
639
+ // Return isLast
640
+ return isLast;
641
+ };
642
+
643
+ /**
644
+ * History.isHashEqual(newHash, oldHash)
645
+ * Checks to see if two hashes are functionally equal
646
+ * @param {string} newHash
647
+ * @param {string} oldHash
648
+ * @return {boolean} true
649
+ */
650
+ History.isHashEqual = function(newHash, oldHash){
651
+ newHash = encodeURIComponent(newHash).replace(/%25/g, "%");
652
+ oldHash = encodeURIComponent(oldHash).replace(/%25/g, "%");
653
+ return newHash === oldHash;
654
+ };
655
+
656
+ /**
657
+ * History.saveHash(newHash)
658
+ * Push a Hash
659
+ * @param {string} newHash
660
+ * @return {boolean} true
661
+ */
662
+ History.saveHash = function(newHash){
663
+ // Check Hash
664
+ if ( History.isLastHash(newHash) ) {
665
+ return false;
666
+ }
667
+
668
+ // Push the Hash
669
+ History.savedHashes.push(newHash);
670
+
671
+ // Return true
672
+ return true;
673
+ };
674
+
675
+ /**
676
+ * History.getHashByIndex()
677
+ * Gets a hash by the index
678
+ * @param {integer} index
679
+ * @return {string}
680
+ */
681
+ History.getHashByIndex = function(index){
682
+ // Prepare
683
+ var hash = null;
684
+
685
+ // Handle
686
+ if ( typeof index === 'undefined' ) {
687
+ // Get the last inserted
688
+ hash = History.savedHashes[History.savedHashes.length-1];
689
+ }
690
+ else if ( index < 0 ) {
691
+ // Get from the end
692
+ hash = History.savedHashes[History.savedHashes.length+index];
693
+ }
694
+ else {
695
+ // Get from the beginning
696
+ hash = History.savedHashes[index];
697
+ }
698
+
699
+ // Return hash
700
+ return hash;
701
+ };
702
+
703
+
704
+ // ====================================================================
705
+ // Discarded States
706
+
707
+ /**
708
+ * History.discardedHashes
709
+ * A hashed array of discarded hashes
710
+ */
711
+ History.discardedHashes = {};
712
+
713
+ /**
714
+ * History.discardedStates
715
+ * A hashed array of discarded states
716
+ */
717
+ History.discardedStates = {};
718
+
719
+ /**
720
+ * History.discardState(State)
721
+ * Discards the state by ignoring it through History
722
+ * @param {object} State
723
+ * @return {true}
724
+ */
725
+ History.discardState = function(discardedState,forwardState,backState){
726
+ //History.debug('History.discardState', arguments);
727
+ // Prepare
728
+ var discardedStateHash = History.getHashByState(discardedState),
729
+ discardObject;
730
+
731
+ // Create Discard Object
732
+ discardObject = {
733
+ 'discardedState': discardedState,
734
+ 'backState': backState,
735
+ 'forwardState': forwardState
736
+ };
737
+
738
+ // Add to DiscardedStates
739
+ History.discardedStates[discardedStateHash] = discardObject;
740
+
741
+ // Return true
742
+ return true;
743
+ };
744
+
745
+ /**
746
+ * History.discardHash(hash)
747
+ * Discards the hash by ignoring it through History
748
+ * @param {string} hash
749
+ * @return {true}
750
+ */
751
+ History.discardHash = function(discardedHash,forwardState,backState){
752
+ //History.debug('History.discardState', arguments);
753
+ // Create Discard Object
754
+ var discardObject = {
755
+ 'discardedHash': discardedHash,
756
+ 'backState': backState,
757
+ 'forwardState': forwardState
758
+ };
759
+
760
+ // Add to discardedHash
761
+ History.discardedHashes[discardedHash] = discardObject;
762
+
763
+ // Return true
764
+ return true;
765
+ };
766
+
767
+ /**
768
+ * History.discardedState(State)
769
+ * Checks to see if the state is discarded
770
+ * @param {object} State
771
+ * @return {bool}
772
+ */
773
+ History.discardedState = function(State){
774
+ // Prepare
775
+ var StateHash = History.getHashByState(State),
776
+ discarded;
777
+
778
+ // Check
779
+ discarded = History.discardedStates[StateHash]||false;
780
+
781
+ // Return true
782
+ return discarded;
783
+ };
784
+
785
+ /**
786
+ * History.discardedHash(hash)
787
+ * Checks to see if the state is discarded
788
+ * @param {string} State
789
+ * @return {bool}
790
+ */
791
+ History.discardedHash = function(hash){
792
+ // Check
793
+ var discarded = History.discardedHashes[hash]||false;
794
+
795
+ // Return true
796
+ return discarded;
797
+ };
798
+
799
+ /**
800
+ * History.recycleState(State)
801
+ * Allows a discarded state to be used again
802
+ * @param {object} data
803
+ * @param {string} title
804
+ * @param {string} url
805
+ * @return {true}
806
+ */
807
+ History.recycleState = function(State){
808
+ //History.debug('History.recycleState', arguments);
809
+ // Prepare
810
+ var StateHash = History.getHashByState(State);
811
+
812
+ // Remove from DiscardedStates
813
+ if ( History.discardedState(State) ) {
814
+ delete History.discardedStates[StateHash];
815
+ }
816
+
817
+ // Return true
818
+ return true;
819
+ };
820
+
821
+
822
+ // ====================================================================
823
+ // HTML4 HashChange Support
824
+
825
+ if ( History.emulated.hashChange ) {
826
+ /*
827
+ * We must emulate the HTML4 HashChange Support by manually checking for hash changes
828
+ */
829
+
830
+ /**
831
+ * History.hashChangeInit()
832
+ * Init the HashChange Emulation
833
+ */
834
+ History.hashChangeInit = function(){
835
+ // Define our Checker Function
836
+ History.checkerFunction = null;
837
+
838
+ // Define some variables that will help in our checker function
839
+ var lastDocumentHash = '',
840
+ iframeId, iframe,
841
+ lastIframeHash, checkerRunning,
842
+ startedWithHash = Boolean(History.getHash());
843
+
844
+ // Handle depending on the browser
845
+ if ( History.isInternetExplorer() ) {
846
+ // IE6 and IE7
847
+ // We need to use an iframe to emulate the back and forward buttons
848
+
849
+ // Create iFrame
850
+ iframeId = 'historyjs-iframe';
851
+ iframe = document.createElement('iframe');
852
+
853
+ // Adjust iFarme
854
+ // IE 6 requires iframe to have a src on HTTPS pages, otherwise it will throw a
855
+ // "This page contains both secure and nonsecure items" warning.
856
+ iframe.setAttribute('id', iframeId);
857
+ iframe.setAttribute('src', '#');
858
+ iframe.style.display = 'none';
859
+
860
+ // Append iFrame
861
+ document.body.appendChild(iframe);
862
+
863
+ // Create initial history entry
864
+ iframe.contentWindow.document.open();
865
+ iframe.contentWindow.document.close();
866
+
867
+ // Define some variables that will help in our checker function
868
+ lastIframeHash = '';
869
+ checkerRunning = false;
870
+
871
+ // Define the checker function
872
+ History.checkerFunction = function(){
873
+ // Check Running
874
+ if ( checkerRunning ) {
875
+ return false;
876
+ }
877
+
878
+ // Update Running
879
+ checkerRunning = true;
880
+
881
+ // Fetch
882
+ var
883
+ documentHash = History.getHash(),
884
+ iframeHash = History.getHash(iframe.contentWindow.document);
885
+
886
+ // The Document Hash has changed (application caused)
887
+ if ( documentHash !== lastDocumentHash ) {
888
+ // Equalise
889
+ lastDocumentHash = documentHash;
890
+
891
+ // Create a history entry in the iframe
892
+ if ( iframeHash !== documentHash ) {
893
+ //History.debug('hashchange.checker: iframe hash change', 'documentHash (new):', documentHash, 'iframeHash (old):', iframeHash);
894
+
895
+ // Equalise
896
+ lastIframeHash = iframeHash = documentHash;
897
+
898
+ // Create History Entry
899
+ iframe.contentWindow.document.open();
900
+ iframe.contentWindow.document.close();
901
+
902
+ // Update the iframe's hash
903
+ iframe.contentWindow.document.location.hash = History.escapeHash(documentHash);
904
+ }
905
+
906
+ // Trigger Hashchange Event
907
+ History.Adapter.trigger(window,'hashchange');
908
+ }
909
+
910
+ // The iFrame Hash has changed (back button caused)
911
+ else if ( iframeHash !== lastIframeHash ) {
912
+ //History.debug('hashchange.checker: iframe hash out of sync', 'iframeHash (new):', iframeHash, 'documentHash (old):', documentHash);
913
+
914
+ // Equalise
915
+ lastIframeHash = iframeHash;
916
+
917
+ // If there is no iframe hash that means we're at the original
918
+ // iframe state.
919
+ // And if there was a hash on the original request, the original
920
+ // iframe state was replaced instantly, so skip this state and take
921
+ // the user back to where they came from.
922
+ if (startedWithHash && iframeHash === '') {
923
+ History.back();
924
+ }
925
+ else {
926
+ // Update the Hash
927
+ History.setHash(iframeHash,false);
928
+ }
929
+ }
930
+
931
+ // Reset Running
932
+ checkerRunning = false;
933
+
934
+ // Return true
935
+ return true;
936
+ };
937
+ }
938
+ else {
939
+ // We are not IE
940
+ // Firefox 1 or 2, Opera
941
+
942
+ // Define the checker function
943
+ History.checkerFunction = function(){
944
+ // Prepare
945
+ var documentHash = History.getHash()||'';
946
+
947
+ // The Document Hash has changed (application caused)
948
+ if ( documentHash !== lastDocumentHash ) {
949
+ // Equalise
950
+ lastDocumentHash = documentHash;
951
+
952
+ // Trigger Hashchange Event
953
+ History.Adapter.trigger(window,'hashchange');
954
+ }
955
+
956
+ // Return true
957
+ return true;
958
+ };
959
+ }
960
+
961
+ // Apply the checker function
962
+ History.intervalList.push(setInterval(History.checkerFunction, History.options.hashChangeInterval));
963
+
964
+ // Done
965
+ return true;
966
+ }; // History.hashChangeInit
967
+
968
+ // Bind hashChangeInit
969
+ History.Adapter.onDomLoad(History.hashChangeInit);
970
+
971
+ } // History.emulated.hashChange
972
+
973
+
974
+ // ====================================================================
975
+ // HTML5 State Support
976
+
977
+ // Non-Native pushState Implementation
978
+ if ( History.emulated.pushState ) {
979
+ /*
980
+ * We must emulate the HTML5 State Management by using HTML4 HashChange
981
+ */
982
+
983
+ /**
984
+ * History.onHashChange(event)
985
+ * Trigger HTML5's window.onpopstate via HTML4 HashChange Support
986
+ */
987
+ History.onHashChange = function(event){
988
+ //History.debug('History.onHashChange', arguments);
989
+
990
+ // Prepare
991
+ var currentUrl = ((event && event.newURL) || History.getLocationHref()),
992
+ currentHash = History.getHashByUrl(currentUrl),
993
+ currentState = null,
994
+ currentStateHash = null,
995
+ currentStateHashExits = null,
996
+ discardObject;
997
+
998
+ // Check if we are the same state
999
+ if ( History.isLastHash(currentHash) ) {
1000
+ // There has been no change (just the page's hash has finally propagated)
1001
+ //History.debug('History.onHashChange: no change');
1002
+ History.busy(false);
1003
+ return false;
1004
+ }
1005
+
1006
+ // Reset the double check
1007
+ History.doubleCheckComplete();
1008
+
1009
+ // Store our location for use in detecting back/forward direction
1010
+ History.saveHash(currentHash);
1011
+
1012
+ // Expand Hash
1013
+ if ( currentHash && History.isTraditionalAnchor(currentHash) ) {
1014
+ //History.debug('History.onHashChange: traditional anchor', currentHash);
1015
+ // Traditional Anchor Hash
1016
+ History.Adapter.trigger(window,'anchorchange');
1017
+ History.busy(false);
1018
+ return false;
1019
+ }
1020
+
1021
+ // Create State
1022
+ currentState = History.extractState(History.getFullUrl(currentHash||History.getLocationHref()),true);
1023
+
1024
+ // Check if we are the same state
1025
+ if ( History.isLastSavedState(currentState) ) {
1026
+ //History.debug('History.onHashChange: no change');
1027
+ // There has been no change (just the page's hash has finally propagated)
1028
+ History.busy(false);
1029
+ return false;
1030
+ }
1031
+
1032
+ // Create the state Hash
1033
+ currentStateHash = History.getHashByState(currentState);
1034
+
1035
+ // Check if we are DiscardedState
1036
+ discardObject = History.discardedState(currentState);
1037
+ if ( discardObject ) {
1038
+ // Ignore this state as it has been discarded and go back to the state before it
1039
+ if ( History.getHashByIndex(-2) === History.getHashByState(discardObject.forwardState) ) {
1040
+ // We are going backwards
1041
+ //History.debug('History.onHashChange: go backwards');
1042
+ History.back(false);
1043
+ } else {
1044
+ // We are going forwards
1045
+ //History.debug('History.onHashChange: go forwards');
1046
+ History.forward(false);
1047
+ }
1048
+ return false;
1049
+ }
1050
+
1051
+ // Push the new HTML5 State
1052
+ //History.debug('History.onHashChange: success hashchange');
1053
+ History.pushState(currentState.data,currentState.title,encodeURI(currentState.url),false);
1054
+
1055
+ // End onHashChange closure
1056
+ return true;
1057
+ };
1058
+ History.Adapter.bind(window,'hashchange',History.onHashChange);
1059
+
1060
+ /**
1061
+ * History.pushState(data,title,url)
1062
+ * Add a new State to the history object, become it, and trigger onpopstate
1063
+ * We have to trigger for HTML4 compatibility
1064
+ * @param {object} data
1065
+ * @param {string} title
1066
+ * @param {string} url
1067
+ * @return {true}
1068
+ */
1069
+ History.pushState = function(data,title,url,queue){
1070
+ //History.debug('History.pushState: called', arguments);
1071
+
1072
+ // We assume that the URL passed in is URI-encoded, but this makes
1073
+ // sure that it's fully URI encoded; any '%'s that are encoded are
1074
+ // converted back into '%'s
1075
+ url = encodeURI(url).replace(/%25/g, "%");
1076
+
1077
+ // Check the State
1078
+ if ( History.getHashByUrl(url) ) {
1079
+ throw new Error('History.js does not support states with fragment-identifiers (hashes/anchors).');
1080
+ }
1081
+
1082
+ // Handle Queueing
1083
+ if ( queue !== false && History.busy() ) {
1084
+ // Wait + Push to Queue
1085
+ //History.debug('History.pushState: we must wait', arguments);
1086
+ History.pushQueue({
1087
+ scope: History,
1088
+ callback: History.pushState,
1089
+ args: arguments,
1090
+ queue: queue
1091
+ });
1092
+ return false;
1093
+ }
1094
+
1095
+ // Make Busy
1096
+ History.busy(true);
1097
+
1098
+ // Fetch the State Object
1099
+ var newState = History.createStateObject(data,title,url),
1100
+ newStateHash = History.getHashByState(newState),
1101
+ oldState = History.getState(false),
1102
+ oldStateHash = History.getHashByState(oldState),
1103
+ html4Hash = History.getHash(),
1104
+ wasExpected = History.expectedStateId == newState.id;
1105
+
1106
+ // Store the newState
1107
+ History.storeState(newState);
1108
+ History.expectedStateId = newState.id;
1109
+
1110
+ // Recycle the State
1111
+ History.recycleState(newState);
1112
+
1113
+ // Force update of the title
1114
+ History.setTitle(newState);
1115
+
1116
+ // Check if we are the same State
1117
+ if ( newStateHash === oldStateHash ) {
1118
+ //History.debug('History.pushState: no change', newStateHash);
1119
+ History.busy(false);
1120
+ return false;
1121
+ }
1122
+
1123
+ // Update HTML5 State
1124
+ History.saveState(newState);
1125
+
1126
+ // Fire HTML5 Event
1127
+ if(!wasExpected)
1128
+ History.Adapter.trigger(window,'statechange');
1129
+
1130
+ // Update HTML4 Hash
1131
+ if ( !History.isHashEqual(newStateHash, html4Hash) && !History.isHashEqual(newStateHash, History.getShortUrl(History.getLocationHref())) ) {
1132
+ History.setHash(newStateHash,false);
1133
+ }
1134
+
1135
+ History.busy(false);
1136
+
1137
+ // End pushState closure
1138
+ return true;
1139
+ };
1140
+
1141
+ /**
1142
+ * History.replaceState(data,title,url)
1143
+ * Replace the State and trigger onpopstate
1144
+ * We have to trigger for HTML4 compatibility
1145
+ * @param {object} data
1146
+ * @param {string} title
1147
+ * @param {string} url
1148
+ * @return {true}
1149
+ */
1150
+ History.replaceState = function(data,title,url,queue){
1151
+ //History.debug('History.replaceState: called', arguments);
1152
+
1153
+ // We assume that the URL passed in is URI-encoded, but this makes
1154
+ // sure that it's fully URI encoded; any '%'s that are encoded are
1155
+ // converted back into '%'s
1156
+ url = encodeURI(url).replace(/%25/g, "%");
1157
+
1158
+ // Check the State
1159
+ if ( History.getHashByUrl(url) ) {
1160
+ throw new Error('History.js does not support states with fragment-identifiers (hashes/anchors).');
1161
+ }
1162
+
1163
+ // Handle Queueing
1164
+ if ( queue !== false && History.busy() ) {
1165
+ // Wait + Push to Queue
1166
+ //History.debug('History.replaceState: we must wait', arguments);
1167
+ History.pushQueue({
1168
+ scope: History,
1169
+ callback: History.replaceState,
1170
+ args: arguments,
1171
+ queue: queue
1172
+ });
1173
+ return false;
1174
+ }
1175
+
1176
+ // Make Busy
1177
+ History.busy(true);
1178
+
1179
+ // Fetch the State Objects
1180
+ var newState = History.createStateObject(data,title,url),
1181
+ newStateHash = History.getHashByState(newState),
1182
+ oldState = History.getState(false),
1183
+ oldStateHash = History.getHashByState(oldState),
1184
+ previousState = History.getStateByIndex(-2);
1185
+
1186
+ // Discard Old State
1187
+ History.discardState(oldState,newState,previousState);
1188
+
1189
+ // If the url hasn't changed, just store and save the state
1190
+ // and fire a statechange event to be consistent with the
1191
+ // html 5 api
1192
+ if ( newStateHash === oldStateHash ) {
1193
+ // Store the newState
1194
+ History.storeState(newState);
1195
+ History.expectedStateId = newState.id;
1196
+
1197
+ // Recycle the State
1198
+ History.recycleState(newState);
1199
+
1200
+ // Force update of the title
1201
+ History.setTitle(newState);
1202
+
1203
+ // Update HTML5 State
1204
+ History.saveState(newState);
1205
+
1206
+ // Fire HTML5 Event
1207
+ //History.debug('History.pushState: trigger popstate');
1208
+ History.Adapter.trigger(window,'statechange');
1209
+ History.busy(false);
1210
+ }
1211
+ else {
1212
+ // Alias to PushState
1213
+ History.pushState(newState.data,newState.title,newState.url,false);
1214
+ }
1215
+
1216
+ // End replaceState closure
1217
+ return true;
1218
+ };
1219
+
1220
+ } // History.emulated.pushState
1221
+
1222
+
1223
+
1224
+ // ====================================================================
1225
+ // Initialise
1226
+
1227
+ // Non-Native pushState Implementation
1228
+ if ( History.emulated.pushState ) {
1229
+ /**
1230
+ * Ensure initial state is handled correctly
1231
+ */
1232
+ if ( History.getHash() && !History.emulated.hashChange ) {
1233
+ History.Adapter.onDomLoad(function(){
1234
+ History.Adapter.trigger(window,'hashchange');
1235
+ });
1236
+ }
1237
+
1238
+ } // History.emulated.pushState
1239
+
1240
+ }; // History.initHtml4
1241
+
1242
+ // Try to Initialise History
1243
+ if ( typeof History.init !== 'undefined' ) {
1244
+ History.init();
1245
+ }
1246
+
1247
+ })(window);
1248
+ /**
1249
+ * History.js Core
1250
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
1251
+ * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
1252
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
1253
+ */
1254
+
1255
+ (function(window,undefined){
1256
+ "use strict";
1257
+
1258
+ // ========================================================================
1259
+ // Initialise
1260
+
1261
+ // Localise Globals
1262
+ var
1263
+ console = window.console||undefined, // Prevent a JSLint complain
1264
+ document = window.document, // Make sure we are using the correct document
1265
+ navigator = window.navigator, // Make sure we are using the correct navigator
1266
+ sessionStorage = false, // sessionStorage
1267
+ setTimeout = window.setTimeout,
1268
+ clearTimeout = window.clearTimeout,
1269
+ setInterval = window.setInterval,
1270
+ clearInterval = window.clearInterval,
1271
+ JSON = window.JSON,
1272
+ alert = window.alert,
1273
+ History = window.History = window.History||{}, // Public History Object
1274
+ history = window.history; // Old History Object
1275
+
1276
+ try {
1277
+ sessionStorage = window.sessionStorage; // This will throw an exception in some browsers when cookies/localStorage are explicitly disabled (i.e. Chrome)
1278
+ sessionStorage.setItem('TEST', '1');
1279
+ sessionStorage.removeItem('TEST');
1280
+ } catch(e) {
1281
+ sessionStorage = false;
1282
+ }
1283
+
1284
+ // MooTools Compatibility
1285
+ JSON.stringify = JSON.stringify||JSON.encode;
1286
+ JSON.parse = JSON.parse||JSON.decode;
1287
+
1288
+ // Check Existence
1289
+ if ( typeof History.init !== 'undefined' ) {
1290
+ throw new Error('History.js Core has already been loaded...');
1291
+ }
1292
+
1293
+ // Initialise History
1294
+ History.init = function(options){
1295
+ // Check Load Status of Adapter
1296
+ if ( typeof History.Adapter === 'undefined' ) {
1297
+ return false;
1298
+ }
1299
+
1300
+ // Check Load Status of Core
1301
+ if ( typeof History.initCore !== 'undefined' ) {
1302
+ History.initCore();
1303
+ }
1304
+
1305
+ // Check Load Status of HTML4 Support
1306
+ if ( typeof History.initHtml4 !== 'undefined' ) {
1307
+ History.initHtml4();
1308
+ }
1309
+
1310
+ // Return true
1311
+ return true;
1312
+ };
1313
+
1314
+
1315
+ // ========================================================================
1316
+ // Initialise Core
1317
+
1318
+ // Initialise Core
1319
+ History.initCore = function(options){
1320
+ // Initialise
1321
+ if ( typeof History.initCore.initialized !== 'undefined' ) {
1322
+ // Already Loaded
1323
+ return false;
1324
+ }
1325
+ else {
1326
+ History.initCore.initialized = true;
1327
+ }
1328
+
1329
+
1330
+ // ====================================================================
1331
+ // Options
1332
+
1333
+ /**
1334
+ * History.options
1335
+ * Configurable options
1336
+ */
1337
+ History.options = History.options||{};
1338
+
1339
+ /**
1340
+ * History.options.hashChangeInterval
1341
+ * How long should the interval be before hashchange checks
1342
+ */
1343
+ History.options.hashChangeInterval = History.options.hashChangeInterval || 100;
1344
+
1345
+ /**
1346
+ * History.options.safariPollInterval
1347
+ * How long should the interval be before safari poll checks
1348
+ */
1349
+ History.options.safariPollInterval = History.options.safariPollInterval || 500;
1350
+
1351
+ /**
1352
+ * History.options.doubleCheckInterval
1353
+ * How long should the interval be before we perform a double check
1354
+ */
1355
+ History.options.doubleCheckInterval = History.options.doubleCheckInterval || 500;
1356
+
1357
+ /**
1358
+ * History.options.disableSuid
1359
+ * Force History not to append suid
1360
+ */
1361
+ History.options.disableSuid = History.options.disableSuid || false;
1362
+
1363
+ /**
1364
+ * History.options.storeInterval
1365
+ * How long should we wait between store calls
1366
+ */
1367
+ History.options.storeInterval = History.options.storeInterval || 1000;
1368
+
1369
+ /**
1370
+ * History.options.busyDelay
1371
+ * How long should we wait between busy events
1372
+ */
1373
+ History.options.busyDelay = History.options.busyDelay || 250;
1374
+
1375
+ /**
1376
+ * History.options.debug
1377
+ * If true will enable debug messages to be logged
1378
+ */
1379
+ History.options.debug = History.options.debug || false;
1380
+
1381
+ /**
1382
+ * History.options.initialTitle
1383
+ * What is the title of the initial state
1384
+ */
1385
+ History.options.initialTitle = History.options.initialTitle || document.title;
1386
+
1387
+ /**
1388
+ * History.options.html4Mode
1389
+ * If true, will force HTMl4 mode (hashtags)
1390
+ */
1391
+ History.options.html4Mode = History.options.html4Mode || false;
1392
+
1393
+ /**
1394
+ * History.options.delayInit
1395
+ * Want to override default options and call init manually.
1396
+ */
1397
+ History.options.delayInit = History.options.delayInit || false;
1398
+
1399
+
1400
+ // ====================================================================
1401
+ // Interval record
1402
+
1403
+ /**
1404
+ * History.intervalList
1405
+ * List of intervals set, to be cleared when document is unloaded.
1406
+ */
1407
+ History.intervalList = [];
1408
+
1409
+ /**
1410
+ * History.clearAllIntervals
1411
+ * Clears all setInterval instances.
1412
+ */
1413
+ History.clearAllIntervals = function(){
1414
+ var i, il = History.intervalList;
1415
+ if (typeof il !== "undefined" && il !== null) {
1416
+ for (i = 0; i < il.length; i++) {
1417
+ clearInterval(il[i]);
1418
+ }
1419
+ History.intervalList = null;
1420
+ }
1421
+ };
1422
+
1423
+
1424
+ // ====================================================================
1425
+ // Debug
1426
+
1427
+ /**
1428
+ * History.debug(message,...)
1429
+ * Logs the passed arguments if debug enabled
1430
+ */
1431
+ History.debug = function(){
1432
+ if ( (History.options.debug||false) ) {
1433
+ History.log.apply(History,arguments);
1434
+ }
1435
+ };
1436
+
1437
+ /**
1438
+ * History.log(message,...)
1439
+ * Logs the passed arguments
1440
+ */
1441
+ History.log = function(){
1442
+ // Prepare
1443
+ var
1444
+ consoleExists = !(typeof console === 'undefined' || typeof console.log === 'undefined' || typeof console.log.apply === 'undefined'),
1445
+ textarea = document.getElementById('log'),
1446
+ message,
1447
+ i,n,
1448
+ args,arg
1449
+ ;
1450
+
1451
+ // Write to Console
1452
+ if ( consoleExists ) {
1453
+ args = Array.prototype.slice.call(arguments);
1454
+ message = args.shift();
1455
+ if ( typeof console.debug !== 'undefined' ) {
1456
+ console.debug.apply(console,[message,args]);
1457
+ }
1458
+ else {
1459
+ console.log.apply(console,[message,args]);
1460
+ }
1461
+ }
1462
+ else {
1463
+ message = ("\n"+arguments[0]+"\n");
1464
+ }
1465
+
1466
+ // Write to log
1467
+ for ( i=1,n=arguments.length; i<n; ++i ) {
1468
+ arg = arguments[i];
1469
+ if ( typeof arg === 'object' && typeof JSON !== 'undefined' ) {
1470
+ try {
1471
+ arg = JSON.stringify(arg);
1472
+ }
1473
+ catch ( Exception ) {
1474
+ // Recursive Object
1475
+ }
1476
+ }
1477
+ message += "\n"+arg+"\n";
1478
+ }
1479
+
1480
+ // Textarea
1481
+ if ( textarea ) {
1482
+ textarea.value += message+"\n-----\n";
1483
+ textarea.scrollTop = textarea.scrollHeight - textarea.clientHeight;
1484
+ }
1485
+ // No Textarea, No Console
1486
+ else if ( !consoleExists ) {
1487
+ alert(message);
1488
+ }
1489
+
1490
+ // Return true
1491
+ return true;
1492
+ };
1493
+
1494
+
1495
+ // ====================================================================
1496
+ // Emulated Status
1497
+
1498
+ /**
1499
+ * History.getInternetExplorerMajorVersion()
1500
+ * Get's the major version of Internet Explorer
1501
+ * @return {integer}
1502
+ * @license Public Domain
1503
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
1504
+ * @author James Padolsey <https://gist.github.com/527683>
1505
+ */
1506
+ History.getInternetExplorerMajorVersion = function(){
1507
+ var result = History.getInternetExplorerMajorVersion.cached =
1508
+ (typeof History.getInternetExplorerMajorVersion.cached !== 'undefined')
1509
+ ? History.getInternetExplorerMajorVersion.cached
1510
+ : (function(){
1511
+ var v = 3,
1512
+ div = document.createElement('div'),
1513
+ all = div.getElementsByTagName('i');
1514
+ while ( (div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->') && all[0] ) {}
1515
+ return (v > 4) ? v : false;
1516
+ })()
1517
+ ;
1518
+ return result;
1519
+ };
1520
+
1521
+ /**
1522
+ * History.isInternetExplorer()
1523
+ * Are we using Internet Explorer?
1524
+ * @return {boolean}
1525
+ * @license Public Domain
1526
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
1527
+ */
1528
+ History.isInternetExplorer = function(){
1529
+ var result =
1530
+ History.isInternetExplorer.cached =
1531
+ (typeof History.isInternetExplorer.cached !== 'undefined')
1532
+ ? History.isInternetExplorer.cached
1533
+ : Boolean(History.getInternetExplorerMajorVersion())
1534
+ ;
1535
+ return result;
1536
+ };
1537
+
1538
+ /**
1539
+ * History.emulated
1540
+ * Which features require emulating?
1541
+ */
1542
+
1543
+ if (History.options.html4Mode) {
1544
+ History.emulated = {
1545
+ pushState : true,
1546
+ hashChange: true
1547
+ };
1548
+ }
1549
+
1550
+ else {
1551
+
1552
+ History.emulated = {
1553
+ pushState: !Boolean(
1554
+ window.history && window.history.pushState && window.history.replaceState
1555
+ && !(
1556
+ (/ 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) */
1557
+ || (/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 */
1558
+ )
1559
+ ),
1560
+ hashChange: Boolean(
1561
+ !(('onhashchange' in window) || ('onhashchange' in document))
1562
+ ||
1563
+ (History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8)
1564
+ )
1565
+ };
1566
+ }
1567
+
1568
+ /**
1569
+ * History.enabled
1570
+ * Is History enabled?
1571
+ */
1572
+ History.enabled = !History.emulated.pushState;
1573
+
1574
+ /**
1575
+ * History.bugs
1576
+ * Which bugs are present
1577
+ */
1578
+ History.bugs = {
1579
+ /**
1580
+ * Safari 5 and Safari iOS 4 fail to return to the correct state once a hash is replaced by a `replaceState` call
1581
+ * https://bugs.webkit.org/show_bug.cgi?id=56249
1582
+ */
1583
+ setHash: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
1584
+
1585
+ /**
1586
+ * Safari 5 and Safari iOS 4 sometimes fail to apply the state change under busy conditions
1587
+ * https://bugs.webkit.org/show_bug.cgi?id=42940
1588
+ */
1589
+ safariPoll: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
1590
+
1591
+ /**
1592
+ * MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function)
1593
+ */
1594
+ ieDoubleCheck: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8),
1595
+
1596
+ /**
1597
+ * MSIE 6 requires the entire hash to be encoded for the hashes to trigger the onHashChange event
1598
+ */
1599
+ hashEscape: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 7)
1600
+ };
1601
+
1602
+ /**
1603
+ * History.isEmptyObject(obj)
1604
+ * Checks to see if the Object is Empty
1605
+ * @param {Object} obj
1606
+ * @return {boolean}
1607
+ */
1608
+ History.isEmptyObject = function(obj) {
1609
+ for ( var name in obj ) {
1610
+ if ( obj.hasOwnProperty(name) ) {
1611
+ return false;
1612
+ }
1613
+ }
1614
+ return true;
1615
+ };
1616
+
1617
+ /**
1618
+ * History.cloneObject(obj)
1619
+ * Clones a object and eliminate all references to the original contexts
1620
+ * @param {Object} obj
1621
+ * @return {Object}
1622
+ */
1623
+ History.cloneObject = function(obj) {
1624
+ var hash,newObj;
1625
+ if ( obj ) {
1626
+ hash = JSON.stringify(obj);
1627
+ newObj = JSON.parse(hash);
1628
+ }
1629
+ else {
1630
+ newObj = {};
1631
+ }
1632
+ return newObj;
1633
+ };
1634
+
1635
+
1636
+ // ====================================================================
1637
+ // URL Helpers
1638
+
1639
+ /**
1640
+ * History.getRootUrl()
1641
+ * Turns "http://mysite.com/dir/page.html?asd" into "http://mysite.com"
1642
+ * @return {String} rootUrl
1643
+ */
1644
+ History.getRootUrl = function(){
1645
+ // Create
1646
+ var rootUrl = document.location.protocol+'//'+(document.location.hostname||document.location.host);
1647
+ if ( document.location.port||false ) {
1648
+ rootUrl += ':'+document.location.port;
1649
+ }
1650
+ rootUrl += '/';
1651
+
1652
+ // Return
1653
+ return rootUrl;
1654
+ };
1655
+
1656
+ /**
1657
+ * History.getBaseHref()
1658
+ * Fetches the `href` attribute of the `<base href="...">` element if it exists
1659
+ * @return {String} baseHref
1660
+ */
1661
+ History.getBaseHref = function(){
1662
+ // Create
1663
+ var
1664
+ baseElements = document.getElementsByTagName('base'),
1665
+ baseElement = null,
1666
+ baseHref = '';
1667
+
1668
+ // Test for Base Element
1669
+ if ( baseElements.length === 1 ) {
1670
+ // Prepare for Base Element
1671
+ baseElement = baseElements[0];
1672
+ baseHref = baseElement.href.replace(/[^\/]+$/,'');
1673
+ }
1674
+
1675
+ // Adjust trailing slash
1676
+ baseHref = baseHref.replace(/\/+$/,'');
1677
+ if ( baseHref ) baseHref += '/';
1678
+
1679
+ // Return
1680
+ return baseHref;
1681
+ };
1682
+
1683
+ /**
1684
+ * History.getBaseUrl()
1685
+ * Fetches the baseHref or basePageUrl or rootUrl (whichever one exists first)
1686
+ * @return {String} baseUrl
1687
+ */
1688
+ History.getBaseUrl = function(){
1689
+ // Create
1690
+ var baseUrl = History.getBaseHref()||History.getBasePageUrl()||History.getRootUrl();
1691
+
1692
+ // Return
1693
+ return baseUrl;
1694
+ };
1695
+
1696
+ /**
1697
+ * History.getPageUrl()
1698
+ * Fetches the URL of the current page
1699
+ * @return {String} pageUrl
1700
+ */
1701
+ History.getPageUrl = function(){
1702
+ // Fetch
1703
+ var
1704
+ State = History.getState(false,false),
1705
+ stateUrl = (State||{}).url||History.getLocationHref(),
1706
+ pageUrl;
1707
+
1708
+ // Create
1709
+ pageUrl = stateUrl.replace(/\/+$/,'').replace(/[^\/]+$/,function(part,index,string){
1710
+ return (/\./).test(part) ? part : part+'/';
1711
+ });
1712
+
1713
+ // Return
1714
+ return pageUrl;
1715
+ };
1716
+
1717
+ /**
1718
+ * History.getBasePageUrl()
1719
+ * Fetches the Url of the directory of the current page
1720
+ * @return {String} basePageUrl
1721
+ */
1722
+ History.getBasePageUrl = function(){
1723
+ // Create
1724
+ var basePageUrl = (History.getLocationHref()).replace(/[#\?].*/,'').replace(/[^\/]+$/,function(part,index,string){
1725
+ return (/[^\/]$/).test(part) ? '' : part;
1726
+ }).replace(/\/+$/,'')+'/';
1727
+
1728
+ // Return
1729
+ return basePageUrl;
1730
+ };
1731
+
1732
+ /**
1733
+ * History.getFullUrl(url)
1734
+ * Ensures that we have an absolute URL and not a relative URL
1735
+ * @param {string} url
1736
+ * @param {Boolean} allowBaseHref
1737
+ * @return {string} fullUrl
1738
+ */
1739
+ History.getFullUrl = function(url,allowBaseHref){
1740
+ // Prepare
1741
+ var fullUrl = url, firstChar = url.substring(0,1);
1742
+ allowBaseHref = (typeof allowBaseHref === 'undefined') ? true : allowBaseHref;
1743
+
1744
+ // Check
1745
+ if ( /[a-z]+\:\/\//.test(url) ) {
1746
+ // Full URL
1747
+ }
1748
+ else if ( firstChar === '/' ) {
1749
+ // Root URL
1750
+ fullUrl = History.getRootUrl()+url.replace(/^\/+/,'');
1751
+ }
1752
+ else if ( firstChar === '#' ) {
1753
+ // Anchor URL
1754
+ fullUrl = History.getPageUrl().replace(/#.*/,'')+url;
1755
+ }
1756
+ else if ( firstChar === '?' ) {
1757
+ // Query URL
1758
+ fullUrl = History.getPageUrl().replace(/[\?#].*/,'')+url;
1759
+ }
1760
+ else {
1761
+ // Relative URL
1762
+ if ( allowBaseHref ) {
1763
+ fullUrl = History.getBaseUrl()+url.replace(/^(\.\/)+/,'');
1764
+ } else {
1765
+ fullUrl = History.getBasePageUrl()+url.replace(/^(\.\/)+/,'');
1766
+ }
1767
+ // We have an if condition above as we do not want hashes
1768
+ // which are relative to the baseHref in our URLs
1769
+ // as if the baseHref changes, then all our bookmarks
1770
+ // would now point to different locations
1771
+ // whereas the basePageUrl will always stay the same
1772
+ }
1773
+
1774
+ // Return
1775
+ return fullUrl.replace(/\#$/,'');
1776
+ };
1777
+
1778
+ /**
1779
+ * History.getShortUrl(url)
1780
+ * Ensures that we have a relative URL and not a absolute URL
1781
+ * @param {string} url
1782
+ * @return {string} url
1783
+ */
1784
+ History.getShortUrl = function(url){
1785
+ // Prepare
1786
+ var shortUrl = url, baseUrl = History.getBaseUrl(), rootUrl = History.getRootUrl();
1787
+
1788
+ // Trim baseUrl
1789
+ if ( History.emulated.pushState ) {
1790
+ // We are in a if statement as when pushState is not emulated
1791
+ // The actual url these short urls are relative to can change
1792
+ // So within the same session, we the url may end up somewhere different
1793
+ shortUrl = shortUrl.replace(baseUrl,'');
1794
+ }
1795
+
1796
+ // Trim rootUrl
1797
+ shortUrl = shortUrl.replace(rootUrl,'/');
1798
+
1799
+ // Ensure we can still detect it as a state
1800
+ if ( History.isTraditionalAnchor(shortUrl) ) {
1801
+ shortUrl = './'+shortUrl;
1802
+ }
1803
+
1804
+ // Clean It
1805
+ shortUrl = shortUrl.replace(/^(\.\/)+/g,'./').replace(/\#$/,'');
1806
+
1807
+ // Return
1808
+ return shortUrl;
1809
+ };
1810
+
1811
+ /**
1812
+ * History.getLocationHref(document)
1813
+ * Returns a normalized version of document.location.href
1814
+ * accounting for browser inconsistencies, etc.
1815
+ *
1816
+ * This URL will be URI-encoded and will include the hash
1817
+ *
1818
+ * @param {object} document
1819
+ * @return {string} url
1820
+ */
1821
+ History.getLocationHref = function(doc) {
1822
+ doc = doc || document;
1823
+
1824
+ // most of the time, this will be true
1825
+ if (doc.URL === doc.location.href)
1826
+ return doc.location.href;
1827
+
1828
+ // some versions of webkit URI-decode document.location.href
1829
+ // but they leave document.URL in an encoded state
1830
+ if (doc.location.href === decodeURIComponent(doc.URL))
1831
+ return doc.URL;
1832
+
1833
+ // FF 3.6 only updates document.URL when a page is reloaded
1834
+ // document.location.href is updated correctly
1835
+ if (doc.location.hash && decodeURIComponent(doc.location.href.replace(/^[^#]+/, "")) === doc.location.hash)
1836
+ return doc.location.href;
1837
+
1838
+ if (doc.URL.indexOf('#') == -1 && doc.location.href.indexOf('#') != -1)
1839
+ return doc.location.href;
1840
+
1841
+ return doc.URL || doc.location.href;
1842
+ };
1843
+
1844
+
1845
+ // ====================================================================
1846
+ // State Storage
1847
+
1848
+ /**
1849
+ * History.store
1850
+ * The store for all session specific data
1851
+ */
1852
+ History.store = {};
1853
+
1854
+ /**
1855
+ * History.idToState
1856
+ * 1-1: State ID to State Object
1857
+ */
1858
+ History.idToState = History.idToState||{};
1859
+
1860
+ /**
1861
+ * History.stateToId
1862
+ * 1-1: State String to State ID
1863
+ */
1864
+ History.stateToId = History.stateToId||{};
1865
+
1866
+ /**
1867
+ * History.urlToId
1868
+ * 1-1: State URL to State ID
1869
+ */
1870
+ History.urlToId = History.urlToId||{};
1871
+
1872
+ /**
1873
+ * History.storedStates
1874
+ * Store the states in an array
1875
+ */
1876
+ History.storedStates = History.storedStates||[];
1877
+
1878
+ /**
1879
+ * History.savedStates
1880
+ * Saved the states in an array
1881
+ */
1882
+ History.savedStates = History.savedStates||[];
1883
+
1884
+ /**
1885
+ * History.noramlizeStore()
1886
+ * Noramlize the store by adding necessary values
1887
+ */
1888
+ History.normalizeStore = function(){
1889
+ History.store.idToState = History.store.idToState||{};
1890
+ History.store.urlToId = History.store.urlToId||{};
1891
+ History.store.stateToId = History.store.stateToId||{};
1892
+ };
1893
+
1894
+ /**
1895
+ * History.getState()
1896
+ * Get an object containing the data, title and url of the current state
1897
+ * @param {Boolean} friendly
1898
+ * @param {Boolean} create
1899
+ * @return {Object} State
1900
+ */
1901
+ History.getState = function(friendly,create){
1902
+ // Prepare
1903
+ if ( typeof friendly === 'undefined' ) { friendly = true; }
1904
+ if ( typeof create === 'undefined' ) { create = true; }
1905
+
1906
+ // Fetch
1907
+ var State = History.getLastSavedState();
1908
+
1909
+ // Create
1910
+ if ( !State && create ) {
1911
+ State = History.createStateObject();
1912
+ }
1913
+
1914
+ // Adjust
1915
+ if ( friendly ) {
1916
+ State = History.cloneObject(State);
1917
+ State.url = State.cleanUrl||State.url;
1918
+ }
1919
+
1920
+ // Return
1921
+ return State;
1922
+ };
1923
+
1924
+ /**
1925
+ * History.getIdByState(State)
1926
+ * Gets a ID for a State
1927
+ * @param {State} newState
1928
+ * @return {String} id
1929
+ */
1930
+ History.getIdByState = function(newState){
1931
+
1932
+ // Fetch ID
1933
+ var id = History.extractId(newState.url),
1934
+ str;
1935
+
1936
+ if ( !id ) {
1937
+ // Find ID via State String
1938
+ str = History.getStateString(newState);
1939
+ if ( typeof History.stateToId[str] !== 'undefined' ) {
1940
+ id = History.stateToId[str];
1941
+ }
1942
+ else if ( typeof History.store.stateToId[str] !== 'undefined' ) {
1943
+ id = History.store.stateToId[str];
1944
+ }
1945
+ else {
1946
+ // Generate a new ID
1947
+ while ( true ) {
1948
+ id = (new Date()).getTime() + String(Math.random()).replace(/\D/g,'');
1949
+ if ( typeof History.idToState[id] === 'undefined' && typeof History.store.idToState[id] === 'undefined' ) {
1950
+ break;
1951
+ }
1952
+ }
1953
+
1954
+ // Apply the new State to the ID
1955
+ History.stateToId[str] = id;
1956
+ History.idToState[id] = newState;
1957
+ }
1958
+ }
1959
+
1960
+ // Return ID
1961
+ return id;
1962
+ };
1963
+
1964
+ /**
1965
+ * History.normalizeState(State)
1966
+ * Expands a State Object
1967
+ * @param {object} State
1968
+ * @return {object}
1969
+ */
1970
+ History.normalizeState = function(oldState){
1971
+ // Variables
1972
+ var newState, dataNotEmpty;
1973
+
1974
+ // Prepare
1975
+ if ( !oldState || (typeof oldState !== 'object') ) {
1976
+ oldState = {};
1977
+ }
1978
+
1979
+ // Check
1980
+ if ( typeof oldState.normalized !== 'undefined' ) {
1981
+ return oldState;
1982
+ }
1983
+
1984
+ // Adjust
1985
+ if ( !oldState.data || (typeof oldState.data !== 'object') ) {
1986
+ oldState.data = {};
1987
+ }
1988
+
1989
+ // ----------------------------------------------------------------
1990
+
1991
+ // Create
1992
+ newState = {};
1993
+ newState.normalized = true;
1994
+ newState.title = oldState.title||'';
1995
+ newState.url = History.getFullUrl(oldState.url?oldState.url:(History.getLocationHref()));
1996
+ newState.hash = History.getShortUrl(newState.url);
1997
+ newState.data = History.cloneObject(oldState.data);
1998
+
1999
+ // Fetch ID
2000
+ newState.id = History.getIdByState(newState);
2001
+
2002
+ // ----------------------------------------------------------------
2003
+
2004
+ // Clean the URL
2005
+ newState.cleanUrl = newState.url.replace(/\??\&_suid.*/,'');
2006
+ newState.url = newState.cleanUrl;
2007
+
2008
+ // Check to see if we have more than just a url
2009
+ dataNotEmpty = !History.isEmptyObject(newState.data);
2010
+
2011
+ // Apply
2012
+ if ( (newState.title || dataNotEmpty) && History.options.disableSuid !== true ) {
2013
+ // Add ID to Hash
2014
+ newState.hash = History.getShortUrl(newState.url).replace(/\??\&_suid.*/,'');
2015
+ if ( !/\?/.test(newState.hash) ) {
2016
+ newState.hash += '?';
2017
+ }
2018
+ newState.hash += '&_suid='+newState.id;
2019
+ }
2020
+
2021
+ // Create the Hashed URL
2022
+ newState.hashedUrl = History.getFullUrl(newState.hash);
2023
+
2024
+ // ----------------------------------------------------------------
2025
+
2026
+ // Update the URL if we have a duplicate
2027
+ if ( (History.emulated.pushState || History.bugs.safariPoll) && History.hasUrlDuplicate(newState) ) {
2028
+ newState.url = newState.hashedUrl;
2029
+ }
2030
+
2031
+ // ----------------------------------------------------------------
2032
+
2033
+ // Return
2034
+ return newState;
2035
+ };
2036
+
2037
+ /**
2038
+ * History.createStateObject(data,title,url)
2039
+ * Creates a object based on the data, title and url state params
2040
+ * @param {object} data
2041
+ * @param {string} title
2042
+ * @param {string} url
2043
+ * @return {object}
2044
+ */
2045
+ History.createStateObject = function(data,title,url){
2046
+ // Hashify
2047
+ var State = {
2048
+ 'data': data,
2049
+ 'title': title,
2050
+ 'url': url
2051
+ };
2052
+
2053
+ // Expand the State
2054
+ State = History.normalizeState(State);
2055
+
2056
+ // Return object
2057
+ return State;
2058
+ };
2059
+
2060
+ /**
2061
+ * History.getStateById(id)
2062
+ * Get a state by it's UID
2063
+ * @param {String} id
2064
+ */
2065
+ History.getStateById = function(id){
2066
+ // Prepare
2067
+ id = String(id);
2068
+
2069
+ // Retrieve
2070
+ var State = History.idToState[id] || History.store.idToState[id] || undefined;
2071
+
2072
+ // Return State
2073
+ return State;
2074
+ };
2075
+
2076
+ /**
2077
+ * Get a State's String
2078
+ * @param {State} passedState
2079
+ */
2080
+ History.getStateString = function(passedState){
2081
+ // Prepare
2082
+ var State, cleanedState, str;
2083
+
2084
+ // Fetch
2085
+ State = History.normalizeState(passedState);
2086
+
2087
+ // Clean
2088
+ cleanedState = {
2089
+ data: State.data,
2090
+ title: passedState.title,
2091
+ url: passedState.url
2092
+ };
2093
+
2094
+ // Fetch
2095
+ str = JSON.stringify(cleanedState);
2096
+
2097
+ // Return
2098
+ return str;
2099
+ };
2100
+
2101
+ /**
2102
+ * Get a State's ID
2103
+ * @param {State} passedState
2104
+ * @return {String} id
2105
+ */
2106
+ History.getStateId = function(passedState){
2107
+ // Prepare
2108
+ var State, id;
2109
+
2110
+ // Fetch
2111
+ State = History.normalizeState(passedState);
2112
+
2113
+ // Fetch
2114
+ id = State.id;
2115
+
2116
+ // Return
2117
+ return id;
2118
+ };
2119
+
2120
+ /**
2121
+ * History.getHashByState(State)
2122
+ * Creates a Hash for the State Object
2123
+ * @param {State} passedState
2124
+ * @return {String} hash
2125
+ */
2126
+ History.getHashByState = function(passedState){
2127
+ // Prepare
2128
+ var State, hash;
2129
+
2130
+ // Fetch
2131
+ State = History.normalizeState(passedState);
2132
+
2133
+ // Hash
2134
+ hash = State.hash;
2135
+
2136
+ // Return
2137
+ return hash;
2138
+ };
2139
+
2140
+ /**
2141
+ * History.extractId(url_or_hash)
2142
+ * Get a State ID by it's URL or Hash
2143
+ * @param {string} url_or_hash
2144
+ * @return {string} id
2145
+ */
2146
+ History.extractId = function ( url_or_hash ) {
2147
+ // Prepare
2148
+ var id,parts,url, tmp;
2149
+
2150
+ // Extract
2151
+
2152
+ // If the URL has a #, use the id from before the #
2153
+ if (url_or_hash.indexOf('#') != -1)
2154
+ {
2155
+ tmp = url_or_hash.split("#")[0];
2156
+ }
2157
+ else
2158
+ {
2159
+ tmp = url_or_hash;
2160
+ }
2161
+
2162
+ parts = /(.*)\&_suid=([0-9]+)$/.exec(tmp);
2163
+ url = parts ? (parts[1]||url_or_hash) : url_or_hash;
2164
+ id = parts ? String(parts[2]||'') : '';
2165
+
2166
+ // Return
2167
+ return id||false;
2168
+ };
2169
+
2170
+ /**
2171
+ * History.isTraditionalAnchor
2172
+ * Checks to see if the url is a traditional anchor or not
2173
+ * @param {String} url_or_hash
2174
+ * @return {Boolean}
2175
+ */
2176
+ History.isTraditionalAnchor = function(url_or_hash){
2177
+ // Check
2178
+ var isTraditional = !(/[\/\?\.]/.test(url_or_hash));
2179
+
2180
+ // Return
2181
+ return isTraditional;
2182
+ };
2183
+
2184
+ /**
2185
+ * History.extractState
2186
+ * Get a State by it's URL or Hash
2187
+ * @param {String} url_or_hash
2188
+ * @return {State|null}
2189
+ */
2190
+ History.extractState = function(url_or_hash,create){
2191
+ // Prepare
2192
+ var State = null, id, url;
2193
+ create = create||false;
2194
+
2195
+ // Fetch SUID
2196
+ id = History.extractId(url_or_hash);
2197
+ if ( id ) {
2198
+ State = History.getStateById(id);
2199
+ }
2200
+
2201
+ // Fetch SUID returned no State
2202
+ if ( !State ) {
2203
+ // Fetch URL
2204
+ url = History.getFullUrl(url_or_hash);
2205
+
2206
+ // Check URL
2207
+ id = History.getIdByUrl(url)||false;
2208
+ if ( id ) {
2209
+ State = History.getStateById(id);
2210
+ }
2211
+
2212
+ // Create State
2213
+ if ( !State && create && !History.isTraditionalAnchor(url_or_hash) ) {
2214
+ State = History.createStateObject(null,null,url);
2215
+ }
2216
+ }
2217
+
2218
+ // Return
2219
+ return State;
2220
+ };
2221
+
2222
+ /**
2223
+ * History.getIdByUrl()
2224
+ * Get a State ID by a State URL
2225
+ */
2226
+ History.getIdByUrl = function(url){
2227
+ // Fetch
2228
+ var id = History.urlToId[url] || History.store.urlToId[url] || undefined;
2229
+
2230
+ // Return
2231
+ return id;
2232
+ };
2233
+
2234
+ /**
2235
+ * History.getLastSavedState()
2236
+ * Get an object containing the data, title and url of the current state
2237
+ * @return {Object} State
2238
+ */
2239
+ History.getLastSavedState = function(){
2240
+ return History.savedStates[History.savedStates.length-1]||undefined;
2241
+ };
2242
+
2243
+ /**
2244
+ * History.getLastStoredState()
2245
+ * Get an object containing the data, title and url of the current state
2246
+ * @return {Object} State
2247
+ */
2248
+ History.getLastStoredState = function(){
2249
+ return History.storedStates[History.storedStates.length-1]||undefined;
2250
+ };
2251
+
2252
+ /**
2253
+ * History.hasUrlDuplicate
2254
+ * Checks if a Url will have a url conflict
2255
+ * @param {Object} newState
2256
+ * @return {Boolean} hasDuplicate
2257
+ */
2258
+ History.hasUrlDuplicate = function(newState) {
2259
+ // Prepare
2260
+ var hasDuplicate = false,
2261
+ oldState;
2262
+
2263
+ // Fetch
2264
+ oldState = History.extractState(newState.url);
2265
+
2266
+ // Check
2267
+ hasDuplicate = oldState && oldState.id !== newState.id;
2268
+
2269
+ // Return
2270
+ return hasDuplicate;
2271
+ };
2272
+
2273
+ /**
2274
+ * History.storeState
2275
+ * Store a State
2276
+ * @param {Object} newState
2277
+ * @return {Object} newState
2278
+ */
2279
+ History.storeState = function(newState){
2280
+ // Store the State
2281
+ History.urlToId[newState.url] = newState.id;
2282
+
2283
+ // Push the State
2284
+ History.storedStates.push(History.cloneObject(newState));
2285
+
2286
+ // Return newState
2287
+ return newState;
2288
+ };
2289
+
2290
+ /**
2291
+ * History.isLastSavedState(newState)
2292
+ * Tests to see if the state is the last state
2293
+ * @param {Object} newState
2294
+ * @return {boolean} isLast
2295
+ */
2296
+ History.isLastSavedState = function(newState){
2297
+ // Prepare
2298
+ var isLast = false,
2299
+ newId, oldState, oldId;
2300
+
2301
+ // Check
2302
+ if ( History.savedStates.length ) {
2303
+ newId = newState.id;
2304
+ oldState = History.getLastSavedState();
2305
+ oldId = oldState.id;
2306
+
2307
+ // Check
2308
+ isLast = (newId === oldId);
2309
+ }
2310
+
2311
+ // Return
2312
+ return isLast;
2313
+ };
2314
+
2315
+ /**
2316
+ * History.saveState
2317
+ * Push a State
2318
+ * @param {Object} newState
2319
+ * @return {boolean} changed
2320
+ */
2321
+ History.saveState = function(newState){
2322
+ // Check Hash
2323
+ if ( History.isLastSavedState(newState) ) {
2324
+ return false;
2325
+ }
2326
+
2327
+ // Push the State
2328
+ History.savedStates.push(History.cloneObject(newState));
2329
+
2330
+ // Return true
2331
+ return true;
2332
+ };
2333
+
2334
+ /**
2335
+ * History.getStateByIndex()
2336
+ * Gets a state by the index
2337
+ * @param {integer} index
2338
+ * @return {Object}
2339
+ */
2340
+ History.getStateByIndex = function(index){
2341
+ // Prepare
2342
+ var State = null;
2343
+
2344
+ // Handle
2345
+ if ( typeof index === 'undefined' ) {
2346
+ // Get the last inserted
2347
+ State = History.savedStates[History.savedStates.length-1];
2348
+ }
2349
+ else if ( index < 0 ) {
2350
+ // Get from the end
2351
+ State = History.savedStates[History.savedStates.length+index];
2352
+ }
2353
+ else {
2354
+ // Get from the beginning
2355
+ State = History.savedStates[index];
2356
+ }
2357
+
2358
+ // Return State
2359
+ return State;
2360
+ };
2361
+
2362
+ /**
2363
+ * History.getCurrentIndex()
2364
+ * Gets the current index
2365
+ * @return (integer)
2366
+ */
2367
+ History.getCurrentIndex = function(){
2368
+ // Prepare
2369
+ var index = null;
2370
+
2371
+ // No states saved
2372
+ if(History.savedStates.length < 1) {
2373
+ index = 0;
2374
+ }
2375
+ else {
2376
+ index = History.savedStates.length-1;
2377
+ }
2378
+ return index;
2379
+ };
2380
+
2381
+ // ====================================================================
2382
+ // Hash Helpers
2383
+
2384
+ /**
2385
+ * History.getHash()
2386
+ * @param {Location=} location
2387
+ * Gets the current document hash
2388
+ * Note: unlike location.hash, this is guaranteed to return the escaped hash in all browsers
2389
+ * @return {string}
2390
+ */
2391
+ History.getHash = function(doc){
2392
+ var url = History.getLocationHref(doc),
2393
+ hash;
2394
+ hash = History.getHashByUrl(url);
2395
+ return hash;
2396
+ };
2397
+
2398
+ /**
2399
+ * History.unescapeHash()
2400
+ * normalize and Unescape a Hash
2401
+ * @param {String} hash
2402
+ * @return {string}
2403
+ */
2404
+ History.unescapeHash = function(hash){
2405
+ // Prepare
2406
+ var result = History.normalizeHash(hash);
2407
+
2408
+ // Unescape hash
2409
+ result = decodeURIComponent(result);
2410
+
2411
+ // Return result
2412
+ return result;
2413
+ };
2414
+
2415
+ /**
2416
+ * History.normalizeHash()
2417
+ * normalize a hash across browsers
2418
+ * @return {string}
2419
+ */
2420
+ History.normalizeHash = function(hash){
2421
+ // Prepare
2422
+ var result = hash.replace(/[^#]*#/,'').replace(/#.*/, '');
2423
+
2424
+ // Return result
2425
+ return result;
2426
+ };
2427
+
2428
+ /**
2429
+ * History.setHash(hash)
2430
+ * Sets the document hash
2431
+ * @param {string} hash
2432
+ * @return {History}
2433
+ */
2434
+ History.setHash = function(hash,queue){
2435
+ // Prepare
2436
+ var State, pageUrl;
2437
+
2438
+ // Handle Queueing
2439
+ if ( queue !== false && History.busy() ) {
2440
+ // Wait + Push to Queue
2441
+ //History.debug('History.setHash: we must wait', arguments);
2442
+ History.pushQueue({
2443
+ scope: History,
2444
+ callback: History.setHash,
2445
+ args: arguments,
2446
+ queue: queue
2447
+ });
2448
+ return false;
2449
+ }
2450
+
2451
+ // Log
2452
+ //History.debug('History.setHash: called',hash);
2453
+
2454
+ // Make Busy + Continue
2455
+ History.busy(true);
2456
+
2457
+ // Check if hash is a state
2458
+ State = History.extractState(hash,true);
2459
+ if ( State && !History.emulated.pushState ) {
2460
+ // Hash is a state so skip the setHash
2461
+ //History.debug('History.setHash: Hash is a state so skipping the hash set with a direct pushState call',arguments);
2462
+
2463
+ // PushState
2464
+ History.pushState(State.data,State.title,State.url,false);
2465
+ }
2466
+ else if ( History.getHash() !== hash ) {
2467
+ // Hash is a proper hash, so apply it
2468
+
2469
+ // Handle browser bugs
2470
+ if ( History.bugs.setHash ) {
2471
+ // Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249
2472
+
2473
+ // Fetch the base page
2474
+ pageUrl = History.getPageUrl();
2475
+
2476
+ // Safari hash apply
2477
+ History.pushState(null,null,pageUrl+'#'+hash,false);
2478
+ }
2479
+ else {
2480
+ // Normal hash apply
2481
+ document.location.hash = hash;
2482
+ }
2483
+ }
2484
+
2485
+ // Chain
2486
+ return History;
2487
+ };
2488
+
2489
+ /**
2490
+ * History.escape()
2491
+ * normalize and Escape a Hash
2492
+ * @return {string}
2493
+ */
2494
+ History.escapeHash = function(hash){
2495
+ // Prepare
2496
+ var result = History.normalizeHash(hash);
2497
+
2498
+ // Escape hash
2499
+ result = window.encodeURIComponent(result);
2500
+
2501
+ // IE6 Escape Bug
2502
+ if ( !History.bugs.hashEscape ) {
2503
+ // Restore common parts
2504
+ result = result
2505
+ .replace(/\%21/g,'!')
2506
+ .replace(/\%26/g,'&')
2507
+ .replace(/\%3D/g,'=')
2508
+ .replace(/\%3F/g,'?');
2509
+ }
2510
+
2511
+ // Return result
2512
+ return result;
2513
+ };
2514
+
2515
+ /**
2516
+ * History.getHashByUrl(url)
2517
+ * Extracts the Hash from a URL
2518
+ * @param {string} url
2519
+ * @return {string} url
2520
+ */
2521
+ History.getHashByUrl = function(url){
2522
+ // Extract the hash
2523
+ var hash = String(url)
2524
+ .replace(/([^#]*)#?([^#]*)#?(.*)/, '$2')
2525
+ ;
2526
+
2527
+ // Unescape hash
2528
+ hash = History.unescapeHash(hash);
2529
+
2530
+ // Return hash
2531
+ return hash;
2532
+ };
2533
+
2534
+ /**
2535
+ * History.setTitle(title)
2536
+ * Applies the title to the document
2537
+ * @param {State} newState
2538
+ * @return {Boolean}
2539
+ */
2540
+ History.setTitle = function(newState){
2541
+ // Prepare
2542
+ var title = newState.title,
2543
+ firstState;
2544
+
2545
+ // Initial
2546
+ if ( !title ) {
2547
+ firstState = History.getStateByIndex(0);
2548
+ if ( firstState && firstState.url === newState.url ) {
2549
+ title = firstState.title||History.options.initialTitle;
2550
+ }
2551
+ }
2552
+
2553
+ // Apply
2554
+ try {
2555
+ document.getElementsByTagName('title')[0].innerHTML = title.replace('<','&lt;').replace('>','&gt;').replace(' & ',' &amp; ');
2556
+ }
2557
+ catch ( Exception ) { }
2558
+ document.title = title;
2559
+
2560
+ // Chain
2561
+ return History;
2562
+ };
2563
+
2564
+
2565
+ // ====================================================================
2566
+ // Queueing
2567
+
2568
+ /**
2569
+ * History.queues
2570
+ * The list of queues to use
2571
+ * First In, First Out
2572
+ */
2573
+ History.queues = [];
2574
+
2575
+ /**
2576
+ * History.busy(value)
2577
+ * @param {boolean} value [optional]
2578
+ * @return {boolean} busy
2579
+ */
2580
+ History.busy = function(value){
2581
+ // Apply
2582
+ if ( typeof value !== 'undefined' ) {
2583
+ //History.debug('History.busy: changing ['+(History.busy.flag||false)+'] to ['+(value||false)+']', History.queues.length);
2584
+ History.busy.flag = value;
2585
+ }
2586
+ // Default
2587
+ else if ( typeof History.busy.flag === 'undefined' ) {
2588
+ History.busy.flag = false;
2589
+ }
2590
+
2591
+ // Queue
2592
+ if ( !History.busy.flag ) {
2593
+ // Execute the next item in the queue
2594
+ clearTimeout(History.busy.timeout);
2595
+ var fireNext = function(){
2596
+ var i, queue, item;
2597
+ if ( History.busy.flag ) return;
2598
+ for ( i=History.queues.length-1; i >= 0; --i ) {
2599
+ queue = History.queues[i];
2600
+ if ( queue.length === 0 ) continue;
2601
+ item = queue.shift();
2602
+ History.fireQueueItem(item);
2603
+ History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
2604
+ }
2605
+ };
2606
+ History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
2607
+ }
2608
+
2609
+ // Return
2610
+ return History.busy.flag;
2611
+ };
2612
+
2613
+ /**
2614
+ * History.busy.flag
2615
+ */
2616
+ History.busy.flag = false;
2617
+
2618
+ /**
2619
+ * History.fireQueueItem(item)
2620
+ * Fire a Queue Item
2621
+ * @param {Object} item
2622
+ * @return {Mixed} result
2623
+ */
2624
+ History.fireQueueItem = function(item){
2625
+ return item.callback.apply(item.scope||History,item.args||[]);
2626
+ };
2627
+
2628
+ /**
2629
+ * History.pushQueue(callback,args)
2630
+ * Add an item to the queue
2631
+ * @param {Object} item [scope,callback,args,queue]
2632
+ */
2633
+ History.pushQueue = function(item){
2634
+ // Prepare the queue
2635
+ History.queues[item.queue||0] = History.queues[item.queue||0]||[];
2636
+
2637
+ // Add to the queue
2638
+ History.queues[item.queue||0].push(item);
2639
+
2640
+ // Chain
2641
+ return History;
2642
+ };
2643
+
2644
+ /**
2645
+ * History.queue (item,queue), (func,queue), (func), (item)
2646
+ * Either firs the item now if not busy, or adds it to the queue
2647
+ */
2648
+ History.queue = function(item,queue){
2649
+ // Prepare
2650
+ if ( typeof item === 'function' ) {
2651
+ item = {
2652
+ callback: item
2653
+ };
2654
+ }
2655
+ if ( typeof queue !== 'undefined' ) {
2656
+ item.queue = queue;
2657
+ }
2658
+
2659
+ // Handle
2660
+ if ( History.busy() ) {
2661
+ History.pushQueue(item);
2662
+ } else {
2663
+ History.fireQueueItem(item);
2664
+ }
2665
+
2666
+ // Chain
2667
+ return History;
2668
+ };
2669
+
2670
+ /**
2671
+ * History.clearQueue()
2672
+ * Clears the Queue
2673
+ */
2674
+ History.clearQueue = function(){
2675
+ History.busy.flag = false;
2676
+ History.queues = [];
2677
+ return History;
2678
+ };
2679
+
2680
+
2681
+ // ====================================================================
2682
+ // IE Bug Fix
2683
+
2684
+ /**
2685
+ * History.stateChanged
2686
+ * States whether or not the state has changed since the last double check was initialised
2687
+ */
2688
+ History.stateChanged = false;
2689
+
2690
+ /**
2691
+ * History.doubleChecker
2692
+ * Contains the timeout used for the double checks
2693
+ */
2694
+ History.doubleChecker = false;
2695
+
2696
+ /**
2697
+ * History.doubleCheckComplete()
2698
+ * Complete a double check
2699
+ * @return {History}
2700
+ */
2701
+ History.doubleCheckComplete = function(){
2702
+ // Update
2703
+ History.stateChanged = true;
2704
+
2705
+ // Clear
2706
+ History.doubleCheckClear();
2707
+
2708
+ // Chain
2709
+ return History;
2710
+ };
2711
+
2712
+ /**
2713
+ * History.doubleCheckClear()
2714
+ * Clear a double check
2715
+ * @return {History}
2716
+ */
2717
+ History.doubleCheckClear = function(){
2718
+ // Clear
2719
+ if ( History.doubleChecker ) {
2720
+ clearTimeout(History.doubleChecker);
2721
+ History.doubleChecker = false;
2722
+ }
2723
+
2724
+ // Chain
2725
+ return History;
2726
+ };
2727
+
2728
+ /**
2729
+ * History.doubleCheck()
2730
+ * Create a double check
2731
+ * @return {History}
2732
+ */
2733
+ History.doubleCheck = function(tryAgain){
2734
+ // Reset
2735
+ History.stateChanged = false;
2736
+ History.doubleCheckClear();
2737
+
2738
+ // Fix IE6,IE7 bug where calling history.back or history.forward does not actually change the hash (whereas doing it manually does)
2739
+ // Fix Safari 5 bug where sometimes the state does not change: https://bugs.webkit.org/show_bug.cgi?id=42940
2740
+ if ( History.bugs.ieDoubleCheck ) {
2741
+ // Apply Check
2742
+ History.doubleChecker = setTimeout(
2743
+ function(){
2744
+ History.doubleCheckClear();
2745
+ if ( !History.stateChanged ) {
2746
+ //History.debug('History.doubleCheck: State has not yet changed, trying again', arguments);
2747
+ // Re-Attempt
2748
+ tryAgain();
2749
+ }
2750
+ return true;
2751
+ },
2752
+ History.options.doubleCheckInterval
2753
+ );
2754
+ }
2755
+
2756
+ // Chain
2757
+ return History;
2758
+ };
2759
+
2760
+
2761
+ // ====================================================================
2762
+ // Safari Bug Fix
2763
+
2764
+ /**
2765
+ * History.safariStatePoll()
2766
+ * Poll the current state
2767
+ * @return {History}
2768
+ */
2769
+ History.safariStatePoll = function(){
2770
+ // Poll the URL
2771
+
2772
+ // Get the Last State which has the new URL
2773
+ var
2774
+ urlState = History.extractState(History.getLocationHref()),
2775
+ newState;
2776
+
2777
+ // Check for a difference
2778
+ if ( !History.isLastSavedState(urlState) ) {
2779
+ newState = urlState;
2780
+ }
2781
+ else {
2782
+ return;
2783
+ }
2784
+
2785
+ // Check if we have a state with that url
2786
+ // If not create it
2787
+ if ( !newState ) {
2788
+ //History.debug('History.safariStatePoll: new');
2789
+ newState = History.createStateObject();
2790
+ }
2791
+
2792
+ // Apply the New State
2793
+ //History.debug('History.safariStatePoll: trigger');
2794
+ History.Adapter.trigger(window,'popstate');
2795
+
2796
+ // Chain
2797
+ return History;
2798
+ };
2799
+
2800
+
2801
+ // ====================================================================
2802
+ // State Aliases
2803
+
2804
+ /**
2805
+ * History.back(queue)
2806
+ * Send the browser history back one item
2807
+ * @param {Integer} queue [optional]
2808
+ */
2809
+ History.back = function(queue){
2810
+ //History.debug('History.back: called', arguments);
2811
+
2812
+ // Handle Queueing
2813
+ if ( queue !== false && History.busy() ) {
2814
+ // Wait + Push to Queue
2815
+ //History.debug('History.back: we must wait', arguments);
2816
+ History.pushQueue({
2817
+ scope: History,
2818
+ callback: History.back,
2819
+ args: arguments,
2820
+ queue: queue
2821
+ });
2822
+ return false;
2823
+ }
2824
+
2825
+ // Make Busy + Continue
2826
+ History.busy(true);
2827
+
2828
+ // Fix certain browser bugs that prevent the state from changing
2829
+ History.doubleCheck(function(){
2830
+ History.back(false);
2831
+ });
2832
+
2833
+ // Go back
2834
+ history.go(-1);
2835
+
2836
+ // End back closure
2837
+ return true;
2838
+ };
2839
+
2840
+ /**
2841
+ * History.forward(queue)
2842
+ * Send the browser history forward one item
2843
+ * @param {Integer} queue [optional]
2844
+ */
2845
+ History.forward = function(queue){
2846
+ //History.debug('History.forward: called', arguments);
2847
+
2848
+ // Handle Queueing
2849
+ if ( queue !== false && History.busy() ) {
2850
+ // Wait + Push to Queue
2851
+ //History.debug('History.forward: we must wait', arguments);
2852
+ History.pushQueue({
2853
+ scope: History,
2854
+ callback: History.forward,
2855
+ args: arguments,
2856
+ queue: queue
2857
+ });
2858
+ return false;
2859
+ }
2860
+
2861
+ // Make Busy + Continue
2862
+ History.busy(true);
2863
+
2864
+ // Fix certain browser bugs that prevent the state from changing
2865
+ History.doubleCheck(function(){
2866
+ History.forward(false);
2867
+ });
2868
+
2869
+ // Go forward
2870
+ history.go(1);
2871
+
2872
+ // End forward closure
2873
+ return true;
2874
+ };
2875
+
2876
+ /**
2877
+ * History.go(index,queue)
2878
+ * Send the browser history back or forward index times
2879
+ * @param {Integer} queue [optional]
2880
+ */
2881
+ History.go = function(index,queue){
2882
+ //History.debug('History.go: called', arguments);
2883
+
2884
+ // Prepare
2885
+ var i;
2886
+
2887
+ // Handle
2888
+ if ( index > 0 ) {
2889
+ // Forward
2890
+ for ( i=1; i<=index; ++i ) {
2891
+ History.forward(queue);
2892
+ }
2893
+ }
2894
+ else if ( index < 0 ) {
2895
+ // Backward
2896
+ for ( i=-1; i>=index; --i ) {
2897
+ History.back(queue);
2898
+ }
2899
+ }
2900
+ else {
2901
+ throw new Error('History.go: History.go requires a positive or negative integer passed.');
2902
+ }
2903
+
2904
+ // Chain
2905
+ return History;
2906
+ };
2907
+
2908
+
2909
+ // ====================================================================
2910
+ // HTML5 State Support
2911
+
2912
+ // Non-Native pushState Implementation
2913
+ if ( History.emulated.pushState ) {
2914
+ /*
2915
+ * Provide Skeleton for HTML4 Browsers
2916
+ */
2917
+
2918
+ // Prepare
2919
+ var emptyFunction = function(){};
2920
+ History.pushState = History.pushState||emptyFunction;
2921
+ History.replaceState = History.replaceState||emptyFunction;
2922
+ } // History.emulated.pushState
2923
+
2924
+ // Native pushState Implementation
2925
+ else {
2926
+ /*
2927
+ * Use native HTML5 History API Implementation
2928
+ */
2929
+
2930
+ /**
2931
+ * History.onPopState(event,extra)
2932
+ * Refresh the Current State
2933
+ */
2934
+ History.onPopState = function(event,extra){
2935
+ // Prepare
2936
+ var stateId = false, newState = false, currentHash, currentState;
2937
+
2938
+ // Reset the double check
2939
+ History.doubleCheckComplete();
2940
+
2941
+ // Check for a Hash, and handle apporiatly
2942
+ currentHash = History.getHash();
2943
+ if ( currentHash ) {
2944
+ // Expand Hash
2945
+ currentState = History.extractState(currentHash||History.getLocationHref(),true);
2946
+ if ( currentState ) {
2947
+ // We were able to parse it, it must be a State!
2948
+ // Let's forward to replaceState
2949
+ //History.debug('History.onPopState: state anchor', currentHash, currentState);
2950
+ History.replaceState(currentState.data, currentState.title, currentState.url, false);
2951
+ }
2952
+ else {
2953
+ // Traditional Anchor
2954
+ //History.debug('History.onPopState: traditional anchor', currentHash);
2955
+ History.Adapter.trigger(window,'anchorchange');
2956
+ History.busy(false);
2957
+ }
2958
+
2959
+ // We don't care for hashes
2960
+ History.expectedStateId = false;
2961
+ return false;
2962
+ }
2963
+
2964
+ // Ensure
2965
+ stateId = History.Adapter.extractEventData('state',event,extra) || false;
2966
+
2967
+ // Fetch State
2968
+ if ( stateId ) {
2969
+ // Vanilla: Back/forward button was used
2970
+ newState = History.getStateById(stateId);
2971
+ }
2972
+ else if ( History.expectedStateId ) {
2973
+ // Vanilla: A new state was pushed, and popstate was called manually
2974
+ newState = History.getStateById(History.expectedStateId);
2975
+ }
2976
+ else {
2977
+ // Initial State
2978
+ newState = History.extractState(History.getLocationHref());
2979
+ }
2980
+
2981
+ // The State did not exist in our store
2982
+ if ( !newState ) {
2983
+ // Regenerate the State
2984
+ newState = History.createStateObject(null,null,History.getLocationHref());
2985
+ }
2986
+
2987
+ // Clean
2988
+ History.expectedStateId = false;
2989
+
2990
+ // Check if we are the same state
2991
+ if ( History.isLastSavedState(newState) ) {
2992
+ // There has been no change (just the page's hash has finally propagated)
2993
+ //History.debug('History.onPopState: no change', newState, History.savedStates);
2994
+ History.busy(false);
2995
+ return false;
2996
+ }
2997
+
2998
+ // Store the State
2999
+ History.storeState(newState);
3000
+ History.saveState(newState);
3001
+
3002
+ // Force update of the title
3003
+ History.setTitle(newState);
3004
+
3005
+ // Fire Our Event
3006
+ History.Adapter.trigger(window,'statechange');
3007
+ History.busy(false);
3008
+
3009
+ // Return true
3010
+ return true;
3011
+ };
3012
+ History.Adapter.bind(window,'popstate',History.onPopState);
3013
+
3014
+ /**
3015
+ * History.pushState(data,title,url)
3016
+ * Add a new State to the history object, become it, and trigger onpopstate
3017
+ * We have to trigger for HTML4 compatibility
3018
+ * @param {object} data
3019
+ * @param {string} title
3020
+ * @param {string} url
3021
+ * @return {true}
3022
+ */
3023
+ History.pushState = function(data,title,url,queue){
3024
+ //History.debug('History.pushState: called', arguments);
3025
+
3026
+ // Check the State
3027
+ if ( History.getHashByUrl(url) && History.emulated.pushState ) {
3028
+ throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
3029
+ }
3030
+
3031
+ // Handle Queueing
3032
+ if ( queue !== false && History.busy() ) {
3033
+ // Wait + Push to Queue
3034
+ //History.debug('History.pushState: we must wait', arguments);
3035
+ History.pushQueue({
3036
+ scope: History,
3037
+ callback: History.pushState,
3038
+ args: arguments,
3039
+ queue: queue
3040
+ });
3041
+ return false;
3042
+ }
3043
+
3044
+ // Make Busy + Continue
3045
+ History.busy(true);
3046
+
3047
+ // Create the newState
3048
+ var newState = History.createStateObject(data,title,url);
3049
+
3050
+ // Check it
3051
+ if ( History.isLastSavedState(newState) ) {
3052
+ // Won't be a change
3053
+ History.busy(false);
3054
+ }
3055
+ else {
3056
+ // Store the newState
3057
+ History.storeState(newState);
3058
+ History.expectedStateId = newState.id;
3059
+
3060
+ // Push the newState
3061
+ history.pushState(newState.id,newState.title,newState.url);
3062
+
3063
+ // Fire HTML5 Event
3064
+ History.Adapter.trigger(window,'popstate');
3065
+ }
3066
+
3067
+ // End pushState closure
3068
+ return true;
3069
+ };
3070
+
3071
+ /**
3072
+ * History.replaceState(data,title,url)
3073
+ * Replace the State and trigger onpopstate
3074
+ * We have to trigger for HTML4 compatibility
3075
+ * @param {object} data
3076
+ * @param {string} title
3077
+ * @param {string} url
3078
+ * @return {true}
3079
+ */
3080
+ History.replaceState = function(data,title,url,queue){
3081
+ //History.debug('History.replaceState: called', arguments);
3082
+
3083
+ // Check the State
3084
+ if ( History.getHashByUrl(url) && History.emulated.pushState ) {
3085
+ throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
3086
+ }
3087
+
3088
+ // Handle Queueing
3089
+ if ( queue !== false && History.busy() ) {
3090
+ // Wait + Push to Queue
3091
+ //History.debug('History.replaceState: we must wait', arguments);
3092
+ History.pushQueue({
3093
+ scope: History,
3094
+ callback: History.replaceState,
3095
+ args: arguments,
3096
+ queue: queue
3097
+ });
3098
+ return false;
3099
+ }
3100
+
3101
+ // Make Busy + Continue
3102
+ History.busy(true);
3103
+
3104
+ // Create the newState
3105
+ var newState = History.createStateObject(data,title,url);
3106
+
3107
+ // Check it
3108
+ if ( History.isLastSavedState(newState) ) {
3109
+ // Won't be a change
3110
+ History.busy(false);
3111
+ }
3112
+ else {
3113
+ // Store the newState
3114
+ History.storeState(newState);
3115
+ History.expectedStateId = newState.id;
3116
+
3117
+ // Push the newState
3118
+ history.replaceState(newState.id,newState.title,newState.url);
3119
+
3120
+ // Fire HTML5 Event
3121
+ History.Adapter.trigger(window,'popstate');
3122
+ }
3123
+
3124
+ // End replaceState closure
3125
+ return true;
3126
+ };
3127
+
3128
+ } // !History.emulated.pushState
3129
+
3130
+
3131
+ // ====================================================================
3132
+ // Initialise
3133
+
3134
+ /**
3135
+ * Load the Store
3136
+ */
3137
+ if ( sessionStorage ) {
3138
+ // Fetch
3139
+ try {
3140
+ History.store = JSON.parse(sessionStorage.getItem('History.store'))||{};
3141
+ }
3142
+ catch ( err ) {
3143
+ History.store = {};
3144
+ }
3145
+
3146
+ // Normalize
3147
+ History.normalizeStore();
3148
+ }
3149
+ else {
3150
+ // Default Load
3151
+ History.store = {};
3152
+ History.normalizeStore();
3153
+ }
3154
+
3155
+ /**
3156
+ * Clear Intervals on exit to prevent memory leaks
3157
+ */
3158
+ History.Adapter.bind(window,"unload",History.clearAllIntervals);
3159
+
3160
+ /**
3161
+ * Create the initial State
3162
+ */
3163
+ History.saveState(History.storeState(History.extractState(History.getLocationHref(),true)));
3164
+
3165
+ /**
3166
+ * Bind for Saving Store
3167
+ */
3168
+ if ( sessionStorage ) {
3169
+ // When the page is closed
3170
+ History.onUnload = function(){
3171
+ // Prepare
3172
+ var currentStore, item, currentStoreString;
3173
+
3174
+ // Fetch
3175
+ try {
3176
+ currentStore = JSON.parse(sessionStorage.getItem('History.store'))||{};
3177
+ }
3178
+ catch ( err ) {
3179
+ currentStore = {};
3180
+ }
3181
+
3182
+ // Ensure
3183
+ currentStore.idToState = currentStore.idToState || {};
3184
+ currentStore.urlToId = currentStore.urlToId || {};
3185
+ currentStore.stateToId = currentStore.stateToId || {};
3186
+
3187
+ // Sync
3188
+ for ( item in History.idToState ) {
3189
+ if ( !History.idToState.hasOwnProperty(item) ) {
3190
+ continue;
3191
+ }
3192
+ currentStore.idToState[item] = History.idToState[item];
3193
+ }
3194
+ for ( item in History.urlToId ) {
3195
+ if ( !History.urlToId.hasOwnProperty(item) ) {
3196
+ continue;
3197
+ }
3198
+ currentStore.urlToId[item] = History.urlToId[item];
3199
+ }
3200
+ for ( item in History.stateToId ) {
3201
+ if ( !History.stateToId.hasOwnProperty(item) ) {
3202
+ continue;
3203
+ }
3204
+ currentStore.stateToId[item] = History.stateToId[item];
3205
+ }
3206
+
3207
+ // Update
3208
+ History.store = currentStore;
3209
+ History.normalizeStore();
3210
+
3211
+ // In Safari, going into Private Browsing mode causes the
3212
+ // Session Storage object to still exist but if you try and use
3213
+ // or set any property/function of it it throws the exception
3214
+ // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to
3215
+ // add something to storage that exceeded the quota." infinitely
3216
+ // every second.
3217
+ currentStoreString = JSON.stringify(currentStore);
3218
+ try {
3219
+ // Store
3220
+ sessionStorage.setItem('History.store', currentStoreString);
3221
+ }
3222
+ catch (e) {
3223
+ if (e.code === DOMException.QUOTA_EXCEEDED_ERR) {
3224
+ if (sessionStorage.length) {
3225
+ // Workaround for a bug seen on iPads. Sometimes the quota exceeded error comes up and simply
3226
+ // removing/resetting the storage can work.
3227
+ sessionStorage.removeItem('History.store');
3228
+ sessionStorage.setItem('History.store', currentStoreString);
3229
+ } else {
3230
+ // Otherwise, we're probably private browsing in Safari, so we'll ignore the exception.
3231
+ }
3232
+ } else {
3233
+ throw e;
3234
+ }
3235
+ }
3236
+ };
3237
+
3238
+ // For Internet Explorer
3239
+ History.intervalList.push(setInterval(History.onUnload,History.options.storeInterval));
3240
+
3241
+ // For Other Browsers
3242
+ History.Adapter.bind(window,'beforeunload',History.onUnload);
3243
+ History.Adapter.bind(window,'unload',History.onUnload);
3244
+
3245
+ // Both are enabled for consistency
3246
+ }
3247
+
3248
+ // Non-Native pushState Implementation
3249
+ if ( !History.emulated.pushState ) {
3250
+ // Be aware, the following is only for native pushState implementations
3251
+ // If you are wanting to include something for all browsers
3252
+ // Then include it above this if block
3253
+
3254
+ /**
3255
+ * Setup Safari Fix
3256
+ */
3257
+ if ( History.bugs.safariPoll ) {
3258
+ History.intervalList.push(setInterval(History.safariStatePoll, History.options.safariPollInterval));
3259
+ }
3260
+
3261
+ /**
3262
+ * Ensure Cross Browser Compatibility
3263
+ */
3264
+ if ( navigator.vendor === 'Apple Computer, Inc.' || (navigator.appCodeName||'') === 'Mozilla' ) {
3265
+ /**
3266
+ * Fix Safari HashChange Issue
3267
+ */
3268
+
3269
+ // Setup Alias
3270
+ History.Adapter.bind(window,'hashchange',function(){
3271
+ History.Adapter.trigger(window,'popstate');
3272
+ });
3273
+
3274
+ // Initialise Alias
3275
+ if ( History.getHash() ) {
3276
+ History.Adapter.onDomLoad(function(){
3277
+ History.Adapter.trigger(window,'hashchange');
3278
+ });
3279
+ }
3280
+ }
3281
+
3282
+ } // !History.emulated.pushState
3283
+
3284
+
3285
+ }; // History.initCore
3286
+
3287
+ // Try to Initialise History
3288
+ if (!History.options || !History.options.delayInit) {
3289
+ History.init();
3290
+ }
3291
+
3292
+ })(window);