colorgy_style 0.0.0.2 → 0.0.0.4

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