//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); //>>description: history.pushState support, layered on top of hashchange. //>>label: Pushstate Support define( [ "jquery", "jquery.mobile.navigation" ], function( $ ) { //>>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 = $( window ), url = $.mobile.path.parseUrl( location.href ); $.extend( pushStateHandler, { // TODO move to a path helper, this is rather common functionality initialFilePath: (function() { return url.pathname + url.search; })(), initialHref: url.hrefNoHash, state: function() { return { hash: location.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; }, hashValueAfterReset: function( url ) { var resetUrl = self.resetUIKeys( url ); return $.mobile.path.parseUrl( resetUrl ).hash; }, // 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, hash = location.hash, isPath = $.mobile.path.isPath( hash ), resolutionUrl = isPath ? location.href : $.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 window.history.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, timeout, fromHash, toHash, hashChanged; // if there's no state its not a popstate we care about, eg chrome's initial popstate if( poppedState ) { // the active url in the history stack will still be from the previous state // so we can use it to verify if a hashchange will be fired from the popstate fromHash = self.hashValueAfterReset( $.mobile.urlHistory.getActive().url ); // the hash stored in the state popped off the stack will be our currenturl or // the url to which we wish to navigate toHash = self.hashValueAfterReset( poppedState.hash.replace("#", "") ); // if the hashes of the urls are different we must assume that the browser // will fire a hashchange hashChanged = fromHash !== toHash; // unlock hash handling once the hashchange caused be the popstate has fired if( hashChanged ) { $win.one( "hashchange.pushstate", function() { self.nextHashChangePrevented( false ); }); } // enable hash handling for the the _handleHashChange call self.nextHashChangePrevented( false ); // change the page based on the hash $.mobile._handleHashChange( poppedState.hash ); // only prevent another hash change handling if a hash change will be fired // by the browser if( hashChanged ) { // disable hash handling until one of the above timers fires self.nextHashChangePrevented( true ); } } }, 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, location.href ); } } }); $( function() { if( $.mobile.pushStateEnabled && $.support.pushState ){ pushStateHandler.init(); } }); })( jQuery, this ); //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); }); //>>excludeEnd("jqmBuildExclude");