--- a/js/jquery.mobile-1.0rc1.js +++ b/js/jquery.mobile-1.0rc1.js @@ -1,5 +1,5 @@ /*! - * jQuery Mobile v1.0b3 + * jQuery Mobile v1.0rc1 * http://jquerymobile.com/ * * Copyright 2010, jQuery Project @@ -278,6 +278,16 @@ (function( $, undefined ) { $.widget( "mobile.widget", { + // decorate the parent _createWidget to trigger `widgetinit` for users + // who wish to do post post `widgetcreate` alterations/additions + // + // TODO create a pull request for jquery ui to trigger this event + // in the original _createWidget + _createWidget: function() { + $.Widget.prototype._createWidget.apply( this, arguments ); + this._trigger( 'init' ); + }, + _getCreateOptions: function() { var elem = this.element, @@ -301,7 +311,7 @@ })( jQuery ); /* -* jQuery Mobile Framework : resolution and CSS media query related helpers and behavior +* jQuery Mobile Framework : a workaround for window.matchMedia * Copyright (c) jQuery Project * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license @@ -421,9 +431,7 @@ touchOverflow: !!propExists( "overflowScrolling" ), boxShadow: !!propExists( "boxShadow" ) && !bb, scrollTop: ( "pageXOffset" in window || "scrollTop" in document.documentElement || "scrollTop" in fakeBody[ 0 ] ) && !webos, - dynamicBaseTag: baseTagTest(), - // TODO: This is a weak test. We may want to beef this up later. - eventCapture: "addEventListener" in document + dynamicBaseTag: baseTagTest() }); fakeBody.remove(); @@ -502,7 +510,7 @@ clickBlockList = [], blockMouseTriggers = false, blockTouchTriggers = false, - eventCaptureSupported = $.support.eventCapture, + eventCaptureSupported = "addEventListener" in document, $document = $( document ), nextTouchID = 1, lastTouchID = 0; @@ -540,6 +548,12 @@ prop = props[ --i ]; event[ prop ] = oe[ prop ]; } + } + + // make sure that if the mouse and click virtual events are generated + // without a .which one is defined + if ( t.search(/mouse(down|up)|click/) > -1 && !event.which ){ + event.which = 1; } if ( t.search(/^touch/) !== -1 ) { @@ -1809,11 +1823,19 @@ // Mobile version of data and removeData and hasData methods // ensures all data is set and retrieved using jQuery Mobile's data namespace $.fn.jqmData = function( prop, value ) { - return this.data( prop ? $.mobile.nsNormalize( prop ) : prop, value ); + var result; + if ( typeof prop != "undefined" ) { + result = this.data( prop ? $.mobile.nsNormalize( prop ) : prop, value ); + } + return result; }; $.jqmData = function( elem, prop, value ) { - return $.data( elem, $.mobile.nsNormalize( prop ), value ); + var result; + if ( typeof prop != "undefined" ) { + result = $.data( elem, prop ? $.mobile.nsNormalize( prop ) : prop, value ); + } + return result; }; $.fn.jqmRemoveData = function( prop ) { @@ -1824,8 +1846,32 @@ return $.removeData( elem, $.mobile.nsNormalize( prop ) ); }; - $.jqmHasData = function( elem, prop ) { - return $.hasData( elem, $.mobile.nsNormalize( prop ) ); + $.fn.removeWithDependents = function() { + $.removeWithDependents( this ); + }; + + $.removeWithDependents = function( elem ) { + var $elem = $( elem ); + + ( $elem.jqmData('dependents') || $() ).remove(); + $elem.remove(); + }; + + $.fn.addDependents = function( newDependents ) { + $.addDependents( $(this), newDependents ); + }; + + $.addDependents = function( elem, newDependents ) { + var dependents = $(elem).jqmData( 'dependents' ) || $(); + + $(elem).jqmData( 'dependents', $.merge(dependents, newDependents) ); + }; + + // note that this helper doesn't attempt to handle the callback + // or setting of an html elements text, its only purpose is + // to return the html encoded version of the text in all cases. (thus the name) + $.fn.getEncodedText = function() { + return $( "
" ).text( $(this).text() ).html(); }; // Monkey-patching Sizzle to filter the :jqmData selector @@ -1875,20 +1921,21 @@ // [2]: http://jblas:password@mycompany.com:8080/mail/inbox // [3]: http://jblas:password@mycompany.com:8080 // [4]: http: - // [5]: jblas:password@mycompany.com:8080 - // [6]: jblas:password - // [7]: jblas - // [8]: password - // [9]: mycompany.com:8080 - // [10]: mycompany.com - // [11]: 8080 - // [12]: /mail/inbox - // [13]: /mail/ - // [14]: inbox - // [15]: ?msg=1234&type=unread - // [16]: #msg-content + // [5]: // + // [6]: jblas:password@mycompany.com:8080 + // [7]: jblas:password + // [8]: jblas + // [9]: password + // [10]: mycompany.com:8080 + // [11]: mycompany.com + // [12]: 8080 + // [13]: /mail/inbox + // [14]: /mail/ + // [15]: inbox + // [16]: ?msg=1234&type=unread + // [17]: #msg-content // - urlParseRE: /^(((([^:\/#\?]+:)?(?:\/\/((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/, + urlParseRE: /^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/, //Parse a URL into a structure that allows easy access to //all of the URL components by name. @@ -1899,34 +1946,31 @@ return url; } - var u = url || "", - matches = path.urlParseRE.exec( url ), - results; - if ( matches ) { + var matches = path.urlParseRE.exec( url || "" ) || []; + // Create an object that allows the caller to access the sub-matches // by name. Note that IE returns an empty string instead of undefined, // like all other browsers do, so we normalize everything so its consistent // no matter what browser we're running on. - results = { - href: matches[0] || "", - hrefNoHash: matches[1] || "", - hrefNoSearch: matches[2] || "", - domain: matches[3] || "", - protocol: matches[4] || "", - authority: matches[5] || "", - username: matches[7] || "", - password: matches[8] || "", - host: matches[9] || "", - hostname: matches[10] || "", - port: matches[11] || "", - pathname: matches[12] || "", - directory: matches[13] || "", - filename: matches[14] || "", - search: matches[15] || "", - hash: matches[16] || "" + return { + href: matches[ 0 ] || "", + hrefNoHash: matches[ 1 ] || "", + hrefNoSearch: matches[ 2 ] || "", + domain: matches[ 3 ] || "", + protocol: matches[ 4 ] || "", + doubleSlash: matches[ 5 ] || "", + authority: matches[ 6 ] || "", + username: matches[ 8 ] || "", + password: matches[ 9 ] || "", + host: matches[ 10 ] || "", + hostname: matches[ 11 ] || "", + port: matches[ 12 ] || "", + pathname: matches[ 13 ] || "", + directory: matches[ 14 ] || "", + filename: matches[ 15 ] || "", + search: matches[ 16 ] || "", + hash: matches[ 17 ] || "" }; - } - return results || {}; }, //Turn relPath into an asbolute path. absPath is @@ -1986,13 +2030,14 @@ var relObj = path.parseUrl( relUrl ), absObj = path.parseUrl( absUrl ), protocol = relObj.protocol || absObj.protocol, + doubleSlash = relObj.protocol ? relObj.doubleSlash : ( relObj.doubleSlash || absObj.doubleSlash ); authority = relObj.authority || absObj.authority, hasPath = relObj.pathname !== "", pathname = path.makePathAbsolute( relObj.pathname || absObj.filename, absObj.pathname ), search = relObj.search || ( !hasPath && absObj.search ) || "", hash = relObj.hash; - return protocol + "//" + authority + pathname + search + hash; + return protocol + doubleSlash + authority + pathname + search + hash; }, //Add search (aka query) params to the specified url. @@ -2248,41 +2293,83 @@ $.mobile.changePage.apply( null, pageTransitionQueue.pop() ); } } - + // Save the last scroll distance per page, before it is hidden - var getLastScroll = (function( lastScrollEnabled ){ - return function(){ - if( !lastScrollEnabled ){ - lastScrollEnabled = true; - return; - } - - lastScrollEnabled = false; - - var active = $.mobile.urlHistory.getActive(), - activePage = $( ".ui-page-active" ), - scrollElem = $( window ), - touchOverflow = $.support.touchOverflow && $.mobile.touchOverflowEnabled; - - if( touchOverflow ){ - scrollElem = activePage.is( ".ui-native-fixed" ) ? activePage.find( ".ui-content" ) : activePage; - } - - if( active ){ - var lastScroll = scrollElem.scrollTop(); - - // Set active page's lastScroll prop. - // If the Y location we're scrolling to is less than minScrollBack, let it go. - active.lastScroll = lastScroll < $.mobile.minScrollBack ? $.mobile.defaultHomeScroll : lastScroll; - } - }; - })( true ); - - // to get last scroll, we need to get scrolltop before the page change - // using beforechangepage or popstate/hashchange (whichever comes first) - $( document ).bind( "beforechangepage", getLastScroll ); - $( window ).bind( $.support.pushState ? "popstate" : "hashchange", getLastScroll ); - + var setLastScrollEnabled = true, + firstScrollElem, getScrollElem, setLastScroll, delayedSetLastScroll; + + getScrollElem = function() { + var scrollElem = $window, activePage, + touchOverflow = $.support.touchOverflow && $.mobile.touchOverflowEnabled; + + if( touchOverflow ){ + activePage = $( ".ui-page-active" ); + scrollElem = activePage.is( ".ui-native-fixed" ) ? activePage.find( ".ui-content" ) : activePage; + } + + return scrollElem; + }; + + setLastScroll = function( scrollElem ) { + // this barrier prevents setting the scroll value based on the browser + // scrolling the window based on a hashchange + if( !setLastScrollEnabled ) { + return; + } + + var active = $.mobile.urlHistory.getActive(); + + if( active ) { + var lastScroll = scrollElem && scrollElem.scrollTop(); + + // Set active page's lastScroll prop. + // If the location we're scrolling to is less than minScrollBack, let it go. + active.lastScroll = lastScroll < $.mobile.minScrollBack ? $.mobile.defaultHomeScroll : lastScroll; + } + }; + + // bind to scrollstop to gather scroll position. The delay allows for the hashchange + // event to fire and disable scroll recording in the case where the browser scrolls + // to the hash targets location (sometimes the top of the page). once pagechange fires + // getLastScroll is again permitted to operate + delayedSetLastScroll = function() { + setTimeout( setLastScroll, 100, $(this) ); + }; + + // disable an scroll setting when a hashchange has been fired, this only works + // because the recording of the scroll position is delayed for 100ms after + // the browser might have changed the position because of the hashchange + $window.bind( $.support.pushState ? "popstate" : "hashchange", function() { + setLastScrollEnabled = false; + }); + + // handle initial hashchange from chrome :( + $window.one( $.support.pushState ? "popstate" : "hashchange", function() { + setLastScrollEnabled = true; + }); + + // wait until the mobile page container has been determined to bind to pagechange + $window.one( "pagecontainercreate", function(){ + // once the page has changed, re-enable the scroll recording + $.mobile.pageContainer.bind( "pagechange", function() { + var scrollElem = getScrollElem(); + + setLastScrollEnabled = true; + + // remove any binding that previously existed on the get scroll + // which may or may not be different than the scroll element determined for + // this page previously + scrollElem.unbind( "scrollstop", delayedSetLastScroll ); + + // determine and bind to the current scoll element which may be the window + // or in the case of touch overflow the element with touch overflow + scrollElem.bind( "scrollstop", delayedSetLastScroll ); + }); + }); + + // bind to scrollstop for the first page as "pagechange" won't be fired in that case + getScrollElem().bind( "scrollstop", delayedSetLastScroll ); + // Make the iOS clock quick-scroll work again if we're using native overflow scrolling /* if( $.support.touchOverflow ){ @@ -2304,7 +2391,7 @@ touchOverflow = $.support.touchOverflow && $.mobile.touchOverflowEnabled, toScroll = active.lastScroll || ( touchOverflow ? 0 : $.mobile.defaultHomeScroll ), screenHeight = getScreenHeight(); - + // Scroll to top, hide addr bar window.scrollTo( 0, $.mobile.defaultHomeScroll ); @@ -2312,22 +2399,22 @@ //trigger before show/hide events fromPage.data( "page" )._trigger( "beforehide", null, { nextPage: toPage } ); } - + if( !touchOverflow){ toPage.height( screenHeight + toScroll ); - } - + } + toPage.data( "page" )._trigger( "beforeshow", null, { prevPage: fromPage || $( "" ) } ); //clear page loader $.mobile.hidePageLoadingMsg(); - + if( touchOverflow && toScroll ){ - + toPage.addClass( "ui-mobile-pre-transition" ); // Send focus to page as it is now display: block reFocus( toPage ); - + //set page's scrollTop to remembered distance if( toPage.is( ".ui-native-fixed" ) ){ toPage.find( ".ui-content" ).scrollTop( toScroll ); @@ -2350,7 +2437,7 @@ // Send focus to the newly shown page reFocus( toPage ); } - + // Jump to top or prev scroll, sometimes on iOS the page has not rendered yet. if( !touchOverflow ){ $.mobile.silentScroll( toScroll ); @@ -2361,7 +2448,7 @@ if( !touchOverflow ){ fromPage.height( "" ); } - + fromPage.data( "page" )._trigger( "hide", null, { nextPage: toPage } ); } @@ -2383,11 +2470,15 @@ return pageMin; } - + $.mobile.getScreenHeight = getScreenHeight; //simply set the active page's minimum height to screen height, depending on orientation function resetActivePageHeight(){ + // Don't apply this height in touch overflow enabled mode + if( $.support.touchOverflow && $.mobile.touchOverflowEnabled ){ + return; + } $( "." + $.mobile.activePageClass ).css( "min-height", getScreenHeight() ); } @@ -2417,19 +2508,11 @@ } }; - //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 - //TODO: deprecate this one at 1.0 - $.mobile.urlstack = urlHistory.stack; //history stack $.mobile.urlHistory = urlHistory; @@ -2465,6 +2548,26 @@ //return the original document base url $.mobile.getDocumentBase = function(asParsedObject) { return asParsedObject ? $.extend( {}, documentBase ) : documentBase.href; + }; + + $.mobile._bindPageRemove = function() { + var page = $(this); + + // when dom caching is not enabled or the page is embedded bind to remove the page on hide + if( !page.data("page").options.domCache + && page.is(":jqmData(external-page='true')") ) { + + page.bind( 'pagehide.remove', function() { + var $this = $( this ), + prEvent = new $.Event( "pageremove" ); + + $this.trigger( prEvent ); + + if( !prEvent.isDefaultPrevented() ){ + $this.removeWithDependents(); + } + }); + } }; // Load a page into the DOM. @@ -2502,6 +2605,11 @@ if ( settings.data && settings.type === "get" ) { absUrl = path.addSearchParams( absUrl, settings.data ); settings.data = undefined; + } + + // If the caller is using a "post" request, reloadPage must be true + if( settings.data && settings.type === "post" ){ + settings.reloadPage = true; } // The absolute version of the URL minus any dialog/subpage params. @@ -2520,6 +2628,15 @@ // Check to see if the page already exists in the DOM. page = settings.pageContainer.children( ":jqmData(url='" + dataUrl + "')" ); + + // If we failed to find the page, check to see if the url is a + // reference to an embedded page. If so, it may have been dynamically + // injected by a developer, in which case it would be lacking a data-url + // attribute and in need of enhancement. + if ( page.length === 0 && !path.isPath( dataUrl ) ) { + page = settings.pageContainer.children( "#" + dataUrl ) + .attr( "data-" + $.mobile.ns + "url", dataUrl ) + } // If we failed to find a page in the DOM, check the URL to see if it // refers to the first page in the application. @@ -2543,6 +2660,18 @@ return deferred.promise(); } dupCachedPage = page; + } + + var mpc = settings.pageContainer, + pblEvent = new $.Event( "pagebeforeload" ), + triggerData = { url: url, absUrl: absUrl, dataUrl: dataUrl, deferred: deferred, options: settings }; + + // Let listeners know we're about to load a page. + mpc.trigger( pblEvent, triggerData ); + + // If the default behavior is prevented, stop here! + if( pblEvent.isDefaultPrevented() ){ + return deferred.promise(); } if ( settings.showLoadMsg ) { @@ -2581,7 +2710,7 @@ newPageTitle = html.match( /