colorgy_style 0.0.0.2 → 0.0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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);