mkdru.removeLimits() no longer leaves dangling limit_subject
[mkdru-moved-to-drupal.org.git] / jquery.ba-bbq.js
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);