From ffdfa4e7c2e36590d42c2a13e6156e17dc04f7c5 Mon Sep 17 00:00:00 2001 From: scottjehl Date: Tue, 21 Jun 2011 11:33:38 -0400 Subject: [PATCH] moved page load event bindings to a post-mobileinit callback so that the useFastClick option can be set before it is used. Unit test included. Fixes #1869 --- js/jquery.mobile.init.js | 3 + js/jquery.mobile.navigation.js | 331 +++++++++++++++++---------------- tests/unit/init/index.html | 1 + tests/unit/init/init_core.js | 11 ++ 4 files changed, 184 insertions(+), 162 deletions(-) diff --git a/js/jquery.mobile.init.js b/js/jquery.mobile.init.js index 5850a9ba..951a18fc 100644 --- a/js/jquery.mobile.init.js +++ b/js/jquery.mobile.init.js @@ -101,6 +101,9 @@ } }); + //initialize events now, after mobileinit has occurred + $.mobile._registerInternalEvents(); + //check which scrollTop value should be used by scrolling to 1 immediately at domready //then check what the scroll top is. Android will report 0... others 1 //note that this initial scroll won't hide the address bar. It's just for the check. diff --git a/js/jquery.mobile.navigation.js b/js/jquery.mobile.navigation.js index ab809884..671e3b2f 100644 --- a/js/jquery.mobile.navigation.js +++ b/js/jquery.mobile.navigation.js @@ -981,168 +981,175 @@ return path.makeUrlAbsolute( url, base); } - - //add active state on vclick - $( document ).bind( "vclick", function( event ) { - var link = findClosestLink( event.target ); - if ( link ) { - if ( path.parseUrl( link.getAttribute( "href" ) || "#" ).hash !== "#" ) { - $( link ).closest( ".ui-btn" ).not( ".ui-disabled" ).addClass( $.mobile.activeBtnClass ); - $( "." + $.mobile.activePageClass + " .ui-btn" ).not( link ).blur(); - } - } - }); - - // click routing - direct to HTTP or Ajax, accordingly - // TODO: most of the time, vclick will be all we need for fastClick bulletproofing. - // However, it seems that in Android 2.1, a click event - // will occasionally arrive independently of the bound vclick - // binding to click as well seems to help in this edge case - // we'll dig into this further in the next release cycle - $( document ).bind( $.mobile.useFastClick ? "vclick click" : "click", function( event ) { - var link = findClosestLink( event.target ); - if ( !link ) { - return; - } - - var $link = $( link ), - //remove active link class if external (then it won't be there if you come back) - httpCleanup = function(){ - window.setTimeout( function() { removeActiveLinkClass( true ); }, 200 ); - }; - - //if there's a data-rel=back attr, go back in history - if( $link.is( ":jqmData(rel='back')" ) ) { - window.history.back(); - return false; - } - - //if ajax is disabled, exit early - if( !$.mobile.ajaxEnabled ){ - httpCleanup(); - //use default click handling - return; - } - - var baseUrl = getClosestBaseUrl( $link ), - - //get href, if defined, otherwise default to empty hash - href = path.makeUrlAbsolute( $link.attr( "href" ) || "#", baseUrl ); - - // XXX_jblas: Ideally links to application pages should be specified as - // an url to the application document with a hash that is either - // the site relative path or id to the page. But some of the - // internal code that dynamically generates sub-pages for nested - // lists and select dialogs, just write a hash in the link they - // create. This means the actual URL path is based on whatever - // the current value of the base tag is at the time this code - // is called. For now we are just assuming that any url with a - // hash in it is an application page reference. - if ( href.search( "#" ) != -1 ) { - href = href.replace( /[^#]*#/, "" ); - if ( !href ) { - //link was an empty hash meant purely - //for interaction, so we ignore it. - event.preventDefault(); - return; - } else if ( path.isPath( href ) ) { - //we have apath so make it the href we want to load. - href = path.makeUrlAbsolute( href, baseUrl ); - } else { - //we have a simple id so use the documentUrl as its base. - href = path.makeUrlAbsolute( "#" + href, documentUrl.hrefNoHash ); - } - } - - // Should we handle this link, or let the browser deal with it? - var useDefaultUrlHandling = $link.is( "[rel='external']" ) || $link.is( ":jqmData(ajax='false')" ) || $link.is( "[target]" ), - - // Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR - // requests if the document doing the request was loaded via the file:// protocol. - // This is usually to allow the application to "phone home" and fetch app specific - // data. We normally let the browser handle external/cross-domain urls, but if the - // allowCrossDomainPages option is true, we will allow cross-domain http/https - // requests to go through our page loading logic. - isCrossDomainPageLoad = ( $.mobile.allowCrossDomainPages && documentUrl.protocol === "file:" && href.search( /^https?:/ ) != -1 ), - - //check for protocol or rel and its not an embedded page - //TODO overlap in logic from isExternal, rel=external check should be - // moved into more comprehensive isExternalLink - isExternal = useDefaultUrlHandling || ( path.isExternal( href ) && !isCrossDomainPageLoad ); - - $activeClickedLink = $link.closest( ".ui-btn" ); - - if( isExternal ) { - httpCleanup(); - //use default click handling - return; - } - - //use ajax - var transition = $link.jqmData( "transition" ), - direction = $link.jqmData( "direction" ), - reverse = ( direction && direction === "reverse" ) || - // deprecated - remove by 1.0 - $link.jqmData( "back" ), - - //this may need to be more specific as we use data-rel more - role = $link.attr( "data-" + $.mobile.ns + "rel" ) || undefined; - - $.mobile.changePage( href, { transition: transition, reverse: reverse, role: role } ); - event.preventDefault(); - }); - - //hashchange event handler - $window.bind( "hashchange", function( e, triggered ) { - //find first page via hash - var to = path.stripHash( location.hash ), - //transition is false if it's the first page, undefined otherwise (and may be overridden by default) - transition = $.mobile.urlHistory.stack.length === 0 ? "none" : undefined; - - //if listening is disabled (either globally or temporarily), or it's a dialog hash - if( !$.mobile.hashListeningEnabled || urlHistory.ignoreNextHashChange ) { - urlHistory.ignoreNextHashChange = false; - return; - } - - // special case for dialogs - if( urlHistory.stack.length > 1 && - to.indexOf( dialogHashKey ) > -1 ) { - - // If current active page is not a dialog skip the dialog and continue - // in the same direction - if(!$.mobile.activePage.is( ".ui-dialog" )) { - //determine if we're heading forward or backward and continue accordingly past - //the current dialog - urlHistory.directHashChange({ - currentUrl: to, - isBack: function() { window.history.back(); }, - isForward: function() { window.history.forward(); } - }); - - // prevent changepage - return; - } else { - var setTo = function() { to = $.mobile.urlHistory.getActive().page; }; - // if the current active page is a dialog and we're navigating - // to a dialog use the dialog objected saved in the stack - urlHistory.directHashChange({ currentUrl: to, isBack: setTo, isForward: setTo }); - } - } - - //if to is defined, load it - if ( to ) { - to = ( typeof to === "string" && !path.isPath( to ) ) ? ( '#' + to ) : to; - $.mobile.changePage( to, { transition: transition, changeHash: false, fromHashChange: true } ); - } - //there's no hash, go to the first page in the dom - else { - $.mobile.changePage( $.mobile.firstPage, { transition: transition, changeHash: false, fromHashChange: true } ); - } - }); - //set page min-heights to be device specific - $( document ).bind( "pageshow", resetActivePageHeight ); - $( window ).bind( "throttledresize", resetActivePageHeight ); + + //The following event bindings should be bound after mobileinit has been triggered + //the following function is called in the init file + $.mobile._registerInternalEvents = function(){ + + //add active state on vclick + $( document ).bind( "vclick", function( event ) { + var link = findClosestLink( event.target ); + if ( link ) { + if ( path.parseUrl( link.getAttribute( "href" ) || "#" ).hash !== "#" ) { + $( link ).closest( ".ui-btn" ).not( ".ui-disabled" ).addClass( $.mobile.activeBtnClass ); + $( "." + $.mobile.activePageClass + " .ui-btn" ).not( link ).blur(); + } + } + }); + + // click routing - direct to HTTP or Ajax, accordingly + // TODO: most of the time, vclick will be all we need for fastClick bulletproofing. + // However, it seems that in Android 2.1, a click event + // will occasionally arrive independently of the bound vclick + // binding to click as well seems to help in this edge case + // we'll dig into this further in the next release cycle + $( document ).bind( $.mobile.useFastClick ? "vclick click" : "click", function( event ) { + var link = findClosestLink( event.target ); + if ( !link ) { + return; + } + + var $link = $( link ), + //remove active link class if external (then it won't be there if you come back) + httpCleanup = function(){ + window.setTimeout( function() { removeActiveLinkClass( true ); }, 200 ); + }; + + //if there's a data-rel=back attr, go back in history + if( $link.is( ":jqmData(rel='back')" ) ) { + window.history.back(); + return false; + } + + //if ajax is disabled, exit early + if( !$.mobile.ajaxEnabled ){ + httpCleanup(); + //use default click handling + return; + } + + var baseUrl = getClosestBaseUrl( $link ), + + //get href, if defined, otherwise default to empty hash + href = path.makeUrlAbsolute( $link.attr( "href" ) || "#", baseUrl ); + + // XXX_jblas: Ideally links to application pages should be specified as + // an url to the application document with a hash that is either + // the site relative path or id to the page. But some of the + // internal code that dynamically generates sub-pages for nested + // lists and select dialogs, just write a hash in the link they + // create. This means the actual URL path is based on whatever + // the current value of the base tag is at the time this code + // is called. For now we are just assuming that any url with a + // hash in it is an application page reference. + if ( href.search( "#" ) != -1 ) { + href = href.replace( /[^#]*#/, "" ); + if ( !href ) { + //link was an empty hash meant purely + //for interaction, so we ignore it. + event.preventDefault(); + return; + } else if ( path.isPath( href ) ) { + //we have apath so make it the href we want to load. + href = path.makeUrlAbsolute( href, baseUrl ); + } else { + //we have a simple id so use the documentUrl as its base. + href = path.makeUrlAbsolute( "#" + href, documentUrl.hrefNoHash ); + } + } + + // Should we handle this link, or let the browser deal with it? + var useDefaultUrlHandling = $link.is( "[rel='external']" ) || $link.is( ":jqmData(ajax='false')" ) || $link.is( "[target]" ), + + // Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR + // requests if the document doing the request was loaded via the file:// protocol. + // This is usually to allow the application to "phone home" and fetch app specific + // data. We normally let the browser handle external/cross-domain urls, but if the + // allowCrossDomainPages option is true, we will allow cross-domain http/https + // requests to go through our page loading logic. + isCrossDomainPageLoad = ( $.mobile.allowCrossDomainPages && documentUrl.protocol === "file:" && href.search( /^https?:/ ) != -1 ), + + //check for protocol or rel and its not an embedded page + //TODO overlap in logic from isExternal, rel=external check should be + // moved into more comprehensive isExternalLink + isExternal = useDefaultUrlHandling || ( path.isExternal( href ) && !isCrossDomainPageLoad ); + + $activeClickedLink = $link.closest( ".ui-btn" ); + + if( isExternal ) { + httpCleanup(); + //use default click handling + return; + } + + //use ajax + var transition = $link.jqmData( "transition" ), + direction = $link.jqmData( "direction" ), + reverse = ( direction && direction === "reverse" ) || + // deprecated - remove by 1.0 + $link.jqmData( "back" ), + + //this may need to be more specific as we use data-rel more + role = $link.attr( "data-" + $.mobile.ns + "rel" ) || undefined; + + $.mobile.changePage( href, { transition: transition, reverse: reverse, role: role } ); + event.preventDefault(); + }); + + //hashchange event handler + $window.bind( "hashchange", function( e, triggered ) { + //find first page via hash + var to = path.stripHash( location.hash ), + //transition is false if it's the first page, undefined otherwise (and may be overridden by default) + transition = $.mobile.urlHistory.stack.length === 0 ? "none" : undefined; + + //if listening is disabled (either globally or temporarily), or it's a dialog hash + if( !$.mobile.hashListeningEnabled || urlHistory.ignoreNextHashChange ) { + urlHistory.ignoreNextHashChange = false; + return; + } + + // special case for dialogs + if( urlHistory.stack.length > 1 && + to.indexOf( dialogHashKey ) > -1 ) { + + // If current active page is not a dialog skip the dialog and continue + // in the same direction + if(!$.mobile.activePage.is( ".ui-dialog" )) { + //determine if we're heading forward or backward and continue accordingly past + //the current dialog + urlHistory.directHashChange({ + currentUrl: to, + isBack: function() { window.history.back(); }, + isForward: function() { window.history.forward(); } + }); + + // prevent changepage + return; + } else { + var setTo = function() { to = $.mobile.urlHistory.getActive().page; }; + // if the current active page is a dialog and we're navigating + // to a dialog use the dialog objected saved in the stack + urlHistory.directHashChange({ currentUrl: to, isBack: setTo, isForward: setTo }); + } + } + + //if to is defined, load it + if ( to ) { + to = ( typeof to === "string" && !path.isPath( to ) ) ? ( '#' + to ) : to; + $.mobile.changePage( to, { transition: transition, changeHash: false, fromHashChange: true } ); + } + //there's no hash, go to the first page in the dom + else { + $.mobile.changePage( $.mobile.firstPage, { transition: transition, changeHash: false, fromHashChange: true } ); + } + }); + + //set page min-heights to be device specific + $( document ).bind( "pageshow", resetActivePageHeight ); + $( window ).bind( "throttledresize", resetActivePageHeight ); + + };//_registerInternalEvents callback })( jQuery ); diff --git a/tests/unit/init/index.html b/tests/unit/init/index.html index 9e9a28f5..c185fc40 100644 --- a/tests/unit/init/index.html +++ b/tests/unit/init/index.html @@ -15,6 +15,7 @@ + diff --git a/tests/unit/init/init_core.js b/tests/unit/init/init_core.js index d47b8b02..d08411c8 100644 --- a/tests/unit/init/init_core.js +++ b/tests/unit/init/init_core.js @@ -72,6 +72,17 @@ ok($("html").hasClass("ui-mobile")); }); + + test( "useFastClick is configurable via mobileinit", function(){ + $(document).bind( "mobileinit", function(){ + $.mobile.useFastClick = false; + }); + + $.testHelper.reloadLib(libName); + + ok( $.mobile.useFastClick == false ); + $.mobile.useFastClick = true; + });