jquery-historyjs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,606 @@
1
+ /**
2
+ * History.js HTML4 Support
3
+ * Depends on the HTML5 Support
4
+ * @author Benjamin Arthur Lupton <contact@balupton.com>
5
+ * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
6
+ * @license New BSD License <http://creativecommons.org/licenses/BSD/>
7
+ */
8
+
9
+ (function(window,undefined){
10
+ "use strict";
11
+
12
+ // --------------------------------------------------------------------------
13
+ // Initialise
14
+
15
+ // Localise Globals
16
+ var
17
+ document = window.document, // Make sure we are using the correct document
18
+ setTimeout = window.setTimeout||setTimeout,
19
+ clearTimeout = window.clearTimeout||clearTimeout,
20
+ setInterval = window.setInterval||setInterval,
21
+ History = window.History = window.History||{}; // Public History Object
22
+
23
+ // Check Existence
24
+ if ( typeof History.initHtml4 !== 'undefined' ) {
25
+ throw new Error('History.js HTML4 Support has already been loaded...');
26
+ }
27
+
28
+ // --------------------------------------------------------------------------
29
+ // Initialise HTML4 Support
30
+
31
+ // Initialise HTML4 Support
32
+ History.initHtml4 = function(){
33
+ // Initialise
34
+ if ( typeof History.initHtml4.initialized !== 'undefined' ) {
35
+ // Already Loaded
36
+ return false;
37
+ }
38
+ else {
39
+ History.initHtml4.initialized = true;
40
+ }
41
+
42
+ // ----------------------------------------------------------------------
43
+ // Properties
44
+
45
+ /**
46
+ * History.enabled
47
+ * Is History enabled?
48
+ */
49
+ History.enabled = true;
50
+
51
+
52
+ // ----------------------------------------------------------------------
53
+ // Hash Storage
54
+
55
+ /**
56
+ * History.savedHashes
57
+ * Store the hashes in an array
58
+ */
59
+ History.savedHashes = [];
60
+
61
+ /**
62
+ * History.isLastHash(newHash)
63
+ * Checks if the hash is the last hash
64
+ * @param {string} newHash
65
+ * @return {boolean} true
66
+ */
67
+ History.isLastHash = function(newHash){
68
+ // Prepare
69
+ var oldHash = History.getHashByIndex();
70
+
71
+ // Check
72
+ var isLast = newHash === oldHash;
73
+
74
+ // Return isLast
75
+ return isLast;
76
+ };
77
+
78
+ /**
79
+ * History.saveHash(newHash)
80
+ * Push a Hash
81
+ * @param {string} newHash
82
+ * @return {boolean} true
83
+ */
84
+ History.saveHash = function(newHash){
85
+ // Check Hash
86
+ if ( History.isLastHash(newHash) ) {
87
+ return false;
88
+ }
89
+
90
+ // Push the Hash
91
+ History.savedHashes.push(newHash);
92
+
93
+ // Return true
94
+ return true;
95
+ };
96
+
97
+ /**
98
+ * History.getHashByIndex()
99
+ * Gets a hash by the index
100
+ * @param {integer} index
101
+ * @return {string}
102
+ */
103
+ History.getHashByIndex = function(index){
104
+ // Prepare
105
+ var hash = null;
106
+
107
+ // Handle
108
+ if ( typeof index === 'undefined' ) {
109
+ // Get the last inserted
110
+ hash = History.savedHashes[History.savedHashes.length-1];
111
+ }
112
+ else if ( index < 0 ) {
113
+ // Get from the end
114
+ hash = History.savedHashes[History.savedHashes.length+index];
115
+ }
116
+ else {
117
+ // Get from the beginning
118
+ hash = History.savedHashes[index];
119
+ }
120
+
121
+ // Return hash
122
+ return hash;
123
+ };
124
+
125
+ // ----------------------------------------------------------------------
126
+ // Discarded States
127
+
128
+ /**
129
+ * History.discardedHashes
130
+ * A hashed array of discarded hashes
131
+ */
132
+ History.discardedHashes = {};
133
+
134
+ /**
135
+ * History.discardedStates
136
+ * A hashed array of discarded states
137
+ */
138
+ History.discardedStates = {};
139
+
140
+ /**
141
+ * History.discardState(State)
142
+ * Discards the state by ignoring it through History
143
+ * @param {object} State
144
+ * @return {true}
145
+ */
146
+ History.discardState = function(discardedState,forwardState,backState){
147
+ //History.debug('History.discardState', arguments);
148
+ // Prepare
149
+ var discardedStateHash = History.getHashByState(discardedState);
150
+
151
+ // Create Discard Object
152
+ var discardObject = {
153
+ 'discardedState': discardedState,
154
+ 'backState': backState,
155
+ 'forwardState': forwardState
156
+ };
157
+
158
+ // Add to DiscardedStates
159
+ History.discardedStates[discardedStateHash] = discardObject;
160
+
161
+ // Return true
162
+ return true;
163
+ };
164
+
165
+ /**
166
+ * History.discardHash(hash)
167
+ * Discards the hash by ignoring it through History
168
+ * @param {string} hash
169
+ * @return {true}
170
+ */
171
+ History.discardHash = function(discardedHash,forwardState,backState){
172
+ //History.debug('History.discardState', arguments);
173
+ // Create Discard Object
174
+ var discardObject = {
175
+ 'discardedHash': discardedHash,
176
+ 'backState': backState,
177
+ 'forwardState': forwardState
178
+ };
179
+
180
+ // Add to discardedHash
181
+ History.discardedHashes[discardedHash] = discardObject;
182
+
183
+ // Return true
184
+ return true;
185
+ };
186
+
187
+ /**
188
+ * History.discardState(State)
189
+ * Checks to see if the state is discarded
190
+ * @param {object} State
191
+ * @return {bool}
192
+ */
193
+ History.discardedState = function(State){
194
+ // Prepare
195
+ var StateHash = History.getHashByState(State);
196
+
197
+ // Check
198
+ var discarded = History.discardedStates[StateHash]||false;
199
+
200
+ // Return true
201
+ return discarded;
202
+ };
203
+
204
+ /**
205
+ * History.discardedHash(hash)
206
+ * Checks to see if the state is discarded
207
+ * @param {string} State
208
+ * @return {bool}
209
+ */
210
+ History.discardedHash = function(hash){
211
+ // Check
212
+ var discarded = History.discardedHashes[hash]||false;
213
+
214
+ // Return true
215
+ return discarded;
216
+ };
217
+
218
+ /**
219
+ * History.recycleState(State)
220
+ * Allows a discarded state to be used again
221
+ * @param {object} data
222
+ * @param {string} title
223
+ * @param {string} url
224
+ * @return {true}
225
+ */
226
+ History.recycleState = function(State){
227
+ //History.debug('History.recycleState', arguments);
228
+ // Prepare
229
+ var StateHash = History.getHashByState(State);
230
+
231
+ // Remove from DiscardedStates
232
+ if ( History.discardedState(State) ) {
233
+ delete History.discardedStates[StateHash];
234
+ }
235
+
236
+ // Return true
237
+ return true;
238
+ };
239
+
240
+ // ----------------------------------------------------------------------
241
+ // HTML4 HashChange Support
242
+
243
+ if ( History.emulated.hashChange ) {
244
+ /*
245
+ * We must emulate the HTML4 HashChange Support by manually checking for hash changes
246
+ */
247
+
248
+ /**
249
+ * History.hashChangeInit()
250
+ * Init the HashChange Emulation
251
+ */
252
+ History.hashChangeInit = function(){
253
+ // Define our Checker Function
254
+ History.checkerFunction = null;
255
+
256
+ // Define some variables that will help in our checker function
257
+ var
258
+ lastDocumentHash = '';
259
+
260
+ // Handle depending on the browser
261
+ if ( History.isInternetExplorer() ) {
262
+ // IE6 and IE7
263
+ // We need to use an iframe to emulate the back and forward buttons
264
+
265
+ // Create iFrame
266
+ var
267
+ iframeId = 'historyjs-iframe',
268
+ iframe = document.createElement('iframe');
269
+
270
+ // Adjust iFarme
271
+ iframe.setAttribute('id', iframeId);
272
+ iframe.style.display = 'none';
273
+
274
+ // Append iFrame
275
+ document.body.appendChild(iframe);
276
+
277
+ // Create initial history entry
278
+ iframe.contentWindow.document.open();
279
+ iframe.contentWindow.document.close();
280
+
281
+ // Define some variables that will help in our checker function
282
+ var
283
+ lastIframeHash = '',
284
+ checkerRunning = false;
285
+
286
+ // Define the checker function
287
+ History.checkerFunction = function(){
288
+ // Check Running
289
+ if ( checkerRunning ) {
290
+ return false;
291
+ }
292
+
293
+ // Update Running
294
+ checkerRunning = true;
295
+
296
+ // Fetch
297
+ var
298
+ documentHash = History.getHash()||'',
299
+ iframeHash = History.unescapeHash(iframe.contentWindow.document.location.hash)||'';
300
+
301
+ // The Document Hash has changed (application caused)
302
+ if ( documentHash !== lastDocumentHash ) {
303
+ // Equalise
304
+ lastDocumentHash = documentHash;
305
+
306
+ // Create a history entry in the iframe
307
+ if ( iframeHash !== documentHash ) {
308
+ //History.debug('hashchange.checker: iframe hash change', 'documentHash (new):', documentHash, 'iframeHash (old):', iframeHash);
309
+
310
+ // Equalise
311
+ lastIframeHash = iframeHash = documentHash;
312
+
313
+ // Create History Entry
314
+ iframe.contentWindow.document.open();
315
+ iframe.contentWindow.document.close();
316
+
317
+ // Update the iframe's hash
318
+ iframe.contentWindow.document.location.hash = History.escapeHash(documentHash);
319
+ }
320
+
321
+ // Trigger Hashchange Event
322
+ History.Adapter.trigger(window,'hashchange');
323
+ }
324
+
325
+ // The iFrame Hash has changed (back button caused)
326
+ else if ( iframeHash !== lastIframeHash ) {
327
+ //History.debug('hashchange.checker: iframe hash out of sync', 'iframeHash (new):', iframeHash, 'documentHash (old):', documentHash);
328
+
329
+ // Equalise
330
+ lastIframeHash = iframeHash;
331
+
332
+ // Update the Hash
333
+ History.setHash(iframeHash,false);
334
+ }
335
+
336
+ // Reset Running
337
+ checkerRunning = false;
338
+
339
+ // Return true
340
+ return true;
341
+ };
342
+ }
343
+ else {
344
+ // We are not IE
345
+ // Firefox 1 or 2, Opera
346
+
347
+ // Define the checker function
348
+ History.checkerFunction = function(){
349
+ // Prepare
350
+ var documentHash = History.getHash();
351
+
352
+ // The Document Hash has changed (application caused)
353
+ if ( documentHash !== lastDocumentHash ) {
354
+ // Equalise
355
+ lastDocumentHash = documentHash;
356
+
357
+ // Trigger Hashchange Event
358
+ History.Adapter.trigger(window,'hashchange');
359
+ }
360
+
361
+ // Return true
362
+ return true;
363
+ };
364
+ }
365
+
366
+ // Apply the checker function
367
+ History.intervalList.push(setInterval(History.checkerFunction, History.options.hashChangeInterval));
368
+
369
+ // Done
370
+ return true;
371
+ }; // History.hashChangeInit
372
+
373
+ // Bind hashChangeInit
374
+ History.Adapter.onDomLoad(History.hashChangeInit);
375
+
376
+ } // History.emulated.hashChange
377
+
378
+
379
+ // ----------------------------------------------------------------------
380
+ // HTML5 State Support
381
+
382
+ if ( History.emulated.pushState ) {
383
+ /*
384
+ * We must emulate the HTML5 State Management by using HTML4 HashChange
385
+ */
386
+
387
+ /**
388
+ * History.onHashChange(event)
389
+ * Trigger HTML5's window.onpopstate via HTML4 HashChange Support
390
+ */
391
+ History.onHashChange = function(event){
392
+ //History.debug('History.onHashChange', arguments);
393
+
394
+ // Prepare
395
+ var
396
+ currentUrl = ((event && event.newURL) || document.location.href),
397
+ currentHash = History.getHashByUrl(currentUrl),
398
+ currentState = null,
399
+ currentStateHash = null,
400
+ currentStateHashExits = null;
401
+
402
+ // Check if we are the same state
403
+ if ( History.isLastHash(currentHash) ) {
404
+ // There has been no change (just the page's hash has finally propagated)
405
+ //History.debug('History.onHashChange: no change');
406
+ History.busy(false);
407
+ return false;
408
+ }
409
+
410
+ // Reset the double check
411
+ History.doubleCheckComplete();
412
+
413
+ // Store our location for use in detecting back/forward direction
414
+ History.saveHash(currentHash);
415
+
416
+ // Expand Hash
417
+ if ( currentHash && History.isTraditionalAnchor(currentHash) ) {
418
+ //History.debug('History.onHashChange: traditional anchor', currentHash);
419
+ // Traditional Anchor Hash
420
+ History.Adapter.trigger(window,'anchorchange');
421
+ History.busy(false);
422
+ return false;
423
+ }
424
+
425
+ // Create State
426
+ currentState = History.extractState(History.getFullUrl(currentHash||document.location.href,false),true);
427
+
428
+ // Check if we are the same state
429
+ if ( History.isLastSavedState(currentState) ) {
430
+ //History.debug('History.onHashChange: no change');
431
+ // There has been no change (just the page's hash has finally propagated)
432
+ History.busy(false);
433
+ return false;
434
+ }
435
+
436
+ // Create the state Hash
437
+ currentStateHash = History.getHashByState(currentState);
438
+
439
+ // Check if we are DiscardedState
440
+ var discardObject = History.discardedState(currentState);
441
+ if ( discardObject ) {
442
+ // Ignore this state as it has been discarded and go back to the state before it
443
+ if ( History.getHashByIndex(-2) === History.getHashByState(discardObject.forwardState) ) {
444
+ // We are going backwards
445
+ //History.debug('History.onHashChange: go backwards');
446
+ History.back(false);
447
+ } else {
448
+ // We are going forwards
449
+ //History.debug('History.onHashChange: go forwards');
450
+ History.forward(false);
451
+ }
452
+ return false;
453
+ }
454
+
455
+ // Push the new HTML5 State
456
+ //History.debug('History.onHashChange: success hashchange');
457
+ History.pushState(currentState.data,currentState.title,currentState.url,false);
458
+
459
+ // End onHashChange closure
460
+ return true;
461
+ };
462
+ History.Adapter.bind(window,'hashchange',History.onHashChange);
463
+
464
+ /**
465
+ * History.pushState(data,title,url)
466
+ * Add a new State to the history object, become it, and trigger onpopstate
467
+ * We have to trigger for HTML4 compatibility
468
+ * @param {object} data
469
+ * @param {string} title
470
+ * @param {string} url
471
+ * @return {true}
472
+ */
473
+ History.pushState = function(data,title,url,queue){
474
+ //History.debug('History.pushState: called', arguments);
475
+
476
+ // Check the State
477
+ if ( History.getHashByUrl(url) ) {
478
+ throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
479
+ }
480
+
481
+ // Handle Queueing
482
+ if ( queue !== false && History.busy() ) {
483
+ // Wait + Push to Queue
484
+ //History.debug('History.pushState: we must wait', arguments);
485
+ History.pushQueue({
486
+ scope: History,
487
+ callback: History.pushState,
488
+ args: arguments,
489
+ queue: queue
490
+ });
491
+ return false;
492
+ }
493
+
494
+ // Make Busy
495
+ History.busy(true);
496
+
497
+ // Fetch the State Object
498
+ var
499
+ newState = History.createStateObject(data,title,url),
500
+ newStateHash = History.getHashByState(newState),
501
+ oldState = History.getState(false),
502
+ oldStateHash = History.getHashByState(oldState),
503
+ html4Hash = History.getHash();
504
+
505
+ // Store the newState
506
+ History.storeState(newState);
507
+ History.expectedStateId = newState.id;
508
+
509
+ // Recycle the State
510
+ History.recycleState(newState);
511
+
512
+ // Force update of the title
513
+ History.setTitle(newState);
514
+
515
+ // Check if we are the same State
516
+ if ( newStateHash === oldStateHash ) {
517
+ //History.debug('History.pushState: no change', newStateHash);
518
+ History.busy(false);
519
+ return false;
520
+ }
521
+
522
+ // Update HTML4 Hash
523
+ if ( newStateHash !== html4Hash && newStateHash !== History.getShortUrl(document.location.href) ) {
524
+ //History.debug('History.pushState: update hash', newStateHash, html4Hash);
525
+ History.setHash(newStateHash,false);
526
+ return false;
527
+ }
528
+
529
+ // Update HTML5 State
530
+ History.saveState(newState);
531
+
532
+ // Fire HTML5 Event
533
+ //History.debug('History.pushState: trigger popstate');
534
+ History.Adapter.trigger(window,'statechange');
535
+ History.busy(false);
536
+
537
+ // End pushState closure
538
+ return true;
539
+ };
540
+
541
+ /**
542
+ * History.replaceState(data,title,url)
543
+ * Replace the State and trigger onpopstate
544
+ * We have to trigger for HTML4 compatibility
545
+ * @param {object} data
546
+ * @param {string} title
547
+ * @param {string} url
548
+ * @return {true}
549
+ */
550
+ History.replaceState = function(data,title,url,queue){
551
+ //History.debug('History.replaceState: called', arguments);
552
+
553
+ // Check the State
554
+ if ( History.getHashByUrl(url) ) {
555
+ throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
556
+ }
557
+
558
+ // Handle Queueing
559
+ if ( queue !== false && History.busy() ) {
560
+ // Wait + Push to Queue
561
+ //History.debug('History.replaceState: we must wait', arguments);
562
+ History.pushQueue({
563
+ scope: History,
564
+ callback: History.replaceState,
565
+ args: arguments,
566
+ queue: queue
567
+ });
568
+ return false;
569
+ }
570
+
571
+ // Make Busy
572
+ History.busy(true);
573
+
574
+ // Fetch the State Objects
575
+ var
576
+ newState = History.createStateObject(data,title,url),
577
+ oldState = History.getState(false),
578
+ previousState = History.getStateByIndex(-2);
579
+
580
+ // Discard Old State
581
+ History.discardState(oldState,newState,previousState);
582
+
583
+ // Alias to PushState
584
+ History.pushState(newState.data,newState.title,newState.url,false);
585
+
586
+ // End replaceState closure
587
+ return true;
588
+ };
589
+
590
+ /**
591
+ * Ensure initial state is handled correctly
592
+ */
593
+ if ( History.getHash() && !History.emulated.hashChange ) {
594
+ History.Adapter.onDomLoad(function(){
595
+ History.Adapter.trigger(window,'hashchange');
596
+ });
597
+ }
598
+
599
+ } // History.emulated.pushState
600
+
601
+ }; // History.initHtml4
602
+
603
+ // Try and Initialise History
604
+ History.init();
605
+
606
+ })(window);