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