blob: fb7296478924d20e4359f9f114bbcbe164556877 [file] [log] [blame]
//>>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");