PageRenderTime 74ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/js/jquery.mobile.hashchange.js

https://github.com/inorganik/jquery-mobile
JavaScript | 390 lines | 93 code | 49 blank | 248 comment | 19 complexity | fc1317e701b671a07e40f939afa0d94f MD5 | raw file
  1. /*!
  2. * jQuery hashchange event - v1.3 - 7/21/2010
  3. * http://benalman.com/projects/jquery-hashchange-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. // Script: jQuery hashchange event
  10. //
  11. // *Version: 1.3, Last updated: 7/21/2010*
  12. //
  13. // Project Home - http://benalman.com/projects/jquery-hashchange-plugin/
  14. // GitHub - http://github.com/cowboy/jquery-hashchange/
  15. // Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js
  16. // (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (0.8kb gzipped)
  17. //
  18. // About: License
  19. //
  20. // Copyright (c) 2010 "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. // hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/
  30. // document.domain - http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/
  31. //
  32. // About: Support and Testing
  33. //
  34. // Information about what version or versions of jQuery this plugin has been
  35. // tested with, what browsers it has been tested in, and where the unit tests
  36. // reside (so you can test it yourself).
  37. //
  38. // jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2
  39. // Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5,
  40. // Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5.
  41. // Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/
  42. //
  43. // About: Known issues
  44. //
  45. // While this jQuery hashchange event implementation is quite stable and
  46. // robust, there are a few unfortunate browser bugs surrounding expected
  47. // hashchange event-based behaviors, independent of any JavaScript
  48. // window.onhashchange abstraction. See the following examples for more
  49. // information:
  50. //
  51. // Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/
  52. // Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/
  53. // WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/
  54. // Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/
  55. //
  56. // Also note that should a browser natively support the window.onhashchange
  57. // event, but not report that it does, the fallback polling loop will be used.
  58. //
  59. // About: Release History
  60. //
  61. // 1.3 - (7/21/2010) Reorganized IE6/7 Iframe code to make it more
  62. // "removable" for mobile-only development. Added IE6/7 document.title
  63. // support. Attempted to make Iframe as hidden as possible by using
  64. // techniques from http://www.paciellogroup.com/blog/?p=604. Added
  65. // support for the "shortcut" format $(window).hashchange( fn ) and
  66. // $(window).hashchange() like jQuery provides for built-in events.
  67. // Renamed jQuery.hashchangeDelay to <jQuery.fn.hashchange.delay> and
  68. // lowered its default value to 50. Added <jQuery.fn.hashchange.domain>
  69. // and <jQuery.fn.hashchange.src> properties plus document-domain.html
  70. // file to address access denied issues when setting document.domain in
  71. // IE6/7.
  72. // 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin
  73. // from a page on another domain would cause an error in Safari 4. Also,
  74. // IE6/7 Iframe is now inserted after the body (this actually works),
  75. // which prevents the page from scrolling when the event is first bound.
  76. // Event can also now be bound before DOM ready, but it won't be usable
  77. // before then in IE6/7.
  78. // 1.1 - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug
  79. // where browser version is incorrectly reported as 8.0, despite
  80. // inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag.
  81. // 1.0 - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special
  82. // window.onhashchange functionality into a separate plugin for users
  83. // who want just the basic event & back button support, without all the
  84. // extra awesomeness that BBQ provides. This plugin will be included as
  85. // part of jQuery BBQ, but also be available separately.
  86. (function($,window,undefined){
  87. '$:nomunge'; // Used by YUI compressor.
  88. // Reused string.
  89. var str_hashchange = 'hashchange',
  90. // Method / object references.
  91. doc = document,
  92. fake_onhashchange,
  93. special = $.event.special,
  94. // Does the browser support window.onhashchange? Note that IE8 running in
  95. // IE7 compatibility mode reports true for 'onhashchange' in window, even
  96. // though the event isn't supported, so also test document.documentMode.
  97. doc_mode = doc.documentMode,
  98. supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 );
  99. // Get location.hash (or what you'd expect location.hash to be) sans any
  100. // leading #. Thanks for making this necessary, Firefox!
  101. function get_fragment( url ) {
  102. url = url || location.href;
  103. return '#' + url.replace( /^[^#]*#?(.*)$/, '$1' );
  104. };
  105. // Method: jQuery.fn.hashchange
  106. //
  107. // Bind a handler to the window.onhashchange event or trigger all bound
  108. // window.onhashchange event handlers. This behavior is consistent with
  109. // jQuery's built-in event handlers.
  110. //
  111. // Usage:
  112. //
  113. // > jQuery(window).hashchange( [ handler ] );
  114. //
  115. // Arguments:
  116. //
  117. // handler - (Function) Optional handler to be bound to the hashchange
  118. // event. This is a "shortcut" for the more verbose form:
  119. // jQuery(window).bind( 'hashchange', handler ). If handler is omitted,
  120. // all bound window.onhashchange event handlers will be triggered. This
  121. // is a shortcut for the more verbose
  122. // jQuery(window).trigger( 'hashchange' ). These forms are described in
  123. // the <hashchange event> section.
  124. //
  125. // Returns:
  126. //
  127. // (jQuery) The initial jQuery collection of elements.
  128. // Allow the "shortcut" format $(elem).hashchange( fn ) for binding and
  129. // $(elem).hashchange() for triggering, like jQuery does for built-in events.
  130. $.fn[ str_hashchange ] = function( fn ) {
  131. return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange );
  132. };
  133. // Property: jQuery.fn.hashchange.delay
  134. //
  135. // The numeric interval (in milliseconds) at which the <hashchange event>
  136. // polling loop executes. Defaults to 50.
  137. // Property: jQuery.fn.hashchange.domain
  138. //
  139. // If you're setting document.domain in your JavaScript, and you want hash
  140. // history to work in IE6/7, not only must this property be set, but you must
  141. // also set document.domain BEFORE jQuery is loaded into the page. This
  142. // property is only applicable if you are supporting IE6/7 (or IE8 operating
  143. // in "IE7 compatibility" mode).
  144. //
  145. // In addition, the <jQuery.fn.hashchange.src> property must be set to the
  146. // path of the included "document-domain.html" file, which can be renamed or
  147. // modified if necessary (note that the document.domain specified must be the
  148. // same in both your main JavaScript as well as in this file).
  149. //
  150. // Usage:
  151. //
  152. // jQuery.fn.hashchange.domain = document.domain;
  153. // Property: jQuery.fn.hashchange.src
  154. //
  155. // If, for some reason, you need to specify an Iframe src file (for example,
  156. // when setting document.domain as in <jQuery.fn.hashchange.domain>), you can
  157. // do so using this property. Note that when using this property, history
  158. // won't be recorded in IE6/7 until the Iframe src file loads. This property
  159. // is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7
  160. // compatibility" mode).
  161. //
  162. // Usage:
  163. //
  164. // jQuery.fn.hashchange.src = 'path/to/file.html';
  165. $.fn[ str_hashchange ].delay = 50;
  166. /*
  167. $.fn[ str_hashchange ].domain = null;
  168. $.fn[ str_hashchange ].src = null;
  169. */
  170. // Event: hashchange event
  171. //
  172. // Fired when location.hash changes. In browsers that support it, the native
  173. // HTML5 window.onhashchange event is used, otherwise a polling loop is
  174. // initialized, running every <jQuery.fn.hashchange.delay> milliseconds to
  175. // see if the hash has changed. In IE6/7 (and IE8 operating in "IE7
  176. // compatibility" mode), a hidden Iframe is created to allow the back button
  177. // and hash-based history to work.
  178. //
  179. // Usage as described in <jQuery.fn.hashchange>:
  180. //
  181. // > // Bind an event handler.
  182. // > jQuery(window).hashchange( function(e) {
  183. // > var hash = location.hash;
  184. // > ...
  185. // > });
  186. // >
  187. // > // Manually trigger the event handler.
  188. // > jQuery(window).hashchange();
  189. //
  190. // A more verbose usage that allows for event namespacing:
  191. //
  192. // > // Bind an event handler.
  193. // > jQuery(window).bind( 'hashchange', function(e) {
  194. // > var hash = location.hash;
  195. // > ...
  196. // > });
  197. // >
  198. // > // Manually trigger the event handler.
  199. // > jQuery(window).trigger( 'hashchange' );
  200. //
  201. // Additional Notes:
  202. //
  203. // * The polling loop and Iframe are not created until at least one handler
  204. // is actually bound to the 'hashchange' event.
  205. // * If you need the bound handler(s) to execute immediately, in cases where
  206. // a location.hash exists on page load, via bookmark or page refresh for
  207. // example, use jQuery(window).hashchange() or the more verbose
  208. // jQuery(window).trigger( 'hashchange' ).
  209. // * The event can be bound before DOM ready, but since it won't be usable
  210. // before then in IE6/7 (due to the necessary Iframe), recommended usage is
  211. // to bind it inside a DOM ready handler.
  212. // Override existing $.event.special.hashchange methods (allowing this plugin
  213. // to be defined after jQuery BBQ in BBQ's source code).
  214. special[ str_hashchange ] = $.extend( special[ str_hashchange ], {
  215. // Called only when the first 'hashchange' event is bound to window.
  216. setup: function() {
  217. // If window.onhashchange is supported natively, there's nothing to do..
  218. if ( supports_onhashchange ) { return false; }
  219. // Otherwise, we need to create our own. And we don't want to call this
  220. // until the user binds to the event, just in case they never do, since it
  221. // will create a polling loop and possibly even a hidden Iframe.
  222. $( fake_onhashchange.start );
  223. },
  224. // Called only when the last 'hashchange' event is unbound from window.
  225. teardown: function() {
  226. // If window.onhashchange is supported natively, there's nothing to do..
  227. if ( supports_onhashchange ) { return false; }
  228. // Otherwise, we need to stop ours (if possible).
  229. $( fake_onhashchange.stop );
  230. }
  231. });
  232. // fake_onhashchange does all the work of triggering the window.onhashchange
  233. // event for browsers that don't natively support it, including creating a
  234. // polling loop to watch for hash changes and in IE 6/7 creating a hidden
  235. // Iframe to enable back and forward.
  236. fake_onhashchange = (function(){
  237. var self = {},
  238. timeout_id,
  239. // Remember the initial hash so it doesn't get triggered immediately.
  240. last_hash = get_fragment(),
  241. fn_retval = function(val){ return val; },
  242. history_set = fn_retval,
  243. history_get = fn_retval;
  244. // Start the polling loop.
  245. self.start = function() {
  246. timeout_id || poll();
  247. };
  248. // Stop the polling loop.
  249. self.stop = function() {
  250. timeout_id && clearTimeout( timeout_id );
  251. timeout_id = undefined;
  252. };
  253. // This polling loop checks every $.fn.hashchange.delay milliseconds to see
  254. // if location.hash has changed, and triggers the 'hashchange' event on
  255. // window when necessary.
  256. function poll() {
  257. var hash = get_fragment(),
  258. history_hash = history_get( last_hash );
  259. if ( hash !== last_hash ) {
  260. history_set( last_hash = hash, history_hash );
  261. $(window).trigger( str_hashchange );
  262. } else if ( history_hash !== last_hash ) {
  263. location.href = location.href.replace( /#.*/, '' ) + history_hash;
  264. }
  265. timeout_id = setTimeout( poll, $.fn[ str_hashchange ].delay );
  266. };
  267. // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
  268. // vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv
  269. // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
  270. $.browser.msie && !supports_onhashchange && (function(){
  271. // Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8
  272. // when running in "IE7 compatibility" mode.
  273. var iframe,
  274. iframe_src;
  275. // When the event is bound and polling starts in IE 6/7, create a hidden
  276. // Iframe for history handling.
  277. self.start = function(){
  278. if ( !iframe ) {
  279. iframe_src = $.fn[ str_hashchange ].src;
  280. iframe_src = iframe_src && iframe_src + get_fragment();
  281. // Create hidden Iframe. Attempt to make Iframe as hidden as possible
  282. // by using techniques from http://www.paciellogroup.com/blog/?p=604.
  283. iframe = $('<iframe tabindex="-1" title="empty"/>').hide()
  284. // When Iframe has completely loaded, initialize the history and
  285. // start polling.
  286. .one( 'load', function(){
  287. iframe_src || history_set( get_fragment() );
  288. poll();
  289. })
  290. // Load Iframe src if specified, otherwise nothing.
  291. .attr( 'src', iframe_src || 'javascript:0' )
  292. // Append Iframe after the end of the body to prevent unnecessary
  293. // initial page scrolling (yes, this works).
  294. .insertAfter( 'body' )[0].contentWindow;
  295. // Whenever `document.title` changes, update the Iframe's title to
  296. // prettify the back/next history menu entries. Since IE sometimes
  297. // errors with "Unspecified error" the very first time this is set
  298. // (yes, very useful) wrap this with a try/catch block.
  299. doc.onpropertychange = function(){
  300. try {
  301. if ( event.propertyName === 'title' ) {
  302. iframe.document.title = doc.title;
  303. }
  304. } catch(e) {}
  305. };
  306. }
  307. };
  308. // Override the "stop" method since an IE6/7 Iframe was created. Even
  309. // if there are no longer any bound event handlers, the polling loop
  310. // is still necessary for back/next to work at all!
  311. self.stop = fn_retval;
  312. // Get history by looking at the hidden Iframe's location.hash.
  313. history_get = function() {
  314. return get_fragment( iframe.location.href );
  315. };
  316. // Set a new history item by opening and then closing the Iframe
  317. // document, *then* setting its location.hash. If document.domain has
  318. // been set, update that as well.
  319. history_set = function( hash, history_hash ) {
  320. var iframe_doc = iframe.document,
  321. domain = $.fn[ str_hashchange ].domain;
  322. if ( hash !== history_hash ) {
  323. // Update Iframe with any initial `document.title` that might be set.
  324. iframe_doc.title = doc.title;
  325. // Opening the Iframe's document after it has been closed is what
  326. // actually adds a history entry.
  327. iframe_doc.open();
  328. // Set document.domain for the Iframe document as well, if necessary.
  329. domain && iframe_doc.write( '<script>document.domain="' + domain + '"</script>' );
  330. iframe_doc.close();
  331. // Update the Iframe's hash, for great justice.
  332. iframe.location.hash = hash;
  333. }
  334. };
  335. })();
  336. // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  337. // ^^^^^^^^^^^^^^^^^^^ REMOVE IF NOT SUPPORTING IE6/7/8 ^^^^^^^^^^^^^^^^^^^
  338. // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  339. return self;
  340. })();
  341. })(jQuery,this);