diff --git a/Makefile b/Makefile index d1691a2f..1361e337 100755 --- a/Makefile +++ b/Makefile @@ -14,9 +14,9 @@ FILES = js/jquery.ui.widget.js \ js/jquery.mobile.support.js \ js/jquery.mobile.event.js \ js/jquery.mobile.hashchange.js \ + js/jquery.mobile.page.js \ js/jquery.mobile.core.js \ js/jquery.mobile.navigation.js \ - js/jquery.mobile.page.js \ js/jquery.ui.position.js \ js/jquery.mobile.fixHeaderFooter.js \ js/jquery.mobile.forms.checkboxradio.js \ diff --git a/build.xml b/build.xml index 7129b7e0..0a0e16fe 100644 --- a/build.xml +++ b/build.xml @@ -27,9 +27,9 @@ jquery.mobile.support.js, jquery.mobile.event.js, jquery.mobile.hashchange.js, + jquery.mobile.page.js, jquery.mobile.core.js, jquery.mobile.navigation.js, - jquery.mobile.page.js, jquery.mobile.buttonMarkup.js, jquery.mobile.collapsible.js, jquery.mobile.controlGroup.js, @@ -52,15 +52,15 @@ - + - - + + - \ No newline at end of file + diff --git a/docs/api/globalconfig.html b/docs/api/globalconfig.html index b06800a5..361f8feb 100755 --- a/docs/api/globalconfig.html +++ b/docs/api/globalconfig.html @@ -80,11 +80,14 @@ $(document).bind("mobileinit", function(){
activeBtnClass (string, default: "ui-page-active"):
The class used for "active" button state, from CSS framework.
+ +
ajaxEnabled (boolean, default: true):
+
jQuery Mobile will automatically handle link clicks and form submissions through Ajax, when possible. If false, url hash listening will be disabled as well, and urls will load as regular http requests.
-
ajaxLinksEnabled (boolean, default: true):
+
ajaxLinksEnabled (deprecated boolean, default: true):
jQuery Mobile will automatically handle link clicks through Ajax, when possible.
-
ajaxFormsEnabled (boolean, default: true):
+
ajaxFormsEnabled (deprecated boolean, default: true):
jQuery Mobile will automatically handle form submissions through Ajax, when possible.
defaultTransition (string, default: 'slide'):
diff --git a/docs/api/methods.html b/docs/api/methods.html index 070e1f64..31bdc284 100755 --- a/docs/api/methods.html +++ b/docs/api/methods.html @@ -40,7 +40,7 @@
transition (string, examples: "pop", "slide"," "none")
-
back (boolean, default: false). True will cause a reverse-direction transition.
+
reverse (boolean, default: false). True will cause a reverse-direction transition.
changeHash (boolean, default: true). Update the hash to the to page's URL when page change is complete.
@@ -89,6 +89,14 @@ $.mobile.pageLoading( true ); +
$.mobile.path (methods, properties)
+
Utilities for getting, setting, and manipulating url paths. TODO: document as public API is finalized.
+ +
$.mobile.base (methods, properties)
+
Utilities for working with generated base element. TODO: document as public API is finalized.
+ + +
$.mobile.silentScroll (method)
Scroll to a particular Y position without triggering scroll event listeners.
diff --git a/docs/pages/docs-transitions.html b/docs/pages/docs-transitions.html index 3a0dd7ba..8debbfdc 100755 --- a/docs/pages/docs-transitions.html +++ b/docs/pages/docs-transitions.html @@ -37,7 +37,7 @@ flip

-

In addition, you can also force a "backwards" transition by specifying data-back="true" on your link.

+

In addition, you can also force a "backwards" transition by specifying data-direction="reverse" on your link. Note: (this was formerly data-back="true", which will remain supported until 1.0)

Transitions from jQtouch (with small modifications): Built by David Kaneda and maintained by Jonathan Stark.

diff --git a/docs/pages/transition-success.html b/docs/pages/transition-success.html index 1c0f38c5..b7567acd 100644 --- a/docs/pages/transition-success.html +++ b/docs/pages/transition-success.html @@ -20,7 +20,7 @@

That was an animated page transition effect that we added with a data-transition attribute on the link.

Since it uses CSS transforms, this should be hardware accelerated on many mobile devices.

What do you think?

- I like it + I like it
diff --git a/js/index.php b/js/index.php index 471da540..a28edeb3 100644 --- a/js/index.php +++ b/js/index.php @@ -7,9 +7,9 @@ $elements = array( 'jquery.mobile.support.js', 'jquery.mobile.event.js', 'jquery.mobile.hashchange.js', + 'jquery.mobile.page.js', 'jquery.mobile.core.js', 'jquery.mobile.navigation.js', - 'jquery.mobile.page.js', 'jquery.ui.position.js', 'jquery.mobile.fixHeaderFooter.js', 'jquery.mobile.forms.checkboxradio.js', diff --git a/js/jquery.mobile.core.js b/js/jquery.mobile.core.js index 012be648..1e4a20b1 100644 --- a/js/jquery.mobile.core.js +++ b/js/jquery.mobile.core.js @@ -27,11 +27,16 @@ //class used for "active" button state, from CSS framework activeBtnClass: 'ui-btn-active', - //automatically handle link clicks through Ajax, when possible - ajaxLinksEnabled: true, + //automatically handle clicks and form submissions through Ajax, when same-domain + ajaxEnabled: true, + + // TODO: deprecated - remove at 1.0 + //automatically handle link clicks through Ajax, when possible + ajaxLinksEnabled: true, - //automatically handle form submissions through Ajax, when possible - ajaxFormsEnabled: true, + // TODO: deprecated - remove at 1.0 + //automatically handle form submissions through Ajax, when possible + ajaxFormsEnabled: true, //set default transition - 'none' for no transitions defaultTransition: 'slide', diff --git a/js/jquery.mobile.dialog.js b/js/jquery.mobile.dialog.js index 799935df..599c4013 100644 --- a/js/jquery.mobile.dialog.js +++ b/js/jquery.mobile.dialog.js @@ -10,19 +10,27 @@ $.widget( "mobile.dialog", $.mobile.widget, { _create: function(){ var self = this, $el = self.element, - $prevPage = $.mobile.activePage, $closeBtn = $('Close'), dialogClickHandler = function(e){ - var $target = $(e.target); + var $target = $(e.target), + href = $.mobile.path.stripHash( $target.closest("a").attr("href") ), + isRelative = $.mobile.path.isRelative( href ), + absUrl = isRelative ? $.mobile.path.makeAbsolute( href ) : href; // fixes issues with target links in dialogs breaking // page transitions by reseting the active page below - if( $.mobile.isExternalLink($target) ) { + if( $.mobile.path.isExternal( href ) || + $target.closest("a[target]").length || + $target.is( "[rel='external']" ) ) { return; } - - if( e.type == "click" && ( $(e.target).closest('[data-back]')[0] || this==$closeBtn[0] ) ){ + + + //if it's a close button click + //or the href is the same as the page we're on, close the dialog + if( e.type == "click" && + ( this==$closeBtn[0] || absUrl == $.mobile.path.stripHash( location.hash ) ) ){ self.close(); return false; } @@ -38,8 +46,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') @@ -54,11 +69,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 ); }); } }); @@ -66,7 +85,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 ); \ No newline at end of file diff --git a/js/jquery.mobile.event.js b/js/jquery.mobile.event.js index c1bdc90d..3dbd8f3f 100644 --- a/js/jquery.mobile.event.js +++ b/js/jquery.mobile.event.js @@ -63,21 +63,37 @@ $.event.special.tap = { $this = $( thisObject ); $this - .bind( touchStartEvent, function( event ) { - if ( event.which && event.which !== 1 ) { - return; + .bind( "mousedown touchstart", function( event ) { + if ( event.which && event.which !== 1 || + //check if event fired once already by a device that fires both mousedown and touchstart (while supporting both events) + $this.data( "prevEvent") && $this.data( "prevEvent") !== event.type ) { + return false; } + //save event type so only this type is let through for a temp duration, + //allowing quick repetitive taps but not duplicative events + $this.data( "prevEvent", event.type ); + setTimeout(function(){ + $this.removeData( "prevEvent" ); + }, 800); + var moved = false, touching = true, origTarget = event.target, - origPos = [ event.pageX, event.pageY ], + origEvent = event.originalEvent, + origPos = event.type == "touchstart" ? [origEvent.touches[0].pageX, origEvent.touches[0].pageY] : [ event.pageX, event.pageY ], originalType, timer; + - function moveHandler() { - if ((Math.abs(origPos[0] - event.pageX) > 10) || - (Math.abs(origPos[1] - event.pageY) > 10)) { + function moveHandler( event ) { + if( event.type == "scroll" ){ + moved = true; + return; + } + var newPageXY = event.type == "touchmove" ? event.originalEvent.touches[0] : event; + if ((Math.abs(origPos[0] - newPageXY.pageX) > 10) || + (Math.abs(origPos[1] - newPageXY.pageY) > 10)) { moved = true; } } @@ -91,17 +107,21 @@ $.event.special.tap = { } }, 750 ); + //scroll now cancels tap + $(window).one("scroll", moveHandler); + $this - .one( touchMoveEvent, moveHandler) - .one( touchStopEvent, function( event ) { - $this.unbind( touchMoveEvent, moveHandler ); + .bind( "mousemove touchmove", moveHandler ) + .one( "mouseup touchend", function( event ) { + $this.unbind( "mousemove touchmove", moveHandler ); + $(window).unbind("scroll", moveHandler); clearTimeout( timer ); touching = false; /* ONLY trigger a 'tap' event if the start target is * the same as the stop target. */ - if ( !moved && (origTarget == event.target)) { + if ( !moved && ( origTarget == event.target ) ) { originalType = event.type; event.type = "tap"; $.event.handle.call( thisObject, event ); diff --git a/js/jquery.mobile.navigation.js b/js/jquery.mobile.navigation.js index 7bd0f5f3..7aba0a42 100644 --- a/js/jquery.mobile.navigation.js +++ b/js/jquery.mobile.navigation.js @@ -19,7 +19,7 @@ if( newPath == undefined ){ newPath = location.hash; } - return newPath.replace(/#/,'').replace(/[^\/]*\.[^\/*]+$/, ''); + return path.stripHash( newPath ).replace(/[^\/]*\.[^\/*]+$/, ''); }, //return the substring of a filepath before the sub-page key, for making a server request @@ -27,9 +27,9 @@ var splitkey = '&' + $.mobile.subPageUrlKey; return path && path.indexOf( splitkey ) > -1 ? path.split( splitkey )[0] : path; }, - - set: function( path, disableListening){ - if(disableListening) { hashListener = false; } + + //set location hash to path + set: function( path ){ location.hash = path; }, @@ -38,29 +38,91 @@ setOrigin: function(){ path.origin = path.get( location.protocol + '//' + location.host + location.pathname ); + }, + + //prefix a relative url with the current path + makeAbsolute: function( url ){ + return path.get() + url; + }, + + //return a url path with the window's location protocol/hostname removed + clean: function( url ){ + return url.replace( location.protocol + "//" + location.host, ""); + }, + + //just return the url without an initial # + stripHash: function( url ){ + return url.replace( /^#/, "" ); + }, + + //check whether a url is referencing the same domain, or an external domain or different protocol + //could be mailto, etc + isExternal: function( url ){ + return path.hasProtocol( path.clean( url ) ); + }, + + hasProtocol: function( url ){ + return /^(:?\w+:)/.test( url ); + }, + + //check if the url is relative + isRelative: function( url ){ + return /^[^\/|#]/.test( url ) && !path.hasProtocol( url ); } }, //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"), @@ -83,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 { @@ -142,7 +204,6 @@ $activeClickedLink = null; }; - //animation complete callback $.fn.animationComplete = function( callback ){ if($.support.cssTransitions){ @@ -158,41 +219,77 @@ /* exposed $.mobile methods */ //update location.hash, with or without triggering hashchange event + //TODO - deprecate this one at 1.0 $.mobile.updateHash = path.set; + + //expose path object on $.mobile + $.mobile.path = path; + + //expose base object on $.mobile + $.mobile.base = base; //url stack, useful when plugins need to be aware of previous pages viewed - $.mobile.urlStack = urlStack; - - //check for an external resource - $.mobile.isExternalLink = function(anchor){ - var $anchor = $(anchor), - hasProtocol = /^(:?\w+:)/.test( $anchor.attr('href') ), - hasRelExternal = $anchor.is( "[rel=external]" ), - hasTarget = $anchor.is( "[target]" ); - - return hasProtocol || hasRelExternal || hasTarget; - }, + //TODO: deprecate this one at 1.0 + $.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, @@ -216,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() { @@ -256,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 @@ -296,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() { @@ -344,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 ) { @@ -417,20 +508,22 @@ /* Event Bindings - hashchange, submit, and click */ //bind to form submit events, handle with Ajax - $("form[data-ajax!='false']").live('submit', function(event){ - if( !$.mobile.ajaxFormsEnabled ){ return; } + $( "form[data-ajax!='false']" ).live('submit', function(event){ + if( !$.mobile.ajaxEnabled || + //TODO: deprecated - remove at 1.0 + !$.mobile.ajaxFormsEnabled ){ return; } var type = $(this).attr("method"), - url = $(this).attr( "action" ).replace( location.protocol + "//" + location.host, ""); + url = path.clean( $(this).attr( "action" ) ); //external submits use regular HTTP - if( /^(:?\w+:)/.test( url ) ){ + if( path.isExternal( url ) ){ return; } //if it's a relative href, prefix href with base url - if( url.indexOf('/') && url.indexOf('#') !== 0 ){ - url = path.get() + url; + if( path.isRelative( url ) ){ + url = path.makeAbsolute( url ); } $.mobile.changePage({ @@ -448,51 +541,57 @@ //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 - href = $this.attr( "href" ).replace( location.protocol + "//" + location.host, ""), - //if target attr is specified, it's external, and we mimic _blank... for now - target = $this.is( "[target]" ), + url = path.clean( $this.attr( "href" ) ), + + //check if it's external + isExternal = path.isExternal( url ) || $this.is( "[rel='external']" ), + + //if target attr is specified we mimic _blank... for now + hasTarget = $this.is( "[target]" ); + - //if it still starts with a protocol, it's external, or could be :mailto, etc - external = $.mobile.isExternalLink(this); - - if( href === '#' ){ + if( url === "#" ){ //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 + if( isExternal || hasTarget || !$.mobile.ajaxEnabled || + // TODO: deprecated - remove at 1.0 + !$.mobile.ajaxLinksEnabled ){ + //remove active link class if external (then it won't be there if you come back) removeActiveLinkClass(true); //deliberately redirect, in case click was triggered - if( target ){ - window.open(href); + if( hasTarget ){ + window.open( url ); } else{ - location.href = href; + location.href = url; } } else { //use ajax var transition = $this.data( "transition" ), - back = $this.data( "back" ); + direction = $this.data("direction"), + reverse = direction && direction == "reverse" || + // deprecated - remove by 1.0 + $this.data( "back" ); 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; + if( path.isRelative( url ) ){ + url = path.makeAbsolute( url ); } - href.replace(/^#/,''); + url = path.stripHash( url ); - $.mobile.changePage(href, transition, back); + $.mobile.changePage( url, transition, reverse); } event.preventDefault(); }); @@ -501,8 +600,10 @@ //hashchange event handler $window.bind( "hashchange", function(e, triggered) { - if( !hashListener ){ - hashListener = true; + if( !urlHistory.listeningEnabled || !$.mobile.ajaxEnabled || + // TODO: deprecated - remove at 1.0 + // only links need to be checked here, as forms don't trigger a hashchange event (they just silently update the hash) + ( !$.mobile.ajaxLinksEnabled ) ){ return; } @@ -510,20 +611,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 ); diff --git a/js/jquery.mobile.page.js b/js/jquery.mobile.page.js index 44dcf3d8..309c40b3 100644 --- a/js/jquery.mobile.page.js +++ b/js/jquery.mobile.page.js @@ -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 ) { $( ""+ o.backBtnText +"" ) diff --git a/tests/functional/addrbar.html b/tests/functional/addrbar.html new file mode 100755 index 00000000..c8f452d6 --- /dev/null +++ b/tests/functional/addrbar.html @@ -0,0 +1,47 @@ + + + + + jQuery Mobile: Event Logger + + + + + + + + + + +
+
+

Event Logger

+
+ +
+

Touch events on this page will log out below, prepending to the top as they arrive.

+ + Click me + +
    + +
+ +
+
+ + diff --git a/tests/functional/eventlogger.html b/tests/functional/eventlogger.html new file mode 100755 index 00000000..39c7664f --- /dev/null +++ b/tests/functional/eventlogger.html @@ -0,0 +1,37 @@ + + + + + jQuery Mobile: Event Logger + + + + + + + + +
+
+

Event Logger

+
+ +
+

Touch events on this page will log out below, prepending to the top as they arrive.

+ +
    + +
+ +
+
+ + diff --git a/speed/basic-page.html b/tests/speed/basic-page.html similarity index 100% rename from speed/basic-page.html rename to tests/speed/basic-page.html diff --git a/speed/lists-ul.html b/tests/speed/lists-ul.html similarity index 100% rename from speed/lists-ul.html rename to tests/speed/lists-ul.html diff --git a/tests/unit/core/core.js b/tests/unit/core/core.js index 3d8e7a09..1ff989d2 100644 --- a/tests/unit/core/core.js +++ b/tests/unit/core/core.js @@ -2,148 +2,148 @@ * mobile core unit tests */ -var libName = "jquery.mobile.core.js", - setGradeA = function(value) { $.support.mediaquery = value; }, - extendFn = $.extend; +(function($){ + var libName = "jquery.mobile.core.js", + setGradeA = function(value) { $.support.mediaquery = value; }, + extendFn = $.extend; -module(libName, { - setup: function(){ - // NOTE reset for gradeA tests - $('html').removeClass('ui-mobile'); + module(libName, { + setup: function(){ + // NOTE reset for gradeA tests + $('html').removeClass('ui-mobile'); - // NOTE reset for pageLoading tests - $('.ui-loader').remove(); - }, - teardown: function(){ - $.extend = extendFn; - } -}); - -$.testHelper.excludeFileProtocol(function(){ - test( "grade A browser support media queries", function(){ - setGradeA(false); - $.testHelper.reloadLib(libName); - ok(!$.mobile.gradeA()); - - setGradeA(true); - $.testHelper.reloadLib(libName); - ok($.mobile.gradeA()); + // NOTE reset for pageLoading tests + $('.ui-loader').remove(); + }, + teardown: function(){ + $.extend = extendFn; + } }); - test( "loading the core library triggers mobilinit on the document", function(){ - expect( 1 ); + $.testHelper.excludeFileProtocol(function(){ + test( "grade A browser support media queries", function(){ + setGradeA(false); + $.testHelper.reloadLib(libName); + ok(!$.mobile.gradeA()); - $(window.document).bind('mobileinit', function(event){ - ok(true); + setGradeA(true); + $.testHelper.reloadLib(libName); + ok($.mobile.gradeA()); }); - $.testHelper.reloadLib(libName); - }); + test( "loading the core library triggers mobilinit on the document", function(){ + expect( 1 ); - test( "enhancments are skipped when the browser is not grade A", function(){ - setGradeA(false); - $.testHelper.reloadLib(libName); + $(window.document).bind('mobileinit', function(event){ + ok(true); + }); - //NOTE easiest way to check for enhancements, not the most obvious - ok(!$("html").hasClass("ui-mobile")); - }); - - test( "enhancments are added when the browser is grade A", function(){ - setGradeA(true); - $.testHelper.reloadLib(libName); - - ok($("html").hasClass("ui-mobile")); - }); - - - //TODO lots of duplication - test( "pageLoading doesn't add the dialog to the page when loading message is false", function(){ - $.testHelper.alterExtend({loadingMessage: false}); - $.testHelper.reloadLib(libName); - $.mobile.pageLoading(false); - ok(!$(".ui-loader").length); - }); - - test( "pageLoading doesn't add the dialog to the page when done is passed as true", function(){ - $.testHelper.alterExtend({loadingMessage: true}); - $.testHelper.reloadLib(libName); - - // TODO add post reload callback - $('.ui-loader').remove(); - - $.mobile.pageLoading(true); - ok(!$(".ui-loader").length); - }); - - test( "pageLoading adds the dialog to the page when done is true", function(){ - $.testHelper.alterExtend({loadingMessage: true}); - $.testHelper.reloadLib(libName); - $.mobile.pageLoading(false); - ok($(".ui-loader").length); - }); - - var metaViewportSelector = "head meta[name=viewport]", - setViewPortContent = function(value){ - $(metaViewportSelector).remove(); - $.testHelper.alterExtend({metaViewportContent: value}); - $.testHelper.reloadLib(libName); - }; - - test( "meta view port element is added to head when defined on mobile", function(){ - setViewPortContent("width=device-width"); - same($(metaViewportSelector).length, 1); - }); - - test( "meta view port element not added to head when not defined on mobile", function(){ - setViewPortContent(false); - same($(metaViewportSelector).length, 0); - }); - - var findFirstPage = function() { - return $("[data-role='page']").first(); - }; - - test( "active page and start page should be set to the fist page in the selected set", function(){ - var firstPage = findFirstPage(); - $.testHelper.reloadLib(libName); - - same($.mobile.startPage, firstPage); - same($.mobile.activePage, firstPage); - }); - - test( "mobile viewport class is defined on the first page's parent", function(){ - var firstPage = findFirstPage(); - $.testHelper.reloadLib(libName); - - ok(firstPage.parent().hasClass('ui-mobile-viewport')); - }); - - test( "mobile page container is the first page's parent", function(){ - var firstPage = findFirstPage(); - $.testHelper.reloadLib(libName); - - same($.mobile.pageContainer, firstPage.parent()); - }); - - test( "page loading is called on document ready", function(){ - expect( 2 ); - - $.testHelper.alterExtend({ pageLoading: function(){ - ok("called"); - }}); - - $.testHelper.reloadLib(libName); - }); - - test( "hashchange triggered on document ready with single argument: true", function(){ - expect( 2 ); - - $(window).bind("hashchange", function(ev, arg){ - same(arg, true); + $.testHelper.reloadLib(libName); }); - $.testHelper.reloadLib(libName); - }); + test( "enhancments are skipped when the browser is not grade A", function(){ + setGradeA(false); + $.testHelper.reloadLib(libName); - //TODO test that silentScroll is called on window load -}); + //NOTE easiest way to check for enhancements, not the most obvious + ok(!$("html").hasClass("ui-mobile")); + }); + + test( "enhancments are added when the browser is grade A", function(){ + setGradeA(true); + $.testHelper.reloadLib(libName); + + ok($("html").hasClass("ui-mobile")); + }); + + + //TODO lots of duplication + test( "pageLoading doesn't add the dialog to the page when loading message is false", function(){ + $.testHelper.alterExtend({loadingMessage: false}); + $.testHelper.reloadLib(libName); + $.mobile.pageLoading(false); + ok(!$(".ui-loader").length); + }); + + test( "pageLoading doesn't add the dialog to the page when done is passed as true", function(){ + $.testHelper.alterExtend({loadingMessage: true}); + $.testHelper.reloadLib(libName); + + // TODO add post reload callback + $('.ui-loader').remove(); + + $.mobile.pageLoading(true); + ok(!$(".ui-loader").length); + }); + + test( "pageLoading adds the dialog to the page when done is true", function(){ + $.testHelper.alterExtend({loadingMessage: true}); + $.testHelper.reloadLib(libName); + $.mobile.pageLoading(false); + ok($(".ui-loader").length); + }); + + var metaViewportSelector = "head meta[name=viewport]", + setViewPortContent = function(value){ + $(metaViewportSelector).remove(); + $.testHelper.alterExtend({metaViewportContent: value}); + $.testHelper.reloadLib(libName); + }; + + test( "meta view port element is added to head when defined on mobile", function(){ + setViewPortContent("width=device-width"); + same($(metaViewportSelector).length, 1); + }); + + test( "meta view port element not added to head when not defined on mobile", function(){ + setViewPortContent(false); + same($(metaViewportSelector).length, 0); + }); + + var findFirstPage = function() { + return $("[data-role='page']").first(); + }; + + test( "active page and start page should be set to the fist page in the selected set", function(){ + var firstPage = findFirstPage(); + $.testHelper.reloadLib(libName); + + same($.mobile.startPage, firstPage); + same($.mobile.activePage, firstPage); + }); + + test( "mobile viewport class is defined on the first page's parent", function(){ + var firstPage = findFirstPage(); + $.testHelper.reloadLib(libName); + + ok(firstPage.parent().hasClass('ui-mobile-viewport')); + }); + + test( "mobile page container is the first page's parent", function(){ + var firstPage = findFirstPage(); + $.testHelper.reloadLib(libName); + + same($.mobile.pageContainer, firstPage.parent()); + }); + + test( "page loading is called on document ready", function(){ + $.testHelper.alterExtend({ pageLoading: function(){ + start(); + ok("called"); + }}); + + stop(); + $.testHelper.reloadLib(libName); + }); + + test( "hashchange triggered on document ready with single argument: true", function(){ + $(window).bind("hashchange", function(ev, arg){ + same(arg, true); + start(); + }); + + stop(); + $.testHelper.reloadLib(libName); + }); + }); +})(jQuery); \ No newline at end of file diff --git a/tests/unit/core/core_mobileinit.js b/tests/unit/core/core_mobileinit.js new file mode 100644 index 00000000..3a4b9506 --- /dev/null +++ b/tests/unit/core/core_mobileinit.js @@ -0,0 +1,18 @@ +/* + * mobile core unit tests + */ + +(function($){ + var mobilePage = undefined; + module('jquery.mobile.core.js'); + + // NOTE important to use $.fn.one here to make sure library reloads don't fire + // the event before the test check below + $(document).one("mobileinit", function(){ + mobilePage = $.mobile.page; + }); + + test( "mobile.page is available when mobile init is fired", function(){ + ok(mobilePage !== undefined, "$.mobile.page is defined"); + }); +})(jQuery); diff --git a/tests/unit/core/core_scroll.js b/tests/unit/core/core_scroll.js index ba3a007d..8724684e 100644 --- a/tests/unit/core/core_scroll.js +++ b/tests/unit/core/core_scroll.js @@ -2,71 +2,75 @@ * mobile core unit tests */ -var libName = "jquery.mobile.core.js", - scrollTimeout = 20, // TODO expose timing as an attribute - scrollStartEnabledTimeout = 150; +(function($){ + var libName = "jquery.mobile.core.js", + scrollTimeout = 20, // TODO expose timing as an attribute + scrollStartEnabledTimeout = 150; -module(libName, { - setup: function(){ - $("
").appendTo("body"); - }, + module(libName, { + setup: function(){ + $("
").appendTo("body"); + }, - teardown: function(){ - $("#scroll-testing").remove(); - } -}); + teardown: function(){ + $("#scroll-testing").remove(); + } + }); -var scrollUp = function( pos ){ - $(window).scrollTop(1000); - ok($(window).scrollTop() > 0); + var scrollUp = function( pos ){ + $(window).scrollTop(1000); + ok($(window).scrollTop() > 0); - if(pos) { - $.mobile.silentScroll(pos); - } else { - $.mobile.silentScroll(); - } -}; + if(pos) { + $.mobile.silentScroll(pos); + } else { + $.mobile.silentScroll(); + } + }; -test( "silent scroll scrolls the page to the top by default", function(){ - scrollUp(); + test( "silent scroll scrolls the page to the top by default", function(){ + scrollUp(); - stop(); - setTimeout(function(){ - same($(window).scrollTop(), 0); - start(); - }, scrollTimeout); -}); + stop(); + setTimeout(function(){ + same($(window).scrollTop(), 0); + start(); + }, scrollTimeout); + }); -test( "silent scroll scrolls the page to the passed y position", function(){ - var pos = 10; - scrollUp(pos); + test( "silent scroll scrolls the page to the passed y position", function(){ + var pos = 10; + scrollUp(pos); - stop(); - setTimeout(function(){ - same($(window).scrollTop(), pos); - start(); - }, scrollTimeout); -}); + stop(); + setTimeout(function(){ + same($(window).scrollTop(), pos); + start(); + }, scrollTimeout); + }); -// NOTE may be brittle depending on timing -test( "silent scroll takes at least 20 ms to scroll to the top", function(){ - scrollUp(); + // NOTE may be brittle depending on timing + test( "silent scroll takes at least 20 ms to scroll to the top", function(){ + scrollUp(); - stop(); - setTimeout(function(){ - ok($(window).scrollTop() != 0); - start(); - }, scrollTimeout - 1); -}); + stop(); + setTimeout(function(){ + ok($(window).scrollTop() != 0); + start(); + }, scrollTimeout - 1); + }); -test( "scrolling marks scrollstart as disabled for 150 ms", function(){ - $.event.special.scrollstart.enabled = true; - scrollUp(); - ok(!$.event.special.scrollstart.enabled); + test( "scrolling marks scrollstart as disabled for 150 ms", function(){ + $.event.special.scrollstart.enabled = true; + scrollUp(); + ok(!$.event.special.scrollstart.enabled); - stop(); - setTimeout(function(){ - ok($.event.special.scrollstart.enabled); - start(); - }, scrollStartEnabledTimeout); -}); + stop(); + setTimeout(function(){ + ok($.event.special.scrollstart.enabled); + start(); + }, scrollStartEnabledTimeout); + }); + + //TODO test that silentScroll is called on window load +})(jQuery); diff --git a/tests/unit/core/index.html b/tests/unit/core/index.html index c9adc415..82f56141 100644 --- a/tests/unit/core/index.html +++ b/tests/unit/core/index.html @@ -5,27 +5,13 @@ jQuery Mobile Core Test Suite - - - - - - - - - - - - - - - - - - - - + + + + + + diff --git a/tests/unit/navigation/navigation_core.js b/tests/unit/navigation/navigation_core.js index 9da2538d..a8f62348 100644 --- a/tests/unit/navigation/navigation_core.js +++ b/tests/unit/navigation/navigation_core.js @@ -37,16 +37,24 @@ same(called, 2, "change page should be called twice"); }); - - test( "check for external link succeeds", function(){ - same($.mobile.isExternalLink(""), true, "mailto"); - same($.mobile.isExternalLink(""), true, "http protocol"); - same($.mobile.isExternalLink(""), true, "rel=external"); - same($.mobile.isExternalLink(""), true, "target"); + + test( "path.get method is working properly", function(){ + same($.mobile.path.get(), window.location.hash, "get method returns location.hash"); + same($.mobile.path.get( "#foo/bar/baz.html" ), "foo/bar/", "get method with hash arg returns path with no filename or hash prefix"); + same($.mobile.path.get( "#foo/bar/baz.html/" ), "foo/bar/baz.html/", "last segment of hash is retained if followed by a trailing slash"); }); - test( "check for external link fails", function(){ - same($.mobile.isExternalLink(""), false, "mailto"); - same($.mobile.isExternalLink(""), false, "mailto"); + test( "path.isExternal method is working properly", function(){ + same($.mobile.path.isExternal("mailto:"), true, "mailto protocol"); + same($.mobile.path.isExternal("http://foo.com"), true, "http protocol"); + same($.mobile.path.isExternal("http://www.foo.com"), true, "http protocol with www"); + same($.mobile.path.isExternal("tel:16178675309"), true, "tel protocol"); + same($.mobile.path.isExternal("foo.html"), false, "filename"); + same($.mobile.path.isExternal("foo/foo/foo.html"), false, "file path"); + same($.mobile.path.isExternal("../../index.html"), false, "relative parent path"); + same($.mobile.path.isExternal("/foo"), false, "root-relative path"); + same($.mobile.path.isExternal("foo"), false, "simple string"); + same($.mobile.path.isExternal("#foo"), false, "local id reference"); }); + })(jQuery); \ No newline at end of file