colorgy_style 0.0.0.2 → 0.0.0.4

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