diff --git a/js/jquery.mobile.navigation.js b/js/jquery.mobile.navigation.js index 17d6a537..b051e21a 100644 --- a/js/jquery.mobile.navigation.js +++ b/js/jquery.mobile.navigation.js @@ -1,9 +1,9 @@ /* -* jQuery Mobile Framework : core utilities for auto ajax navigation, base tag mgmt, +* jQuery Mobile Framework : core utilities for auto ajax navigation, base tag mgmt, * Copyright (c) jQuery Project * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license -*/ +*/ (function($, undefined ) { //define vars for interal use @@ -13,7 +13,7 @@ //url path helpers for use in relative url management path = { - + //get path from current hash, or from a file path get: function( newPath ){ if( newPath == undefined ){ @@ -28,48 +28,48 @@ } return newPath.join('/') + (newPath.length ? '/' : ''); }, - - //return the substring of a filepath before the sub-page key, for making a server request + + //return the substring of a filepath before the sub-page key, for making a server request getFilePath: function( path ){ var splitkey = '&' + $.mobile.subPageUrlKey; return path && path.indexOf( splitkey ) > -1 ? path.split( splitkey )[0] : path; }, - + set: function( path, disableListening){ if(disableListening) { hashListener = false; } location.hash = path; }, - + //location pathname from intial directory request origin: '', - + setOrigin: function(){ path.origin = path.get( location.protocol + '//' + location.host + location.pathname ); } }, - + //base element management, defined depending on dynamic base tag support base = $.support.dynamicBaseTag ? { - + //define base element, for use in routing asset urls that are referenced in Ajax-requested markup element: $("", { href: path.origin }).prependTo( $head ), - + //set the generated BASE element's href attribute to a new page's base path set: function( href ){ base.element.attr('href', path.origin + path.get( href )); }, - + //set the generated BASE element's href attribute to a new page's base path reset: function(){ base.element.attr('href', path.origin ); } - - } : undefined, - + } : undefined, + + //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) @@ -77,23 +77,23 @@ url: location.hash.replace( /^#/, "" ), transition: undefined } ], - + //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; - + hashListener = true; + //set location pathname from intial directory request path.setOrigin(); -/* +/* internal utility functions ---------------------------------------*/ +--------------------------------------*/ //direct focus to the page title, or otherwise first focusable element @@ -106,7 +106,7 @@ page.find( focusable ).eq(0).focus(); } }; - + //remove active classes after page transition or error function removeActiveLinkClass( forceRemoval ){ if( !!$activeClickedLink && (!$activeClickedLink.closest( '.ui-page-active' ).length || forceRemoval )){ @@ -115,7 +115,7 @@ $activeClickedLink = null; }; - + //animation complete callback $.fn.animationComplete = function( callback ){ if($.support.cssTransitions){ @@ -124,7 +124,7 @@ else{ callback(); } - }; + }; @@ -132,13 +132,13 @@ //update location.hash, with or without triggering hashchange event $.mobile.updateHash = path.set; - - //url stack, useful when plugins need to be aware of previous pages viewed - $.mobile.urlStack = urlStack; - // changepage function + //url stack, useful when plugins need to be aware of previous pages viewed + $.mobile.urlStack = urlStack; + + // changepage function $.mobile.changePage = function( to, transition, back, changeHash){ - + //from is always the currently viewed page var toIsArray = $.type(to) === "array", from = toIsArray ? to[0] : $.mobile.activePage, @@ -150,14 +150,14 @@ duplicateCachedPage = null, back = (back !== undefined) ? back : ( urlStack.length > 1 && urlStack[ urlStack.length - 2 ].url === url ), transition = (transition !== undefined) ? transition : $.mobile.defaultTransition; - + //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 ) { return; } - - + + if( $.type(to) === "object" && to.url ){ url = to.url, data = to.data, @@ -169,16 +169,16 @@ data = undefined; } } - - - - + + + + //reset base to pathname for new request if(base){ base.reset(); } - + //kill the keyboard $( window.document.activeElement ).add(':focus').blur(); - + // if the new href is the same as the previous one if ( back ) { var pop = urlStack.pop(); @@ -188,58 +188,78 @@ } else { urlStack.push({ url: url, transition: transition }); } - + //function for transitioning between two existing pages function transitionPages() { - + //get current scroll distance - var currScroll = $window.scrollTop(); - + var currScroll = $window.scrollTop(), + perspectiveTransitions = ["flip"], + pageContainerClasses = []; + //set as data for returning to that spot from.data('lastScroll', currScroll); - + //trigger before show/hide events from.data("page")._trigger("beforehide", {nextPage: to}); to.data("page")._trigger("beforeshow", {prevPage: from}); - + function loadComplete(){ $.mobile.pageLoading( true ); - + reFocus( to ); - + if( changeHash !== false && url ){ path.set(url, (back !== true)); } removeActiveLinkClass(); - + //if there's a duplicateCachedPage, remove it from the DOM now that it's hidden if( duplicateCachedPage != null ){ duplicateCachedPage.remove(); } - + //jump to top or prev scroll, if set $.mobile.silentScroll( to.data( 'lastScroll' ) ); - - //trigger show/hide events, allow preventing focus change through return false + + //trigger show/hide events, allow preventing focus change through return false from.data("page")._trigger("hide", null, {nextPage: to}); if( to.data("page")._trigger("show", null, {prevPage: from}) !== false ){ $.mobile.activePage = to; } }; - - if(transition && (transition !== 'none')){ - $.mobile.pageContainer.addClass('ui-mobile-viewport-transitioning'); + + function addContainerClass(className){ + $.mobile.pageContainer.addClass(className); + pageContainerClasses.push(className); + }; + + function removeContainerClasses(){ + $.mobile + .pageContainer + .removeClass(pageContainerClasses.join(" ")); + + pageContainerClasses = []; + }; + + if(transition && (transition !== 'none')){ + if( perspectiveTransitions.indexOf(transition) >= 0 ){ + addContainerClass('ui-mobile-viewport-perspective'); + } + + addContainerClass('ui-mobile-viewport-transitioning'); + // animate in / out from.addClass( transition + " out " + ( back ? "reverse" : "" ) ); to.addClass( $.mobile.activePageClass + " " + transition + " in " + ( back ? "reverse" : "" ) ); - + // callback - remove classes, etc to.animationComplete(function() { from.add( to ).removeClass("out in reverse " + transition ); from.removeClass( $.mobile.activePageClass ); loadComplete(); - $.mobile.pageContainer.removeClass('ui-mobile-viewport-transitioning'); + removeContainerClasses(); }); } else{ @@ -248,10 +268,10 @@ loadComplete(); } }; - + //shared page enhancements function enhancePage(){ - + //set next page role, if defined if ( nextPageRole || to.data('role') == 'dialog' ) { changeHash = false; @@ -260,8 +280,8 @@ nextPageRole = null; } } - - //run page plugin + + //run page plugin to.page(); }; @@ -273,26 +293,26 @@ else{ //find base url of element, if avail var toID = to.attr('data-url'), toIDfileurl = path.getFilePath(toID); - + if(toID != toIDfileurl){ fileUrl = toIDfileurl; - } + } } - + // find the "to" page, either locally existing in the dom or by creating it through ajax if ( to.length && !isFormRequest ) { if( fileUrl && base ){ base.set( fileUrl ); - } + } enhancePage(); transitionPages(); - } else { - + } else { + //if to exists in DOM, save a reference to it in duplicateCachedPage for removal after page change if( to.length ){ duplicateCachedPage = to; } - + $.mobile.pageLoading(); $.ajax({ @@ -301,22 +321,22 @@ data: data, success: function( html ) { if(base){ base.set(fileUrl); } - + var all = $("
"); //workaround to allow scripts to execute when included in page divs all.get(0).innerHTML = html; to = all.find('[data-role="page"], [data-role="dialog"]').first(); - + //rewrite src and href attrs to use a base url if( !$.support.dynamicBaseTag ){ var newPath = path.get( fileUrl ); to.find('[src],link[href]').each(function(){ var thisAttr = $(this).is('[href]') ? 'href' : 'src', thisUrl = $(this).attr(thisAttr); - + //if full path exists and is same, chop it - helps IE out thisUrl.replace( location.protocol + '//' + location.host + location.pathname, '' ); - + if( !/^(\w+:|#|\/)/.test(thisUrl) ){ $(this).attr(thisAttr, newPath + thisUrl); } @@ -326,7 +346,7 @@ to .attr( "data-url", fileUrl ) .appendTo( $.mobile.pageContainer ); - + enhancePage(); transitionPages(); }, @@ -347,26 +367,26 @@ }; - -/* Event Bindings - hashchange, submit, and click */ - + +/* Event Bindings - hashchange, submit, and click */ + //bind to form submit events, handle with Ajax $('form').live('submit', function(event){ if( !$.mobile.ajaxFormsEnabled ){ return; } - + var type = $(this).attr("method"), - url = $(this).attr( "action" ).replace( location.protocol + "//" + location.host, ""); - + url = $(this).attr( "action" ).replace( location.protocol + "//" + location.host, ""); + //external submits use regular HTTP if( /^(:?\w+:)/.test( url ) ){ return; - } - + } + //if it's a relative href, prefix href with base url if( url.indexOf('/') && url.indexOf('#') !== 0 ){ url = path.get() + url; } - + $.mobile.changePage({ url: url, type: type, @@ -377,12 +397,12 @@ true ); event.preventDefault(); - }); - - + }); + + //click routing - direct to HTTP or Ajax, accordingly $( "a" ).live( "click", function(event) { - + if( !$.mobile.ajaxLinksEnabled ){ return; } var $this = $(this), //get href, remove same-domain protocol and host @@ -396,13 +416,13 @@ //for links created purely for interaction - ignore return false; } - + $activeClickedLink = $this.closest( ".ui-btn" ).addClass( $.mobile.activeBtnClass ); - + if( external || !$.mobile.ajaxLinksEnabled ){ //remove active link class if external removeActiveLinkClass(true); - + //deliberately redirect, in case click was triggered if( target ){ window.open(href); @@ -411,41 +431,41 @@ location.href = href; } } - else { + else { //use ajax var transition = $this.data( "transition" ), back = $this.data( "back" ); - - nextPageRole = $this.attr( "data-rel" ); - + + nextPageRole = $this.attr( "data-rel" ); + //if it's a relative href, prefix href with base url if( href.indexOf('/') && href.indexOf('#') !== 0 ){ href = path.get() + href; } - + href.replace(/^#/,''); - - $.mobile.changePage(href, transition, back); + + $.mobile.changePage(href, transition, back); } event.preventDefault(); }); - - - - //hashchange event handler + + + + //hashchange event handler $window.bind( "hashchange", function(e, triggered) { - if( !hashListener ){ + if( !hashListener ){ hashListener = true; - return; - } - + return; + } + if( $(".ui-page-active").is("[data-role=" + $.mobile.nonHistorySelectors + "]") ){ return; } - + var to = location.hash, transition = triggered ? false : undefined; - + //if to is defined, use it if ( to ){ $.mobile.changePage( to, transition, undefined, false); @@ -460,7 +480,7 @@ $.mobile.startPage.trigger("pagebeforeshow", {prevPage: $('')}); $.mobile.startPage.addClass( $.mobile.activePageClass ); $.mobile.pageLoading( true ); - + if( $.mobile.startPage.trigger("pageshow", {prevPage: $('')}) !== false ){ reFocus($.mobile.startPage); } diff --git a/tests/unit/navigation/index.html b/tests/unit/navigation/index.html new file mode 100644 index 00000000..16b6c952 --- /dev/null +++ b/tests/unit/navigation/index.html @@ -0,0 +1,57 @@ + + + + + jQuery Mobile Navigation Test Suite + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

jQuery Mobile Navigation Test Suite

+

+

+
    +
+ +
+
+ foo +
+ +
+
+ + diff --git a/tests/unit/navigation/navigation_core.js b/tests/unit/navigation/navigation_core.js new file mode 100644 index 00000000..07dde872 --- /dev/null +++ b/tests/unit/navigation/navigation_core.js @@ -0,0 +1,70 @@ +/* + * mobile navigation unit tests + */ +(function( $ ) { + var perspective = "ui-mobile-viewport-perspective", + transitioning = "ui-mobile-viewport-transitioning", + animationCompleteFn = $.fn.animationComplete, + + removeClasses = function(){ + $("#main").removeClass([perspective, transitioning].join(" ")); + }, + + insertNewTestPage = function(i){ + var id = "other" + i; + $("
").appendTo("#main"); + return "#" + id; + }; + + module('jquery.mobile.navigation.js', { + teardown: function(){ + // unmock animation complete + $.fn.animationComplete = animationCompleteFn; + + removeClasses(); + }}); + + test( "changePage applys perspective class to mobile viewport for flip", function(){ + //stub to prevent class removal + $.fn.animationComplete = function(){}; + + $.mobile.changePage("#other", "flip", false, false); + ok($("#main"). + hasClass(perspective), "has perspective class"); + }); + + test( "changePage doesn't apply perspective class to mobile viewport for other transitions", function(){ + //stub to prevent class removal + $.fn.animationComplete = function(){}; + + $.each("slide slideup slidedown pop fade flip".split(" "), function(i, trans){ + $.mobile.changePage(insertNewTestPage(i), trans, false, false); + + ok(!$("#main"). + hasClass(perspective), "has no perspective class"); + }); + }); + + test( "changePage applys transitioning class", function(){ + var classSet = false; + + //stub to prevent class removal + $.fn.animationComplete = function(){}; + + $.mobile.changePage("#other", "flip", false, false); + + ok($("#main"). + hasClass(transitioning), "has transitioning class"); + }); + + test( "changePage applys and removes transitioning class", function(){ + $.mobile.changePage("#other", "flip", false, false); + + stop(); + setTimeout(function(){ + ok(!$("#main"). + hasClass(transitioning), "has transitioning class"); + start(); + }, 1000); + }); +})(jQuery); \ No newline at end of file diff --git a/themes/default/jquery.mobile.transitions.css b/themes/default/jquery.mobile.transitions.css index 999c9019..0b828635 100644 --- a/themes/default/jquery.mobile.transitions.css +++ b/themes/default/jquery.mobile.transitions.css @@ -151,7 +151,7 @@ Built by David Kaneda and maintained by Jonathan Stark. * the viewer from the z-plane. According to the CSS3 spec, 1000 is a moderate * value. */ -.ui-mobile-viewport-transitioning { +.ui-mobile-viewport-perspective { -webkit-perspective: 1000; position: absolute; }