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