1+ ( function ( $ ) {
2+ '$:nomunge' ; // Used by YUI compressor.
3+
4+ // Method / object references.
5+ var fake_onhashchange ,
6+ jq_event_special = $ . event . special ,
7+
8+ // Reused strings.
9+ str_location = 'location' ,
10+ str_hashchange = 'hashchange' ,
11+ str_href = 'href' ,
12+
13+ // IE6/7 specifically need some special love when it comes to back-button
14+ // support, so let's do a little browser sniffing..
15+ browser = $ . browser ,
16+ mode = document . documentMode ,
17+ is_old_ie = browser . msie && ( mode === undefined || mode < 8 ) ,
18+
19+ // Does the browser support window.onhashchange? Test for IE version, since
20+ // IE8 incorrectly reports this when in "IE7" or "IE8 Compatibility View"!
21+ supports_onhashchange = 'on' + str_hashchange in window && ! is_old_ie ;
22+
23+ // Get location.hash (or what you'd expect location.hash to be) sans any
24+ // leading #. Thanks for making this necessary, Firefox!
25+ function get_fragment ( url ) {
26+ url = url || window [ str_location ] [ str_href ] ;
27+ return url . replace ( / ^ [ ^ # ] * # ? ( .* ) $ / , '$1' ) ;
28+ } ;
29+
30+ // Property: jQuery.hashchangeDelay
31+ //
32+ // The numeric interval (in milliseconds) at which the <hashchange event>
33+ // polling loop executes. Defaults to 100.
34+
35+ $ [ str_hashchange + 'Delay' ] = 100 ;
36+
37+ // Event: hashchange event
38+ //
39+ // Fired when location.hash changes. In browsers that support it, the native
40+ // window.onhashchange event is used (IE8, FF3.6), otherwise a polling loop is
41+ // initialized, running every <jQuery.hashchangeDelay> milliseconds to see if
42+ // the hash has changed. In IE 6 and 7, a hidden Iframe is created to allow
43+ // the back button and hash-based history to work.
44+ //
45+ // Usage:
46+ //
47+ // > $(window).bind( 'hashchange', function(e) {
48+ // > var hash = location.hash;
49+ // > ...
50+ // > });
51+ //
52+ // Additional Notes:
53+ //
54+ // * The polling loop and Iframe are not created until at least one callback
55+ // is actually bound to 'hashchange'.
56+ // * If you need the bound callback(s) to execute immediately, in cases where
57+ // the page 'state' exists on page load (via bookmark or page refresh, for
58+ // example) use $(window).trigger( 'hashchange' );
59+ // * The event can be bound before DOM ready, but since it won't be usable
60+ // before then in IE6/7 (due to the necessary Iframe), recommended usage is
61+ // to bind it inside a $(document).ready() callback.
62+
63+ jq_event_special [ str_hashchange ] = $ . extend ( jq_event_special [ str_hashchange ] , {
64+
65+ // Called only when the first 'hashchange' event is bound to window.
66+ setup : function ( ) {
67+ // If window.onhashchange is supported natively, there's nothing to do..
68+ if ( supports_onhashchange ) { return false ; }
69+
70+ // Otherwise, we need to create our own. And we don't want to call this
71+ // until the user binds to the event, just in case they never do, since it
72+ // will create a polling loop and possibly even a hidden Iframe.
73+ $ ( fake_onhashchange . start ) ;
74+ } ,
75+
76+ // Called only when the last 'hashchange' event is unbound from window.
77+ teardown : function ( ) {
78+ // If window.onhashchange is supported natively, there's nothing to do..
79+ if ( supports_onhashchange ) { return false ; }
80+
81+ // Otherwise, we need to stop ours (if possible).
82+ $ ( fake_onhashchange . stop ) ;
83+ }
84+
85+ } ) ;
86+
87+ // fake_onhashchange does all the work of triggering the window.onhashchange
88+ // event for browsers that don't natively support it, including creating a
89+ // polling loop to watch for hash changes and in IE 6/7 creating a hidden
90+ // Iframe to enable back and forward.
91+ fake_onhashchange = ( function ( ) {
92+ var self = { } ,
93+ timeout_id ,
94+ iframe ,
95+ set_history ,
96+ get_history ;
97+
98+ // Initialize. In IE 6/7, creates a hidden Iframe for history handling.
99+ function init ( ) {
100+ // Most browsers don't need special methods here..
101+ set_history = get_history = function ( val ) { return val ; } ;
102+
103+ // But IE6/7 do!
104+ if ( is_old_ie ) {
105+
106+ // Create hidden Iframe after the end of the body to prevent initial
107+ // page load from scrolling unnecessarily.
108+ iframe = $ ( '<iframe src="javascript:0"/>' ) . hide ( ) . insertAfter ( 'body' ) [ 0 ] . contentWindow ;
109+
110+ // Get history by looking at the hidden Iframe's location.hash.
111+ get_history = function ( ) {
112+ return get_fragment ( iframe . document [ str_location ] [ str_href ] ) ;
113+ } ;
114+
115+ // Set a new history item by opening and then closing the Iframe
116+ // document, *then* setting its location.hash.
117+ set_history = function ( hash , history_hash ) {
118+ if ( hash !== history_hash ) {
119+ var doc = iframe . document ;
120+ doc . open ( ) . close ( ) ;
121+ doc [ str_location ] . hash = '#' + hash ;
122+ }
123+ } ;
124+
125+ // Set initial history.
126+ set_history ( get_fragment ( ) ) ;
127+ }
128+ } ;
129+
130+ // Start the polling loop.
131+ self . start = function ( ) {
132+ // Polling loop is already running!
133+ if ( timeout_id ) { return ; }
134+
135+ // Remember the initial hash so it doesn't get triggered immediately.
136+ var last_hash = get_fragment ( ) ;
137+
138+ // Initialize if not yet initialized.
139+ set_history || init ( ) ;
140+
141+ // This polling loop checks every $.hashchangeDelay milliseconds to see if
142+ // location.hash has changed, and triggers the 'hashchange' event on
143+ // window when necessary.
144+ if ( ! navigator . userAgent . match ( / R h i n o / ) )
145+ ( function loopy ( ) {
146+ var hash = get_fragment ( ) ,
147+ history_hash = get_history ( last_hash ) ;
148+
149+ if ( hash !== last_hash ) {
150+ set_history ( last_hash = hash , history_hash ) ;
151+
152+ $ ( window ) . trigger ( str_hashchange ) ;
153+
154+ } else if ( history_hash !== last_hash ) {
155+ window [ str_location ] [ str_href ] = window [ str_location ] [ str_href ] . replace ( / # .* / , '' ) + '#' + history_hash ;
156+ }
157+
158+ timeout_id = setTimeout ( loopy , $ [ str_hashchange + 'Delay' ] ) ;
159+ } ) ( ) ;
160+ } ;
161+
162+ // Stop the polling loop, but only if an IE6/7 Iframe wasn't created. In
163+ // that case, even if there are no longer any bound event handlers, the
164+ // polling loop is still necessary for back/next to work at all!
165+ self . stop = function ( ) {
166+ if ( ! iframe ) {
167+ timeout_id && clearTimeout ( timeout_id ) ;
168+ timeout_id = 0 ;
169+ }
170+ } ;
171+
172+ return self ;
173+ } ) ( ) ;
174+ } ) ( jQuery ) ;
0 commit comments