Refactored urlStack and updated dialog and page plugins to match. jQuery Mobile's internal history now attempts to follow the history menu when urls change from hashchange, even for changes that go multiple steps forward or back. The internal history stack is now pruned based on whether a user goes back and then changes direction, whereas before a back-button click would result in a pop off the history. Instead we now maintain an active index number in the history stack, which allows us to maintain references to transitions that are saved on pages reached through clicking the forward button as well. This fixes #636.

In the process, some other small changes should be noted:
urlStack is now urlHistory, a hash of methods and properties used for history stack management (stack, activeIndex, getActive, getPrev, getNext, addNew, clearForward, and listening Enabled). All these are documented inline and exposed on $.mobile.urlHistory (I'm not sure these will be publicly documented, but just exposed internally for plugins for now).

$.changePage has two argument changes: the "back" argument is now called "reverse"; this results in no change from an end-user standpoint, but reflects the fact that it only reverses the direction of a transition without affecting the internal history stack, and second, a new argument at the end defines whether changePage was called from a hashChange which makes that url open to history menu guessing.
This commit is contained in:
scottjehl 2011-01-23 17:33:36 -05:00
parent c63d570b60
commit 9b57c46349
4 changed files with 133 additions and 62 deletions

View file

@ -40,7 +40,7 @@
</dd>
<dd><code>transition</code> (<em>string</em>, examples: "pop", "slide"," "none")</dd>
<dd><code>back</code> (<em>boolean</em>, default: false). True will cause a reverse-direction transition.</dd>
<dd><code>reverse</code> (<em>boolean</em>, default: false). True will cause a reverse-direction transition.</dd>
<dd><code>changeHash</code> (<em>boolean</em>, default: true). Update the hash to the to page's URL when page change is complete.</dd>
</dl>
</dd>

View file

@ -10,7 +10,6 @@ $.widget( "mobile.dialog", $.mobile.widget, {
_create: function(){
var self = this,
$el = self.element,
$prevPage = $.mobile.activePage,
$closeBtn = $('<a href="#" data-icon="delete" data-iconpos="notext">Close</a>'),
dialogClickHandler = function(e){
@ -40,8 +39,15 @@ $.widget( "mobile.dialog", $.mobile.widget, {
this.element
.bind("pageshow",function(){
self.thisPage = $.mobile.urlHistory.getActive();
self.prevPage = $.mobile.urlHistory.getPrev();
return false;
})
.bind("pagehide", function(){
$.mobile.urlHistory.stack = $.mobile.urlHistory.stack.slice(0,$.mobile.urlHistory.stack.length-2);
$.mobile.urlHistory.activeIndex = $.mobile.urlHistory.stack.length -1;
})
//add ARIA role
.attr("role","dialog")
.addClass('ui-page ui-dialog ui-body-a')
@ -56,11 +62,15 @@ $.widget( "mobile.dialog", $.mobile.widget, {
.last()
.addClass('ui-corner-bottom ui-overlay-shadow');
$(window).bind('hashchange',function(){
$.mobile.urlHistory.listeningEnabled = true;
if( $el.is('.ui-page-active') ){
self.close();
$el.bind('pagehide',function(){
$.mobile.updateHash( $prevPage.attr('data-url'), true);
$.mobile.urlHistory.listeningEnabled = false;
$.mobile.updateHash( self.prevPage.url );
});
}
});
@ -68,7 +78,7 @@ $.widget( "mobile.dialog", $.mobile.widget, {
},
close: function(){
$.mobile.changePage([this.element, $.mobile.activePage], undefined, true, true );
$.mobile.changePage([this.element, $.mobile.activePage], this.thisPage.transition, true, true, true );
}
});
})( jQuery );

View file

@ -28,11 +28,8 @@
return path && path.indexOf( splitkey ) > -1 ? path.split( splitkey )[0] : path;
},
//set location hash to path, optionally disable hash listening
set: function( path, disableListening){
if( disableListening ) {
hashListener = false;
}
//set location hash to path
set: function( path ){
location.hash = path;
},
@ -76,24 +73,56 @@
//will be defined when a link is clicked and given an active class
$activeClickedLink = null,
//array of pages that are visited during a single page load
//length will grow as pages are visited, and shrink as "back" link/button is clicked
//each item has a url (string matches ID), and transition (saved for reuse when "back" link/button is clicked)
urlStack = [ {
url: location.hash.replace( /^#/, "" ),
transition: undefined
} ],
//urlHistory is purely here to make guesses at whether the back or forward button was clicked
//and provide an appropriate transition
urlHistory = {
//array of pages that are visited during a single page load. each has a url and optional transition
stack: [],
//maintain an index number for the active page in the stack
activeIndex: 0,
//get active
getActive: function(){
return urlHistory.stack[ urlHistory.activeIndex ];
},
getPrev: function(){
return urlHistory.stack[ urlHistory.activeIndex - 1 ];
},
getNext: function(){
return urlHistory.stack[ urlHistory.activeIndex + 1 ];
},
// addNew is used whenever a new page is added
addNew: function( url, transition ){
//if there's forward history, wipe it
if( urlHistory.getNext() ){
urlHistory.clearForward();
}
urlHistory.stack.push( {url : url, transition: transition } );
urlHistory.activeIndex = urlHistory.stack.length - 1;
},
//wipe urls ahead of active index
clearForward: function(){
urlHistory.stack = urlHistory.stack.slice( 0, urlHistory.activeIndex + 1 );
},
//enable/disable hashchange event listener
//toggled internally when location.hash is updated to match the url of a successful page load
listeningEnabled: true
},
//define first selector to receive focus when a page is shown
focusable = "[tabindex],a,button:visible,select:visible,input",
//contains role for next page, if defined on clicked link via data-rel
nextPageRole = null,
//enable/disable hashchange event listener
//toggled internally when location.hash is updated to match the url of a successful page load
hashListener = true;
nextPageRole = null;
//existing base tag?
var $base = $head.children("base"),
@ -116,7 +145,7 @@
//the href is a document relative url
docBase = docLocation + href;
//XXX: we need some code here to calculate the final path
// just in case the docBase contains up-level (../) references.
// just in case the docBase contains up-level (../) references.
}
}
else {
@ -200,29 +229,67 @@
$.mobile.base = base;
//url stack, useful when plugins need to be aware of previous pages viewed
$.mobile.urlStack = urlStack;
//TODO: deprecate this one
$.mobile.urlstack = urlHistory.stack;
//history stack
$.mobile.urlHistory = urlHistory;
// changepage function
$.mobile.changePage = function( to, transition, back, changeHash){
// TODO : consider moving args to an object hash
$.mobile.changePage = function( to, transition, reverse, changeHash, fromHashChange ){
//from is always the currently viewed page
var toIsArray = $.type(to) === "array",
toIsObject = $.type(to) === "object",
from = toIsArray ? to[0] : $.mobile.activePage,
to = toIsArray ? to[1] : to,
url = fileUrl = $.type(to) === "string" ? to.replace( /^#/, "" ) : null,
url = fileUrl = $.type(to) === "string" ? path.stripHash( to ) : "",
data = undefined,
type = 'get',
isFormRequest = false,
duplicateCachedPage = null,
back = (back !== undefined) ? back : ( urlStack.length > 1 && urlStack[ urlStack.length - 2 ].url === url );
//If we are trying to transition to the same page that we are currently on ignore the request.
if(urlStack.length > 1 && url === urlStack[urlStack.length -1].url && !toIsArray ) {
currPage = urlHistory.getActive(),
back = false,
forward = false;
// If we are trying to transition to the same page that we are currently on ignore the request.
// an illegal same page request is defined by the current page being the same as the url, as long as there's history
// and to is not an array or object (those are allowed to be "same")
if( currPage && urlHistory.stack.length > 1 && currPage.url === url && !toIsArray && !toIsObject ) {
return;
}
// if the changePage was sent from a hashChange event
// guess if it came from the history menu
if( fromHashChange ){
// check if url is in history and if it's ahead or behind current page
$.each( urlHistory.stack, function( i ){
//if the url is in the stack, it's a forward or a back
if( this.url == url ){
urlIndex = i;
//define back and forward by whether url is older or newer than current page
back = i < urlHistory.activeIndex;
//forward set to opposite of back
forward = !back;
//reset activeIndex to this one
urlHistory.activeIndex = i;
}
});
//if it's a back, use reverse animation
if( back ){
reverse = true;
transition = transition || currPage.transition;
}
else if ( forward ){
transition = transition || urlHistory.getActive().transition;
}
}
if( $.type(to) === "object" && to.url ){
if( toIsObject && to.url ){
url = to.url,
data = to.data,
type = to.type,
@ -246,25 +313,6 @@
}
}
// if the new href is the same as the previous one
if ( back ) {
var pop = urlStack.pop();
// prefer the explicitly set transition
if( pop && !transition ){
transition = pop.transition;
}
// ensure a transition has been set where pop is undefined
defaultTransition();
} else {
// If no transition has been passed
defaultTransition();
// push the url and transition onto the stack
urlStack.push({ url: url, transition: transition });
}
//function for transitioning between two existing pages
function transitionPages() {
@ -286,8 +334,18 @@
reFocus( to );
if( changeHash !== false && url ){
path.set(url, (back !== true));
if( !back ){
urlHistory.listeningEnabled = false;
}
path.set( url );
urlHistory.listeningEnabled = true;
}
//add page to history stack if it's not back or forward, or a dialog
if( !back && !forward ){
urlHistory.addNew( url, transition );
}
removeActiveLinkClass();
//jump to top or prev scroll, if set
@ -326,9 +384,9 @@
addContainerClass('ui-mobile-viewport-transitioning');
// animate in / out
from.addClass( transition + " out " + ( back ? "reverse" : "" ) );
from.addClass( transition + " out " + ( reverse ? "reverse" : "" ) );
to.addClass( $.mobile.activePageClass + " " + transition +
" in " + ( back ? "reverse" : "" ) );
" in " + ( reverse ? "reverse" : "" ) );
// callback - remove classes, etc
to.animationComplete(function() {
@ -374,6 +432,9 @@
fileUrl = toIDfileurl;
}
}
// ensure a transition has been set where pop is undefined
defaultTransition();
// find the "to" page, either locally existing in the dom or by creating it through ajax
if ( to.length && !isFormRequest ) {
@ -512,7 +573,7 @@
else {
//use ajax
var transition = $this.data( "transition" ),
back = $this.data( "back" );
reverse = $this.data( "back" );
nextPageRole = $this.attr( "data-rel" );
@ -523,7 +584,7 @@
url = path.stripHash( url );
$.mobile.changePage( url, transition, back);
$.mobile.changePage( url, transition, reverse);
}
event.preventDefault();
});
@ -532,8 +593,7 @@
//hashchange event handler
$window.bind( "hashchange", function(e, triggered) {
if( !hashListener ){
hashListener = true;
if( !urlHistory.listeningEnabled ){
return;
}
@ -541,20 +601,21 @@
return;
}
var to = location.hash,
var to = path.stripHash( location.hash ),
transition = triggered ? false : undefined;
//if to is defined, use it
if ( to ){
$.mobile.changePage( to, transition, undefined, false);
$.mobile.changePage( to, transition, undefined, false, true );
}
//there's no hash, the active page is not the start page, and it's not manually triggered hashchange
//we probably backed out to the first page visited
else if( $.mobile.activePage.length && $.mobile.startPage[0] !== $.mobile.activePage[0] && !triggered ) {
$.mobile.changePage( $.mobile.startPage, transition, true, false );
$.mobile.changePage( $.mobile.startPage, transition, true, false, true );
}
//probably the first page - show it
else{
urlHistory.addNew( "" );
$.mobile.startPage.trigger("pagebeforeshow", {prevPage: $('')});
$.mobile.startPage.addClass( $.mobile.activePageClass );
$.mobile.pageLoading( true );

View file

@ -75,7 +75,7 @@ $.widget( "mobile.page", $.mobile.widget, {
// auto-add back btn on pages beyond first view
if ( o.addBackBtn && role === "header" &&
($.mobile.urlStack.length > 1 || $(".ui-page").length > 1) &&
($.mobile.urlHistory.getPrev() || $(".ui-page").length > 1) &&
!leftbtn && $this.data( "backbtn" ) !== false ) {
$( "<a href='#' class='ui-btn-left' data-icon='arrow-l'>"+ o.backBtnText +"</a>" )