diff --git a/js/jquery.ba-bbq.js b/js/jquery.ba-bbq.js index 78e409fd7..84a9f05ff 100644 --- a/js/jquery.ba-bbq.js +++ b/js/jquery.ba-bbq.js @@ -1,5 +1,5 @@ /*! - * jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010 + * jQuery BBQ: Back Button & Query Library - v1.3pre - 8/26/2010 * http://benalman.com/projects/jquery-bbq-plugin/ * * Copyright (c) 2010 "Cowboy" Ben Alman @@ -9,12 +9,12 @@ // Script: jQuery BBQ: Back Button & Query Library // -// *Version: 1.2.1, Last updated: 2/17/2010* +// *Version: 1.3pre, Last updated: 8/26/2010* // // Project Home - http://benalman.com/projects/jquery-bbq-plugin/ // GitHub - http://github.com/cowboy/jquery-bbq/ // Source - http://github.com/cowboy/jquery-bbq/raw/master/jquery.ba-bbq.js -// (Minified) - http://github.com/cowboy/jquery-bbq/raw/master/jquery.ba-bbq.min.js (4.0kb) +// (Minified) - http://github.com/cowboy/jquery-bbq/raw/master/jquery.ba-bbq.min.js (2.2kb gzipped) // // About: License // @@ -38,13 +38,21 @@ // tested with, what browsers it has been tested in, and where the unit tests // reside (so you can test it yourself). // -// jQuery Versions - 1.3.2, 1.4.1, 1.4.2 -// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.7, Safari 3-4, -// Chrome 4-5, Opera 9.6-10.1. +// jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2 +// Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5, +// Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5. // Unit Tests - http://benalman.com/code/projects/jquery-bbq/unit/ // // About: Release History // +// 1.3pre - (8/26/2010) Integrated v1.3, which adds +// document.title and document.domain support in IE6/7, BlackBerry +// support, better Iframe hiding for accessibility reasons, and the new +// "shortcut" method. Added the +// method which reduces the possibility of +// extraneous hashchange event triggering. Added the +// method which can be used to +// enable Google "AJAX Crawlable mode." // 1.2.1 - (2/17/2010) Actually fixed the stale window.location Safari bug from // in BBQ, which was the main reason for the // previous release! @@ -87,6 +95,7 @@ // Method / object references. jq_param = $.param, + jq_param_sorted, jq_param_fragment, jq_deparam, jq_deparam_fragment, @@ -94,22 +103,25 @@ jq_bbq_pushState, jq_bbq_getState, jq_elemUrlAttr, - jq_event_special = $.event.special, + special = $.event.special, // Reused strings. str_hashchange = 'hashchange', str_querystring = 'querystring', str_fragment = 'fragment', str_elemUrlAttr = 'elemUrlAttr', - str_location = 'location', str_href = 'href', str_src = 'src', // Reused RegExp. - re_trim_querystring = /^.*\?|#.*$/g, - re_trim_fragment = /^.*\#/, + re_params_querystring = /^.*\?|#.*$/g, + re_params_fragment, + re_fragment, re_no_escape, + ajax_crawlable, + fragment_prefix, + // Used by jQuery.elemUrlAttr. elemUrlAttr_cache = {}; @@ -132,7 +144,7 @@ // Get location.hash (or what you'd expect location.hash to be) sans any // leading #. Thanks for making this necessary, Firefox! function get_fragment( url ) { - return url.replace( /^[^#]*#?(.*)$/, '$1' ); + return url.replace( re_fragment, '$2' ); }; // Get location.search (or what you'd expect location.search to be) sans any @@ -146,7 +158,7 @@ // Method: jQuery.param.querystring // // Retrieve the query string from a URL or if no arguments are passed, the - // current window.location. + // current window.location.href. // // Usage: // @@ -155,7 +167,7 @@ // Arguments: // // url - (String) A URL containing query string params to be parsed. If url - // is not passed, the current window.location is used. + // is not passed, the current window.location.href is used. // // Returns: // @@ -189,13 +201,12 @@ // // Returns: // - // (String) Either a params string with urlencoded data or a URL with a - // urlencoded query string in the format 'a=b&c=d&e=f'. + // (String) A URL with a urlencoded query string in the format '?a=b&c=d&e=f'. // Method: jQuery.param.fragment // // Retrieve the fragment (hash) from a URL or if no arguments are passed, the - // current window.location. + // current window.location.href. // // Usage: // @@ -204,7 +215,7 @@ // Arguments: // // url - (String) A URL containing fragment (hash) params to be parsed. If - // url is not passed, the current window.location is used. + // url is not passed, the current window.location.href is used. // // Returns: // @@ -238,8 +249,7 @@ // // Returns: // - // (String) Either a params string with urlencoded data or a URL with a - // urlencoded fragment (hash) in the format 'a=b&c=d&e=f'. + // (String) A URL with a urlencoded fragment (hash) in the format '#a=b&c=d&e=f'. function jq_param_sub( is_fragment, get_func, url, params, merge_mode ) { var result, @@ -254,7 +264,7 @@ // matches[1] = url part that precedes params, not including trailing ?/# // matches[2] = params, not including leading ?/# // matches[3] = if in 'querystring' mode, hash including leading #, otherwise '' - matches = url.match( is_fragment ? /^([^#]*)\#?(.*)$/ : /^([^#?]*)\??([^#]*)(#?.*)/ ); + matches = url.match( is_fragment ? re_fragment : /^([^#?]*)\??([^#]*)(#?.*)/ ); // Get the hash if in 'querystring' mode, and it exists. hash = matches[3] || ''; @@ -262,7 +272,7 @@ if ( merge_mode === 2 && is_string( params ) ) { // If merge_mode is 2 and params is a string, merge the fragment / query // string into the URL wholesale, without converting it into an object. - qs = params.replace( is_fragment ? re_trim_fragment : re_trim_querystring, '' ); + qs = params.replace( is_fragment ? re_params_fragment : re_params_querystring, '' ); } else { // Convert relevant params in url to object. @@ -280,8 +290,8 @@ : merge_mode === 1 ? $.extend( {}, params, url_params ) // url params override passed params : $.extend( {}, url_params, params ); // passed params override url params - // Convert params object to a string. - qs = jq_param( qs ); + // Convert params object into a sorted params string. + qs = jq_param_sorted( qs ); // Unescape characters specified via $.param.noEscape. Since only hash- // history users have requested this feature, it's only enabled for @@ -294,12 +304,12 @@ // Build URL from the base url, querystring and hash. In 'querystring' // mode, ? is only added if a query string exists. In 'fragment' mode, # // is always added. - result = matches[1] + ( is_fragment ? '#' : qs || !matches[1] ? '?' : '' ) + qs + hash; + result = matches[1] + ( is_fragment ? fragment_prefix : qs || !matches[1] ? '?' : '' ) + qs + hash; } else { // If URL was passed in, parse params from URL string, otherwise parse - // params from window.location. - result = get_func( url !== undefined ? url : window[ str_location ][ str_href ] ); + // params from window.location.href. + result = get_func( url !== undefined ? url : location.href ); } return result; @@ -308,6 +318,56 @@ jq_param[ str_querystring ] = curry( jq_param_sub, 0, get_querystring ); jq_param[ str_fragment ] = jq_param_fragment = curry( jq_param_sub, 1, get_fragment ); + // Method: jQuery.param.sorted + // + // Returns a params string equivalent to that returned by the internal + // jQuery.param method, but sorted, which makes it suitable for use as a + // cache key. + // + // For example, in most browsers jQuery.param({z:1,a:2}) returns "z=1&a=2" + // and jQuery.param({a:2,z:1}) returns "a=2&z=1". Even though both the + // objects being serialized and the resulting params strings are equivalent, + // if these params strings were set into the location.hash fragment + // sequentially, the hashchange event would be triggered unnecessarily, since + // the strings are different (even though the data described by them is the + // same). By sorting the params string, unecessary hashchange event triggering + // can be avoided. + // + // Usage: + // + // > jQuery.param.sorted( obj [, traditional ] ); + // + // Arguments: + // + // obj - (Object) An object to be serialized. + // traditional - (Boolean) Params deep/shallow serialization mode. See the + // documentation at http://api.jquery.com/jQuery.param/ for more detail. + // + // Returns: + // + // (String) A sorted params string. + + jq_param.sorted = jq_param_sorted = function( a, traditional ) { + var arr = [], + obj = {}; + + $.each( jq_param( a, traditional ).split( '&' ), function(i,v){ + var key = v.replace( /(?:%5B|=).*$/, '' ), + key_obj = obj[ key ]; + + if ( !key_obj ) { + key_obj = obj[ key ] = []; + arr.push( key ); + } + + key_obj.push( v ); + }); + + return $.map( arr.sort(), function(v){ + return obj[ v ]; + }).join( '&' ); + }; + // Method: jQuery.param.fragment.noEscape // // Specify characters that will be left unescaped when fragments are created @@ -346,6 +406,41 @@ // "uglifying up the URL" the most. jq_param_fragment.noEscape( ',/' ); + // Method: jQuery.param.fragment.ajaxCrawlable + // + // TODO: DESCRIBE + // + // Usage: + // + // > jQuery.param.fragment.ajaxCrawlable( [ state ] ); + // + // Arguments: + // + // state - (Boolean) TODO: DESCRIBE + // + // Returns: + // + // (Boolean) The current ajaxCrawlable state. + + jq_param_fragment.ajaxCrawlable = function( state ) { + if ( state !== undefined ) { + if ( state ) { + re_params_fragment = /^.*(?:#!|#)/; + re_fragment = /^([^#]*)(?:#!|#)?(.*)$/; + fragment_prefix = '#!'; + } else { + re_params_fragment = /^.*#/; + re_fragment = /^([^#]*)#?(.*)$/; + fragment_prefix = '#'; + } + ajax_crawlable = !!state; + } + + return ajax_crawlable; + }; + + jq_param_fragment.ajaxCrawlable( 0 ); + // Section: Deparam (from string) // // Method: jQuery.deparam @@ -369,7 +464,7 @@ // (Object) An object representing the deserialized params string. $.deparam = jq_deparam = function( params, coerce ) { - var obj = {}, + var obj = Object.create(null), coerce_types = { 'true': !0, 'false': !1, 'null': null }; // Iterate over all name=value pairs. @@ -426,7 +521,7 @@ for ( ; i <= keys_last; i++ ) { key = keys[i] === '' ? cur.length : keys[i]; cur = cur[key] = i < keys_last - ? cur[key] || ( keys[i+1] && isNaN( keys[i+1] ) ? {} : [] ) + ? cur[key] || ( keys[i+1] && isNaN( keys[i+1] ) ? Object.create(null) : [] ) : val; } @@ -462,7 +557,7 @@ // Method: jQuery.deparam.querystring // - // Parse the query string from a URL or the current window.location, + // Parse the query string from a URL or the current window.location.href, // deserializing it into an object, optionally coercing numbers, booleans, // null and undefined values. // @@ -473,8 +568,8 @@ // Arguments: // // url - (String) An optional params string or URL containing query string - // params to be parsed. If url is omitted, the current window.location - // is used. + // params to be parsed. If url is omitted, the current + // window.location.href is used. // coerce - (Boolean) If true, coerces any numbers or true, false, null, and // undefined to their actual value. Defaults to false if omitted. // @@ -484,7 +579,7 @@ // Method: jQuery.deparam.fragment // - // Parse the fragment (hash) from a URL or the current window.location, + // Parse the fragment (hash) from a URL or the current window.location.href, // deserializing it into an object, optionally coercing numbers, booleans, // null and undefined values. // @@ -495,7 +590,7 @@ // Arguments: // // url - (String) An optional params string or URL containing fragment (hash) - // params to be parsed. If url is omitted, the current window.location + // params to be parsed. If url is omitted, the current window.location.href // is used. // coerce - (Boolean) If true, coerces any numbers or true, false, null, and // undefined to their actual value. Defaults to false if omitted. @@ -511,7 +606,7 @@ url_or_params = jq_param[ is_fragment ? str_fragment : str_querystring ](); } else { url_or_params = is_string( url_or_params ) - ? url_or_params.replace( is_fragment ? re_trim_fragment : re_trim_querystring, '' ) + ? url_or_params.replace( is_fragment ? re_params_fragment : re_params_querystring, '' ) : url_or_params; } @@ -715,13 +810,12 @@ var has_args = params !== undefined, // Merge params into window.location using $.param.fragment. - url = jq_param_fragment( window[ str_location ][ str_href ], + url = jq_param_fragment( location.href, has_args ? params : {}, has_args ? merge_mode : 2 ); - // Set new window.location.href. If hash is empty, use just # to prevent - // browser from reloading the page. Note that Safari 3 & Chrome barf on - // location.hash = '#'. - window[ str_location ][ str_href ] = url + ( /#/.test( url ) ? '' : '#' ); + // Set new window.location.href. Note that Safari 3 & Chrome barf on + // location.hash = '#' so the entire URL is set. + location.href = url; }; // Method: jQuery.bbq.getState @@ -850,7 +944,7 @@ // required to enable the augmented event object in jQuery 1.4.2 and newer. // * See for more detailed information. - jq_event_special[ str_hashchange ] = $.extend( jq_event_special[ str_hashchange ], { + special[ str_hashchange ] = $.extend( special[ str_hashchange ], { // Augmenting the event object with the .fragment property and .getState // method requires jQuery 1.4 or newer. Note: with 1.3.2, everything will @@ -892,7 +986,7 @@ })(jQuery,this); /*! - * jQuery hashchange event - v1.2 - 2/11/2010 + * jQuery hashchange event - v1.3 - 7/21/2010 * http://benalman.com/projects/jquery-hashchange-plugin/ * * Copyright (c) 2010 "Cowboy" Ben Alman @@ -902,12 +996,12 @@ // Script: jQuery hashchange event // -// *Version: 1.2, Last updated: 2/11/2010* +// *Version: 1.3, Last updated: 7/21/2010* // // Project Home - http://benalman.com/projects/jquery-hashchange-plugin/ // GitHub - http://github.com/cowboy/jquery-hashchange/ // Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js -// (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (1.1kb) +// (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (0.8kb gzipped) // // About: License // @@ -917,10 +1011,11 @@ // // About: Examples // -// This working example, complete with fully commented code, illustrate one way -// in which this plugin can be used. +// These working examples, complete with fully commented code, illustrate a few +// ways in which this plugin can be used. // // hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/ +// document.domain - http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/ // // About: Support and Testing // @@ -928,24 +1023,40 @@ // tested with, what browsers it has been tested in, and where the unit tests // reside (so you can test it yourself). // -// jQuery Versions - 1.3.2, 1.4.1, 1.4.2 -// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.7, Safari 3-4, Chrome, Opera 9.6-10.1. +// jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2 +// Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5, +// Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5. // Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/ // // About: Known issues // -// While this jQuery hashchange event implementation is quite stable and robust, -// there are a few unfortunate browser bugs surrounding expected hashchange -// event-based behaviors, independent of any JavaScript window.onhashchange -// abstraction. See the following examples for more information: +// While this jQuery hashchange event implementation is quite stable and +// robust, there are a few unfortunate browser bugs surrounding expected +// hashchange event-based behaviors, independent of any JavaScript +// window.onhashchange abstraction. See the following examples for more +// information: // // Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/ // Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/ // WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/ // Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/ // +// Also note that should a browser natively support the window.onhashchange +// event, but not report that it does, the fallback polling loop will be used. +// // About: Release History // +// 1.3 - (7/21/2010) Reorganized IE6/7 Iframe code to make it more +// "removable" for mobile-only development. Added IE6/7 document.title +// support. Attempted to make Iframe as hidden as possible by using +// techniques from http://www.paciellogroup.com/blog/?p=604. Added +// support for the "shortcut" format $(window).hashchange( fn ) and +// $(window).hashchange() like jQuery provides for built-in events. +// Renamed jQuery.hashchangeDelay to and +// lowered its default value to 50. Added +// and properties plus document-domain.html +// file to address access denied issues when setting document.domain in +// IE6/7. // 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin // from a page on another domain would cause an error in Safari 4. Also, // IE6/7 Iframe is now inserted after the body (this actually works), @@ -964,63 +1075,144 @@ (function($,window,undefined){ '$:nomunge'; // Used by YUI compressor. - // Method / object references. - var fake_onhashchange, - jq_event_special = $.event.special, + // Reused string. + var str_hashchange = 'hashchange', - // Reused strings. - str_location = 'location', - str_hashchange = 'hashchange', - str_href = 'href', + // Method / object references. + doc = document, + fake_onhashchange, + special = $.event.special, - mode = document.documentMode, - is_old_ie = false, - - // Does the browser support window.onhashchange? Test for IE version, since - // IE8 incorrectly reports this when in "IE7" or "IE8 Compatibility View"! - supports_onhashchange = 'on' + str_hashchange in window && !is_old_ie; + // Does the browser support window.onhashchange? Note that IE8 running in + // IE7 compatibility mode reports true for 'onhashchange' in window, even + // though the event isn't supported, so also test document.documentMode. + doc_mode = doc.documentMode, + supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 ); // Get location.hash (or what you'd expect location.hash to be) sans any // leading #. Thanks for making this necessary, Firefox! function get_fragment( url ) { - url = url || window[ str_location ][ str_href ]; - return url.replace( /^[^#]*#?(.*)$/, '$1' ); + url = url || location.href; + return '#' + url.replace( /^[^#]*#?(.*)$/, '$1' ); }; - // Property: jQuery.hashchangeDelay + // Method: jQuery.fn.hashchange + // + // Bind a handler to the window.onhashchange event or trigger all bound + // window.onhashchange event handlers. This behavior is consistent with + // jQuery's built-in event handlers. + // + // Usage: + // + // > jQuery(window).hashchange( [ handler ] ); + // + // Arguments: + // + // handler - (Function) Optional handler to be bound to the hashchange + // event. This is a "shortcut" for the more verbose form: + // jQuery(window).bind( 'hashchange', handler ). If handler is omitted, + // all bound window.onhashchange event handlers will be triggered. This + // is a shortcut for the more verbose + // jQuery(window).trigger( 'hashchange' ). These forms are described in + // the section. + // + // Returns: + // + // (jQuery) The initial jQuery collection of elements. + + // Allow the "shortcut" format $(elem).hashchange( fn ) for binding and + // $(elem).hashchange() for triggering, like jQuery does for built-in events. + $.fn[ str_hashchange ] = function( fn ) { + return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange ); + }; + + // Property: jQuery.fn.hashchange.delay // // The numeric interval (in milliseconds) at which the - // polling loop executes. Defaults to 100. + // polling loop executes. Defaults to 50. - $[ str_hashchange + 'Delay' ] = 100; + // Property: jQuery.fn.hashchange.domain + // + // If you're setting document.domain in your JavaScript, and you want hash + // history to work in IE6/7, not only must this property be set, but you must + // also set document.domain BEFORE jQuery is loaded into the page. This + // property is only applicable if you are supporting IE6/7 (or IE8 operating + // in "IE7 compatibility" mode). + // + // In addition, the property must be set to the + // path of the included "document-domain.html" file, which can be renamed or + // modified if necessary (note that the document.domain specified must be the + // same in both your main JavaScript as well as in this file). + // + // Usage: + // + // jQuery.fn.hashchange.domain = document.domain; + + // Property: jQuery.fn.hashchange.src + // + // If, for some reason, you need to specify an Iframe src file (for example, + // when setting document.domain as in ), you can + // do so using this property. Note that when using this property, history + // won't be recorded in IE6/7 until the Iframe src file loads. This property + // is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7 + // compatibility" mode). + // + // Usage: + // + // jQuery.fn.hashchange.src = 'path/to/file.html'; + + $.fn[ str_hashchange ].delay = 50; + /* + $.fn[ str_hashchange ].domain = null; + $.fn[ str_hashchange ].src = null; + */ // Event: hashchange event // // Fired when location.hash changes. In browsers that support it, the native - // window.onhashchange event is used (IE8, FF3.6), otherwise a polling loop is - // initialized, running every milliseconds to see if - // the hash has changed. In IE 6 and 7, a hidden Iframe is created to allow - // the back button and hash-based history to work. + // HTML5 window.onhashchange event is used, otherwise a polling loop is + // initialized, running every milliseconds to + // see if the hash has changed. In IE6/7 (and IE8 operating in "IE7 + // compatibility" mode), a hidden Iframe is created to allow the back button + // and hash-based history to work. // - // Usage: + // Usage as described in : // - // > $(window).bind( 'hashchange', function(e) { + // > // Bind an event handler. + // > jQuery(window).hashchange( function(e) { // > var hash = location.hash; // > ... // > }); + // > + // > // Manually trigger the event handler. + // > jQuery(window).hashchange(); + // + // A more verbose usage that allows for event namespacing: + // + // > // Bind an event handler. + // > jQuery(window).bind( 'hashchange', function(e) { + // > var hash = location.hash; + // > ... + // > }); + // > + // > // Manually trigger the event handler. + // > jQuery(window).trigger( 'hashchange' ); // // Additional Notes: // - // * The polling loop and Iframe are not created until at least one callback - // is actually bound to 'hashchange'. - // * If you need the bound callback(s) to execute immediately, in cases where - // the page 'state' exists on page load (via bookmark or page refresh, for - // example) use $(window).trigger( 'hashchange' ); + // * The polling loop and Iframe are not created until at least one handler + // is actually bound to the 'hashchange' event. + // * If you need the bound handler(s) to execute immediately, in cases where + // a location.hash exists on page load, via bookmark or page refresh for + // example, use jQuery(window).hashchange() or the more verbose + // jQuery(window).trigger( 'hashchange' ). // * The event can be bound before DOM ready, but since it won't be usable // before then in IE6/7 (due to the necessary Iframe), recommended usage is - // to bind it inside a $(document).ready() callback. + // to bind it inside a DOM ready handler. - jq_event_special[ str_hashchange ] = $.extend( jq_event_special[ str_hashchange ], { + // Override existing $.event.special.hashchange methods (allowing this plugin + // to be defined after jQuery BBQ in BBQ's source code). + special[ str_hashchange ] = $.extend( special[ str_hashchange ], { // Called only when the first 'hashchange' event is bound to window. setup: function() { @@ -1051,83 +1243,134 @@ fake_onhashchange = (function(){ var self = {}, timeout_id, - iframe, - set_history, - get_history; - - // Initialize. In IE 6/7, creates a hidden Iframe for history handling. - function init(){ - // Most browsers don't need special methods here.. - set_history = get_history = function(val){ return val; }; - // But IE6/7 do! - if ( is_old_ie ) { - - // Create hidden Iframe after the end of the body to prevent initial - // page load from scrolling unnecessarily. - iframe = $('