ajax_pagination 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/.gitignore +1 -1
  2. data/CHANGELOG.md +4 -0
  3. data/README.md +30 -3
  4. data/Rakefile +10 -0
  5. data/ajax_pagination.gemspec +1 -1
  6. data/lib/ajax_pagination/version.rb +1 -1
  7. data/lib/generators/ajax_pagination/assets_generator.rb +37 -0
  8. data/spec/R30PORT +1 -0
  9. data/spec/ajax_pagination/integration/ajaxpaginate_spec.rb +85 -75
  10. data/spec/ajax_pagination/integration/rails30_spec.rb +35 -0
  11. data/spec/rails30_app/.gitignore +8 -0
  12. data/spec/rails30_app/Gemfile +28 -0
  13. data/spec/rails30_app/Gemfile.lock +96 -0
  14. data/spec/rails30_app/Rakefile +7 -0
  15. data/spec/rails30_app/app/controllers/application_controller.rb +13 -0
  16. data/spec/rails30_app/app/controllers/changelog_controller.rb +12 -0
  17. data/spec/rails30_app/app/controllers/pages_controller.rb +28 -0
  18. data/spec/rails30_app/app/controllers/sessions_controller.rb +9 -0
  19. data/spec/rails30_app/app/helpers/application_helper.rb +2 -0
  20. data/spec/rails30_app/app/helpers/posts_helper.rb +2 -0
  21. data/spec/rails30_app/app/mailers/.gitkeep +0 -0
  22. data/spec/rails30_app/app/models/.gitkeep +0 -0
  23. data/spec/rails30_app/app/models/changelog.rb +19 -0
  24. data/spec/rails30_app/app/views/changelog/_page.html.erb +10 -0
  25. data/spec/rails30_app/app/views/changelog/index.html.erb +3 -0
  26. data/spec/rails30_app/app/views/layouts/ajax.html.erb +8 -0
  27. data/spec/rails30_app/app/views/layouts/application.html.erb +49 -0
  28. data/spec/rails30_app/app/views/layouts/flash.html.erb +7 -0
  29. data/spec/rails30_app/app/views/pages/about.html.erb +4 -0
  30. data/spec/rails30_app/app/views/pages/readme.html.erb +4 -0
  31. data/spec/rails30_app/app/views/pages/warnings.html.erb +43 -0
  32. data/spec/rails30_app/app/views/pages/welcome.html.erb +6 -0
  33. data/spec/rails30_app/app/views/sessions/count.html.erb +7 -0
  34. data/spec/rails30_app/config.ru +4 -0
  35. data/spec/rails30_app/config/application.rb +53 -0
  36. data/spec/rails30_app/config/boot.rb +10 -0
  37. data/spec/rails30_app/config/database.yml +25 -0
  38. data/spec/rails30_app/config/environment.rb +6 -0
  39. data/spec/rails30_app/config/environments/development.rb +25 -0
  40. data/spec/rails30_app/config/environments/production.rb +43 -0
  41. data/spec/rails30_app/config/environments/test.rb +30 -0
  42. data/spec/rails30_app/config/initializers/ajax_pagination.rb +12 -0
  43. data/spec/rails30_app/config/initializers/backtrace_silencers.rb +7 -0
  44. data/spec/rails30_app/config/initializers/inflections.rb +15 -0
  45. data/spec/rails30_app/config/initializers/mime_types.rb +5 -0
  46. data/spec/rails30_app/config/initializers/secret_token.rb +7 -0
  47. data/spec/rails30_app/config/initializers/session_store.rb +8 -0
  48. data/spec/rails30_app/config/locales/en.yml +5 -0
  49. data/spec/rails30_app/config/routes.rb +71 -0
  50. data/spec/rails30_app/log/.gitkeep +0 -0
  51. data/spec/rails30_app/public/404.html +26 -0
  52. data/spec/rails30_app/public/422.html +26 -0
  53. data/spec/rails30_app/public/500.html +25 -0
  54. data/spec/rails30_app/public/favicon.ico +0 -0
  55. data/spec/rails30_app/public/images/ajax-loader.gif +0 -0
  56. data/spec/rails30_app/public/javascripts/ajax_pagination.js +380 -0
  57. data/spec/rails30_app/public/javascripts/history.js +4 -0
  58. data/spec/rails30_app/public/javascripts/history_adapter_jquery.js +77 -0
  59. data/spec/rails30_app/public/javascripts/history_core.js +1943 -0
  60. data/spec/rails30_app/public/javascripts/history_html4.js +621 -0
  61. data/spec/rails30_app/public/javascripts/jquery-ui.js +11767 -0
  62. data/spec/rails30_app/public/javascripts/jquery-ui.min.js +791 -0
  63. data/spec/rails30_app/public/javascripts/jquery.ba-bbq.js +18 -0
  64. data/spec/rails30_app/public/javascripts/jquery.js +9266 -0
  65. data/spec/rails30_app/public/javascripts/jquery.min.js +4 -0
  66. data/spec/rails30_app/public/javascripts/jquery.url.js +160 -0
  67. data/spec/rails30_app/public/javascripts/jquery_ujs.js +373 -0
  68. data/spec/rails30_app/public/javascripts/json2.js +487 -0
  69. data/spec/rails30_app/public/stylesheets/ajax_pagination.css +24 -0
  70. data/spec/rails30_app/public/stylesheets/application.css +12 -0
  71. data/spec/rails30_app/public/stylesheets/scaffold.css +56 -0
  72. data/spec/rails30_app/script/rails +6 -0
  73. data/spec/rails_app/Gemfile.lock +107 -0
  74. data/spec/spec_helper.rb +16 -1
  75. metadata +72 -10
  76. data/spec/rails_app/test/fixtures/posts.yml +0 -9
  77. data/spec/rails_app/test/functional/posts_controller_test.rb +0 -49
  78. data/spec/rails_app/test/unit/helpers/posts_helper_test.rb +0 -4
  79. data/spec/rails_app/test/unit/post_test.rb +0 -7
@@ -0,0 +1,4 @@
1
+ //= require json2
2
+ //= require history_core
3
+ //= require history_adapter_jquery
4
+ //= require history_html4
@@ -0,0 +1,77 @@
1
+ /**
2
+ * History.js jQuery 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
+ jQuery = window.jQuery;
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
+ jQuery(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
+ * @param {Object=} extra - a object of extra event data (optional)
40
+ * @return {void}
41
+ */
42
+ trigger: function(el,event,extra){
43
+ jQuery(el).trigger(event,extra);
44
+ },
45
+
46
+ /**
47
+ * History.Adapter.extractEventData(key,event,extra)
48
+ * @param {string} key - key for the event data to extract
49
+ * @param {string} event - custom and standard events
50
+ * @param {Object=} extra - a object of extra event data (optional)
51
+ * @return {mixed}
52
+ */
53
+ extractEventData: function(key,event,extra){
54
+ // jQuery Native then jQuery Custom
55
+ var result = (event && event.originalEvent && event.originalEvent[key]) || (extra && extra[key]) || undefined;
56
+
57
+ // Return
58
+ return result;
59
+ },
60
+
61
+ /**
62
+ * History.Adapter.onDomLoad(callback)
63
+ * @param {function} callback
64
+ * @return {void}
65
+ */
66
+ onDomLoad: function(callback) {
67
+ jQuery(callback);
68
+ }
69
+ };
70
+
71
+ // Try and Initialise History
72
+ if ( typeof History.init !== 'undefined' ) {
73
+ History.init();
74
+ }
75
+
76
+ })(window);
77
+
@@ -0,0 +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);