hat-trick 0.1.2 → 0.1.3

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