oojs 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1377 @@
1
+ /*!
2
+ * jQuery BBQ: Back Button & Query Library - v1.3pre - 8/26/2010
3
+ * http://benalman.com/projects/jquery-bbq-plugin/
4
+ *
5
+ * Copyright (c) 2010 "Cowboy" Ben Alman
6
+ * Dual licensed under the MIT and GPL licenses.
7
+ * http://benalman.com/about/license/
8
+ */
9
+
10
+ // Script: jQuery BBQ: Back Button & Query Library
11
+ //
12
+ // *Version: 1.3pre, Last updated: 8/26/2010*
13
+ //
14
+ // Project Home - http://benalman.com/projects/jquery-bbq-plugin/
15
+ // GitHub - http://github.com/cowboy/jquery-bbq/
16
+ // Source - http://github.com/cowboy/jquery-bbq/raw/master/jquery.ba-bbq.js
17
+ // (Minified) - http://github.com/cowboy/jquery-bbq/raw/master/jquery.ba-bbq.min.js (2.2kb gzipped)
18
+ //
19
+ // About: License
20
+ //
21
+ // Copyright (c) 2010 "Cowboy" Ben Alman,
22
+ // Dual licensed under the MIT and GPL licenses.
23
+ // http://benalman.com/about/license/
24
+ //
25
+ // About: Examples
26
+ //
27
+ // These working examples, complete with fully commented code, illustrate a few
28
+ // ways in which this plugin can be used.
29
+ //
30
+ // Basic AJAX - http://benalman.com/code/projects/jquery-bbq/examples/fragment-basic/
31
+ // Advanced AJAX - http://benalman.com/code/projects/jquery-bbq/examples/fragment-advanced/
32
+ // jQuery UI Tabs - http://benalman.com/code/projects/jquery-bbq/examples/fragment-jquery-ui-tabs/
33
+ // Deparam - http://benalman.com/code/projects/jquery-bbq/examples/deparam/
34
+ //
35
+ // About: Support and Testing
36
+ //
37
+ // Information about what version or versions of jQuery this plugin has been
38
+ // tested with, what browsers it has been tested in, and where the unit tests
39
+ // reside (so you can test it yourself).
40
+ //
41
+ // jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2
42
+ // Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5,
43
+ // Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5.
44
+ // Unit Tests - http://benalman.com/code/projects/jquery-bbq/unit/
45
+ //
46
+ // About: Release History
47
+ //
48
+ // 1.3pre - (8/26/2010) Integrated <jQuery hashchange event> v1.3, which adds
49
+ // document.title and document.domain support in IE6/7, BlackBerry
50
+ // support, better Iframe hiding for accessibility reasons, and the new
51
+ // <jQuery.fn.hashchange> "shortcut" method. Added the
52
+ // <jQuery.param.sorted> method which reduces the possibility of
53
+ // extraneous hashchange event triggering. Added the
54
+ // <jQuery.param.fragment.ajaxCrawlable> method which can be used to
55
+ // enable Google "AJAX Crawlable mode."
56
+ // 1.2.1 - (2/17/2010) Actually fixed the stale window.location Safari bug from
57
+ // <jQuery hashchange event> in BBQ, which was the main reason for the
58
+ // previous release!
59
+ // 1.2 - (2/16/2010) Integrated <jQuery hashchange event> v1.2, which fixes a
60
+ // Safari bug, the event can now be bound before DOM ready, and IE6/7
61
+ // page should no longer scroll when the event is first bound. Also
62
+ // added the <jQuery.param.fragment.noEscape> method, and reworked the
63
+ // <hashchange event (BBQ)> internal "add" method to be compatible with
64
+ // changes made to the jQuery 1.4.2 special events API.
65
+ // 1.1.1 - (1/22/2010) Integrated <jQuery hashchange event> v1.1, which fixes an
66
+ // obscure IE8 EmulateIE7 meta tag compatibility mode bug.
67
+ // 1.1 - (1/9/2010) Broke out the jQuery BBQ event.special <hashchange event>
68
+ // functionality into a separate plugin for users who want just the
69
+ // basic event & back button support, without all the extra awesomeness
70
+ // that BBQ provides. This plugin will be included as part of jQuery BBQ,
71
+ // but also be available separately. See <jQuery hashchange event>
72
+ // plugin for more information. Also added the <jQuery.bbq.removeState>
73
+ // method and added additional <jQuery.deparam> examples.
74
+ // 1.0.3 - (12/2/2009) Fixed an issue in IE 6 where location.search and
75
+ // location.hash would report incorrectly if the hash contained the ?
76
+ // character. Also <jQuery.param.querystring> and <jQuery.param.fragment>
77
+ // will no longer parse params out of a URL that doesn't contain ? or #,
78
+ // respectively.
79
+ // 1.0.2 - (10/10/2009) Fixed an issue in IE 6/7 where the hidden IFRAME caused
80
+ // a "This page contains both secure and nonsecure items." warning when
81
+ // used on an https:// page.
82
+ // 1.0.1 - (10/7/2009) Fixed an issue in IE 8. Since both "IE7" and "IE8
83
+ // Compatibility View" modes erroneously report that the browser
84
+ // supports the native window.onhashchange event, a slightly more
85
+ // robust test needed to be added.
86
+ // 1.0 - (10/2/2009) Initial release
87
+
88
+ (function($,window){
89
+ '$:nomunge'; // Used by YUI compressor.
90
+
91
+ // Some convenient shortcuts.
92
+ var undefined,
93
+ aps = Array.prototype.slice,
94
+ decode = decodeURIComponent,
95
+
96
+ // Method / object references.
97
+ jq_param = $.param,
98
+ jq_param_sorted,
99
+ jq_param_fragment,
100
+ jq_deparam,
101
+ jq_deparam_fragment,
102
+ jq_bbq = $.bbq = $.bbq || {},
103
+ jq_bbq_pushState,
104
+ jq_bbq_getState,
105
+ jq_elemUrlAttr,
106
+ special = $.event.special,
107
+
108
+ // Reused strings.
109
+ str_hashchange = 'hashchange',
110
+ str_querystring = 'querystring',
111
+ str_fragment = 'fragment',
112
+ str_elemUrlAttr = 'elemUrlAttr',
113
+ str_href = 'href',
114
+ str_src = 'src',
115
+
116
+ // Reused RegExp.
117
+ re_params_querystring = /^.*\?|#.*$/g,
118
+ re_params_fragment,
119
+ re_fragment,
120
+ re_no_escape,
121
+
122
+ ajax_crawlable,
123
+ fragment_prefix,
124
+
125
+ // Used by jQuery.elemUrlAttr.
126
+ elemUrlAttr_cache = {};
127
+
128
+ // A few commonly used bits, broken out to help reduce minified file size.
129
+
130
+ function is_string( arg ) {
131
+ return typeof arg === 'string';
132
+ };
133
+
134
+ // Why write the same function twice? Let's curry! Mmmm, curry..
135
+
136
+ function curry( func ) {
137
+ var args = aps.call( arguments, 1 );
138
+
139
+ return function() {
140
+ return func.apply( this, args.concat( aps.call( arguments ) ) );
141
+ };
142
+ };
143
+
144
+ // Get location.hash (or what you'd expect location.hash to be) sans any
145
+ // leading #. Thanks for making this necessary, Firefox!
146
+ function get_fragment( url ) {
147
+ return url.replace( re_fragment, '$2' );
148
+ };
149
+
150
+ // Get location.search (or what you'd expect location.search to be) sans any
151
+ // leading #. Thanks for making this necessary, IE6!
152
+ function get_querystring( url ) {
153
+ return url.replace( /(?:^[^?#]*\?([^#]*).*$)?.*/, '$1' );
154
+ };
155
+
156
+ // Section: Param (to string)
157
+ //
158
+ // Method: jQuery.param.querystring
159
+ //
160
+ // Retrieve the query string from a URL or if no arguments are passed, the
161
+ // current window.location.href.
162
+ //
163
+ // Usage:
164
+ //
165
+ // > jQuery.param.querystring( [ url ] );
166
+ //
167
+ // Arguments:
168
+ //
169
+ // url - (String) A URL containing query string params to be parsed. If url
170
+ // is not passed, the current window.location.href is used.
171
+ //
172
+ // Returns:
173
+ //
174
+ // (String) The parsed query string, with any leading "?" removed.
175
+ //
176
+
177
+ // Method: jQuery.param.querystring (build url)
178
+ //
179
+ // Merge a URL, with or without pre-existing query string params, plus any
180
+ // object, params string or URL containing query string params into a new URL.
181
+ //
182
+ // Usage:
183
+ //
184
+ // > jQuery.param.querystring( url, params [, merge_mode ] );
185
+ //
186
+ // Arguments:
187
+ //
188
+ // url - (String) A valid URL for params to be merged into. This URL may
189
+ // contain a query string and/or fragment (hash).
190
+ // params - (String) A params string or URL containing query string params to
191
+ // be merged into url.
192
+ // params - (Object) A params object to be merged into url.
193
+ // merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
194
+ // specified, and is as-follows:
195
+ //
196
+ // * 0: params in the params argument will override any query string
197
+ // params in url.
198
+ // * 1: any query string params in url will override params in the params
199
+ // argument.
200
+ // * 2: params argument will completely replace any query string in url.
201
+ //
202
+ // Returns:
203
+ //
204
+ // (String) A URL with a urlencoded query string in the format '?a=b&c=d&e=f'.
205
+
206
+ // Method: jQuery.param.fragment
207
+ //
208
+ // Retrieve the fragment (hash) from a URL or if no arguments are passed, the
209
+ // current window.location.href.
210
+ //
211
+ // Usage:
212
+ //
213
+ // > jQuery.param.fragment( [ url ] );
214
+ //
215
+ // Arguments:
216
+ //
217
+ // url - (String) A URL containing fragment (hash) params to be parsed. If
218
+ // url is not passed, the current window.location.href is used.
219
+ //
220
+ // Returns:
221
+ //
222
+ // (String) The parsed fragment (hash) string, with any leading "#" removed.
223
+
224
+ // Method: jQuery.param.fragment (build url)
225
+ //
226
+ // Merge a URL, with or without pre-existing fragment (hash) params, plus any
227
+ // object, params string or URL containing fragment (hash) params into a new
228
+ // URL.
229
+ //
230
+ // Usage:
231
+ //
232
+ // > jQuery.param.fragment( url, params [, merge_mode ] );
233
+ //
234
+ // Arguments:
235
+ //
236
+ // url - (String) A valid URL for params to be merged into. This URL may
237
+ // contain a query string and/or fragment (hash).
238
+ // params - (String) A params string or URL containing fragment (hash) params
239
+ // to be merged into url.
240
+ // params - (Object) A params object to be merged into url.
241
+ // merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
242
+ // specified, and is as-follows:
243
+ //
244
+ // * 0: params in the params argument will override any fragment (hash)
245
+ // params in url.
246
+ // * 1: any fragment (hash) params in url will override params in the
247
+ // params argument.
248
+ // * 2: params argument will completely replace any query string in url.
249
+ //
250
+ // Returns:
251
+ //
252
+ // (String) A URL with a urlencoded fragment (hash) in the format '#a=b&c=d&e=f'.
253
+
254
+ function jq_param_sub( is_fragment, get_func, url, params, merge_mode ) {
255
+ var result,
256
+ qs,
257
+ matches,
258
+ url_params,
259
+ hash;
260
+
261
+ if ( params !== undefined ) {
262
+ // Build URL by merging params into url string.
263
+
264
+ // matches[1] = url part that precedes params, not including trailing ?/#
265
+ // matches[2] = params, not including leading ?/#
266
+ // matches[3] = if in 'querystring' mode, hash including leading #, otherwise ''
267
+ matches = url.match( is_fragment ? re_fragment : /^([^#?]*)\??([^#]*)(#?.*)/ );
268
+
269
+ // Get the hash if in 'querystring' mode, and it exists.
270
+ hash = matches[3] || '';
271
+
272
+ if ( merge_mode === 2 && is_string( params ) ) {
273
+ // If merge_mode is 2 and params is a string, merge the fragment / query
274
+ // string into the URL wholesale, without converting it into an object.
275
+ qs = params.replace( is_fragment ? re_params_fragment : re_params_querystring, '' );
276
+
277
+ } else {
278
+ // Convert relevant params in url to object.
279
+ url_params = jq_deparam( matches[2] );
280
+
281
+ params = is_string( params )
282
+
283
+ // Convert passed params string into object.
284
+ ? jq_deparam[ is_fragment ? str_fragment : str_querystring ]( params )
285
+
286
+ // Passed params object.
287
+ : params;
288
+
289
+ qs = merge_mode === 2 ? params // passed params replace url params
290
+ : merge_mode === 1 ? $.extend( {}, params, url_params ) // url params override passed params
291
+ : $.extend( {}, url_params, params ); // passed params override url params
292
+
293
+ // Convert params object into a sorted params string.
294
+ qs = jq_param_sorted( qs );
295
+
296
+ // Unescape characters specified via $.param.noEscape. Since only hash-
297
+ // history users have requested this feature, it's only enabled for
298
+ // fragment-related params strings.
299
+ if ( is_fragment ) {
300
+ qs = qs.replace( re_no_escape, decode );
301
+ }
302
+ }
303
+
304
+ // Build URL from the base url, querystring and hash. In 'querystring'
305
+ // mode, ? is only added if a query string exists. In 'fragment' mode, #
306
+ // is always added.
307
+ result = matches[1] + ( is_fragment ? fragment_prefix : qs || !matches[1] ? '?' : '' ) + qs + hash;
308
+
309
+ } else {
310
+ // If URL was passed in, parse params from URL string, otherwise parse
311
+ // params from window.location.href.
312
+ result = get_func( url !== undefined ? url : location.href );
313
+ }
314
+
315
+ return result;
316
+ };
317
+
318
+ jq_param[ str_querystring ] = curry( jq_param_sub, 0, get_querystring );
319
+ jq_param[ str_fragment ] = jq_param_fragment = curry( jq_param_sub, 1, get_fragment );
320
+
321
+ // Method: jQuery.param.sorted
322
+ //
323
+ // Returns a params string equivalent to that returned by the internal
324
+ // jQuery.param method, but sorted, which makes it suitable for use as a
325
+ // cache key.
326
+ //
327
+ // For example, in most browsers jQuery.param({z:1,a:2}) returns "z=1&a=2"
328
+ // and jQuery.param({a:2,z:1}) returns "a=2&z=1". Even though both the
329
+ // objects being serialized and the resulting params strings are equivalent,
330
+ // if these params strings were set into the location.hash fragment
331
+ // sequentially, the hashchange event would be triggered unnecessarily, since
332
+ // the strings are different (even though the data described by them is the
333
+ // same). By sorting the params string, unecessary hashchange event triggering
334
+ // can be avoided.
335
+ //
336
+ // Usage:
337
+ //
338
+ // > jQuery.param.sorted( obj [, traditional ] );
339
+ //
340
+ // Arguments:
341
+ //
342
+ // obj - (Object) An object to be serialized.
343
+ // traditional - (Boolean) Params deep/shallow serialization mode. See the
344
+ // documentation at http://api.jquery.com/jQuery.param/ for more detail.
345
+ //
346
+ // Returns:
347
+ //
348
+ // (String) A sorted params string.
349
+
350
+ jq_param.sorted = jq_param_sorted = function( a, traditional ) {
351
+ var arr = [],
352
+ obj = {};
353
+
354
+ $.each( jq_param( a, traditional ).split( '&' ), function(i,v){
355
+ var key = v.replace( /(?:%5B|=).*$/, '' ),
356
+ key_obj = obj[ key ];
357
+
358
+ if ( !key_obj ) {
359
+ key_obj = obj[ key ] = [];
360
+ arr.push( key );
361
+ }
362
+
363
+ key_obj.push( v );
364
+ });
365
+
366
+ return $.map( arr.sort(), function(v){
367
+ return obj[ v ];
368
+ }).join( '&' );
369
+ };
370
+
371
+ // Method: jQuery.param.fragment.noEscape
372
+ //
373
+ // Specify characters that will be left unescaped when fragments are created
374
+ // or merged using <jQuery.param.fragment>, or when the fragment is modified
375
+ // using <jQuery.bbq.pushState>. This option only applies to serialized data
376
+ // object fragments, and not set-as-string fragments. Does not affect the
377
+ // query string. Defaults to ",/" (comma, forward slash).
378
+ //
379
+ // Note that this is considered a purely aesthetic option, and will help to
380
+ // create URLs that "look pretty" in the address bar or bookmarks, without
381
+ // affecting functionality in any way. That being said, be careful to not
382
+ // unescape characters that are used as delimiters or serve a special
383
+ // purpose, such as the "#?&=+" (octothorpe, question mark, ampersand,
384
+ // equals, plus) characters.
385
+ //
386
+ // Usage:
387
+ //
388
+ // > jQuery.param.fragment.noEscape( [ chars ] );
389
+ //
390
+ // Arguments:
391
+ //
392
+ // chars - (String) The characters to not escape in the fragment. If
393
+ // unspecified, defaults to empty string (escape all characters).
394
+ //
395
+ // Returns:
396
+ //
397
+ // Nothing.
398
+
399
+ jq_param_fragment.noEscape = function( chars ) {
400
+ chars = chars || '';
401
+ var arr = $.map( chars.split(''), encodeURIComponent );
402
+ re_no_escape = new RegExp( arr.join('|'), 'g' );
403
+ };
404
+
405
+ // A sensible default. These are the characters people seem to complain about
406
+ // "uglifying up the URL" the most.
407
+ jq_param_fragment.noEscape( ',/' );
408
+
409
+ // Method: jQuery.param.fragment.ajaxCrawlable
410
+ //
411
+ // TODO: DESCRIBE
412
+ //
413
+ // Usage:
414
+ //
415
+ // > jQuery.param.fragment.ajaxCrawlable( [ state ] );
416
+ //
417
+ // Arguments:
418
+ //
419
+ // state - (Boolean) TODO: DESCRIBE
420
+ //
421
+ // Returns:
422
+ //
423
+ // (Boolean) The current ajaxCrawlable state.
424
+
425
+ jq_param_fragment.ajaxCrawlable = function( state ) {
426
+ if ( state !== undefined ) {
427
+ if ( state ) {
428
+ re_params_fragment = /^.*(?:#!|#)/;
429
+ re_fragment = /^([^#]*)(?:#!|#)?(.*)$/;
430
+ fragment_prefix = '#!';
431
+ } else {
432
+ re_params_fragment = /^.*#/;
433
+ re_fragment = /^([^#]*)#?(.*)$/;
434
+ fragment_prefix = '#';
435
+ }
436
+ ajax_crawlable = !!state;
437
+ }
438
+
439
+ return ajax_crawlable;
440
+ };
441
+
442
+ jq_param_fragment.ajaxCrawlable( 0 );
443
+
444
+ // Section: Deparam (from string)
445
+ //
446
+ // Method: jQuery.deparam
447
+ //
448
+ // Deserialize a params string into an object, optionally coercing numbers,
449
+ // booleans, null and undefined values; this method is the counterpart to the
450
+ // internal jQuery.param method.
451
+ //
452
+ // Usage:
453
+ //
454
+ // > jQuery.deparam( params [, coerce ] );
455
+ //
456
+ // Arguments:
457
+ //
458
+ // params - (String) A params string to be parsed.
459
+ // coerce - (Boolean) If true, coerces any numbers or true, false, null, and
460
+ // undefined to their actual value. Defaults to false if omitted.
461
+ //
462
+ // Returns:
463
+ //
464
+ // (Object) An object representing the deserialized params string.
465
+
466
+ $.deparam = jq_deparam = function( params, coerce ) {
467
+ var obj = {},
468
+ coerce_types = { 'true': !0, 'false': !1, 'null': null };
469
+
470
+ // Iterate over all name=value pairs.
471
+ $.each( params.replace( /\+/g, ' ' ).split( '&' ), function(j,v){
472
+ var param = v.split( '=' ),
473
+ key = decode( param[0] ),
474
+ val,
475
+ cur = obj,
476
+ i = 0,
477
+
478
+ // If key is more complex than 'foo', like 'a[]' or 'a[b][c]', split it
479
+ // into its component parts.
480
+ keys = key.split( '][' ),
481
+ keys_last = keys.length - 1;
482
+
483
+ // If the first keys part contains [ and the last ends with ], then []
484
+ // are correctly balanced.
485
+ if ( /\[/.test( keys[0] ) && /\]$/.test( keys[ keys_last ] ) ) {
486
+ // Remove the trailing ] from the last keys part.
487
+ keys[ keys_last ] = keys[ keys_last ].replace( /\]$/, '' );
488
+
489
+ // Split first keys part into two parts on the [ and add them back onto
490
+ // the beginning of the keys array.
491
+ keys = keys.shift().split('[').concat( keys );
492
+
493
+ keys_last = keys.length - 1;
494
+ } else {
495
+ // Basic 'foo' style key.
496
+ keys_last = 0;
497
+ }
498
+
499
+ // Are we dealing with a name=value pair, or just a name?
500
+ if ( param.length === 2 ) {
501
+ val = decode( param[1] );
502
+
503
+ // Coerce values.
504
+ if ( coerce ) {
505
+ val = val && !isNaN(val) ? +val // number
506
+ : val === 'undefined' ? undefined // undefined
507
+ : coerce_types[val] !== undefined ? coerce_types[val] // true, false, null
508
+ : val; // string
509
+ }
510
+
511
+ if ( keys_last ) {
512
+ // Complex key, build deep object structure based on a few rules:
513
+ // * The 'cur' pointer starts at the object top-level.
514
+ // * [] = array push (n is set to array length), [n] = array if n is
515
+ // numeric, otherwise object.
516
+ // * If at the last keys part, set the value.
517
+ // * For each keys part, if the current level is undefined create an
518
+ // object or array based on the type of the next keys part.
519
+ // * Move the 'cur' pointer to the next level.
520
+ // * Rinse & repeat.
521
+ for ( ; i <= keys_last; i++ ) {
522
+ key = keys[i] === '' ? cur.length : keys[i];
523
+ cur = cur[key] = i < keys_last
524
+ ? cur[key] || ( keys[i+1] && isNaN( keys[i+1] ) ? {} : [] )
525
+ : val;
526
+ }
527
+
528
+ } else {
529
+ // Simple key, even simpler rules, since only scalars and shallow
530
+ // arrays are allowed.
531
+
532
+ if ( $.isArray( obj[key] ) ) {
533
+ // val is already an array, so push on the next value.
534
+ obj[key].push( val );
535
+
536
+ } else if ( obj[key] !== undefined ) {
537
+ // val isn't an array, but since a second value has been specified,
538
+ // convert val into an array.
539
+ obj[key] = [ obj[key], val ];
540
+
541
+ } else {
542
+ // val is a scalar.
543
+ obj[key] = val;
544
+ }
545
+ }
546
+
547
+ } else if ( key ) {
548
+ // No value was defined, so set something meaningful.
549
+ obj[key] = coerce
550
+ ? undefined
551
+ : '';
552
+ }
553
+ });
554
+
555
+ return obj;
556
+ };
557
+
558
+ // Method: jQuery.deparam.querystring
559
+ //
560
+ // Parse the query string from a URL or the current window.location.href,
561
+ // deserializing it into an object, optionally coercing numbers, booleans,
562
+ // null and undefined values.
563
+ //
564
+ // Usage:
565
+ //
566
+ // > jQuery.deparam.querystring( [ url ] [, coerce ] );
567
+ //
568
+ // Arguments:
569
+ //
570
+ // url - (String) An optional params string or URL containing query string
571
+ // params to be parsed. If url is omitted, the current
572
+ // window.location.href is used.
573
+ // coerce - (Boolean) If true, coerces any numbers or true, false, null, and
574
+ // undefined to their actual value. Defaults to false if omitted.
575
+ //
576
+ // Returns:
577
+ //
578
+ // (Object) An object representing the deserialized params string.
579
+
580
+ // Method: jQuery.deparam.fragment
581
+ //
582
+ // Parse the fragment (hash) from a URL or the current window.location.href,
583
+ // deserializing it into an object, optionally coercing numbers, booleans,
584
+ // null and undefined values.
585
+ //
586
+ // Usage:
587
+ //
588
+ // > jQuery.deparam.fragment( [ url ] [, coerce ] );
589
+ //
590
+ // Arguments:
591
+ //
592
+ // url - (String) An optional params string or URL containing fragment (hash)
593
+ // params to be parsed. If url is omitted, the current window.location.href
594
+ // is used.
595
+ // coerce - (Boolean) If true, coerces any numbers or true, false, null, and
596
+ // undefined to their actual value. Defaults to false if omitted.
597
+ //
598
+ // Returns:
599
+ //
600
+ // (Object) An object representing the deserialized params string.
601
+
602
+ function jq_deparam_sub( is_fragment, url_or_params, coerce ) {
603
+ if ( url_or_params === undefined || typeof url_or_params === 'boolean' ) {
604
+ // url_or_params not specified.
605
+ coerce = url_or_params;
606
+ url_or_params = jq_param[ is_fragment ? str_fragment : str_querystring ]();
607
+ } else {
608
+ url_or_params = is_string( url_or_params )
609
+ ? url_or_params.replace( is_fragment ? re_params_fragment : re_params_querystring, '' )
610
+ : url_or_params;
611
+ }
612
+
613
+ return jq_deparam( url_or_params, coerce );
614
+ };
615
+
616
+ jq_deparam[ str_querystring ] = curry( jq_deparam_sub, 0 );
617
+ jq_deparam[ str_fragment ] = jq_deparam_fragment = curry( jq_deparam_sub, 1 );
618
+
619
+ // Section: Element manipulation
620
+ //
621
+ // Method: jQuery.elemUrlAttr
622
+ //
623
+ // Get the internal "Default URL attribute per tag" list, or augment the list
624
+ // with additional tag-attribute pairs, in case the defaults are insufficient.
625
+ //
626
+ // In the <jQuery.fn.querystring> and <jQuery.fn.fragment> methods, this list
627
+ // is used to determine which attribute contains the URL to be modified, if
628
+ // an "attr" param is not specified.
629
+ //
630
+ // Default Tag-Attribute List:
631
+ //
632
+ // a - href
633
+ // base - href
634
+ // iframe - src
635
+ // img - src
636
+ // input - src
637
+ // form - action
638
+ // link - href
639
+ // script - src
640
+ //
641
+ // Usage:
642
+ //
643
+ // > jQuery.elemUrlAttr( [ tag_attr ] );
644
+ //
645
+ // Arguments:
646
+ //
647
+ // tag_attr - (Object) An object containing a list of tag names and their
648
+ // associated default attribute names in the format { tag: 'attr', ... } to
649
+ // be merged into the internal tag-attribute list.
650
+ //
651
+ // Returns:
652
+ //
653
+ // (Object) An object containing all stored tag-attribute values.
654
+
655
+ // Only define function and set defaults if function doesn't already exist, as
656
+ // the urlInternal plugin will provide this method as well.
657
+ $[ str_elemUrlAttr ] || ($[ str_elemUrlAttr ] = function( obj ) {
658
+ return $.extend( elemUrlAttr_cache, obj );
659
+ })({
660
+ a: str_href,
661
+ base: str_href,
662
+ iframe: str_src,
663
+ img: str_src,
664
+ input: str_src,
665
+ form: 'action',
666
+ link: str_href,
667
+ script: str_src
668
+ });
669
+
670
+ jq_elemUrlAttr = $[ str_elemUrlAttr ];
671
+
672
+ // Method: jQuery.fn.querystring
673
+ //
674
+ // Update URL attribute in one or more elements, merging the current URL (with
675
+ // or without pre-existing query string params) plus any params object or
676
+ // string into a new URL, which is then set into that attribute. Like
677
+ // <jQuery.param.querystring (build url)>, but for all elements in a jQuery
678
+ // collection.
679
+ //
680
+ // Usage:
681
+ //
682
+ // > jQuery('selector').querystring( [ attr, ] params [, merge_mode ] );
683
+ //
684
+ // Arguments:
685
+ //
686
+ // attr - (String) Optional name of an attribute that will contain a URL to
687
+ // merge params or url into. See <jQuery.elemUrlAttr> for a list of default
688
+ // attributes.
689
+ // params - (Object) A params object to be merged into the URL attribute.
690
+ // params - (String) A URL containing query string params, or params string
691
+ // to be merged into the URL attribute.
692
+ // merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
693
+ // specified, and is as-follows:
694
+ //
695
+ // * 0: params in the params argument will override any params in attr URL.
696
+ // * 1: any params in attr URL will override params in the params argument.
697
+ // * 2: params argument will completely replace any query string in attr
698
+ // URL.
699
+ //
700
+ // Returns:
701
+ //
702
+ // (jQuery) The initial jQuery collection of elements, but with modified URL
703
+ // attribute values.
704
+
705
+ // Method: jQuery.fn.fragment
706
+ //
707
+ // Update URL attribute in one or more elements, merging the current URL (with
708
+ // or without pre-existing fragment/hash params) plus any params object or
709
+ // string into a new URL, which is then set into that attribute. Like
710
+ // <jQuery.param.fragment (build url)>, but for all elements in a jQuery
711
+ // collection.
712
+ //
713
+ // Usage:
714
+ //
715
+ // > jQuery('selector').fragment( [ attr, ] params [, merge_mode ] );
716
+ //
717
+ // Arguments:
718
+ //
719
+ // attr - (String) Optional name of an attribute that will contain a URL to
720
+ // merge params into. See <jQuery.elemUrlAttr> for a list of default
721
+ // attributes.
722
+ // params - (Object) A params object to be merged into the URL attribute.
723
+ // params - (String) A URL containing fragment (hash) params, or params
724
+ // string to be merged into the URL attribute.
725
+ // merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
726
+ // specified, and is as-follows:
727
+ //
728
+ // * 0: params in the params argument will override any params in attr URL.
729
+ // * 1: any params in attr URL will override params in the params argument.
730
+ // * 2: params argument will completely replace any fragment (hash) in attr
731
+ // URL.
732
+ //
733
+ // Returns:
734
+ //
735
+ // (jQuery) The initial jQuery collection of elements, but with modified URL
736
+ // attribute values.
737
+
738
+ function jq_fn_sub( mode, force_attr, params, merge_mode ) {
739
+ if ( !is_string( params ) && typeof params !== 'object' ) {
740
+ // force_attr not specified.
741
+ merge_mode = params;
742
+ params = force_attr;
743
+ force_attr = undefined;
744
+ }
745
+
746
+ return this.each(function(){
747
+ var that = $(this),
748
+
749
+ // Get attribute specified, or default specified via $.elemUrlAttr.
750
+ attr = force_attr || jq_elemUrlAttr()[ ( this.nodeName || '' ).toLowerCase() ] || '',
751
+
752
+ // Get URL value.
753
+ url = attr && that.attr( attr ) || '';
754
+
755
+ // Update attribute with new URL.
756
+ that.attr( attr, jq_param[ mode ]( url, params, merge_mode ) );
757
+ });
758
+
759
+ };
760
+
761
+ $.fn[ str_querystring ] = curry( jq_fn_sub, str_querystring );
762
+ $.fn[ str_fragment ] = curry( jq_fn_sub, str_fragment );
763
+
764
+ // Section: History, hashchange event
765
+ //
766
+ // Method: jQuery.bbq.pushState
767
+ //
768
+ // Adds a 'state' into the browser history at the current position, setting
769
+ // location.hash and triggering any bound <hashchange event> callbacks
770
+ // (provided the new state is different than the previous state).
771
+ //
772
+ // If no arguments are passed, an empty state is created, which is just a
773
+ // shortcut for jQuery.bbq.pushState( {}, 2 ).
774
+ //
775
+ // Usage:
776
+ //
777
+ // > jQuery.bbq.pushState( [ params [, merge_mode ] ] );
778
+ //
779
+ // Arguments:
780
+ //
781
+ // params - (String) A serialized params string or a hash string beginning
782
+ // with # to merge into location.hash.
783
+ // params - (Object) A params object to merge into location.hash.
784
+ // merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
785
+ // specified (unless a hash string beginning with # is specified, in which
786
+ // case merge behavior defaults to 2), and is as-follows:
787
+ //
788
+ // * 0: params in the params argument will override any params in the
789
+ // current state.
790
+ // * 1: any params in the current state will override params in the params
791
+ // argument.
792
+ // * 2: params argument will completely replace current state.
793
+ //
794
+ // Returns:
795
+ //
796
+ // Nothing.
797
+ //
798
+ // Additional Notes:
799
+ //
800
+ // * Setting an empty state may cause the browser to scroll.
801
+ // * Unlike the fragment and querystring methods, if a hash string beginning
802
+ // with # is specified as the params agrument, merge_mode defaults to 2.
803
+
804
+ jq_bbq.pushState = jq_bbq_pushState = function( params, merge_mode ) {
805
+ if ( is_string( params ) && /^#/.test( params ) && merge_mode === undefined ) {
806
+ // Params string begins with # and merge_mode not specified, so completely
807
+ // overwrite window.location.hash.
808
+ merge_mode = 2;
809
+ }
810
+
811
+ var has_args = params !== undefined,
812
+ // Merge params into window.location using $.param.fragment.
813
+ url = jq_param_fragment( location.href,
814
+ has_args ? params : {}, has_args ? merge_mode : 2 );
815
+
816
+ // Set new window.location.href. Note that Safari 3 & Chrome barf on
817
+ // location.hash = '#' so the entire URL is set.
818
+ location.href = url;
819
+ };
820
+
821
+ // Method: jQuery.bbq.getState
822
+ //
823
+ // Retrieves the current 'state' from the browser history, parsing
824
+ // location.hash for a specific key or returning an object containing the
825
+ // entire state, optionally coercing numbers, booleans, null and undefined
826
+ // values.
827
+ //
828
+ // Usage:
829
+ //
830
+ // > jQuery.bbq.getState( [ key ] [, coerce ] );
831
+ //
832
+ // Arguments:
833
+ //
834
+ // key - (String) An optional state key for which to return a value.
835
+ // coerce - (Boolean) If true, coerces any numbers or true, false, null, and
836
+ // undefined to their actual value. Defaults to false.
837
+ //
838
+ // Returns:
839
+ //
840
+ // (Anything) If key is passed, returns the value corresponding with that key
841
+ // in the location.hash 'state', or undefined. If not, an object
842
+ // representing the entire 'state' is returned.
843
+
844
+ jq_bbq.getState = jq_bbq_getState = function( key, coerce ) {
845
+ return key === undefined || typeof key === 'boolean'
846
+ ? jq_deparam_fragment( key ) // 'key' really means 'coerce' here
847
+ : jq_deparam_fragment( coerce )[ key ];
848
+ };
849
+
850
+ // Method: jQuery.bbq.removeState
851
+ //
852
+ // Remove one or more keys from the current browser history 'state', creating
853
+ // a new state, setting location.hash and triggering any bound
854
+ // <hashchange event> callbacks (provided the new state is different than
855
+ // the previous state).
856
+ //
857
+ // If no arguments are passed, an empty state is created, which is just a
858
+ // shortcut for jQuery.bbq.pushState( {}, 2 ).
859
+ //
860
+ // Usage:
861
+ //
862
+ // > jQuery.bbq.removeState( [ key [, key ... ] ] );
863
+ //
864
+ // Arguments:
865
+ //
866
+ // key - (String) One or more key values to remove from the current state,
867
+ // passed as individual arguments.
868
+ // key - (Array) A single array argument that contains a list of key values
869
+ // to remove from the current state.
870
+ //
871
+ // Returns:
872
+ //
873
+ // Nothing.
874
+ //
875
+ // Additional Notes:
876
+ //
877
+ // * Setting an empty state may cause the browser to scroll.
878
+
879
+ jq_bbq.removeState = function( arr ) {
880
+ var state = {};
881
+
882
+ // If one or more arguments is passed..
883
+ if ( arr !== undefined ) {
884
+
885
+ // Get the current state.
886
+ state = jq_bbq_getState();
887
+
888
+ // For each passed key, delete the corresponding property from the current
889
+ // state.
890
+ $.each( $.isArray( arr ) ? arr : arguments, function(i,v){
891
+ delete state[ v ];
892
+ });
893
+ }
894
+
895
+ // Set the state, completely overriding any existing state.
896
+ jq_bbq_pushState( state, 2 );
897
+ };
898
+
899
+ // Event: hashchange event (BBQ)
900
+ //
901
+ // Usage in jQuery 1.4 and newer:
902
+ //
903
+ // In jQuery 1.4 and newer, the event object passed into any hashchange event
904
+ // callback is augmented with a copy of the location.hash fragment at the time
905
+ // the event was triggered as its event.fragment property. In addition, the
906
+ // event.getState method operates on this property (instead of location.hash)
907
+ // which allows this fragment-as-a-state to be referenced later, even after
908
+ // window.location may have changed.
909
+ //
910
+ // Note that event.fragment and event.getState are not defined according to
911
+ // W3C (or any other) specification, but will still be available whether or
912
+ // not the hashchange event exists natively in the browser, because of the
913
+ // utility they provide.
914
+ //
915
+ // The event.fragment property contains the output of <jQuery.param.fragment>
916
+ // and the event.getState method is equivalent to the <jQuery.bbq.getState>
917
+ // method.
918
+ //
919
+ // > $(window).bind( 'hashchange', function( event ) {
920
+ // > var hash_str = event.fragment,
921
+ // > param_obj = event.getState(),
922
+ // > param_val = event.getState( 'param_name' ),
923
+ // > param_val_coerced = event.getState( 'param_name', true );
924
+ // > ...
925
+ // > });
926
+ //
927
+ // Usage in jQuery 1.3.2:
928
+ //
929
+ // In jQuery 1.3.2, the event object cannot to be augmented as in jQuery 1.4+,
930
+ // so the fragment state isn't bound to the event object and must instead be
931
+ // parsed using the <jQuery.param.fragment> and <jQuery.bbq.getState> methods.
932
+ //
933
+ // > $(window).bind( 'hashchange', function( event ) {
934
+ // > var hash_str = $.param.fragment(),
935
+ // > param_obj = $.bbq.getState(),
936
+ // > param_val = $.bbq.getState( 'param_name' ),
937
+ // > param_val_coerced = $.bbq.getState( 'param_name', true );
938
+ // > ...
939
+ // > });
940
+ //
941
+ // Additional Notes:
942
+ //
943
+ // * Due to changes in the special events API, jQuery BBQ v1.2 or newer is
944
+ // required to enable the augmented event object in jQuery 1.4.2 and newer.
945
+ // * See <jQuery hashchange event> for more detailed information.
946
+
947
+ special[ str_hashchange ] = $.extend( special[ str_hashchange ], {
948
+
949
+ // Augmenting the event object with the .fragment property and .getState
950
+ // method requires jQuery 1.4 or newer. Note: with 1.3.2, everything will
951
+ // work, but the event won't be augmented)
952
+ add: function( handleObj ) {
953
+ var old_handler;
954
+
955
+ function new_handler(e) {
956
+ // e.fragment is set to the value of location.hash (with any leading #
957
+ // removed) at the time the event is triggered.
958
+ var hash = e[ str_fragment ] = jq_param_fragment();
959
+
960
+ // e.getState() works just like $.bbq.getState(), but uses the
961
+ // e.fragment property stored on the event object.
962
+ e.getState = function( key, coerce ) {
963
+ return key === undefined || typeof key === 'boolean'
964
+ ? jq_deparam( hash, key ) // 'key' really means 'coerce' here
965
+ : jq_deparam( hash, coerce )[ key ];
966
+ };
967
+
968
+ old_handler.apply( this, arguments );
969
+ };
970
+
971
+ // This may seem a little complicated, but it normalizes the special event
972
+ // .add method between jQuery 1.4/1.4.1 and 1.4.2+
973
+ if ( $.isFunction( handleObj ) ) {
974
+ // 1.4, 1.4.1
975
+ old_handler = handleObj;
976
+ return new_handler;
977
+ } else {
978
+ // 1.4.2+
979
+ old_handler = handleObj.handler;
980
+ handleObj.handler = new_handler;
981
+ }
982
+ }
983
+
984
+ });
985
+
986
+ })(jQuery,this);
987
+
988
+ /*!
989
+ * jQuery hashchange event - v1.3 - 7/21/2010
990
+ * http://benalman.com/projects/jquery-hashchange-plugin/
991
+ *
992
+ * Copyright (c) 2010 "Cowboy" Ben Alman
993
+ * Dual licensed under the MIT and GPL licenses.
994
+ * http://benalman.com/about/license/
995
+ */
996
+
997
+ // Script: jQuery hashchange event
998
+ //
999
+ // *Version: 1.3, Last updated: 7/21/2010*
1000
+ //
1001
+ // Project Home - http://benalman.com/projects/jquery-hashchange-plugin/
1002
+ // GitHub - http://github.com/cowboy/jquery-hashchange/
1003
+ // Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js
1004
+ // (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (0.8kb gzipped)
1005
+ //
1006
+ // About: License
1007
+ //
1008
+ // Copyright (c) 2010 "Cowboy" Ben Alman,
1009
+ // Dual licensed under the MIT and GPL licenses.
1010
+ // http://benalman.com/about/license/
1011
+ //
1012
+ // About: Examples
1013
+ //
1014
+ // These working examples, complete with fully commented code, illustrate a few
1015
+ // ways in which this plugin can be used.
1016
+ //
1017
+ // hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/
1018
+ // document.domain - http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/
1019
+ //
1020
+ // About: Support and Testing
1021
+ //
1022
+ // Information about what version or versions of jQuery this plugin has been
1023
+ // tested with, what browsers it has been tested in, and where the unit tests
1024
+ // reside (so you can test it yourself).
1025
+ //
1026
+ // jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2
1027
+ // Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5,
1028
+ // Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5.
1029
+ // Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/
1030
+ //
1031
+ // About: Known issues
1032
+ //
1033
+ // While this jQuery hashchange event implementation is quite stable and
1034
+ // robust, there are a few unfortunate browser bugs surrounding expected
1035
+ // hashchange event-based behaviors, independent of any JavaScript
1036
+ // window.onhashchange abstraction. See the following examples for more
1037
+ // information:
1038
+ //
1039
+ // Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/
1040
+ // Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/
1041
+ // WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/
1042
+ // Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/
1043
+ //
1044
+ // Also note that should a browser natively support the window.onhashchange
1045
+ // event, but not report that it does, the fallback polling loop will be used.
1046
+ //
1047
+ // About: Release History
1048
+ //
1049
+ // 1.3 - (7/21/2010) Reorganized IE6/7 Iframe code to make it more
1050
+ // "removable" for mobile-only development. Added IE6/7 document.title
1051
+ // support. Attempted to make Iframe as hidden as possible by using
1052
+ // techniques from http://www.paciellogroup.com/blog/?p=604. Added
1053
+ // support for the "shortcut" format $(window).hashchange( fn ) and
1054
+ // $(window).hashchange() like jQuery provides for built-in events.
1055
+ // Renamed jQuery.hashchangeDelay to <jQuery.fn.hashchange.delay> and
1056
+ // lowered its default value to 50. Added <jQuery.fn.hashchange.domain>
1057
+ // and <jQuery.fn.hashchange.src> properties plus document-domain.html
1058
+ // file to address access denied issues when setting document.domain in
1059
+ // IE6/7.
1060
+ // 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin
1061
+ // from a page on another domain would cause an error in Safari 4. Also,
1062
+ // IE6/7 Iframe is now inserted after the body (this actually works),
1063
+ // which prevents the page from scrolling when the event is first bound.
1064
+ // Event can also now be bound before DOM ready, but it won't be usable
1065
+ // before then in IE6/7.
1066
+ // 1.1 - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug
1067
+ // where browser version is incorrectly reported as 8.0, despite
1068
+ // inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag.
1069
+ // 1.0 - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special
1070
+ // window.onhashchange functionality into a separate plugin for users
1071
+ // who want just the basic event & back button support, without all the
1072
+ // extra awesomeness that BBQ provides. This plugin will be included as
1073
+ // part of jQuery BBQ, but also be available separately.
1074
+
1075
+ (function($,window,undefined){
1076
+ '$:nomunge'; // Used by YUI compressor.
1077
+
1078
+ // Reused string.
1079
+ var str_hashchange = 'hashchange',
1080
+
1081
+ // Method / object references.
1082
+ doc = document,
1083
+ fake_onhashchange,
1084
+ special = $.event.special,
1085
+
1086
+ // Does the browser support window.onhashchange? Note that IE8 running in
1087
+ // IE7 compatibility mode reports true for 'onhashchange' in window, even
1088
+ // though the event isn't supported, so also test document.documentMode.
1089
+ doc_mode = doc.documentMode,
1090
+ supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 );
1091
+
1092
+ // Get location.hash (or what you'd expect location.hash to be) sans any
1093
+ // leading #. Thanks for making this necessary, Firefox!
1094
+ function get_fragment( url ) {
1095
+ url = url || location.href;
1096
+ return '#' + url.replace( /^[^#]*#?(.*)$/, '$1' );
1097
+ };
1098
+
1099
+ // Method: jQuery.fn.hashchange
1100
+ //
1101
+ // Bind a handler to the window.onhashchange event or trigger all bound
1102
+ // window.onhashchange event handlers. This behavior is consistent with
1103
+ // jQuery's built-in event handlers.
1104
+ //
1105
+ // Usage:
1106
+ //
1107
+ // > jQuery(window).hashchange( [ handler ] );
1108
+ //
1109
+ // Arguments:
1110
+ //
1111
+ // handler - (Function) Optional handler to be bound to the hashchange
1112
+ // event. This is a "shortcut" for the more verbose form:
1113
+ // jQuery(window).bind( 'hashchange', handler ). If handler is omitted,
1114
+ // all bound window.onhashchange event handlers will be triggered. This
1115
+ // is a shortcut for the more verbose
1116
+ // jQuery(window).trigger( 'hashchange' ). These forms are described in
1117
+ // the <hashchange event> section.
1118
+ //
1119
+ // Returns:
1120
+ //
1121
+ // (jQuery) The initial jQuery collection of elements.
1122
+
1123
+ // Allow the "shortcut" format $(elem).hashchange( fn ) for binding and
1124
+ // $(elem).hashchange() for triggering, like jQuery does for built-in events.
1125
+ $.fn[ str_hashchange ] = function( fn ) {
1126
+ return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange );
1127
+ };
1128
+
1129
+ // Property: jQuery.fn.hashchange.delay
1130
+ //
1131
+ // The numeric interval (in milliseconds) at which the <hashchange event>
1132
+ // polling loop executes. Defaults to 50.
1133
+
1134
+ // Property: jQuery.fn.hashchange.domain
1135
+ //
1136
+ // If you're setting document.domain in your JavaScript, and you want hash
1137
+ // history to work in IE6/7, not only must this property be set, but you must
1138
+ // also set document.domain BEFORE jQuery is loaded into the page. This
1139
+ // property is only applicable if you are supporting IE6/7 (or IE8 operating
1140
+ // in "IE7 compatibility" mode).
1141
+ //
1142
+ // In addition, the <jQuery.fn.hashchange.src> property must be set to the
1143
+ // path of the included "document-domain.html" file, which can be renamed or
1144
+ // modified if necessary (note that the document.domain specified must be the
1145
+ // same in both your main JavaScript as well as in this file).
1146
+ //
1147
+ // Usage:
1148
+ //
1149
+ // jQuery.fn.hashchange.domain = document.domain;
1150
+
1151
+ // Property: jQuery.fn.hashchange.src
1152
+ //
1153
+ // If, for some reason, you need to specify an Iframe src file (for example,
1154
+ // when setting document.domain as in <jQuery.fn.hashchange.domain>), you can
1155
+ // do so using this property. Note that when using this property, history
1156
+ // won't be recorded in IE6/7 until the Iframe src file loads. This property
1157
+ // is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7
1158
+ // compatibility" mode).
1159
+ //
1160
+ // Usage:
1161
+ //
1162
+ // jQuery.fn.hashchange.src = 'path/to/file.html';
1163
+
1164
+ $.fn[ str_hashchange ].delay = 50;
1165
+ /*
1166
+ $.fn[ str_hashchange ].domain = null;
1167
+ $.fn[ str_hashchange ].src = null;
1168
+ */
1169
+
1170
+ // Event: hashchange event
1171
+ //
1172
+ // Fired when location.hash changes. In browsers that support it, the native
1173
+ // HTML5 window.onhashchange event is used, otherwise a polling loop is
1174
+ // initialized, running every <jQuery.fn.hashchange.delay> milliseconds to
1175
+ // see if the hash has changed. In IE6/7 (and IE8 operating in "IE7
1176
+ // compatibility" mode), a hidden Iframe is created to allow the back button
1177
+ // and hash-based history to work.
1178
+ //
1179
+ // Usage as described in <jQuery.fn.hashchange>:
1180
+ //
1181
+ // > // Bind an event handler.
1182
+ // > jQuery(window).hashchange( function(e) {
1183
+ // > var hash = location.hash;
1184
+ // > ...
1185
+ // > });
1186
+ // >
1187
+ // > // Manually trigger the event handler.
1188
+ // > jQuery(window).hashchange();
1189
+ //
1190
+ // A more verbose usage that allows for event namespacing:
1191
+ //
1192
+ // > // Bind an event handler.
1193
+ // > jQuery(window).bind( 'hashchange', function(e) {
1194
+ // > var hash = location.hash;
1195
+ // > ...
1196
+ // > });
1197
+ // >
1198
+ // > // Manually trigger the event handler.
1199
+ // > jQuery(window).trigger( 'hashchange' );
1200
+ //
1201
+ // Additional Notes:
1202
+ //
1203
+ // * The polling loop and Iframe are not created until at least one handler
1204
+ // is actually bound to the 'hashchange' event.
1205
+ // * If you need the bound handler(s) to execute immediately, in cases where
1206
+ // a location.hash exists on page load, via bookmark or page refresh for
1207
+ // example, use jQuery(window).hashchange() or the more verbose
1208
+ // jQuery(window).trigger( 'hashchange' ).
1209
+ // * The event can be bound before DOM ready, but since it won't be usable
1210
+ // before then in IE6/7 (due to the necessary Iframe), recommended usage is
1211
+ // to bind it inside a DOM ready handler.
1212
+
1213
+ // Override existing $.event.special.hashchange methods (allowing this plugin
1214
+ // to be defined after jQuery BBQ in BBQ's source code).
1215
+ special[ str_hashchange ] = $.extend( special[ str_hashchange ], {
1216
+
1217
+ // Called only when the first 'hashchange' event is bound to window.
1218
+ setup: function() {
1219
+ // If window.onhashchange is supported natively, there's nothing to do..
1220
+ if ( supports_onhashchange ) { return false; }
1221
+
1222
+ // Otherwise, we need to create our own. And we don't want to call this
1223
+ // until the user binds to the event, just in case they never do, since it
1224
+ // will create a polling loop and possibly even a hidden Iframe.
1225
+ $( fake_onhashchange.start );
1226
+ },
1227
+
1228
+ // Called only when the last 'hashchange' event is unbound from window.
1229
+ teardown: function() {
1230
+ // If window.onhashchange is supported natively, there's nothing to do..
1231
+ if ( supports_onhashchange ) { return false; }
1232
+
1233
+ // Otherwise, we need to stop ours (if possible).
1234
+ $( fake_onhashchange.stop );
1235
+ }
1236
+
1237
+ });
1238
+
1239
+ // fake_onhashchange does all the work of triggering the window.onhashchange
1240
+ // event for browsers that don't natively support it, including creating a
1241
+ // polling loop to watch for hash changes and in IE 6/7 creating a hidden
1242
+ // Iframe to enable back and forward.
1243
+ fake_onhashchange = (function(){
1244
+ var self = {},
1245
+ timeout_id,
1246
+
1247
+ // Remember the initial hash so it doesn't get triggered immediately.
1248
+ last_hash = get_fragment(),
1249
+
1250
+ fn_retval = function(val){ return val; },
1251
+ history_set = fn_retval,
1252
+ history_get = fn_retval;
1253
+
1254
+ // Start the polling loop.
1255
+ self.start = function() {
1256
+ timeout_id || poll();
1257
+ };
1258
+
1259
+ // Stop the polling loop.
1260
+ self.stop = function() {
1261
+ timeout_id && clearTimeout( timeout_id );
1262
+ timeout_id = undefined;
1263
+ };
1264
+
1265
+ // This polling loop checks every $.fn.hashchange.delay milliseconds to see
1266
+ // if location.hash has changed, and triggers the 'hashchange' event on
1267
+ // window when necessary.
1268
+ function poll() {
1269
+ var hash = get_fragment(),
1270
+ history_hash = history_get( last_hash );
1271
+
1272
+ if ( hash !== last_hash ) {
1273
+ history_set( last_hash = hash, history_hash );
1274
+
1275
+ $(window).trigger( str_hashchange );
1276
+
1277
+ } else if ( history_hash !== last_hash ) {
1278
+ location.href = location.href.replace( /#.*/, '' ) + history_hash;
1279
+ }
1280
+
1281
+ timeout_id = setTimeout( poll, $.fn[ str_hashchange ].delay );
1282
+ };
1283
+
1284
+ // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
1285
+ // vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv
1286
+ // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
1287
+ $.browser.msie && !supports_onhashchange && (function(){
1288
+ // Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8
1289
+ // when running in "IE7 compatibility" mode.
1290
+
1291
+ var iframe,
1292
+ iframe_src;
1293
+
1294
+ // When the event is bound and polling starts in IE 6/7, create a hidden
1295
+ // Iframe for history handling.
1296
+ self.start = function(){
1297
+ if ( !iframe ) {
1298
+ iframe_src = $.fn[ str_hashchange ].src;
1299
+ iframe_src = iframe_src && iframe_src + get_fragment();
1300
+
1301
+ // Create hidden Iframe. Attempt to make Iframe as hidden as possible
1302
+ // by using techniques from http://www.paciellogroup.com/blog/?p=604.
1303
+ iframe = $('<iframe tabindex="-1" title="empty"/>').hide()
1304
+
1305
+ // When Iframe has completely loaded, initialize the history and
1306
+ // start polling.
1307
+ .one( 'load', function(){
1308
+ iframe_src || history_set( get_fragment() );
1309
+ poll();
1310
+ })
1311
+
1312
+ // Load Iframe src if specified, otherwise nothing.
1313
+ .attr( 'src', iframe_src || 'javascript:0' )
1314
+
1315
+ // Append Iframe after the end of the body to prevent unnecessary
1316
+ // initial page scrolling (yes, this works).
1317
+ .insertAfter( 'body' )[0].contentWindow;
1318
+
1319
+ // Whenever `document.title` changes, update the Iframe's title to
1320
+ // prettify the back/next history menu entries. Since IE sometimes
1321
+ // errors with "Unspecified error" the very first time this is set
1322
+ // (yes, very useful) wrap this with a try/catch block.
1323
+ doc.onpropertychange = function(){
1324
+ try {
1325
+ if ( event.propertyName === 'title' ) {
1326
+ iframe.document.title = doc.title;
1327
+ }
1328
+ } catch(e) {}
1329
+ };
1330
+
1331
+ }
1332
+ };
1333
+
1334
+ // Override the "stop" method since an IE6/7 Iframe was created. Even
1335
+ // if there are no longer any bound event handlers, the polling loop
1336
+ // is still necessary for back/next to work at all!
1337
+ self.stop = fn_retval;
1338
+
1339
+ // Get history by looking at the hidden Iframe's location.hash.
1340
+ history_get = function() {
1341
+ return get_fragment( iframe.location.href );
1342
+ };
1343
+
1344
+ // Set a new history item by opening and then closing the Iframe
1345
+ // document, *then* setting its location.hash. If document.domain has
1346
+ // been set, update that as well.
1347
+ history_set = function( hash, history_hash ) {
1348
+ var iframe_doc = iframe.document,
1349
+ domain = $.fn[ str_hashchange ].domain;
1350
+
1351
+ if ( hash !== history_hash ) {
1352
+ // Update Iframe with any initial `document.title` that might be set.
1353
+ iframe_doc.title = doc.title;
1354
+
1355
+ // Opening the Iframe's document after it has been closed is what
1356
+ // actually adds a history entry.
1357
+ iframe_doc.open();
1358
+
1359
+ // Set document.domain for the Iframe document as well, if necessary.
1360
+ domain && iframe_doc.write( '<script>document.domain="' + domain + '"</script>' );
1361
+
1362
+ iframe_doc.close();
1363
+
1364
+ // Update the Iframe's hash, for great justice.
1365
+ iframe.location.hash = hash;
1366
+ }
1367
+ };
1368
+
1369
+ })();
1370
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1371
+ // ^^^^^^^^^^^^^^^^^^^ REMOVE IF NOT SUPPORTING IE6/7/8 ^^^^^^^^^^^^^^^^^^^
1372
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1373
+
1374
+ return self;
1375
+ })();
1376
+
1377
+ })(jQuery,this);