| //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); |
| //>>description: history.pushState support, layered on top of hashchange. |
| //>>label: Pushstate Support |
| //>>group: Navigation |
| |
| define( [ "jquery", "./jquery.mobile.navigation", "./jquery.hashchange" ], function( jQuery ) { |
| //>>excludeEnd("jqmBuildExclude"); |
| (function( $, window ) { |
| // For now, let's Monkeypatch this onto the end of $.mobile._registerInternalEvents |
| // Scope self to pushStateHandler so we can reference it sanely within the |
| // methods handed off as event handlers |
| var pushStateHandler = {}, |
| self = pushStateHandler, |
| $win = $.mobile.$window, |
| url = $.mobile.path.parseLocation(), |
| mobileinitDeferred = $.Deferred(), |
| domreadyDeferred = $.Deferred(); |
| |
| $.mobile.$document.ready( $.proxy( domreadyDeferred, "resolve" ) ); |
| |
| $.mobile.$document.one( "mobileinit", $.proxy( mobileinitDeferred, "resolve" ) ); |
| |
| $.extend( pushStateHandler, { |
| // TODO move to a path helper, this is rather common functionality |
| initialFilePath: (function() { |
| return url.pathname + url.search; |
| })(), |
| |
| hashChangeTimeout: 200, |
| |
| hashChangeEnableTimer: undefined, |
| |
| initialHref: url.hrefNoHash, |
| |
| state: function() { |
| return { |
| // firefox auto decodes the url when using location.hash but not href |
| hash: $.mobile.path.parseLocation().hash || "#" + self.initialFilePath, |
| title: document.title, |
| |
| // persist across refresh |
| initialHref: self.initialHref |
| }; |
| }, |
| |
| resetUIKeys: function( url ) { |
| var dialog = $.mobile.dialogHashKey, |
| subkey = "&" + $.mobile.subPageUrlKey, |
| dialogIndex = url.indexOf( dialog ); |
| |
| if ( dialogIndex > -1 ) { |
| url = url.slice( 0, dialogIndex ) + "#" + url.slice( dialogIndex ); |
| } else if ( url.indexOf( subkey ) > -1 ) { |
| url = url.split( subkey ).join( "#" + subkey ); |
| } |
| |
| return url; |
| }, |
| |
| // TODO sort out a single barrier to hashchange functionality |
| nextHashChangePrevented: function( value ) { |
| $.mobile.urlHistory.ignoreNextHashChange = value; |
| self.onHashChangeDisabled = value; |
| }, |
| |
| // on hash change we want to clean up the url |
| // NOTE this takes place *after* the vanilla navigation hash change |
| // handling has taken place and set the state of the DOM |
| onHashChange: function( e ) { |
| // disable this hash change |
| if ( self.onHashChangeDisabled ) { |
| return; |
| } |
| |
| var href, state, |
| // firefox auto decodes the url when using location.hash but not href |
| hash = $.mobile.path.parseLocation().hash, |
| isPath = $.mobile.path.isPath( hash ), |
| resolutionUrl = isPath ? $.mobile.path.getLocation() : $.mobile.getDocumentUrl(); |
| |
| hash = isPath ? hash.replace( "#", "" ) : hash; |
| |
| |
| // propulate the hash when its not available |
| state = self.state(); |
| |
| // make the hash abolute with the current href |
| href = $.mobile.path.makeUrlAbsolute( hash, resolutionUrl ); |
| |
| if ( isPath ) { |
| href = self.resetUIKeys( href ); |
| } |
| |
| // replace the current url with the new href and store the state |
| // Note that in some cases we might be replacing an url with the |
| // same url. We do this anyways because we need to make sure that |
| // all of our history entries have a state object associated with |
| // them. This allows us to work around the case where $.mobile.back() |
| // is called to transition from an external page to an embedded page. |
| // In that particular case, a hashchange event is *NOT* generated by the browser. |
| // Ensuring each history entry has a state object means that onPopState() |
| // will always trigger our hashchange callback even when a hashchange event |
| // is not fired. |
| history.replaceState( state, document.title, href ); |
| }, |
| |
| // on popstate (ie back or forward) we need to replace the hash that was there previously |
| // cleaned up by the additional hash handling |
| onPopState: function( e ) { |
| var poppedState = e.originalEvent.state, |
| fromHash, toHash, hashChanged; |
| |
| // if there's no state its not a popstate we care about, eg chrome's initial popstate |
| if ( poppedState ) { |
| // if we get two pop states in under this.hashChangeTimeout |
| // make sure to clear any timer set for the previous change |
| clearTimeout( self.hashChangeEnableTimer ); |
| |
| // make sure to enable hash handling for the the _handleHashChange call |
| self.nextHashChangePrevented( false ); |
| |
| // change the page based on the hash in the popped state |
| $.mobile._handleHashChange( poppedState.hash ); |
| |
| // prevent any hashchange in the next self.hashChangeTimeout |
| self.nextHashChangePrevented( true ); |
| |
| // re-enable hash change handling after swallowing a possible hash |
| // change event that comes on all popstates courtesy of browsers like Android |
| self.hashChangeEnableTimer = setTimeout( function() { |
| self.nextHashChangePrevented( false ); |
| }, self.hashChangeTimeout ); |
| } |
| }, |
| |
| init: function() { |
| $win.bind( "hashchange", self.onHashChange ); |
| |
| // Handle popstate events the occur through history changes |
| $win.bind( "popstate", self.onPopState ); |
| |
| // if there's no hash, we need to replacestate for returning to home |
| if ( location.hash === "" ) { |
| history.replaceState( self.state(), document.title, $.mobile.path.getLocation() ); |
| } |
| } |
| }); |
| |
| // We need to init when "mobileinit", "domready", and "navready" have all happened |
| $.when( domreadyDeferred, mobileinitDeferred, $.mobile.navreadyDeferred ).done(function() { |
| if ( $.mobile.pushStateEnabled && $.support.pushState ) { |
| pushStateHandler.init(); |
| } |
| }); |
| })( jQuery, this ); |
| //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); |
| }); |
| //>>excludeEnd("jqmBuildExclude"); |