jquery.ba-bbq.js 52 KB

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