/* * history.pushState support, layered on top of hashchange */ ( 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, // Flag for tracking if a Hashchange naturally occurs after each popstate + replace hashchangeFired: false, 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; }, // 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, holdnexthashchange = false; // if there's no state its not a popstate we care about, ie chrome's initial popstate // or forward popstate if( poppedState ) { // disable any hashchange triggered by the browser self.nextHashChangePrevented( true ); // defer our manual hashchange until after the browser fired // version has come and gone setTimeout(function() { // make sure that the manual hash handling takes place self.nextHashChangePrevented( false ); // change the page based on the hash $.mobile._handleHashChange( poppedState.hash ); }, 100); } }, 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 );