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