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