historyjs-rails 1.0.0 → 1.0.1

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.
@@ -1,78 +1,78 @@
1
- /**
2
- * History.js RightJS Adapter
3
- * @author Benjamin Arthur Lupton <contact@balupton.com>
4
- * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
5
- * @license New BSD License <http://creativecommons.org/licenses/BSD/>
6
- */
7
-
8
- // Closure
9
- (function(window,undefined){
10
- "use strict";
11
-
12
- // Localise Globals
13
- var
14
- History = window.History = window.History||{},
15
- document = window.document,
16
- RightJS = window.RightJS,
17
- $ = RightJS.$;
18
-
19
- // Check Existence
20
- if ( typeof History.Adapter !== 'undefined' ) {
21
- throw new Error('History.js Adapter has already been loaded...');
22
- }
23
-
24
- // Add the Adapter
25
- History.Adapter = {
26
- /**
27
- * History.Adapter.bind(el,event,callback)
28
- * @param {Element|Selector} el
29
- * @param {String} event - custom and standard events
30
- * @param {Function} callback
31
- * @return
32
- */
33
- bind: function(el,event,callback){
34
- $(el).on(event,callback);
35
- },
36
-
37
- /**
38
- * History.Adapter.trigger(el,event)
39
- * @param {Element|Selector} el
40
- * @param {String} event - custom and standard events
41
- * @param {Object} extraEventData - a object of extra event data
42
- * @return
43
- */
44
- trigger: function(el,event,extraEventData){
45
- $(el).fire(event,extraEventData);
46
- },
47
-
48
- /**
49
- * History.Adapter.extractEventData(key,event,extra)
50
- * @param {String} key - key for the event data to extract
51
- * @param {String} event - custom and standard events
52
- * @return {mixed}
53
- */
54
- extractEventData: function(key,event){
55
- // Right.js Native
56
- // Right.js Custom
57
- var result = (event && event._ && event._[key]) || undefined;
58
-
59
- // Return
60
- return result;
61
- },
62
-
63
- /**
64
- * History.Adapter.onDomLoad(callback)
65
- * @param {Function} callback
66
- * @return
67
- */
68
- onDomLoad: function(callback) {
69
- $(document).onReady(callback);
70
- }
71
- };
72
-
73
- // Try and Initialise History
74
- if ( typeof History.init !== 'undefined' ) {
75
- History.init();
76
- }
77
-
78
- })(window);
1
+ /**
2
+ * History.js RightJS Adapter
3
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
4
+ * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
5
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
6
+ */
7
+
8
+ // Closure
9
+ (function(window,undefined){
10
+ "use strict";
11
+
12
+ // Localise Globals
13
+ var
14
+ History = window.History = window.History||{},
15
+ document = window.document,
16
+ RightJS = window.RightJS,
17
+ $ = RightJS.$;
18
+
19
+ // Check Existence
20
+ if ( typeof History.Adapter !== 'undefined' ) {
21
+ throw new Error('History.js Adapter has already been loaded...');
22
+ }
23
+
24
+ // Add the Adapter
25
+ History.Adapter = {
26
+ /**
27
+ * History.Adapter.bind(el,event,callback)
28
+ * @param {Element|Selector} el
29
+ * @param {String} event - custom and standard events
30
+ * @param {Function} callback
31
+ * @return
32
+ */
33
+ bind: function(el,event,callback){
34
+ $(el).on(event,callback);
35
+ },
36
+
37
+ /**
38
+ * History.Adapter.trigger(el,event)
39
+ * @param {Element|Selector} el
40
+ * @param {String} event - custom and standard events
41
+ * @param {Object} extraEventData - a object of extra event data
42
+ * @return
43
+ */
44
+ trigger: function(el,event,extraEventData){
45
+ $(el).fire(event,extraEventData);
46
+ },
47
+
48
+ /**
49
+ * History.Adapter.extractEventData(key,event,extra)
50
+ * @param {String} key - key for the event data to extract
51
+ * @param {String} event - custom and standard events
52
+ * @return {mixed}
53
+ */
54
+ extractEventData: function(key,event){
55
+ // Right.js Native
56
+ // Right.js Custom
57
+ var result = (event && event._ && event._[key]) || undefined;
58
+
59
+ // Return
60
+ return result;
61
+ },
62
+
63
+ /**
64
+ * History.Adapter.onDomLoad(callback)
65
+ * @param {Function} callback
66
+ * @return
67
+ */
68
+ onDomLoad: function(callback) {
69
+ $(document).onReady(callback);
70
+ }
71
+ };
72
+
73
+ // Try and Initialise History
74
+ if ( typeof History.init !== 'undefined' ) {
75
+ History.init();
76
+ }
77
+
78
+ })(window);
@@ -1,74 +1,74 @@
1
- /**
2
- * History.js Zepto Adapter
3
- * @author Benjamin Arthur Lupton <contact@balupton.com>
4
- * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
5
- * @license New BSD License <http://creativecommons.org/licenses/BSD/>
6
- */
7
-
8
- // Closure
9
- (function(window,undefined){
10
- "use strict";
11
-
12
- // Localise Globals
13
- var
14
- History = window.History = window.History||{},
15
- Zepto = window.Zepto;
16
-
17
- // Check Existence
18
- if ( typeof History.Adapter !== 'undefined' ) {
19
- throw new Error('History.js Adapter has already been loaded...');
20
- }
21
-
22
- // Add the Adapter
23
- History.Adapter = {
24
- /**
25
- * History.Adapter.bind(el,event,callback)
26
- * @param {Element|string} el
27
- * @param {string} event - custom and standard events
28
- * @param {function} callback
29
- * @return {void}
30
- */
31
- bind: function(el,event,callback){
32
- new Zepto(el).bind(event,callback);
33
- },
34
-
35
- /**
36
- * History.Adapter.trigger(el,event)
37
- * @param {Element|string} el
38
- * @param {string} event - custom and standard events
39
- * @return {void}
40
- */
41
- trigger: function(el,event){
42
- new Zepto(el).trigger(event);
43
- },
44
-
45
- /**
46
- * History.Adapter.extractEventData(key,event,extra)
47
- * @param {string} key - key for the event data to extract
48
- * @param {string} event - custom and standard events
49
- * @return {mixed}
50
- */
51
- extractEventData: function(key,event){
52
- // Zepto Native
53
- var result = (event && event[key]) || undefined;
54
-
55
- // Return
56
- return result;
57
- },
58
-
59
- /**
60
- * History.Adapter.onDomLoad(callback)
61
- * @param {function} callback
62
- * @return {void}
63
- */
64
- onDomLoad: function(callback) {
65
- new Zepto(callback);
66
- }
67
- };
68
-
69
- // Try and Initialise History
70
- if ( typeof History.init !== 'undefined' ) {
71
- History.init();
72
- }
73
-
74
- })(window);
1
+ /**
2
+ * History.js Zepto Adapter
3
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
4
+ * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
5
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
6
+ */
7
+
8
+ // Closure
9
+ (function(window,undefined){
10
+ "use strict";
11
+
12
+ // Localise Globals
13
+ var
14
+ History = window.History = window.History||{},
15
+ Zepto = window.Zepto;
16
+
17
+ // Check Existence
18
+ if ( typeof History.Adapter !== 'undefined' ) {
19
+ throw new Error('History.js Adapter has already been loaded...');
20
+ }
21
+
22
+ // Add the Adapter
23
+ History.Adapter = {
24
+ /**
25
+ * History.Adapter.bind(el,event,callback)
26
+ * @param {Element|string} el
27
+ * @param {string} event - custom and standard events
28
+ * @param {function} callback
29
+ * @return {void}
30
+ */
31
+ bind: function(el,event,callback){
32
+ new Zepto(el).bind(event,callback);
33
+ },
34
+
35
+ /**
36
+ * History.Adapter.trigger(el,event)
37
+ * @param {Element|string} el
38
+ * @param {string} event - custom and standard events
39
+ * @return {void}
40
+ */
41
+ trigger: function(el,event){
42
+ new Zepto(el).trigger(event);
43
+ },
44
+
45
+ /**
46
+ * History.Adapter.extractEventData(key,event,extra)
47
+ * @param {string} key - key for the event data to extract
48
+ * @param {string} event - custom and standard events
49
+ * @return {mixed}
50
+ */
51
+ extractEventData: function(key,event){
52
+ // Zepto Native
53
+ var result = (event && event[key]) || undefined;
54
+
55
+ // Return
56
+ return result;
57
+ },
58
+
59
+ /**
60
+ * History.Adapter.onDomLoad(callback)
61
+ * @param {function} callback
62
+ * @return {void}
63
+ */
64
+ onDomLoad: function(callback) {
65
+ new Zepto(callback);
66
+ }
67
+ };
68
+
69
+ // Try and Initialise History
70
+ if ( typeof History.init !== 'undefined' ) {
71
+ History.init();
72
+ }
73
+
74
+ })(window);
@@ -1,1943 +1,1943 @@
1
- /**
2
- * History.js Core
3
- * @author Benjamin Arthur Lupton <contact@balupton.com>
4
- * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
5
- * @license New BSD License <http://creativecommons.org/licenses/BSD/>
6
- */
7
-
8
- (function(window,undefined){
9
- "use strict";
10
-
11
- // ========================================================================
12
- // Initialise
13
-
14
- // Localise Globals
15
- var
16
- console = window.console||undefined, // Prevent a JSLint complain
17
- document = window.document, // Make sure we are using the correct document
18
- navigator = window.navigator, // Make sure we are using the correct navigator
19
- sessionStorage = window.sessionStorage||false, // sessionStorage
20
- setTimeout = window.setTimeout,
21
- clearTimeout = window.clearTimeout,
22
- setInterval = window.setInterval,
23
- clearInterval = window.clearInterval,
24
- JSON = window.JSON,
25
- alert = window.alert,
26
- History = window.History = window.History||{}, // Public History Object
27
- history = window.history; // Old History Object
28
-
29
- // MooTools Compatibility
30
- JSON.stringify = JSON.stringify||JSON.encode;
31
- JSON.parse = JSON.parse||JSON.decode;
32
-
33
- // Check Existence
34
- if ( typeof History.init !== 'undefined' ) {
35
- throw new Error('History.js Core has already been loaded...');
36
- }
37
-
38
- // Initialise History
39
- History.init = function(){
40
- // Check Load Status of Adapter
41
- if ( typeof History.Adapter === 'undefined' ) {
42
- return false;
43
- }
44
-
45
- // Check Load Status of Core
46
- if ( typeof History.initCore !== 'undefined' ) {
47
- History.initCore();
48
- }
49
-
50
- // Check Load Status of HTML4 Support
51
- if ( typeof History.initHtml4 !== 'undefined' ) {
52
- History.initHtml4();
53
- }
54
-
55
- // Return true
56
- return true;
57
- };
58
-
59
-
60
- // ========================================================================
61
- // Initialise Core
62
-
63
- // Initialise Core
64
- History.initCore = function(){
65
- // Initialise
66
- if ( typeof History.initCore.initialized !== 'undefined' ) {
67
- // Already Loaded
68
- return false;
69
- }
70
- else {
71
- History.initCore.initialized = true;
72
- }
73
-
74
-
75
- // ====================================================================
76
- // Options
77
-
78
- /**
79
- * History.options
80
- * Configurable options
81
- */
82
- History.options = History.options||{};
83
-
84
- /**
85
- * History.options.hashChangeInterval
86
- * How long should the interval be before hashchange checks
87
- */
88
- History.options.hashChangeInterval = History.options.hashChangeInterval || 100;
89
-
90
- /**
91
- * History.options.safariPollInterval
92
- * How long should the interval be before safari poll checks
93
- */
94
- History.options.safariPollInterval = History.options.safariPollInterval || 500;
95
-
96
- /**
97
- * History.options.doubleCheckInterval
98
- * How long should the interval be before we perform a double check
99
- */
100
- History.options.doubleCheckInterval = History.options.doubleCheckInterval || 500;
101
-
102
- /**
103
- * History.options.storeInterval
104
- * How long should we wait between store calls
105
- */
106
- History.options.storeInterval = History.options.storeInterval || 1000;
107
-
108
- /**
109
- * History.options.busyDelay
110
- * How long should we wait between busy events
111
- */
112
- History.options.busyDelay = History.options.busyDelay || 250;
113
-
114
- /**
115
- * History.options.debug
116
- * If true will enable debug messages to be logged
117
- */
118
- History.options.debug = History.options.debug || false;
119
-
120
- /**
121
- * History.options.initialTitle
122
- * What is the title of the initial state
123
- */
124
- History.options.initialTitle = History.options.initialTitle || document.title;
125
-
126
-
127
- // ====================================================================
128
- // Interval record
129
-
130
- /**
131
- * History.intervalList
132
- * List of intervals set, to be cleared when document is unloaded.
133
- */
134
- History.intervalList = [];
135
-
136
- /**
137
- * History.clearAllIntervals
138
- * Clears all setInterval instances.
139
- */
140
- History.clearAllIntervals = function(){
141
- var i, il = History.intervalList;
142
- if (typeof il !== "undefined" && il !== null) {
143
- for (i = 0; i < il.length; i++) {
144
- clearInterval(il[i]);
145
- }
146
- History.intervalList = null;
147
- }
148
- };
149
-
150
-
151
- // ====================================================================
152
- // Debug
153
-
154
- /**
155
- * History.debug(message,...)
156
- * Logs the passed arguments if debug enabled
157
- */
158
- History.debug = function(){
159
- if ( (History.options.debug||false) ) {
160
- History.log.apply(History,arguments);
161
- }
162
- };
163
-
164
- /**
165
- * History.log(message,...)
166
- * Logs the passed arguments
167
- */
168
- History.log = function(){
169
- // Prepare
170
- var
171
- consoleExists = !(typeof console === 'undefined' || typeof console.log === 'undefined' || typeof console.log.apply === 'undefined'),
172
- textarea = document.getElementById('log'),
173
- message,
174
- i,n,
175
- args,arg
176
- ;
177
-
178
- // Write to Console
179
- if ( consoleExists ) {
180
- args = Array.prototype.slice.call(arguments);
181
- message = args.shift();
182
- if ( typeof console.debug !== 'undefined' ) {
183
- console.debug.apply(console,[message,args]);
184
- }
185
- else {
186
- console.log.apply(console,[message,args]);
187
- }
188
- }
189
- else {
190
- message = ("\n"+arguments[0]+"\n");
191
- }
192
-
193
- // Write to log
194
- for ( i=1,n=arguments.length; i<n; ++i ) {
195
- arg = arguments[i];
196
- if ( typeof arg === 'object' && typeof JSON !== 'undefined' ) {
197
- try {
198
- arg = JSON.stringify(arg);
199
- }
200
- catch ( Exception ) {
201
- // Recursive Object
202
- }
203
- }
204
- message += "\n"+arg+"\n";
205
- }
206
-
207
- // Textarea
208
- if ( textarea ) {
209
- textarea.value += message+"\n-----\n";
210
- textarea.scrollTop = textarea.scrollHeight - textarea.clientHeight;
211
- }
212
- // No Textarea, No Console
213
- else if ( !consoleExists ) {
214
- alert(message);
215
- }
216
-
217
- // Return true
218
- return true;
219
- };
220
-
221
-
222
- // ====================================================================
223
- // Emulated Status
224
-
225
- /**
226
- * History.getInternetExplorerMajorVersion()
227
- * Get's the major version of Internet Explorer
228
- * @return {integer}
229
- * @license Public Domain
230
- * @author Benjamin Arthur Lupton <contact@balupton.com>
231
- * @author James Padolsey <https://gist.github.com/527683>
232
- */
233
- History.getInternetExplorerMajorVersion = function(){
234
- var result = History.getInternetExplorerMajorVersion.cached =
235
- (typeof History.getInternetExplorerMajorVersion.cached !== 'undefined')
236
- ? History.getInternetExplorerMajorVersion.cached
237
- : (function(){
238
- var v = 3,
239
- div = document.createElement('div'),
240
- all = div.getElementsByTagName('i');
241
- while ( (div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->') && all[0] ) {}
242
- return (v > 4) ? v : false;
243
- })()
244
- ;
245
- return result;
246
- };
247
-
248
- /**
249
- * History.isInternetExplorer()
250
- * Are we using Internet Explorer?
251
- * @return {boolean}
252
- * @license Public Domain
253
- * @author Benjamin Arthur Lupton <contact@balupton.com>
254
- */
255
- History.isInternetExplorer = function(){
256
- var result =
257
- History.isInternetExplorer.cached =
258
- (typeof History.isInternetExplorer.cached !== 'undefined')
259
- ? History.isInternetExplorer.cached
260
- : Boolean(History.getInternetExplorerMajorVersion())
261
- ;
262
- return result;
263
- };
264
-
265
- /**
266
- * History.emulated
267
- * Which features require emulating?
268
- */
269
- History.emulated = {
270
- pushState: !Boolean(
271
- window.history && window.history.pushState && window.history.replaceState
272
- && !(
273
- (/ 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) */
274
- || (/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 */
275
- )
276
- ),
277
- hashChange: Boolean(
278
- !(('onhashchange' in window) || ('onhashchange' in document))
279
- ||
280
- (History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8)
281
- )
282
- };
283
-
284
- /**
285
- * History.enabled
286
- * Is History enabled?
287
- */
288
- History.enabled = !History.emulated.pushState;
289
-
290
- /**
291
- * History.bugs
292
- * Which bugs are present
293
- */
294
- History.bugs = {
295
- /**
296
- * Safari 5 and Safari iOS 4 fail to return to the correct state once a hash is replaced by a `replaceState` call
297
- * https://bugs.webkit.org/show_bug.cgi?id=56249
298
- */
299
- setHash: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
300
-
301
- /**
302
- * Safari 5 and Safari iOS 4 sometimes fail to apply the state change under busy conditions
303
- * https://bugs.webkit.org/show_bug.cgi?id=42940
304
- */
305
- safariPoll: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
306
-
307
- /**
308
- * MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function)
309
- */
310
- ieDoubleCheck: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8),
311
-
312
- /**
313
- * MSIE 6 requires the entire hash to be encoded for the hashes to trigger the onHashChange event
314
- */
315
- hashEscape: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 7)
316
- };
317
-
318
- /**
319
- * History.isEmptyObject(obj)
320
- * Checks to see if the Object is Empty
321
- * @param {Object} obj
322
- * @return {boolean}
323
- */
324
- History.isEmptyObject = function(obj) {
325
- for ( var name in obj ) {
326
- return false;
327
- }
328
- return true;
329
- };
330
-
331
- /**
332
- * History.cloneObject(obj)
333
- * Clones a object and eliminate all references to the original contexts
334
- * @param {Object} obj
335
- * @return {Object}
336
- */
337
- History.cloneObject = function(obj) {
338
- var hash,newObj;
339
- if ( obj ) {
340
- hash = JSON.stringify(obj);
341
- newObj = JSON.parse(hash);
342
- }
343
- else {
344
- newObj = {};
345
- }
346
- return newObj;
347
- };
348
-
349
-
350
- // ====================================================================
351
- // URL Helpers
352
-
353
- /**
354
- * History.getRootUrl()
355
- * Turns "http://mysite.com/dir/page.html?asd" into "http://mysite.com"
356
- * @return {String} rootUrl
357
- */
358
- History.getRootUrl = function(){
359
- // Create
360
- var rootUrl = document.location.protocol+'//'+(document.location.hostname||document.location.host);
361
- if ( document.location.port||false ) {
362
- rootUrl += ':'+document.location.port;
363
- }
364
- rootUrl += '/';
365
-
366
- // Return
367
- return rootUrl;
368
- };
369
-
370
- /**
371
- * History.getBaseHref()
372
- * Fetches the `href` attribute of the `<base href="...">` element if it exists
373
- * @return {String} baseHref
374
- */
375
- History.getBaseHref = function(){
376
- // Create
377
- var
378
- baseElements = document.getElementsByTagName('base'),
379
- baseElement = null,
380
- baseHref = '';
381
-
382
- // Test for Base Element
383
- if ( baseElements.length === 1 ) {
384
- // Prepare for Base Element
385
- baseElement = baseElements[0];
386
- baseHref = baseElement.href.replace(/[^\/]+$/,'');
387
- }
388
-
389
- // Adjust trailing slash
390
- baseHref = baseHref.replace(/\/+$/,'');
391
- if ( baseHref ) baseHref += '/';
392
-
393
- // Return
394
- return baseHref;
395
- };
396
-
397
- /**
398
- * History.getBaseUrl()
399
- * Fetches the baseHref or basePageUrl or rootUrl (whichever one exists first)
400
- * @return {String} baseUrl
401
- */
402
- History.getBaseUrl = function(){
403
- // Create
404
- var baseUrl = History.getBaseHref()||History.getBasePageUrl()||History.getRootUrl();
405
-
406
- // Return
407
- return baseUrl;
408
- };
409
-
410
- /**
411
- * History.getPageUrl()
412
- * Fetches the URL of the current page
413
- * @return {String} pageUrl
414
- */
415
- History.getPageUrl = function(){
416
- // Fetch
417
- var
418
- State = History.getState(false,false),
419
- stateUrl = (State||{}).url||document.location.href,
420
- pageUrl;
421
-
422
- // Create
423
- pageUrl = stateUrl.replace(/\/+$/,'').replace(/[^\/]+$/,function(part,index,string){
424
- return (/\./).test(part) ? part : part+'/';
425
- });
426
-
427
- // Return
428
- return pageUrl;
429
- };
430
-
431
- /**
432
- * History.getBasePageUrl()
433
- * Fetches the Url of the directory of the current page
434
- * @return {String} basePageUrl
435
- */
436
- History.getBasePageUrl = function(){
437
- // Create
438
- var basePageUrl = document.location.href.replace(/[#\?].*/,'').replace(/[^\/]+$/,function(part,index,string){
439
- return (/[^\/]$/).test(part) ? '' : part;
440
- }).replace(/\/+$/,'')+'/';
441
-
442
- // Return
443
- return basePageUrl;
444
- };
445
-
446
- /**
447
- * History.getFullUrl(url)
448
- * Ensures that we have an absolute URL and not a relative URL
449
- * @param {string} url
450
- * @param {Boolean} allowBaseHref
451
- * @return {string} fullUrl
452
- */
453
- History.getFullUrl = function(url,allowBaseHref){
454
- // Prepare
455
- var fullUrl = url, firstChar = url.substring(0,1);
456
- allowBaseHref = (typeof allowBaseHref === 'undefined') ? true : allowBaseHref;
457
-
458
- // Check
459
- if ( /[a-z]+\:\/\//.test(url) ) {
460
- // Full URL
461
- }
462
- else if ( firstChar === '/' ) {
463
- // Root URL
464
- fullUrl = History.getRootUrl()+url.replace(/^\/+/,'');
465
- }
466
- else if ( firstChar === '#' ) {
467
- // Anchor URL
468
- fullUrl = History.getPageUrl().replace(/#.*/,'')+url;
469
- }
470
- else if ( firstChar === '?' ) {
471
- // Query URL
472
- fullUrl = History.getPageUrl().replace(/[\?#].*/,'')+url;
473
- }
474
- else {
475
- // Relative URL
476
- if ( allowBaseHref ) {
477
- fullUrl = History.getBaseUrl()+url.replace(/^(\.\/)+/,'');
478
- } else {
479
- fullUrl = History.getBasePageUrl()+url.replace(/^(\.\/)+/,'');
480
- }
481
- // We have an if condition above as we do not want hashes
482
- // which are relative to the baseHref in our URLs
483
- // as if the baseHref changes, then all our bookmarks
484
- // would now point to different locations
485
- // whereas the basePageUrl will always stay the same
486
- }
487
-
488
- // Return
489
- return fullUrl.replace(/\#$/,'');
490
- };
491
-
492
- /**
493
- * History.getShortUrl(url)
494
- * Ensures that we have a relative URL and not a absolute URL
495
- * @param {string} url
496
- * @return {string} url
497
- */
498
- History.getShortUrl = function(url){
499
- // Prepare
500
- var shortUrl = url, baseUrl = History.getBaseUrl(), rootUrl = History.getRootUrl();
501
-
502
- // Trim baseUrl
503
- if ( History.emulated.pushState ) {
504
- // We are in a if statement as when pushState is not emulated
505
- // The actual url these short urls are relative to can change
506
- // So within the same session, we the url may end up somewhere different
507
- shortUrl = shortUrl.replace(baseUrl,'');
508
- }
509
-
510
- // Trim rootUrl
511
- shortUrl = shortUrl.replace(rootUrl,'/');
512
-
513
- // Ensure we can still detect it as a state
514
- if ( History.isTraditionalAnchor(shortUrl) ) {
515
- shortUrl = './'+shortUrl;
516
- }
517
-
518
- // Clean It
519
- shortUrl = shortUrl.replace(/^(\.\/)+/g,'./').replace(/\#$/,'');
520
-
521
- // Return
522
- return shortUrl;
523
- };
524
-
525
-
526
- // ====================================================================
527
- // State Storage
528
-
529
- /**
530
- * History.store
531
- * The store for all session specific data
532
- */
533
- History.store = {};
534
-
535
- /**
536
- * History.idToState
537
- * 1-1: State ID to State Object
538
- */
539
- History.idToState = History.idToState||{};
540
-
541
- /**
542
- * History.stateToId
543
- * 1-1: State String to State ID
544
- */
545
- History.stateToId = History.stateToId||{};
546
-
547
- /**
548
- * History.urlToId
549
- * 1-1: State URL to State ID
550
- */
551
- History.urlToId = History.urlToId||{};
552
-
553
- /**
554
- * History.storedStates
555
- * Store the states in an array
556
- */
557
- History.storedStates = History.storedStates||[];
558
-
559
- /**
560
- * History.savedStates
561
- * Saved the states in an array
562
- */
563
- History.savedStates = History.savedStates||[];
564
-
565
- /**
566
- * History.noramlizeStore()
567
- * Noramlize the store by adding necessary values
568
- */
569
- History.normalizeStore = function(){
570
- History.store.idToState = History.store.idToState||{};
571
- History.store.urlToId = History.store.urlToId||{};
572
- History.store.stateToId = History.store.stateToId||{};
573
- };
574
-
575
- /**
576
- * History.getState()
577
- * Get an object containing the data, title and url of the current state
578
- * @param {Boolean} friendly
579
- * @param {Boolean} create
580
- * @return {Object} State
581
- */
582
- History.getState = function(friendly,create){
583
- // Prepare
584
- if ( typeof friendly === 'undefined' ) { friendly = true; }
585
- if ( typeof create === 'undefined' ) { create = true; }
586
-
587
- // Fetch
588
- var State = History.getLastSavedState();
589
-
590
- // Create
591
- if ( !State && create ) {
592
- State = History.createStateObject();
593
- }
594
-
595
- // Adjust
596
- if ( friendly ) {
597
- State = History.cloneObject(State);
598
- State.url = State.cleanUrl||State.url;
599
- }
600
-
601
- // Return
602
- return State;
603
- };
604
-
605
- /**
606
- * History.getIdByState(State)
607
- * Gets a ID for a State
608
- * @param {State} newState
609
- * @return {String} id
610
- */
611
- History.getIdByState = function(newState){
612
-
613
- // Fetch ID
614
- var id = History.extractId(newState.url),
615
- str;
616
-
617
- if ( !id ) {
618
- // Find ID via State String
619
- str = History.getStateString(newState);
620
- if ( typeof History.stateToId[str] !== 'undefined' ) {
621
- id = History.stateToId[str];
622
- }
623
- else if ( typeof History.store.stateToId[str] !== 'undefined' ) {
624
- id = History.store.stateToId[str];
625
- }
626
- else {
627
- // Generate a new ID
628
- while ( true ) {
629
- id = (new Date()).getTime() + String(Math.random()).replace(/\D/g,'');
630
- if ( typeof History.idToState[id] === 'undefined' && typeof History.store.idToState[id] === 'undefined' ) {
631
- break;
632
- }
633
- }
634
-
635
- // Apply the new State to the ID
636
- History.stateToId[str] = id;
637
- History.idToState[id] = newState;
638
- }
639
- }
640
-
641
- // Return ID
642
- return id;
643
- };
644
-
645
- /**
646
- * History.normalizeState(State)
647
- * Expands a State Object
648
- * @param {object} State
649
- * @return {object}
650
- */
651
- History.normalizeState = function(oldState){
652
- // Variables
653
- var newState, dataNotEmpty;
654
-
655
- // Prepare
656
- if ( !oldState || (typeof oldState !== 'object') ) {
657
- oldState = {};
658
- }
659
-
660
- // Check
661
- if ( typeof oldState.normalized !== 'undefined' ) {
662
- return oldState;
663
- }
664
-
665
- // Adjust
666
- if ( !oldState.data || (typeof oldState.data !== 'object') ) {
667
- oldState.data = {};
668
- }
669
-
670
- // ----------------------------------------------------------------
671
-
672
- // Create
673
- newState = {};
674
- newState.normalized = true;
675
- newState.title = oldState.title||'';
676
- newState.url = History.getFullUrl(History.unescapeString(oldState.url||document.location.href));
677
- newState.hash = History.getShortUrl(newState.url);
678
- newState.data = History.cloneObject(oldState.data);
679
-
680
- // Fetch ID
681
- newState.id = History.getIdByState(newState);
682
-
683
- // ----------------------------------------------------------------
684
-
685
- // Clean the URL
686
- newState.cleanUrl = newState.url.replace(/\??\&_suid.*/,'');
687
- newState.url = newState.cleanUrl;
688
-
689
- // Check to see if we have more than just a url
690
- dataNotEmpty = !History.isEmptyObject(newState.data);
691
-
692
- // Apply
693
- if ( newState.title || dataNotEmpty ) {
694
- // Add ID to Hash
695
- newState.hash = History.getShortUrl(newState.url).replace(/\??\&_suid.*/,'');
696
- if ( !/\?/.test(newState.hash) ) {
697
- newState.hash += '?';
698
- }
699
- newState.hash += '&_suid='+newState.id;
700
- }
701
-
702
- // Create the Hashed URL
703
- newState.hashedUrl = History.getFullUrl(newState.hash);
704
-
705
- // ----------------------------------------------------------------
706
-
707
- // Update the URL if we have a duplicate
708
- if ( (History.emulated.pushState || History.bugs.safariPoll) && History.hasUrlDuplicate(newState) ) {
709
- newState.url = newState.hashedUrl;
710
- }
711
-
712
- // ----------------------------------------------------------------
713
-
714
- // Return
715
- return newState;
716
- };
717
-
718
- /**
719
- * History.createStateObject(data,title,url)
720
- * Creates a object based on the data, title and url state params
721
- * @param {object} data
722
- * @param {string} title
723
- * @param {string} url
724
- * @return {object}
725
- */
726
- History.createStateObject = function(data,title,url){
727
- // Hashify
728
- var State = {
729
- 'data': data,
730
- 'title': title,
731
- 'url': url
732
- };
733
-
734
- // Expand the State
735
- State = History.normalizeState(State);
736
-
737
- // Return object
738
- return State;
739
- };
740
-
741
- /**
742
- * History.getStateById(id)
743
- * Get a state by it's UID
744
- * @param {String} id
745
- */
746
- History.getStateById = function(id){
747
- // Prepare
748
- id = String(id);
749
-
750
- // Retrieve
751
- var State = History.idToState[id] || History.store.idToState[id] || undefined;
752
-
753
- // Return State
754
- return State;
755
- };
756
-
757
- /**
758
- * Get a State's String
759
- * @param {State} passedState
760
- */
761
- History.getStateString = function(passedState){
762
- // Prepare
763
- var State, cleanedState, str;
764
-
765
- // Fetch
766
- State = History.normalizeState(passedState);
767
-
768
- // Clean
769
- cleanedState = {
770
- data: State.data,
771
- title: passedState.title,
772
- url: passedState.url
773
- };
774
-
775
- // Fetch
776
- str = JSON.stringify(cleanedState);
777
-
778
- // Return
779
- return str;
780
- };
781
-
782
- /**
783
- * Get a State's ID
784
- * @param {State} passedState
785
- * @return {String} id
786
- */
787
- History.getStateId = function(passedState){
788
- // Prepare
789
- var State, id;
790
-
791
- // Fetch
792
- State = History.normalizeState(passedState);
793
-
794
- // Fetch
795
- id = State.id;
796
-
797
- // Return
798
- return id;
799
- };
800
-
801
- /**
802
- * History.getHashByState(State)
803
- * Creates a Hash for the State Object
804
- * @param {State} passedState
805
- * @return {String} hash
806
- */
807
- History.getHashByState = function(passedState){
808
- // Prepare
809
- var State, hash;
810
-
811
- // Fetch
812
- State = History.normalizeState(passedState);
813
-
814
- // Hash
815
- hash = State.hash;
816
-
817
- // Return
818
- return hash;
819
- };
820
-
821
- /**
822
- * History.extractId(url_or_hash)
823
- * Get a State ID by it's URL or Hash
824
- * @param {string} url_or_hash
825
- * @return {string} id
826
- */
827
- History.extractId = function ( url_or_hash ) {
828
- // Prepare
829
- var id,parts,url;
830
-
831
- // Extract
832
- parts = /(.*)\&_suid=([0-9]+)$/.exec(url_or_hash);
833
- url = parts ? (parts[1]||url_or_hash) : url_or_hash;
834
- id = parts ? String(parts[2]||'') : '';
835
-
836
- // Return
837
- return id||false;
838
- };
839
-
840
- /**
841
- * History.isTraditionalAnchor
842
- * Checks to see if the url is a traditional anchor or not
843
- * @param {String} url_or_hash
844
- * @return {Boolean}
845
- */
846
- History.isTraditionalAnchor = function(url_or_hash){
847
- // Check
848
- var isTraditional = !(/[\/\?\.]/.test(url_or_hash));
849
-
850
- // Return
851
- return isTraditional;
852
- };
853
-
854
- /**
855
- * History.extractState
856
- * Get a State by it's URL or Hash
857
- * @param {String} url_or_hash
858
- * @return {State|null}
859
- */
860
- History.extractState = function(url_or_hash,create){
861
- // Prepare
862
- var State = null, id, url;
863
- create = create||false;
864
-
865
- // Fetch SUID
866
- id = History.extractId(url_or_hash);
867
- if ( id ) {
868
- State = History.getStateById(id);
869
- }
870
-
871
- // Fetch SUID returned no State
872
- if ( !State ) {
873
- // Fetch URL
874
- url = History.getFullUrl(url_or_hash);
875
-
876
- // Check URL
877
- id = History.getIdByUrl(url)||false;
878
- if ( id ) {
879
- State = History.getStateById(id);
880
- }
881
-
882
- // Create State
883
- if ( !State && create && !History.isTraditionalAnchor(url_or_hash) ) {
884
- State = History.createStateObject(null,null,url);
885
- }
886
- }
887
-
888
- // Return
889
- return State;
890
- };
891
-
892
- /**
893
- * History.getIdByUrl()
894
- * Get a State ID by a State URL
895
- */
896
- History.getIdByUrl = function(url){
897
- // Fetch
898
- var id = History.urlToId[url] || History.store.urlToId[url] || undefined;
899
-
900
- // Return
901
- return id;
902
- };
903
-
904
- /**
905
- * History.getLastSavedState()
906
- * Get an object containing the data, title and url of the current state
907
- * @return {Object} State
908
- */
909
- History.getLastSavedState = function(){
910
- return History.savedStates[History.savedStates.length-1]||undefined;
911
- };
912
-
913
- /**
914
- * History.getLastStoredState()
915
- * Get an object containing the data, title and url of the current state
916
- * @return {Object} State
917
- */
918
- History.getLastStoredState = function(){
919
- return History.storedStates[History.storedStates.length-1]||undefined;
920
- };
921
-
922
- /**
923
- * History.hasUrlDuplicate
924
- * Checks if a Url will have a url conflict
925
- * @param {Object} newState
926
- * @return {Boolean} hasDuplicate
927
- */
928
- History.hasUrlDuplicate = function(newState) {
929
- // Prepare
930
- var hasDuplicate = false,
931
- oldState;
932
-
933
- // Fetch
934
- oldState = History.extractState(newState.url);
935
-
936
- // Check
937
- hasDuplicate = oldState && oldState.id !== newState.id;
938
-
939
- // Return
940
- return hasDuplicate;
941
- };
942
-
943
- /**
944
- * History.storeState
945
- * Store a State
946
- * @param {Object} newState
947
- * @return {Object} newState
948
- */
949
- History.storeState = function(newState){
950
- // Store the State
951
- History.urlToId[newState.url] = newState.id;
952
-
953
- // Push the State
954
- History.storedStates.push(History.cloneObject(newState));
955
-
956
- // Return newState
957
- return newState;
958
- };
959
-
960
- /**
961
- * History.isLastSavedState(newState)
962
- * Tests to see if the state is the last state
963
- * @param {Object} newState
964
- * @return {boolean} isLast
965
- */
966
- History.isLastSavedState = function(newState){
967
- // Prepare
968
- var isLast = false,
969
- newId, oldState, oldId;
970
-
971
- // Check
972
- if ( History.savedStates.length ) {
973
- newId = newState.id;
974
- oldState = History.getLastSavedState();
975
- oldId = oldState.id;
976
-
977
- // Check
978
- isLast = (newId === oldId);
979
- }
980
-
981
- // Return
982
- return isLast;
983
- };
984
-
985
- /**
986
- * History.saveState
987
- * Push a State
988
- * @param {Object} newState
989
- * @return {boolean} changed
990
- */
991
- History.saveState = function(newState){
992
- // Check Hash
993
- if ( History.isLastSavedState(newState) ) {
994
- return false;
995
- }
996
-
997
- // Push the State
998
- History.savedStates.push(History.cloneObject(newState));
999
-
1000
- // Return true
1001
- return true;
1002
- };
1003
-
1004
- /**
1005
- * History.getStateByIndex()
1006
- * Gets a state by the index
1007
- * @param {integer} index
1008
- * @return {Object}
1009
- */
1010
- History.getStateByIndex = function(index){
1011
- // Prepare
1012
- var State = null;
1013
-
1014
- // Handle
1015
- if ( typeof index === 'undefined' ) {
1016
- // Get the last inserted
1017
- State = History.savedStates[History.savedStates.length-1];
1018
- }
1019
- else if ( index < 0 ) {
1020
- // Get from the end
1021
- State = History.savedStates[History.savedStates.length+index];
1022
- }
1023
- else {
1024
- // Get from the beginning
1025
- State = History.savedStates[index];
1026
- }
1027
-
1028
- // Return State
1029
- return State;
1030
- };
1031
-
1032
-
1033
- // ====================================================================
1034
- // Hash Helpers
1035
-
1036
- /**
1037
- * History.getHash()
1038
- * Gets the current document hash
1039
- * @return {string}
1040
- */
1041
- History.getHash = function(){
1042
- var hash = History.unescapeHash(document.location.hash);
1043
- return hash;
1044
- };
1045
-
1046
- /**
1047
- * History.unescapeString()
1048
- * Unescape a string
1049
- * @param {String} str
1050
- * @return {string}
1051
- */
1052
- History.unescapeString = function(str){
1053
- // Prepare
1054
- var result = str,
1055
- tmp;
1056
-
1057
- // Unescape hash
1058
- while ( true ) {
1059
- tmp = window.unescape(result);
1060
- if ( tmp === result ) {
1061
- break;
1062
- }
1063
- result = tmp;
1064
- }
1065
-
1066
- // Return result
1067
- return result;
1068
- };
1069
-
1070
- /**
1071
- * History.unescapeHash()
1072
- * normalize and Unescape a Hash
1073
- * @param {String} hash
1074
- * @return {string}
1075
- */
1076
- History.unescapeHash = function(hash){
1077
- // Prepare
1078
- var result = History.normalizeHash(hash);
1079
-
1080
- // Unescape hash
1081
- result = History.unescapeString(result);
1082
-
1083
- // Return result
1084
- return result;
1085
- };
1086
-
1087
- /**
1088
- * History.normalizeHash()
1089
- * normalize a hash across browsers
1090
- * @return {string}
1091
- */
1092
- History.normalizeHash = function(hash){
1093
- // Prepare
1094
- var result = hash.replace(/[^#]*#/,'').replace(/#.*/, '');
1095
-
1096
- // Return result
1097
- return result;
1098
- };
1099
-
1100
- /**
1101
- * History.setHash(hash)
1102
- * Sets the document hash
1103
- * @param {string} hash
1104
- * @return {History}
1105
- */
1106
- History.setHash = function(hash,queue){
1107
- // Prepare
1108
- var adjustedHash, State, pageUrl;
1109
-
1110
- // Handle Queueing
1111
- if ( queue !== false && History.busy() ) {
1112
- // Wait + Push to Queue
1113
- //History.debug('History.setHash: we must wait', arguments);
1114
- History.pushQueue({
1115
- scope: History,
1116
- callback: History.setHash,
1117
- args: arguments,
1118
- queue: queue
1119
- });
1120
- return false;
1121
- }
1122
-
1123
- // Log
1124
- //History.debug('History.setHash: called',hash);
1125
-
1126
- // Prepare
1127
- adjustedHash = History.escapeHash(hash);
1128
-
1129
- // Make Busy + Continue
1130
- History.busy(true);
1131
-
1132
- // Check if hash is a state
1133
- State = History.extractState(hash,true);
1134
- if ( State && !History.emulated.pushState ) {
1135
- // Hash is a state so skip the setHash
1136
- //History.debug('History.setHash: Hash is a state so skipping the hash set with a direct pushState call',arguments);
1137
-
1138
- // PushState
1139
- History.pushState(State.data,State.title,State.url,false);
1140
- }
1141
- else if ( document.location.hash !== adjustedHash ) {
1142
- // Hash is a proper hash, so apply it
1143
-
1144
- // Handle browser bugs
1145
- if ( History.bugs.setHash ) {
1146
- // Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249
1147
-
1148
- // Fetch the base page
1149
- pageUrl = History.getPageUrl();
1150
-
1151
- // Safari hash apply
1152
- History.pushState(null,null,pageUrl+'#'+adjustedHash,false);
1153
- }
1154
- else {
1155
- // Normal hash apply
1156
- document.location.hash = adjustedHash;
1157
- }
1158
- }
1159
-
1160
- // Chain
1161
- return History;
1162
- };
1163
-
1164
- /**
1165
- * History.escape()
1166
- * normalize and Escape a Hash
1167
- * @return {string}
1168
- */
1169
- History.escapeHash = function(hash){
1170
- // Prepare
1171
- var result = History.normalizeHash(hash);
1172
-
1173
- // Escape hash
1174
- result = window.escape(result);
1175
-
1176
- // IE6 Escape Bug
1177
- if ( !History.bugs.hashEscape ) {
1178
- // Restore common parts
1179
- result = result
1180
- .replace(/\%21/g,'!')
1181
- .replace(/\%26/g,'&')
1182
- .replace(/\%3D/g,'=')
1183
- .replace(/\%3F/g,'?');
1184
- }
1185
-
1186
- // Return result
1187
- return result;
1188
- };
1189
-
1190
- /**
1191
- * History.getHashByUrl(url)
1192
- * Extracts the Hash from a URL
1193
- * @param {string} url
1194
- * @return {string} url
1195
- */
1196
- History.getHashByUrl = function(url){
1197
- // Extract the hash
1198
- var hash = String(url)
1199
- .replace(/([^#]*)#?([^#]*)#?(.*)/, '$2')
1200
- ;
1201
-
1202
- // Unescape hash
1203
- hash = History.unescapeHash(hash);
1204
-
1205
- // Return hash
1206
- return hash;
1207
- };
1208
-
1209
- /**
1210
- * History.setTitle(title)
1211
- * Applies the title to the document
1212
- * @param {State} newState
1213
- * @return {Boolean}
1214
- */
1215
- History.setTitle = function(newState){
1216
- // Prepare
1217
- var title = newState.title,
1218
- firstState;
1219
-
1220
- // Initial
1221
- if ( !title ) {
1222
- firstState = History.getStateByIndex(0);
1223
- if ( firstState && firstState.url === newState.url ) {
1224
- title = firstState.title||History.options.initialTitle;
1225
- }
1226
- }
1227
-
1228
- // Apply
1229
- try {
1230
- document.getElementsByTagName('title')[0].innerHTML = title.replace('<','&lt;').replace('>','&gt;').replace(' & ',' &amp; ');
1231
- }
1232
- catch ( Exception ) { }
1233
- document.title = title;
1234
-
1235
- // Chain
1236
- return History;
1237
- };
1238
-
1239
-
1240
- // ====================================================================
1241
- // Queueing
1242
-
1243
- /**
1244
- * History.queues
1245
- * The list of queues to use
1246
- * First In, First Out
1247
- */
1248
- History.queues = [];
1249
-
1250
- /**
1251
- * History.busy(value)
1252
- * @param {boolean} value [optional]
1253
- * @return {boolean} busy
1254
- */
1255
- History.busy = function(value){
1256
- // Apply
1257
- if ( typeof value !== 'undefined' ) {
1258
- //History.debug('History.busy: changing ['+(History.busy.flag||false)+'] to ['+(value||false)+']', History.queues.length);
1259
- History.busy.flag = value;
1260
- }
1261
- // Default
1262
- else if ( typeof History.busy.flag === 'undefined' ) {
1263
- History.busy.flag = false;
1264
- }
1265
-
1266
- // Queue
1267
- if ( !History.busy.flag ) {
1268
- // Execute the next item in the queue
1269
- clearTimeout(History.busy.timeout);
1270
- var fireNext = function(){
1271
- var i, queue, item;
1272
- if ( History.busy.flag ) return;
1273
- for ( i=History.queues.length-1; i >= 0; --i ) {
1274
- queue = History.queues[i];
1275
- if ( queue.length === 0 ) continue;
1276
- item = queue.shift();
1277
- History.fireQueueItem(item);
1278
- History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
1279
- }
1280
- };
1281
- History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
1282
- }
1283
-
1284
- // Return
1285
- return History.busy.flag;
1286
- };
1287
-
1288
- /**
1289
- * History.busy.flag
1290
- */
1291
- History.busy.flag = false;
1292
-
1293
- /**
1294
- * History.fireQueueItem(item)
1295
- * Fire a Queue Item
1296
- * @param {Object} item
1297
- * @return {Mixed} result
1298
- */
1299
- History.fireQueueItem = function(item){
1300
- return item.callback.apply(item.scope||History,item.args||[]);
1301
- };
1302
-
1303
- /**
1304
- * History.pushQueue(callback,args)
1305
- * Add an item to the queue
1306
- * @param {Object} item [scope,callback,args,queue]
1307
- */
1308
- History.pushQueue = function(item){
1309
- // Prepare the queue
1310
- History.queues[item.queue||0] = History.queues[item.queue||0]||[];
1311
-
1312
- // Add to the queue
1313
- History.queues[item.queue||0].push(item);
1314
-
1315
- // Chain
1316
- return History;
1317
- };
1318
-
1319
- /**
1320
- * History.queue (item,queue), (func,queue), (func), (item)
1321
- * Either firs the item now if not busy, or adds it to the queue
1322
- */
1323
- History.queue = function(item,queue){
1324
- // Prepare
1325
- if ( typeof item === 'function' ) {
1326
- item = {
1327
- callback: item
1328
- };
1329
- }
1330
- if ( typeof queue !== 'undefined' ) {
1331
- item.queue = queue;
1332
- }
1333
-
1334
- // Handle
1335
- if ( History.busy() ) {
1336
- History.pushQueue(item);
1337
- } else {
1338
- History.fireQueueItem(item);
1339
- }
1340
-
1341
- // Chain
1342
- return History;
1343
- };
1344
-
1345
- /**
1346
- * History.clearQueue()
1347
- * Clears the Queue
1348
- */
1349
- History.clearQueue = function(){
1350
- History.busy.flag = false;
1351
- History.queues = [];
1352
- return History;
1353
- };
1354
-
1355
-
1356
- // ====================================================================
1357
- // IE Bug Fix
1358
-
1359
- /**
1360
- * History.stateChanged
1361
- * States whether or not the state has changed since the last double check was initialised
1362
- */
1363
- History.stateChanged = false;
1364
-
1365
- /**
1366
- * History.doubleChecker
1367
- * Contains the timeout used for the double checks
1368
- */
1369
- History.doubleChecker = false;
1370
-
1371
- /**
1372
- * History.doubleCheckComplete()
1373
- * Complete a double check
1374
- * @return {History}
1375
- */
1376
- History.doubleCheckComplete = function(){
1377
- // Update
1378
- History.stateChanged = true;
1379
-
1380
- // Clear
1381
- History.doubleCheckClear();
1382
-
1383
- // Chain
1384
- return History;
1385
- };
1386
-
1387
- /**
1388
- * History.doubleCheckClear()
1389
- * Clear a double check
1390
- * @return {History}
1391
- */
1392
- History.doubleCheckClear = function(){
1393
- // Clear
1394
- if ( History.doubleChecker ) {
1395
- clearTimeout(History.doubleChecker);
1396
- History.doubleChecker = false;
1397
- }
1398
-
1399
- // Chain
1400
- return History;
1401
- };
1402
-
1403
- /**
1404
- * History.doubleCheck()
1405
- * Create a double check
1406
- * @return {History}
1407
- */
1408
- History.doubleCheck = function(tryAgain){
1409
- // Reset
1410
- History.stateChanged = false;
1411
- History.doubleCheckClear();
1412
-
1413
- // Fix IE6,IE7 bug where calling history.back or history.forward does not actually change the hash (whereas doing it manually does)
1414
- // Fix Safari 5 bug where sometimes the state does not change: https://bugs.webkit.org/show_bug.cgi?id=42940
1415
- if ( History.bugs.ieDoubleCheck ) {
1416
- // Apply Check
1417
- History.doubleChecker = setTimeout(
1418
- function(){
1419
- History.doubleCheckClear();
1420
- if ( !History.stateChanged ) {
1421
- //History.debug('History.doubleCheck: State has not yet changed, trying again', arguments);
1422
- // Re-Attempt
1423
- tryAgain();
1424
- }
1425
- return true;
1426
- },
1427
- History.options.doubleCheckInterval
1428
- );
1429
- }
1430
-
1431
- // Chain
1432
- return History;
1433
- };
1434
-
1435
-
1436
- // ====================================================================
1437
- // Safari Bug Fix
1438
-
1439
- /**
1440
- * History.safariStatePoll()
1441
- * Poll the current state
1442
- * @return {History}
1443
- */
1444
- History.safariStatePoll = function(){
1445
- // Poll the URL
1446
-
1447
- // Get the Last State which has the new URL
1448
- var
1449
- urlState = History.extractState(document.location.href),
1450
- newState;
1451
-
1452
- // Check for a difference
1453
- if ( !History.isLastSavedState(urlState) ) {
1454
- newState = urlState;
1455
- }
1456
- else {
1457
- return;
1458
- }
1459
-
1460
- // Check if we have a state with that url
1461
- // If not create it
1462
- if ( !newState ) {
1463
- //History.debug('History.safariStatePoll: new');
1464
- newState = History.createStateObject();
1465
- }
1466
-
1467
- // Apply the New State
1468
- //History.debug('History.safariStatePoll: trigger');
1469
- History.Adapter.trigger(window,'popstate');
1470
-
1471
- // Chain
1472
- return History;
1473
- };
1474
-
1475
-
1476
- // ====================================================================
1477
- // State Aliases
1478
-
1479
- /**
1480
- * History.back(queue)
1481
- * Send the browser history back one item
1482
- * @param {Integer} queue [optional]
1483
- */
1484
- History.back = function(queue){
1485
- //History.debug('History.back: called', arguments);
1486
-
1487
- // Handle Queueing
1488
- if ( queue !== false && History.busy() ) {
1489
- // Wait + Push to Queue
1490
- //History.debug('History.back: we must wait', arguments);
1491
- History.pushQueue({
1492
- scope: History,
1493
- callback: History.back,
1494
- args: arguments,
1495
- queue: queue
1496
- });
1497
- return false;
1498
- }
1499
-
1500
- // Make Busy + Continue
1501
- History.busy(true);
1502
-
1503
- // Fix certain browser bugs that prevent the state from changing
1504
- History.doubleCheck(function(){
1505
- History.back(false);
1506
- });
1507
-
1508
- // Go back
1509
- history.go(-1);
1510
-
1511
- // End back closure
1512
- return true;
1513
- };
1514
-
1515
- /**
1516
- * History.forward(queue)
1517
- * Send the browser history forward one item
1518
- * @param {Integer} queue [optional]
1519
- */
1520
- History.forward = function(queue){
1521
- //History.debug('History.forward: called', arguments);
1522
-
1523
- // Handle Queueing
1524
- if ( queue !== false && History.busy() ) {
1525
- // Wait + Push to Queue
1526
- //History.debug('History.forward: we must wait', arguments);
1527
- History.pushQueue({
1528
- scope: History,
1529
- callback: History.forward,
1530
- args: arguments,
1531
- queue: queue
1532
- });
1533
- return false;
1534
- }
1535
-
1536
- // Make Busy + Continue
1537
- History.busy(true);
1538
-
1539
- // Fix certain browser bugs that prevent the state from changing
1540
- History.doubleCheck(function(){
1541
- History.forward(false);
1542
- });
1543
-
1544
- // Go forward
1545
- history.go(1);
1546
-
1547
- // End forward closure
1548
- return true;
1549
- };
1550
-
1551
- /**
1552
- * History.go(index,queue)
1553
- * Send the browser history back or forward index times
1554
- * @param {Integer} queue [optional]
1555
- */
1556
- History.go = function(index,queue){
1557
- //History.debug('History.go: called', arguments);
1558
-
1559
- // Prepare
1560
- var i;
1561
-
1562
- // Handle
1563
- if ( index > 0 ) {
1564
- // Forward
1565
- for ( i=1; i<=index; ++i ) {
1566
- History.forward(queue);
1567
- }
1568
- }
1569
- else if ( index < 0 ) {
1570
- // Backward
1571
- for ( i=-1; i>=index; --i ) {
1572
- History.back(queue);
1573
- }
1574
- }
1575
- else {
1576
- throw new Error('History.go: History.go requires a positive or negative integer passed.');
1577
- }
1578
-
1579
- // Chain
1580
- return History;
1581
- };
1582
-
1583
-
1584
- // ====================================================================
1585
- // HTML5 State Support
1586
-
1587
- // Non-Native pushState Implementation
1588
- if ( History.emulated.pushState ) {
1589
- /*
1590
- * Provide Skeleton for HTML4 Browsers
1591
- */
1592
-
1593
- // Prepare
1594
- var emptyFunction = function(){};
1595
- History.pushState = History.pushState||emptyFunction;
1596
- History.replaceState = History.replaceState||emptyFunction;
1597
- } // History.emulated.pushState
1598
-
1599
- // Native pushState Implementation
1600
- else {
1601
- /*
1602
- * Use native HTML5 History API Implementation
1603
- */
1604
-
1605
- /**
1606
- * History.onPopState(event,extra)
1607
- * Refresh the Current State
1608
- */
1609
- History.onPopState = function(event,extra){
1610
- // Prepare
1611
- var stateId = false, newState = false, currentHash, currentState;
1612
-
1613
- // Reset the double check
1614
- History.doubleCheckComplete();
1615
-
1616
- // Check for a Hash, and handle apporiatly
1617
- currentHash = History.getHash();
1618
- if ( currentHash ) {
1619
- // Expand Hash
1620
- currentState = History.extractState(currentHash||document.location.href,true);
1621
- if ( currentState ) {
1622
- // We were able to parse it, it must be a State!
1623
- // Let's forward to replaceState
1624
- //History.debug('History.onPopState: state anchor', currentHash, currentState);
1625
- History.replaceState(currentState.data, currentState.title, currentState.url, false);
1626
- }
1627
- else {
1628
- // Traditional Anchor
1629
- //History.debug('History.onPopState: traditional anchor', currentHash);
1630
- History.Adapter.trigger(window,'anchorchange');
1631
- History.busy(false);
1632
- }
1633
-
1634
- // We don't care for hashes
1635
- History.expectedStateId = false;
1636
- return false;
1637
- }
1638
-
1639
- // Ensure
1640
- stateId = History.Adapter.extractEventData('state',event,extra) || false;
1641
-
1642
- // Fetch State
1643
- if ( stateId ) {
1644
- // Vanilla: Back/forward button was used
1645
- newState = History.getStateById(stateId);
1646
- }
1647
- else if ( History.expectedStateId ) {
1648
- // Vanilla: A new state was pushed, and popstate was called manually
1649
- newState = History.getStateById(History.expectedStateId);
1650
- }
1651
- else {
1652
- // Initial State
1653
- newState = History.extractState(document.location.href);
1654
- }
1655
-
1656
- // The State did not exist in our store
1657
- if ( !newState ) {
1658
- // Regenerate the State
1659
- newState = History.createStateObject(null,null,document.location.href);
1660
- }
1661
-
1662
- // Clean
1663
- History.expectedStateId = false;
1664
-
1665
- // Check if we are the same state
1666
- if ( History.isLastSavedState(newState) ) {
1667
- // There has been no change (just the page's hash has finally propagated)
1668
- //History.debug('History.onPopState: no change', newState, History.savedStates);
1669
- History.busy(false);
1670
- return false;
1671
- }
1672
-
1673
- // Store the State
1674
- History.storeState(newState);
1675
- History.saveState(newState);
1676
-
1677
- // Force update of the title
1678
- History.setTitle(newState);
1679
-
1680
- // Fire Our Event
1681
- History.Adapter.trigger(window,'statechange');
1682
- History.busy(false);
1683
-
1684
- // Return true
1685
- return true;
1686
- };
1687
- History.Adapter.bind(window,'popstate',History.onPopState);
1688
-
1689
- /**
1690
- * History.pushState(data,title,url)
1691
- * Add a new State to the history object, become it, and trigger onpopstate
1692
- * We have to trigger for HTML4 compatibility
1693
- * @param {object} data
1694
- * @param {string} title
1695
- * @param {string} url
1696
- * @return {true}
1697
- */
1698
- History.pushState = function(data,title,url,queue){
1699
- //History.debug('History.pushState: called', arguments);
1700
-
1701
- // Check the State
1702
- if ( History.getHashByUrl(url) && History.emulated.pushState ) {
1703
- throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
1704
- }
1705
-
1706
- // Handle Queueing
1707
- if ( queue !== false && History.busy() ) {
1708
- // Wait + Push to Queue
1709
- //History.debug('History.pushState: we must wait', arguments);
1710
- History.pushQueue({
1711
- scope: History,
1712
- callback: History.pushState,
1713
- args: arguments,
1714
- queue: queue
1715
- });
1716
- return false;
1717
- }
1718
-
1719
- // Make Busy + Continue
1720
- History.busy(true);
1721
-
1722
- // Create the newState
1723
- var newState = History.createStateObject(data,title,url);
1724
-
1725
- // Check it
1726
- if ( History.isLastSavedState(newState) ) {
1727
- // Won't be a change
1728
- History.busy(false);
1729
- }
1730
- else {
1731
- // Store the newState
1732
- History.storeState(newState);
1733
- History.expectedStateId = newState.id;
1734
-
1735
- // Push the newState
1736
- history.pushState(newState.id,newState.title,newState.url);
1737
-
1738
- // Fire HTML5 Event
1739
- History.Adapter.trigger(window,'popstate');
1740
- }
1741
-
1742
- // End pushState closure
1743
- return true;
1744
- };
1745
-
1746
- /**
1747
- * History.replaceState(data,title,url)
1748
- * Replace the State and trigger onpopstate
1749
- * We have to trigger for HTML4 compatibility
1750
- * @param {object} data
1751
- * @param {string} title
1752
- * @param {string} url
1753
- * @return {true}
1754
- */
1755
- History.replaceState = function(data,title,url,queue){
1756
- //History.debug('History.replaceState: called', arguments);
1757
-
1758
- // Check the State
1759
- if ( History.getHashByUrl(url) && History.emulated.pushState ) {
1760
- throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
1761
- }
1762
-
1763
- // Handle Queueing
1764
- if ( queue !== false && History.busy() ) {
1765
- // Wait + Push to Queue
1766
- //History.debug('History.replaceState: we must wait', arguments);
1767
- History.pushQueue({
1768
- scope: History,
1769
- callback: History.replaceState,
1770
- args: arguments,
1771
- queue: queue
1772
- });
1773
- return false;
1774
- }
1775
-
1776
- // Make Busy + Continue
1777
- History.busy(true);
1778
-
1779
- // Create the newState
1780
- var newState = History.createStateObject(data,title,url);
1781
-
1782
- // Check it
1783
- if ( History.isLastSavedState(newState) ) {
1784
- // Won't be a change
1785
- History.busy(false);
1786
- }
1787
- else {
1788
- // Store the newState
1789
- History.storeState(newState);
1790
- History.expectedStateId = newState.id;
1791
-
1792
- // Push the newState
1793
- history.replaceState(newState.id,newState.title,newState.url);
1794
-
1795
- // Fire HTML5 Event
1796
- History.Adapter.trigger(window,'popstate');
1797
- }
1798
-
1799
- // End replaceState closure
1800
- return true;
1801
- };
1802
-
1803
- } // !History.emulated.pushState
1804
-
1805
-
1806
- // ====================================================================
1807
- // Initialise
1808
-
1809
- /**
1810
- * Load the Store
1811
- */
1812
- if ( sessionStorage ) {
1813
- // Fetch
1814
- try {
1815
- History.store = JSON.parse(sessionStorage.getItem('History.store'))||{};
1816
- }
1817
- catch ( err ) {
1818
- History.store = {};
1819
- }
1820
-
1821
- // Normalize
1822
- History.normalizeStore();
1823
- }
1824
- else {
1825
- // Default Load
1826
- History.store = {};
1827
- History.normalizeStore();
1828
- }
1829
-
1830
- /**
1831
- * Clear Intervals on exit to prevent memory leaks
1832
- */
1833
- History.Adapter.bind(window,"beforeunload",History.clearAllIntervals);
1834
- History.Adapter.bind(window,"unload",History.clearAllIntervals);
1835
-
1836
- /**
1837
- * Create the initial State
1838
- */
1839
- History.saveState(History.storeState(History.extractState(document.location.href,true)));
1840
-
1841
- /**
1842
- * Bind for Saving Store
1843
- */
1844
- if ( sessionStorage ) {
1845
- // When the page is closed
1846
- History.onUnload = function(){
1847
- // Prepare
1848
- var currentStore, item;
1849
-
1850
- // Fetch
1851
- try {
1852
- currentStore = JSON.parse(sessionStorage.getItem('History.store'))||{};
1853
- }
1854
- catch ( err ) {
1855
- currentStore = {};
1856
- }
1857
-
1858
- // Ensure
1859
- currentStore.idToState = currentStore.idToState || {};
1860
- currentStore.urlToId = currentStore.urlToId || {};
1861
- currentStore.stateToId = currentStore.stateToId || {};
1862
-
1863
- // Sync
1864
- for ( item in History.idToState ) {
1865
- if ( !History.idToState.hasOwnProperty(item) ) {
1866
- continue;
1867
- }
1868
- currentStore.idToState[item] = History.idToState[item];
1869
- }
1870
- for ( item in History.urlToId ) {
1871
- if ( !History.urlToId.hasOwnProperty(item) ) {
1872
- continue;
1873
- }
1874
- currentStore.urlToId[item] = History.urlToId[item];
1875
- }
1876
- for ( item in History.stateToId ) {
1877
- if ( !History.stateToId.hasOwnProperty(item) ) {
1878
- continue;
1879
- }
1880
- currentStore.stateToId[item] = History.stateToId[item];
1881
- }
1882
-
1883
- // Update
1884
- History.store = currentStore;
1885
- History.normalizeStore();
1886
-
1887
- // Store
1888
- sessionStorage.setItem('History.store',JSON.stringify(currentStore));
1889
- };
1890
-
1891
- // For Internet Explorer
1892
- History.intervalList.push(setInterval(History.onUnload,History.options.storeInterval));
1893
-
1894
- // For Other Browsers
1895
- History.Adapter.bind(window,'beforeunload',History.onUnload);
1896
- History.Adapter.bind(window,'unload',History.onUnload);
1897
-
1898
- // Both are enabled for consistency
1899
- }
1900
-
1901
- // Non-Native pushState Implementation
1902
- if ( !History.emulated.pushState ) {
1903
- // Be aware, the following is only for native pushState implementations
1904
- // If you are wanting to include something for all browsers
1905
- // Then include it above this if block
1906
-
1907
- /**
1908
- * Setup Safari Fix
1909
- */
1910
- if ( History.bugs.safariPoll ) {
1911
- History.intervalList.push(setInterval(History.safariStatePoll, History.options.safariPollInterval));
1912
- }
1913
-
1914
- /**
1915
- * Ensure Cross Browser Compatibility
1916
- */
1917
- if ( navigator.vendor === 'Apple Computer, Inc.' || (navigator.appCodeName||'') === 'Mozilla' ) {
1918
- /**
1919
- * Fix Safari HashChange Issue
1920
- */
1921
-
1922
- // Setup Alias
1923
- History.Adapter.bind(window,'hashchange',function(){
1924
- History.Adapter.trigger(window,'popstate');
1925
- });
1926
-
1927
- // Initialise Alias
1928
- if ( History.getHash() ) {
1929
- History.Adapter.onDomLoad(function(){
1930
- History.Adapter.trigger(window,'hashchange');
1931
- });
1932
- }
1933
- }
1934
-
1935
- } // !History.emulated.pushState
1936
-
1937
-
1938
- }; // History.initCore
1939
-
1940
- // Try and Initialise History
1941
- History.init();
1942
-
1943
- })(window);
1
+ /**
2
+ * History.js Core
3
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
4
+ * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
5
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
6
+ */
7
+
8
+ (function(window,undefined){
9
+ "use strict";
10
+
11
+ // ========================================================================
12
+ // Initialise
13
+
14
+ // Localise Globals
15
+ var
16
+ console = window.console||undefined, // Prevent a JSLint complain
17
+ document = window.document, // Make sure we are using the correct document
18
+ navigator = window.navigator, // Make sure we are using the correct navigator
19
+ sessionStorage = window.sessionStorage||false, // sessionStorage
20
+ setTimeout = window.setTimeout,
21
+ clearTimeout = window.clearTimeout,
22
+ setInterval = window.setInterval,
23
+ clearInterval = window.clearInterval,
24
+ JSON = window.JSON,
25
+ alert = window.alert,
26
+ History = window.History = window.History||{}, // Public History Object
27
+ history = window.history; // Old History Object
28
+
29
+ // MooTools Compatibility
30
+ JSON.stringify = JSON.stringify||JSON.encode;
31
+ JSON.parse = JSON.parse||JSON.decode;
32
+
33
+ // Check Existence
34
+ if ( typeof History.init !== 'undefined' ) {
35
+ throw new Error('History.js Core has already been loaded...');
36
+ }
37
+
38
+ // Initialise History
39
+ History.init = function(){
40
+ // Check Load Status of Adapter
41
+ if ( typeof History.Adapter === 'undefined' ) {
42
+ return false;
43
+ }
44
+
45
+ // Check Load Status of Core
46
+ if ( typeof History.initCore !== 'undefined' ) {
47
+ History.initCore();
48
+ }
49
+
50
+ // Check Load Status of HTML4 Support
51
+ if ( typeof History.initHtml4 !== 'undefined' ) {
52
+ History.initHtml4();
53
+ }
54
+
55
+ // Return true
56
+ return true;
57
+ };
58
+
59
+
60
+ // ========================================================================
61
+ // Initialise Core
62
+
63
+ // Initialise Core
64
+ History.initCore = function(){
65
+ // Initialise
66
+ if ( typeof History.initCore.initialized !== 'undefined' ) {
67
+ // Already Loaded
68
+ return false;
69
+ }
70
+ else {
71
+ History.initCore.initialized = true;
72
+ }
73
+
74
+
75
+ // ====================================================================
76
+ // Options
77
+
78
+ /**
79
+ * History.options
80
+ * Configurable options
81
+ */
82
+ History.options = History.options||{};
83
+
84
+ /**
85
+ * History.options.hashChangeInterval
86
+ * How long should the interval be before hashchange checks
87
+ */
88
+ History.options.hashChangeInterval = History.options.hashChangeInterval || 100;
89
+
90
+ /**
91
+ * History.options.safariPollInterval
92
+ * How long should the interval be before safari poll checks
93
+ */
94
+ History.options.safariPollInterval = History.options.safariPollInterval || 500;
95
+
96
+ /**
97
+ * History.options.doubleCheckInterval
98
+ * How long should the interval be before we perform a double check
99
+ */
100
+ History.options.doubleCheckInterval = History.options.doubleCheckInterval || 500;
101
+
102
+ /**
103
+ * History.options.storeInterval
104
+ * How long should we wait between store calls
105
+ */
106
+ History.options.storeInterval = History.options.storeInterval || 1000;
107
+
108
+ /**
109
+ * History.options.busyDelay
110
+ * How long should we wait between busy events
111
+ */
112
+ History.options.busyDelay = History.options.busyDelay || 250;
113
+
114
+ /**
115
+ * History.options.debug
116
+ * If true will enable debug messages to be logged
117
+ */
118
+ History.options.debug = History.options.debug || false;
119
+
120
+ /**
121
+ * History.options.initialTitle
122
+ * What is the title of the initial state
123
+ */
124
+ History.options.initialTitle = History.options.initialTitle || document.title;
125
+
126
+
127
+ // ====================================================================
128
+ // Interval record
129
+
130
+ /**
131
+ * History.intervalList
132
+ * List of intervals set, to be cleared when document is unloaded.
133
+ */
134
+ History.intervalList = [];
135
+
136
+ /**
137
+ * History.clearAllIntervals
138
+ * Clears all setInterval instances.
139
+ */
140
+ History.clearAllIntervals = function(){
141
+ var i, il = History.intervalList;
142
+ if (typeof il !== "undefined" && il !== null) {
143
+ for (i = 0; i < il.length; i++) {
144
+ clearInterval(il[i]);
145
+ }
146
+ History.intervalList = null;
147
+ }
148
+ };
149
+
150
+
151
+ // ====================================================================
152
+ // Debug
153
+
154
+ /**
155
+ * History.debug(message,...)
156
+ * Logs the passed arguments if debug enabled
157
+ */
158
+ History.debug = function(){
159
+ if ( (History.options.debug||false) ) {
160
+ History.log.apply(History,arguments);
161
+ }
162
+ };
163
+
164
+ /**
165
+ * History.log(message,...)
166
+ * Logs the passed arguments
167
+ */
168
+ History.log = function(){
169
+ // Prepare
170
+ var
171
+ consoleExists = !(typeof console === 'undefined' || typeof console.log === 'undefined' || typeof console.log.apply === 'undefined'),
172
+ textarea = document.getElementById('log'),
173
+ message,
174
+ i,n,
175
+ args,arg
176
+ ;
177
+
178
+ // Write to Console
179
+ if ( consoleExists ) {
180
+ args = Array.prototype.slice.call(arguments);
181
+ message = args.shift();
182
+ if ( typeof console.debug !== 'undefined' ) {
183
+ console.debug.apply(console,[message,args]);
184
+ }
185
+ else {
186
+ console.log.apply(console,[message,args]);
187
+ }
188
+ }
189
+ else {
190
+ message = ("\n"+arguments[0]+"\n");
191
+ }
192
+
193
+ // Write to log
194
+ for ( i=1,n=arguments.length; i<n; ++i ) {
195
+ arg = arguments[i];
196
+ if ( typeof arg === 'object' && typeof JSON !== 'undefined' ) {
197
+ try {
198
+ arg = JSON.stringify(arg);
199
+ }
200
+ catch ( Exception ) {
201
+ // Recursive Object
202
+ }
203
+ }
204
+ message += "\n"+arg+"\n";
205
+ }
206
+
207
+ // Textarea
208
+ if ( textarea ) {
209
+ textarea.value += message+"\n-----\n";
210
+ textarea.scrollTop = textarea.scrollHeight - textarea.clientHeight;
211
+ }
212
+ // No Textarea, No Console
213
+ else if ( !consoleExists ) {
214
+ alert(message);
215
+ }
216
+
217
+ // Return true
218
+ return true;
219
+ };
220
+
221
+
222
+ // ====================================================================
223
+ // Emulated Status
224
+
225
+ /**
226
+ * History.getInternetExplorerMajorVersion()
227
+ * Get's the major version of Internet Explorer
228
+ * @return {integer}
229
+ * @license Public Domain
230
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
231
+ * @author James Padolsey <https://gist.github.com/527683>
232
+ */
233
+ History.getInternetExplorerMajorVersion = function(){
234
+ var result = History.getInternetExplorerMajorVersion.cached =
235
+ (typeof History.getInternetExplorerMajorVersion.cached !== 'undefined')
236
+ ? History.getInternetExplorerMajorVersion.cached
237
+ : (function(){
238
+ var v = 3,
239
+ div = document.createElement('div'),
240
+ all = div.getElementsByTagName('i');
241
+ while ( (div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->') && all[0] ) {}
242
+ return (v > 4) ? v : false;
243
+ })()
244
+ ;
245
+ return result;
246
+ };
247
+
248
+ /**
249
+ * History.isInternetExplorer()
250
+ * Are we using Internet Explorer?
251
+ * @return {boolean}
252
+ * @license Public Domain
253
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
254
+ */
255
+ History.isInternetExplorer = function(){
256
+ var result =
257
+ History.isInternetExplorer.cached =
258
+ (typeof History.isInternetExplorer.cached !== 'undefined')
259
+ ? History.isInternetExplorer.cached
260
+ : Boolean(History.getInternetExplorerMajorVersion())
261
+ ;
262
+ return result;
263
+ };
264
+
265
+ /**
266
+ * History.emulated
267
+ * Which features require emulating?
268
+ */
269
+ History.emulated = {
270
+ pushState: !Boolean(
271
+ window.history && window.history.pushState && window.history.replaceState
272
+ && !(
273
+ (/ 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) */
274
+ || (/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 */
275
+ )
276
+ ),
277
+ hashChange: Boolean(
278
+ !(('onhashchange' in window) || ('onhashchange' in document))
279
+ ||
280
+ (History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8)
281
+ )
282
+ };
283
+
284
+ /**
285
+ * History.enabled
286
+ * Is History enabled?
287
+ */
288
+ History.enabled = !History.emulated.pushState;
289
+
290
+ /**
291
+ * History.bugs
292
+ * Which bugs are present
293
+ */
294
+ History.bugs = {
295
+ /**
296
+ * Safari 5 and Safari iOS 4 fail to return to the correct state once a hash is replaced by a `replaceState` call
297
+ * https://bugs.webkit.org/show_bug.cgi?id=56249
298
+ */
299
+ setHash: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
300
+
301
+ /**
302
+ * Safari 5 and Safari iOS 4 sometimes fail to apply the state change under busy conditions
303
+ * https://bugs.webkit.org/show_bug.cgi?id=42940
304
+ */
305
+ safariPoll: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
306
+
307
+ /**
308
+ * MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function)
309
+ */
310
+ ieDoubleCheck: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8),
311
+
312
+ /**
313
+ * MSIE 6 requires the entire hash to be encoded for the hashes to trigger the onHashChange event
314
+ */
315
+ hashEscape: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 7)
316
+ };
317
+
318
+ /**
319
+ * History.isEmptyObject(obj)
320
+ * Checks to see if the Object is Empty
321
+ * @param {Object} obj
322
+ * @return {boolean}
323
+ */
324
+ History.isEmptyObject = function(obj) {
325
+ for ( var name in obj ) {
326
+ return false;
327
+ }
328
+ return true;
329
+ };
330
+
331
+ /**
332
+ * History.cloneObject(obj)
333
+ * Clones a object and eliminate all references to the original contexts
334
+ * @param {Object} obj
335
+ * @return {Object}
336
+ */
337
+ History.cloneObject = function(obj) {
338
+ var hash,newObj;
339
+ if ( obj ) {
340
+ hash = JSON.stringify(obj);
341
+ newObj = JSON.parse(hash);
342
+ }
343
+ else {
344
+ newObj = {};
345
+ }
346
+ return newObj;
347
+ };
348
+
349
+
350
+ // ====================================================================
351
+ // URL Helpers
352
+
353
+ /**
354
+ * History.getRootUrl()
355
+ * Turns "http://mysite.com/dir/page.html?asd" into "http://mysite.com"
356
+ * @return {String} rootUrl
357
+ */
358
+ History.getRootUrl = function(){
359
+ // Create
360
+ var rootUrl = document.location.protocol+'//'+(document.location.hostname||document.location.host);
361
+ if ( document.location.port||false ) {
362
+ rootUrl += ':'+document.location.port;
363
+ }
364
+ rootUrl += '/';
365
+
366
+ // Return
367
+ return rootUrl;
368
+ };
369
+
370
+ /**
371
+ * History.getBaseHref()
372
+ * Fetches the `href` attribute of the `<base href="...">` element if it exists
373
+ * @return {String} baseHref
374
+ */
375
+ History.getBaseHref = function(){
376
+ // Create
377
+ var
378
+ baseElements = document.getElementsByTagName('base'),
379
+ baseElement = null,
380
+ baseHref = '';
381
+
382
+ // Test for Base Element
383
+ if ( baseElements.length === 1 ) {
384
+ // Prepare for Base Element
385
+ baseElement = baseElements[0];
386
+ baseHref = baseElement.href.replace(/[^\/]+$/,'');
387
+ }
388
+
389
+ // Adjust trailing slash
390
+ baseHref = baseHref.replace(/\/+$/,'');
391
+ if ( baseHref ) baseHref += '/';
392
+
393
+ // Return
394
+ return baseHref;
395
+ };
396
+
397
+ /**
398
+ * History.getBaseUrl()
399
+ * Fetches the baseHref or basePageUrl or rootUrl (whichever one exists first)
400
+ * @return {String} baseUrl
401
+ */
402
+ History.getBaseUrl = function(){
403
+ // Create
404
+ var baseUrl = History.getBaseHref()||History.getBasePageUrl()||History.getRootUrl();
405
+
406
+ // Return
407
+ return baseUrl;
408
+ };
409
+
410
+ /**
411
+ * History.getPageUrl()
412
+ * Fetches the URL of the current page
413
+ * @return {String} pageUrl
414
+ */
415
+ History.getPageUrl = function(){
416
+ // Fetch
417
+ var
418
+ State = History.getState(false,false),
419
+ stateUrl = (State||{}).url||document.location.href,
420
+ pageUrl;
421
+
422
+ // Create
423
+ pageUrl = stateUrl.replace(/\/+$/,'').replace(/[^\/]+$/,function(part,index,string){
424
+ return (/\./).test(part) ? part : part+'/';
425
+ });
426
+
427
+ // Return
428
+ return pageUrl;
429
+ };
430
+
431
+ /**
432
+ * History.getBasePageUrl()
433
+ * Fetches the Url of the directory of the current page
434
+ * @return {String} basePageUrl
435
+ */
436
+ History.getBasePageUrl = function(){
437
+ // Create
438
+ var basePageUrl = document.location.href.replace(/[#\?].*/,'').replace(/[^\/]+$/,function(part,index,string){
439
+ return (/[^\/]$/).test(part) ? '' : part;
440
+ }).replace(/\/+$/,'')+'/';
441
+
442
+ // Return
443
+ return basePageUrl;
444
+ };
445
+
446
+ /**
447
+ * History.getFullUrl(url)
448
+ * Ensures that we have an absolute URL and not a relative URL
449
+ * @param {string} url
450
+ * @param {Boolean} allowBaseHref
451
+ * @return {string} fullUrl
452
+ */
453
+ History.getFullUrl = function(url,allowBaseHref){
454
+ // Prepare
455
+ var fullUrl = url, firstChar = url.substring(0,1);
456
+ allowBaseHref = (typeof allowBaseHref === 'undefined') ? true : allowBaseHref;
457
+
458
+ // Check
459
+ if ( /[a-z]+\:\/\//.test(url) ) {
460
+ // Full URL
461
+ }
462
+ else if ( firstChar === '/' ) {
463
+ // Root URL
464
+ fullUrl = History.getRootUrl()+url.replace(/^\/+/,'');
465
+ }
466
+ else if ( firstChar === '#' ) {
467
+ // Anchor URL
468
+ fullUrl = History.getPageUrl().replace(/#.*/,'')+url;
469
+ }
470
+ else if ( firstChar === '?' ) {
471
+ // Query URL
472
+ fullUrl = History.getPageUrl().replace(/[\?#].*/,'')+url;
473
+ }
474
+ else {
475
+ // Relative URL
476
+ if ( allowBaseHref ) {
477
+ fullUrl = History.getBaseUrl()+url.replace(/^(\.\/)+/,'');
478
+ } else {
479
+ fullUrl = History.getBasePageUrl()+url.replace(/^(\.\/)+/,'');
480
+ }
481
+ // We have an if condition above as we do not want hashes
482
+ // which are relative to the baseHref in our URLs
483
+ // as if the baseHref changes, then all our bookmarks
484
+ // would now point to different locations
485
+ // whereas the basePageUrl will always stay the same
486
+ }
487
+
488
+ // Return
489
+ return fullUrl.replace(/\#$/,'');
490
+ };
491
+
492
+ /**
493
+ * History.getShortUrl(url)
494
+ * Ensures that we have a relative URL and not a absolute URL
495
+ * @param {string} url
496
+ * @return {string} url
497
+ */
498
+ History.getShortUrl = function(url){
499
+ // Prepare
500
+ var shortUrl = url, baseUrl = History.getBaseUrl(), rootUrl = History.getRootUrl();
501
+
502
+ // Trim baseUrl
503
+ if ( History.emulated.pushState ) {
504
+ // We are in a if statement as when pushState is not emulated
505
+ // The actual url these short urls are relative to can change
506
+ // So within the same session, we the url may end up somewhere different
507
+ shortUrl = shortUrl.replace(baseUrl,'');
508
+ }
509
+
510
+ // Trim rootUrl
511
+ shortUrl = shortUrl.replace(rootUrl,'/');
512
+
513
+ // Ensure we can still detect it as a state
514
+ if ( History.isTraditionalAnchor(shortUrl) ) {
515
+ shortUrl = './'+shortUrl;
516
+ }
517
+
518
+ // Clean It
519
+ shortUrl = shortUrl.replace(/^(\.\/)+/g,'./').replace(/\#$/,'');
520
+
521
+ // Return
522
+ return shortUrl;
523
+ };
524
+
525
+
526
+ // ====================================================================
527
+ // State Storage
528
+
529
+ /**
530
+ * History.store
531
+ * The store for all session specific data
532
+ */
533
+ History.store = {};
534
+
535
+ /**
536
+ * History.idToState
537
+ * 1-1: State ID to State Object
538
+ */
539
+ History.idToState = History.idToState||{};
540
+
541
+ /**
542
+ * History.stateToId
543
+ * 1-1: State String to State ID
544
+ */
545
+ History.stateToId = History.stateToId||{};
546
+
547
+ /**
548
+ * History.urlToId
549
+ * 1-1: State URL to State ID
550
+ */
551
+ History.urlToId = History.urlToId||{};
552
+
553
+ /**
554
+ * History.storedStates
555
+ * Store the states in an array
556
+ */
557
+ History.storedStates = History.storedStates||[];
558
+
559
+ /**
560
+ * History.savedStates
561
+ * Saved the states in an array
562
+ */
563
+ History.savedStates = History.savedStates||[];
564
+
565
+ /**
566
+ * History.noramlizeStore()
567
+ * Noramlize the store by adding necessary values
568
+ */
569
+ History.normalizeStore = function(){
570
+ History.store.idToState = History.store.idToState||{};
571
+ History.store.urlToId = History.store.urlToId||{};
572
+ History.store.stateToId = History.store.stateToId||{};
573
+ };
574
+
575
+ /**
576
+ * History.getState()
577
+ * Get an object containing the data, title and url of the current state
578
+ * @param {Boolean} friendly
579
+ * @param {Boolean} create
580
+ * @return {Object} State
581
+ */
582
+ History.getState = function(friendly,create){
583
+ // Prepare
584
+ if ( typeof friendly === 'undefined' ) { friendly = true; }
585
+ if ( typeof create === 'undefined' ) { create = true; }
586
+
587
+ // Fetch
588
+ var State = History.getLastSavedState();
589
+
590
+ // Create
591
+ if ( !State && create ) {
592
+ State = History.createStateObject();
593
+ }
594
+
595
+ // Adjust
596
+ if ( friendly ) {
597
+ State = History.cloneObject(State);
598
+ State.url = State.cleanUrl||State.url;
599
+ }
600
+
601
+ // Return
602
+ return State;
603
+ };
604
+
605
+ /**
606
+ * History.getIdByState(State)
607
+ * Gets a ID for a State
608
+ * @param {State} newState
609
+ * @return {String} id
610
+ */
611
+ History.getIdByState = function(newState){
612
+
613
+ // Fetch ID
614
+ var id = History.extractId(newState.url),
615
+ str;
616
+
617
+ if ( !id ) {
618
+ // Find ID via State String
619
+ str = History.getStateString(newState);
620
+ if ( typeof History.stateToId[str] !== 'undefined' ) {
621
+ id = History.stateToId[str];
622
+ }
623
+ else if ( typeof History.store.stateToId[str] !== 'undefined' ) {
624
+ id = History.store.stateToId[str];
625
+ }
626
+ else {
627
+ // Generate a new ID
628
+ while ( true ) {
629
+ id = (new Date()).getTime() + String(Math.random()).replace(/\D/g,'');
630
+ if ( typeof History.idToState[id] === 'undefined' && typeof History.store.idToState[id] === 'undefined' ) {
631
+ break;
632
+ }
633
+ }
634
+
635
+ // Apply the new State to the ID
636
+ History.stateToId[str] = id;
637
+ History.idToState[id] = newState;
638
+ }
639
+ }
640
+
641
+ // Return ID
642
+ return id;
643
+ };
644
+
645
+ /**
646
+ * History.normalizeState(State)
647
+ * Expands a State Object
648
+ * @param {object} State
649
+ * @return {object}
650
+ */
651
+ History.normalizeState = function(oldState){
652
+ // Variables
653
+ var newState, dataNotEmpty;
654
+
655
+ // Prepare
656
+ if ( !oldState || (typeof oldState !== 'object') ) {
657
+ oldState = {};
658
+ }
659
+
660
+ // Check
661
+ if ( typeof oldState.normalized !== 'undefined' ) {
662
+ return oldState;
663
+ }
664
+
665
+ // Adjust
666
+ if ( !oldState.data || (typeof oldState.data !== 'object') ) {
667
+ oldState.data = {};
668
+ }
669
+
670
+ // ----------------------------------------------------------------
671
+
672
+ // Create
673
+ newState = {};
674
+ newState.normalized = true;
675
+ newState.title = oldState.title||'';
676
+ newState.url = History.getFullUrl(History.unescapeString(oldState.url||document.location.href));
677
+ newState.hash = History.getShortUrl(newState.url);
678
+ newState.data = History.cloneObject(oldState.data);
679
+
680
+ // Fetch ID
681
+ newState.id = History.getIdByState(newState);
682
+
683
+ // ----------------------------------------------------------------
684
+
685
+ // Clean the URL
686
+ newState.cleanUrl = newState.url.replace(/\??\&_suid.*/,'');
687
+ newState.url = newState.cleanUrl;
688
+
689
+ // Check to see if we have more than just a url
690
+ dataNotEmpty = !History.isEmptyObject(newState.data);
691
+
692
+ // Apply
693
+ if ( newState.title || dataNotEmpty ) {
694
+ // Add ID to Hash
695
+ newState.hash = History.getShortUrl(newState.url).replace(/\??\&_suid.*/,'');
696
+ if ( !/\?/.test(newState.hash) ) {
697
+ newState.hash += '?';
698
+ }
699
+ newState.hash += '&_suid='+newState.id;
700
+ }
701
+
702
+ // Create the Hashed URL
703
+ newState.hashedUrl = History.getFullUrl(newState.hash);
704
+
705
+ // ----------------------------------------------------------------
706
+
707
+ // Update the URL if we have a duplicate
708
+ if ( (History.emulated.pushState || History.bugs.safariPoll) && History.hasUrlDuplicate(newState) ) {
709
+ newState.url = newState.hashedUrl;
710
+ }
711
+
712
+ // ----------------------------------------------------------------
713
+
714
+ // Return
715
+ return newState;
716
+ };
717
+
718
+ /**
719
+ * History.createStateObject(data,title,url)
720
+ * Creates a object based on the data, title and url state params
721
+ * @param {object} data
722
+ * @param {string} title
723
+ * @param {string} url
724
+ * @return {object}
725
+ */
726
+ History.createStateObject = function(data,title,url){
727
+ // Hashify
728
+ var State = {
729
+ 'data': data,
730
+ 'title': title,
731
+ 'url': url
732
+ };
733
+
734
+ // Expand the State
735
+ State = History.normalizeState(State);
736
+
737
+ // Return object
738
+ return State;
739
+ };
740
+
741
+ /**
742
+ * History.getStateById(id)
743
+ * Get a state by it's UID
744
+ * @param {String} id
745
+ */
746
+ History.getStateById = function(id){
747
+ // Prepare
748
+ id = String(id);
749
+
750
+ // Retrieve
751
+ var State = History.idToState[id] || History.store.idToState[id] || undefined;
752
+
753
+ // Return State
754
+ return State;
755
+ };
756
+
757
+ /**
758
+ * Get a State's String
759
+ * @param {State} passedState
760
+ */
761
+ History.getStateString = function(passedState){
762
+ // Prepare
763
+ var State, cleanedState, str;
764
+
765
+ // Fetch
766
+ State = History.normalizeState(passedState);
767
+
768
+ // Clean
769
+ cleanedState = {
770
+ data: State.data,
771
+ title: passedState.title,
772
+ url: passedState.url
773
+ };
774
+
775
+ // Fetch
776
+ str = JSON.stringify(cleanedState);
777
+
778
+ // Return
779
+ return str;
780
+ };
781
+
782
+ /**
783
+ * Get a State's ID
784
+ * @param {State} passedState
785
+ * @return {String} id
786
+ */
787
+ History.getStateId = function(passedState){
788
+ // Prepare
789
+ var State, id;
790
+
791
+ // Fetch
792
+ State = History.normalizeState(passedState);
793
+
794
+ // Fetch
795
+ id = State.id;
796
+
797
+ // Return
798
+ return id;
799
+ };
800
+
801
+ /**
802
+ * History.getHashByState(State)
803
+ * Creates a Hash for the State Object
804
+ * @param {State} passedState
805
+ * @return {String} hash
806
+ */
807
+ History.getHashByState = function(passedState){
808
+ // Prepare
809
+ var State, hash;
810
+
811
+ // Fetch
812
+ State = History.normalizeState(passedState);
813
+
814
+ // Hash
815
+ hash = State.hash;
816
+
817
+ // Return
818
+ return hash;
819
+ };
820
+
821
+ /**
822
+ * History.extractId(url_or_hash)
823
+ * Get a State ID by it's URL or Hash
824
+ * @param {string} url_or_hash
825
+ * @return {string} id
826
+ */
827
+ History.extractId = function ( url_or_hash ) {
828
+ // Prepare
829
+ var id,parts,url;
830
+
831
+ // Extract
832
+ parts = /(.*)\&_suid=([0-9]+)$/.exec(url_or_hash);
833
+ url = parts ? (parts[1]||url_or_hash) : url_or_hash;
834
+ id = parts ? String(parts[2]||'') : '';
835
+
836
+ // Return
837
+ return id||false;
838
+ };
839
+
840
+ /**
841
+ * History.isTraditionalAnchor
842
+ * Checks to see if the url is a traditional anchor or not
843
+ * @param {String} url_or_hash
844
+ * @return {Boolean}
845
+ */
846
+ History.isTraditionalAnchor = function(url_or_hash){
847
+ // Check
848
+ var isTraditional = !(/[\/\?\.]/.test(url_or_hash));
849
+
850
+ // Return
851
+ return isTraditional;
852
+ };
853
+
854
+ /**
855
+ * History.extractState
856
+ * Get a State by it's URL or Hash
857
+ * @param {String} url_or_hash
858
+ * @return {State|null}
859
+ */
860
+ History.extractState = function(url_or_hash,create){
861
+ // Prepare
862
+ var State = null, id, url;
863
+ create = create||false;
864
+
865
+ // Fetch SUID
866
+ id = History.extractId(url_or_hash);
867
+ if ( id ) {
868
+ State = History.getStateById(id);
869
+ }
870
+
871
+ // Fetch SUID returned no State
872
+ if ( !State ) {
873
+ // Fetch URL
874
+ url = History.getFullUrl(url_or_hash);
875
+
876
+ // Check URL
877
+ id = History.getIdByUrl(url)||false;
878
+ if ( id ) {
879
+ State = History.getStateById(id);
880
+ }
881
+
882
+ // Create State
883
+ if ( !State && create && !History.isTraditionalAnchor(url_or_hash) ) {
884
+ State = History.createStateObject(null,null,url);
885
+ }
886
+ }
887
+
888
+ // Return
889
+ return State;
890
+ };
891
+
892
+ /**
893
+ * History.getIdByUrl()
894
+ * Get a State ID by a State URL
895
+ */
896
+ History.getIdByUrl = function(url){
897
+ // Fetch
898
+ var id = History.urlToId[url] || History.store.urlToId[url] || undefined;
899
+
900
+ // Return
901
+ return id;
902
+ };
903
+
904
+ /**
905
+ * History.getLastSavedState()
906
+ * Get an object containing the data, title and url of the current state
907
+ * @return {Object} State
908
+ */
909
+ History.getLastSavedState = function(){
910
+ return History.savedStates[History.savedStates.length-1]||undefined;
911
+ };
912
+
913
+ /**
914
+ * History.getLastStoredState()
915
+ * Get an object containing the data, title and url of the current state
916
+ * @return {Object} State
917
+ */
918
+ History.getLastStoredState = function(){
919
+ return History.storedStates[History.storedStates.length-1]||undefined;
920
+ };
921
+
922
+ /**
923
+ * History.hasUrlDuplicate
924
+ * Checks if a Url will have a url conflict
925
+ * @param {Object} newState
926
+ * @return {Boolean} hasDuplicate
927
+ */
928
+ History.hasUrlDuplicate = function(newState) {
929
+ // Prepare
930
+ var hasDuplicate = false,
931
+ oldState;
932
+
933
+ // Fetch
934
+ oldState = History.extractState(newState.url);
935
+
936
+ // Check
937
+ hasDuplicate = oldState && oldState.id !== newState.id;
938
+
939
+ // Return
940
+ return hasDuplicate;
941
+ };
942
+
943
+ /**
944
+ * History.storeState
945
+ * Store a State
946
+ * @param {Object} newState
947
+ * @return {Object} newState
948
+ */
949
+ History.storeState = function(newState){
950
+ // Store the State
951
+ History.urlToId[newState.url] = newState.id;
952
+
953
+ // Push the State
954
+ History.storedStates.push(History.cloneObject(newState));
955
+
956
+ // Return newState
957
+ return newState;
958
+ };
959
+
960
+ /**
961
+ * History.isLastSavedState(newState)
962
+ * Tests to see if the state is the last state
963
+ * @param {Object} newState
964
+ * @return {boolean} isLast
965
+ */
966
+ History.isLastSavedState = function(newState){
967
+ // Prepare
968
+ var isLast = false,
969
+ newId, oldState, oldId;
970
+
971
+ // Check
972
+ if ( History.savedStates.length ) {
973
+ newId = newState.id;
974
+ oldState = History.getLastSavedState();
975
+ oldId = oldState.id;
976
+
977
+ // Check
978
+ isLast = (newId === oldId);
979
+ }
980
+
981
+ // Return
982
+ return isLast;
983
+ };
984
+
985
+ /**
986
+ * History.saveState
987
+ * Push a State
988
+ * @param {Object} newState
989
+ * @return {boolean} changed
990
+ */
991
+ History.saveState = function(newState){
992
+ // Check Hash
993
+ if ( History.isLastSavedState(newState) ) {
994
+ return false;
995
+ }
996
+
997
+ // Push the State
998
+ History.savedStates.push(History.cloneObject(newState));
999
+
1000
+ // Return true
1001
+ return true;
1002
+ };
1003
+
1004
+ /**
1005
+ * History.getStateByIndex()
1006
+ * Gets a state by the index
1007
+ * @param {integer} index
1008
+ * @return {Object}
1009
+ */
1010
+ History.getStateByIndex = function(index){
1011
+ // Prepare
1012
+ var State = null;
1013
+
1014
+ // Handle
1015
+ if ( typeof index === 'undefined' ) {
1016
+ // Get the last inserted
1017
+ State = History.savedStates[History.savedStates.length-1];
1018
+ }
1019
+ else if ( index < 0 ) {
1020
+ // Get from the end
1021
+ State = History.savedStates[History.savedStates.length+index];
1022
+ }
1023
+ else {
1024
+ // Get from the beginning
1025
+ State = History.savedStates[index];
1026
+ }
1027
+
1028
+ // Return State
1029
+ return State;
1030
+ };
1031
+
1032
+
1033
+ // ====================================================================
1034
+ // Hash Helpers
1035
+
1036
+ /**
1037
+ * History.getHash()
1038
+ * Gets the current document hash
1039
+ * @return {string}
1040
+ */
1041
+ History.getHash = function(){
1042
+ var hash = History.unescapeHash(document.location.hash);
1043
+ return hash;
1044
+ };
1045
+
1046
+ /**
1047
+ * History.unescapeString()
1048
+ * Unescape a string
1049
+ * @param {String} str
1050
+ * @return {string}
1051
+ */
1052
+ History.unescapeString = function(str){
1053
+ // Prepare
1054
+ var result = str,
1055
+ tmp;
1056
+
1057
+ // Unescape hash
1058
+ while ( true ) {
1059
+ tmp = window.unescape(result);
1060
+ if ( tmp === result ) {
1061
+ break;
1062
+ }
1063
+ result = tmp;
1064
+ }
1065
+
1066
+ // Return result
1067
+ return result;
1068
+ };
1069
+
1070
+ /**
1071
+ * History.unescapeHash()
1072
+ * normalize and Unescape a Hash
1073
+ * @param {String} hash
1074
+ * @return {string}
1075
+ */
1076
+ History.unescapeHash = function(hash){
1077
+ // Prepare
1078
+ var result = History.normalizeHash(hash);
1079
+
1080
+ // Unescape hash
1081
+ result = History.unescapeString(result);
1082
+
1083
+ // Return result
1084
+ return result;
1085
+ };
1086
+
1087
+ /**
1088
+ * History.normalizeHash()
1089
+ * normalize a hash across browsers
1090
+ * @return {string}
1091
+ */
1092
+ History.normalizeHash = function(hash){
1093
+ // Prepare
1094
+ var result = hash.replace(/[^#]*#/,'').replace(/#.*/, '');
1095
+
1096
+ // Return result
1097
+ return result;
1098
+ };
1099
+
1100
+ /**
1101
+ * History.setHash(hash)
1102
+ * Sets the document hash
1103
+ * @param {string} hash
1104
+ * @return {History}
1105
+ */
1106
+ History.setHash = function(hash,queue){
1107
+ // Prepare
1108
+ var adjustedHash, State, pageUrl;
1109
+
1110
+ // Handle Queueing
1111
+ if ( queue !== false && History.busy() ) {
1112
+ // Wait + Push to Queue
1113
+ //History.debug('History.setHash: we must wait', arguments);
1114
+ History.pushQueue({
1115
+ scope: History,
1116
+ callback: History.setHash,
1117
+ args: arguments,
1118
+ queue: queue
1119
+ });
1120
+ return false;
1121
+ }
1122
+
1123
+ // Log
1124
+ //History.debug('History.setHash: called',hash);
1125
+
1126
+ // Prepare
1127
+ adjustedHash = History.escapeHash(hash);
1128
+
1129
+ // Make Busy + Continue
1130
+ History.busy(true);
1131
+
1132
+ // Check if hash is a state
1133
+ State = History.extractState(hash,true);
1134
+ if ( State && !History.emulated.pushState ) {
1135
+ // Hash is a state so skip the setHash
1136
+ //History.debug('History.setHash: Hash is a state so skipping the hash set with a direct pushState call',arguments);
1137
+
1138
+ // PushState
1139
+ History.pushState(State.data,State.title,State.url,false);
1140
+ }
1141
+ else if ( document.location.hash !== adjustedHash ) {
1142
+ // Hash is a proper hash, so apply it
1143
+
1144
+ // Handle browser bugs
1145
+ if ( History.bugs.setHash ) {
1146
+ // Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249
1147
+
1148
+ // Fetch the base page
1149
+ pageUrl = History.getPageUrl();
1150
+
1151
+ // Safari hash apply
1152
+ History.pushState(null,null,pageUrl+'#'+adjustedHash,false);
1153
+ }
1154
+ else {
1155
+ // Normal hash apply
1156
+ document.location.hash = adjustedHash;
1157
+ }
1158
+ }
1159
+
1160
+ // Chain
1161
+ return History;
1162
+ };
1163
+
1164
+ /**
1165
+ * History.escape()
1166
+ * normalize and Escape a Hash
1167
+ * @return {string}
1168
+ */
1169
+ History.escapeHash = function(hash){
1170
+ // Prepare
1171
+ var result = History.normalizeHash(hash);
1172
+
1173
+ // Escape hash
1174
+ result = window.escape(result);
1175
+
1176
+ // IE6 Escape Bug
1177
+ if ( !History.bugs.hashEscape ) {
1178
+ // Restore common parts
1179
+ result = result
1180
+ .replace(/\%21/g,'!')
1181
+ .replace(/\%26/g,'&')
1182
+ .replace(/\%3D/g,'=')
1183
+ .replace(/\%3F/g,'?');
1184
+ }
1185
+
1186
+ // Return result
1187
+ return result;
1188
+ };
1189
+
1190
+ /**
1191
+ * History.getHashByUrl(url)
1192
+ * Extracts the Hash from a URL
1193
+ * @param {string} url
1194
+ * @return {string} url
1195
+ */
1196
+ History.getHashByUrl = function(url){
1197
+ // Extract the hash
1198
+ var hash = String(url)
1199
+ .replace(/([^#]*)#?([^#]*)#?(.*)/, '$2')
1200
+ ;
1201
+
1202
+ // Unescape hash
1203
+ hash = History.unescapeHash(hash);
1204
+
1205
+ // Return hash
1206
+ return hash;
1207
+ };
1208
+
1209
+ /**
1210
+ * History.setTitle(title)
1211
+ * Applies the title to the document
1212
+ * @param {State} newState
1213
+ * @return {Boolean}
1214
+ */
1215
+ History.setTitle = function(newState){
1216
+ // Prepare
1217
+ var title = newState.title,
1218
+ firstState;
1219
+
1220
+ // Initial
1221
+ if ( !title ) {
1222
+ firstState = History.getStateByIndex(0);
1223
+ if ( firstState && firstState.url === newState.url ) {
1224
+ title = firstState.title||History.options.initialTitle;
1225
+ }
1226
+ }
1227
+
1228
+ // Apply
1229
+ try {
1230
+ document.getElementsByTagName('title')[0].innerHTML = title.replace('<','&lt;').replace('>','&gt;').replace(' & ',' &amp; ');
1231
+ }
1232
+ catch ( Exception ) { }
1233
+ document.title = title;
1234
+
1235
+ // Chain
1236
+ return History;
1237
+ };
1238
+
1239
+
1240
+ // ====================================================================
1241
+ // Queueing
1242
+
1243
+ /**
1244
+ * History.queues
1245
+ * The list of queues to use
1246
+ * First In, First Out
1247
+ */
1248
+ History.queues = [];
1249
+
1250
+ /**
1251
+ * History.busy(value)
1252
+ * @param {boolean} value [optional]
1253
+ * @return {boolean} busy
1254
+ */
1255
+ History.busy = function(value){
1256
+ // Apply
1257
+ if ( typeof value !== 'undefined' ) {
1258
+ //History.debug('History.busy: changing ['+(History.busy.flag||false)+'] to ['+(value||false)+']', History.queues.length);
1259
+ History.busy.flag = value;
1260
+ }
1261
+ // Default
1262
+ else if ( typeof History.busy.flag === 'undefined' ) {
1263
+ History.busy.flag = false;
1264
+ }
1265
+
1266
+ // Queue
1267
+ if ( !History.busy.flag ) {
1268
+ // Execute the next item in the queue
1269
+ clearTimeout(History.busy.timeout);
1270
+ var fireNext = function(){
1271
+ var i, queue, item;
1272
+ if ( History.busy.flag ) return;
1273
+ for ( i=History.queues.length-1; i >= 0; --i ) {
1274
+ queue = History.queues[i];
1275
+ if ( queue.length === 0 ) continue;
1276
+ item = queue.shift();
1277
+ History.fireQueueItem(item);
1278
+ History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
1279
+ }
1280
+ };
1281
+ History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
1282
+ }
1283
+
1284
+ // Return
1285
+ return History.busy.flag;
1286
+ };
1287
+
1288
+ /**
1289
+ * History.busy.flag
1290
+ */
1291
+ History.busy.flag = false;
1292
+
1293
+ /**
1294
+ * History.fireQueueItem(item)
1295
+ * Fire a Queue Item
1296
+ * @param {Object} item
1297
+ * @return {Mixed} result
1298
+ */
1299
+ History.fireQueueItem = function(item){
1300
+ return item.callback.apply(item.scope||History,item.args||[]);
1301
+ };
1302
+
1303
+ /**
1304
+ * History.pushQueue(callback,args)
1305
+ * Add an item to the queue
1306
+ * @param {Object} item [scope,callback,args,queue]
1307
+ */
1308
+ History.pushQueue = function(item){
1309
+ // Prepare the queue
1310
+ History.queues[item.queue||0] = History.queues[item.queue||0]||[];
1311
+
1312
+ // Add to the queue
1313
+ History.queues[item.queue||0].push(item);
1314
+
1315
+ // Chain
1316
+ return History;
1317
+ };
1318
+
1319
+ /**
1320
+ * History.queue (item,queue), (func,queue), (func), (item)
1321
+ * Either firs the item now if not busy, or adds it to the queue
1322
+ */
1323
+ History.queue = function(item,queue){
1324
+ // Prepare
1325
+ if ( typeof item === 'function' ) {
1326
+ item = {
1327
+ callback: item
1328
+ };
1329
+ }
1330
+ if ( typeof queue !== 'undefined' ) {
1331
+ item.queue = queue;
1332
+ }
1333
+
1334
+ // Handle
1335
+ if ( History.busy() ) {
1336
+ History.pushQueue(item);
1337
+ } else {
1338
+ History.fireQueueItem(item);
1339
+ }
1340
+
1341
+ // Chain
1342
+ return History;
1343
+ };
1344
+
1345
+ /**
1346
+ * History.clearQueue()
1347
+ * Clears the Queue
1348
+ */
1349
+ History.clearQueue = function(){
1350
+ History.busy.flag = false;
1351
+ History.queues = [];
1352
+ return History;
1353
+ };
1354
+
1355
+
1356
+ // ====================================================================
1357
+ // IE Bug Fix
1358
+
1359
+ /**
1360
+ * History.stateChanged
1361
+ * States whether or not the state has changed since the last double check was initialised
1362
+ */
1363
+ History.stateChanged = false;
1364
+
1365
+ /**
1366
+ * History.doubleChecker
1367
+ * Contains the timeout used for the double checks
1368
+ */
1369
+ History.doubleChecker = false;
1370
+
1371
+ /**
1372
+ * History.doubleCheckComplete()
1373
+ * Complete a double check
1374
+ * @return {History}
1375
+ */
1376
+ History.doubleCheckComplete = function(){
1377
+ // Update
1378
+ History.stateChanged = true;
1379
+
1380
+ // Clear
1381
+ History.doubleCheckClear();
1382
+
1383
+ // Chain
1384
+ return History;
1385
+ };
1386
+
1387
+ /**
1388
+ * History.doubleCheckClear()
1389
+ * Clear a double check
1390
+ * @return {History}
1391
+ */
1392
+ History.doubleCheckClear = function(){
1393
+ // Clear
1394
+ if ( History.doubleChecker ) {
1395
+ clearTimeout(History.doubleChecker);
1396
+ History.doubleChecker = false;
1397
+ }
1398
+
1399
+ // Chain
1400
+ return History;
1401
+ };
1402
+
1403
+ /**
1404
+ * History.doubleCheck()
1405
+ * Create a double check
1406
+ * @return {History}
1407
+ */
1408
+ History.doubleCheck = function(tryAgain){
1409
+ // Reset
1410
+ History.stateChanged = false;
1411
+ History.doubleCheckClear();
1412
+
1413
+ // Fix IE6,IE7 bug where calling history.back or history.forward does not actually change the hash (whereas doing it manually does)
1414
+ // Fix Safari 5 bug where sometimes the state does not change: https://bugs.webkit.org/show_bug.cgi?id=42940
1415
+ if ( History.bugs.ieDoubleCheck ) {
1416
+ // Apply Check
1417
+ History.doubleChecker = setTimeout(
1418
+ function(){
1419
+ History.doubleCheckClear();
1420
+ if ( !History.stateChanged ) {
1421
+ //History.debug('History.doubleCheck: State has not yet changed, trying again', arguments);
1422
+ // Re-Attempt
1423
+ tryAgain();
1424
+ }
1425
+ return true;
1426
+ },
1427
+ History.options.doubleCheckInterval
1428
+ );
1429
+ }
1430
+
1431
+ // Chain
1432
+ return History;
1433
+ };
1434
+
1435
+
1436
+ // ====================================================================
1437
+ // Safari Bug Fix
1438
+
1439
+ /**
1440
+ * History.safariStatePoll()
1441
+ * Poll the current state
1442
+ * @return {History}
1443
+ */
1444
+ History.safariStatePoll = function(){
1445
+ // Poll the URL
1446
+
1447
+ // Get the Last State which has the new URL
1448
+ var
1449
+ urlState = History.extractState(document.location.href),
1450
+ newState;
1451
+
1452
+ // Check for a difference
1453
+ if ( !History.isLastSavedState(urlState) ) {
1454
+ newState = urlState;
1455
+ }
1456
+ else {
1457
+ return;
1458
+ }
1459
+
1460
+ // Check if we have a state with that url
1461
+ // If not create it
1462
+ if ( !newState ) {
1463
+ //History.debug('History.safariStatePoll: new');
1464
+ newState = History.createStateObject();
1465
+ }
1466
+
1467
+ // Apply the New State
1468
+ //History.debug('History.safariStatePoll: trigger');
1469
+ History.Adapter.trigger(window,'popstate');
1470
+
1471
+ // Chain
1472
+ return History;
1473
+ };
1474
+
1475
+
1476
+ // ====================================================================
1477
+ // State Aliases
1478
+
1479
+ /**
1480
+ * History.back(queue)
1481
+ * Send the browser history back one item
1482
+ * @param {Integer} queue [optional]
1483
+ */
1484
+ History.back = function(queue){
1485
+ //History.debug('History.back: called', arguments);
1486
+
1487
+ // Handle Queueing
1488
+ if ( queue !== false && History.busy() ) {
1489
+ // Wait + Push to Queue
1490
+ //History.debug('History.back: we must wait', arguments);
1491
+ History.pushQueue({
1492
+ scope: History,
1493
+ callback: History.back,
1494
+ args: arguments,
1495
+ queue: queue
1496
+ });
1497
+ return false;
1498
+ }
1499
+
1500
+ // Make Busy + Continue
1501
+ History.busy(true);
1502
+
1503
+ // Fix certain browser bugs that prevent the state from changing
1504
+ History.doubleCheck(function(){
1505
+ History.back(false);
1506
+ });
1507
+
1508
+ // Go back
1509
+ history.go(-1);
1510
+
1511
+ // End back closure
1512
+ return true;
1513
+ };
1514
+
1515
+ /**
1516
+ * History.forward(queue)
1517
+ * Send the browser history forward one item
1518
+ * @param {Integer} queue [optional]
1519
+ */
1520
+ History.forward = function(queue){
1521
+ //History.debug('History.forward: called', arguments);
1522
+
1523
+ // Handle Queueing
1524
+ if ( queue !== false && History.busy() ) {
1525
+ // Wait + Push to Queue
1526
+ //History.debug('History.forward: we must wait', arguments);
1527
+ History.pushQueue({
1528
+ scope: History,
1529
+ callback: History.forward,
1530
+ args: arguments,
1531
+ queue: queue
1532
+ });
1533
+ return false;
1534
+ }
1535
+
1536
+ // Make Busy + Continue
1537
+ History.busy(true);
1538
+
1539
+ // Fix certain browser bugs that prevent the state from changing
1540
+ History.doubleCheck(function(){
1541
+ History.forward(false);
1542
+ });
1543
+
1544
+ // Go forward
1545
+ history.go(1);
1546
+
1547
+ // End forward closure
1548
+ return true;
1549
+ };
1550
+
1551
+ /**
1552
+ * History.go(index,queue)
1553
+ * Send the browser history back or forward index times
1554
+ * @param {Integer} queue [optional]
1555
+ */
1556
+ History.go = function(index,queue){
1557
+ //History.debug('History.go: called', arguments);
1558
+
1559
+ // Prepare
1560
+ var i;
1561
+
1562
+ // Handle
1563
+ if ( index > 0 ) {
1564
+ // Forward
1565
+ for ( i=1; i<=index; ++i ) {
1566
+ History.forward(queue);
1567
+ }
1568
+ }
1569
+ else if ( index < 0 ) {
1570
+ // Backward
1571
+ for ( i=-1; i>=index; --i ) {
1572
+ History.back(queue);
1573
+ }
1574
+ }
1575
+ else {
1576
+ throw new Error('History.go: History.go requires a positive or negative integer passed.');
1577
+ }
1578
+
1579
+ // Chain
1580
+ return History;
1581
+ };
1582
+
1583
+
1584
+ // ====================================================================
1585
+ // HTML5 State Support
1586
+
1587
+ // Non-Native pushState Implementation
1588
+ if ( History.emulated.pushState ) {
1589
+ /*
1590
+ * Provide Skeleton for HTML4 Browsers
1591
+ */
1592
+
1593
+ // Prepare
1594
+ var emptyFunction = function(){};
1595
+ History.pushState = History.pushState||emptyFunction;
1596
+ History.replaceState = History.replaceState||emptyFunction;
1597
+ } // History.emulated.pushState
1598
+
1599
+ // Native pushState Implementation
1600
+ else {
1601
+ /*
1602
+ * Use native HTML5 History API Implementation
1603
+ */
1604
+
1605
+ /**
1606
+ * History.onPopState(event,extra)
1607
+ * Refresh the Current State
1608
+ */
1609
+ History.onPopState = function(event,extra){
1610
+ // Prepare
1611
+ var stateId = false, newState = false, currentHash, currentState;
1612
+
1613
+ // Reset the double check
1614
+ History.doubleCheckComplete();
1615
+
1616
+ // Check for a Hash, and handle apporiatly
1617
+ currentHash = History.getHash();
1618
+ if ( currentHash ) {
1619
+ // Expand Hash
1620
+ currentState = History.extractState(currentHash||document.location.href,true);
1621
+ if ( currentState ) {
1622
+ // We were able to parse it, it must be a State!
1623
+ // Let's forward to replaceState
1624
+ //History.debug('History.onPopState: state anchor', currentHash, currentState);
1625
+ History.replaceState(currentState.data, currentState.title, currentState.url, false);
1626
+ }
1627
+ else {
1628
+ // Traditional Anchor
1629
+ //History.debug('History.onPopState: traditional anchor', currentHash);
1630
+ History.Adapter.trigger(window,'anchorchange');
1631
+ History.busy(false);
1632
+ }
1633
+
1634
+ // We don't care for hashes
1635
+ History.expectedStateId = false;
1636
+ return false;
1637
+ }
1638
+
1639
+ // Ensure
1640
+ stateId = History.Adapter.extractEventData('state',event,extra) || false;
1641
+
1642
+ // Fetch State
1643
+ if ( stateId ) {
1644
+ // Vanilla: Back/forward button was used
1645
+ newState = History.getStateById(stateId);
1646
+ }
1647
+ else if ( History.expectedStateId ) {
1648
+ // Vanilla: A new state was pushed, and popstate was called manually
1649
+ newState = History.getStateById(History.expectedStateId);
1650
+ }
1651
+ else {
1652
+ // Initial State
1653
+ newState = History.extractState(document.location.href);
1654
+ }
1655
+
1656
+ // The State did not exist in our store
1657
+ if ( !newState ) {
1658
+ // Regenerate the State
1659
+ newState = History.createStateObject(null,null,document.location.href);
1660
+ }
1661
+
1662
+ // Clean
1663
+ History.expectedStateId = false;
1664
+
1665
+ // Check if we are the same state
1666
+ if ( History.isLastSavedState(newState) ) {
1667
+ // There has been no change (just the page's hash has finally propagated)
1668
+ //History.debug('History.onPopState: no change', newState, History.savedStates);
1669
+ History.busy(false);
1670
+ return false;
1671
+ }
1672
+
1673
+ // Store the State
1674
+ History.storeState(newState);
1675
+ History.saveState(newState);
1676
+
1677
+ // Force update of the title
1678
+ History.setTitle(newState);
1679
+
1680
+ // Fire Our Event
1681
+ History.Adapter.trigger(window,'statechange');
1682
+ History.busy(false);
1683
+
1684
+ // Return true
1685
+ return true;
1686
+ };
1687
+ History.Adapter.bind(window,'popstate',History.onPopState);
1688
+
1689
+ /**
1690
+ * History.pushState(data,title,url)
1691
+ * Add a new State to the history object, become it, and trigger onpopstate
1692
+ * We have to trigger for HTML4 compatibility
1693
+ * @param {object} data
1694
+ * @param {string} title
1695
+ * @param {string} url
1696
+ * @return {true}
1697
+ */
1698
+ History.pushState = function(data,title,url,queue){
1699
+ //History.debug('History.pushState: called', arguments);
1700
+
1701
+ // Check the State
1702
+ if ( History.getHashByUrl(url) && History.emulated.pushState ) {
1703
+ throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
1704
+ }
1705
+
1706
+ // Handle Queueing
1707
+ if ( queue !== false && History.busy() ) {
1708
+ // Wait + Push to Queue
1709
+ //History.debug('History.pushState: we must wait', arguments);
1710
+ History.pushQueue({
1711
+ scope: History,
1712
+ callback: History.pushState,
1713
+ args: arguments,
1714
+ queue: queue
1715
+ });
1716
+ return false;
1717
+ }
1718
+
1719
+ // Make Busy + Continue
1720
+ History.busy(true);
1721
+
1722
+ // Create the newState
1723
+ var newState = History.createStateObject(data,title,url);
1724
+
1725
+ // Check it
1726
+ if ( History.isLastSavedState(newState) ) {
1727
+ // Won't be a change
1728
+ History.busy(false);
1729
+ }
1730
+ else {
1731
+ // Store the newState
1732
+ History.storeState(newState);
1733
+ History.expectedStateId = newState.id;
1734
+
1735
+ // Push the newState
1736
+ history.pushState(newState.id,newState.title,newState.url);
1737
+
1738
+ // Fire HTML5 Event
1739
+ History.Adapter.trigger(window,'popstate');
1740
+ }
1741
+
1742
+ // End pushState closure
1743
+ return true;
1744
+ };
1745
+
1746
+ /**
1747
+ * History.replaceState(data,title,url)
1748
+ * Replace the State and trigger onpopstate
1749
+ * We have to trigger for HTML4 compatibility
1750
+ * @param {object} data
1751
+ * @param {string} title
1752
+ * @param {string} url
1753
+ * @return {true}
1754
+ */
1755
+ History.replaceState = function(data,title,url,queue){
1756
+ //History.debug('History.replaceState: called', arguments);
1757
+
1758
+ // Check the State
1759
+ if ( History.getHashByUrl(url) && History.emulated.pushState ) {
1760
+ throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
1761
+ }
1762
+
1763
+ // Handle Queueing
1764
+ if ( queue !== false && History.busy() ) {
1765
+ // Wait + Push to Queue
1766
+ //History.debug('History.replaceState: we must wait', arguments);
1767
+ History.pushQueue({
1768
+ scope: History,
1769
+ callback: History.replaceState,
1770
+ args: arguments,
1771
+ queue: queue
1772
+ });
1773
+ return false;
1774
+ }
1775
+
1776
+ // Make Busy + Continue
1777
+ History.busy(true);
1778
+
1779
+ // Create the newState
1780
+ var newState = History.createStateObject(data,title,url);
1781
+
1782
+ // Check it
1783
+ if ( History.isLastSavedState(newState) ) {
1784
+ // Won't be a change
1785
+ History.busy(false);
1786
+ }
1787
+ else {
1788
+ // Store the newState
1789
+ History.storeState(newState);
1790
+ History.expectedStateId = newState.id;
1791
+
1792
+ // Push the newState
1793
+ history.replaceState(newState.id,newState.title,newState.url);
1794
+
1795
+ // Fire HTML5 Event
1796
+ History.Adapter.trigger(window,'popstate');
1797
+ }
1798
+
1799
+ // End replaceState closure
1800
+ return true;
1801
+ };
1802
+
1803
+ } // !History.emulated.pushState
1804
+
1805
+
1806
+ // ====================================================================
1807
+ // Initialise
1808
+
1809
+ /**
1810
+ * Load the Store
1811
+ */
1812
+ if ( sessionStorage ) {
1813
+ // Fetch
1814
+ try {
1815
+ History.store = JSON.parse(sessionStorage.getItem('History.store'))||{};
1816
+ }
1817
+ catch ( err ) {
1818
+ History.store = {};
1819
+ }
1820
+
1821
+ // Normalize
1822
+ History.normalizeStore();
1823
+ }
1824
+ else {
1825
+ // Default Load
1826
+ History.store = {};
1827
+ History.normalizeStore();
1828
+ }
1829
+
1830
+ /**
1831
+ * Clear Intervals on exit to prevent memory leaks
1832
+ */
1833
+ History.Adapter.bind(window,"beforeunload",History.clearAllIntervals);
1834
+ History.Adapter.bind(window,"unload",History.clearAllIntervals);
1835
+
1836
+ /**
1837
+ * Create the initial State
1838
+ */
1839
+ History.saveState(History.storeState(History.extractState(document.location.href,true)));
1840
+
1841
+ /**
1842
+ * Bind for Saving Store
1843
+ */
1844
+ if ( sessionStorage ) {
1845
+ // When the page is closed
1846
+ History.onUnload = function(){
1847
+ // Prepare
1848
+ var currentStore, item;
1849
+
1850
+ // Fetch
1851
+ try {
1852
+ currentStore = JSON.parse(sessionStorage.getItem('History.store'))||{};
1853
+ }
1854
+ catch ( err ) {
1855
+ currentStore = {};
1856
+ }
1857
+
1858
+ // Ensure
1859
+ currentStore.idToState = currentStore.idToState || {};
1860
+ currentStore.urlToId = currentStore.urlToId || {};
1861
+ currentStore.stateToId = currentStore.stateToId || {};
1862
+
1863
+ // Sync
1864
+ for ( item in History.idToState ) {
1865
+ if ( !History.idToState.hasOwnProperty(item) ) {
1866
+ continue;
1867
+ }
1868
+ currentStore.idToState[item] = History.idToState[item];
1869
+ }
1870
+ for ( item in History.urlToId ) {
1871
+ if ( !History.urlToId.hasOwnProperty(item) ) {
1872
+ continue;
1873
+ }
1874
+ currentStore.urlToId[item] = History.urlToId[item];
1875
+ }
1876
+ for ( item in History.stateToId ) {
1877
+ if ( !History.stateToId.hasOwnProperty(item) ) {
1878
+ continue;
1879
+ }
1880
+ currentStore.stateToId[item] = History.stateToId[item];
1881
+ }
1882
+
1883
+ // Update
1884
+ History.store = currentStore;
1885
+ History.normalizeStore();
1886
+
1887
+ // Store
1888
+ sessionStorage.setItem('History.store',JSON.stringify(currentStore));
1889
+ };
1890
+
1891
+ // For Internet Explorer
1892
+ History.intervalList.push(setInterval(History.onUnload,History.options.storeInterval));
1893
+
1894
+ // For Other Browsers
1895
+ History.Adapter.bind(window,'beforeunload',History.onUnload);
1896
+ History.Adapter.bind(window,'unload',History.onUnload);
1897
+
1898
+ // Both are enabled for consistency
1899
+ }
1900
+
1901
+ // Non-Native pushState Implementation
1902
+ if ( !History.emulated.pushState ) {
1903
+ // Be aware, the following is only for native pushState implementations
1904
+ // If you are wanting to include something for all browsers
1905
+ // Then include it above this if block
1906
+
1907
+ /**
1908
+ * Setup Safari Fix
1909
+ */
1910
+ if ( History.bugs.safariPoll ) {
1911
+ History.intervalList.push(setInterval(History.safariStatePoll, History.options.safariPollInterval));
1912
+ }
1913
+
1914
+ /**
1915
+ * Ensure Cross Browser Compatibility
1916
+ */
1917
+ if ( navigator.vendor === 'Apple Computer, Inc.' || (navigator.appCodeName||'') === 'Mozilla' ) {
1918
+ /**
1919
+ * Fix Safari HashChange Issue
1920
+ */
1921
+
1922
+ // Setup Alias
1923
+ History.Adapter.bind(window,'hashchange',function(){
1924
+ History.Adapter.trigger(window,'popstate');
1925
+ });
1926
+
1927
+ // Initialise Alias
1928
+ if ( History.getHash() ) {
1929
+ History.Adapter.onDomLoad(function(){
1930
+ History.Adapter.trigger(window,'hashchange');
1931
+ });
1932
+ }
1933
+ }
1934
+
1935
+ } // !History.emulated.pushState
1936
+
1937
+
1938
+ }; // History.initCore
1939
+
1940
+ // Try and Initialise History
1941
+ History.init();
1942
+
1943
+ })(window);