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