--- 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( /]*>([^<]*)/ ) && RegExp.$1, // TODO handle dialogs again - pageElemRegex = new RegExp( ".*(<[^>]+\\bdata-" + $.mobile.ns + "role=[\"']?page[\"']?[^>]*>).*" ), + pageElemRegex = new RegExp( "(<[^>]+\\bdata-" + $.mobile.ns + "role=[\"']?page[\"']?[^>]*>)" ), dataUrlRegex = new RegExp( "\\bdata-" + $.mobile.ns + "url=[\"']?([^\"'>]*)[\"']?" ); @@ -2593,9 +2722,6 @@ && RegExp.$1 ) { url = fileUrl = path.getFilePath( RegExp.$1 ); } - else{ - - } if ( base ) { base.set( fileUrl ); @@ -2634,20 +2760,16 @@ } //append to page and enhance + // TODO taging a page with external to make sure that embedded pages aren't removed + // by the various page handling code is bad. Having page handling code in many + // places is bad. Solutions post 1.0 page .attr( "data-" + $.mobile.ns + "url", path.convertUrlToDataUrl( fileUrl ) ) + .attr( "data-" + $.mobile.ns + "external-page", true ) .appendTo( settings.pageContainer ); // wait for page creation to leverage options defined on widget - page.one('pagecreate', function(){ - - // when dom caching is not enabled bind to remove the page on hide - if( !page.data("page").options.domCache ){ - page.bind( "pagehide.remove", function(){ - $(this).remove(); - }); - } - }); + page.one( 'pagecreate', $.mobile._bindPageRemove ); enhancePage( page, settings.role ); @@ -2665,12 +2787,31 @@ hideMsg(); } + // Add the page reference to our triggerData. + triggerData.page = page; + + // Let listeners know the page loaded successfully. + settings.pageContainer.trigger( "pageload", triggerData ); + deferred.resolve( absUrl, options, page, dupCachedPage ); }, error: function() { //set base back to current path if( base ) { base.set( path.get() ); + } + + var plfEvent = new $.Event( "pageloadfailed" ); + + // Let listeners know the page load failed. + settings.pageContainer.trigger( plfEvent, triggerData ); + + // If the default behavior is prevented, stop here! + // Note that it is the responsibility of the listener/handler + // that called preventDefault(), to resolve/reject the + // deferred object within the triggerData. + if( plfEvent.isDefaultPrevented() ){ + return; } // Remove loading message. @@ -2709,43 +2850,6 @@ // Show a specific page in the page container. $.mobile.changePage = function( toPage, options ) { - // XXX: REMOVE_BEFORE_SHIPPING_1.0 - // This is temporary code that makes changePage() compatible with previous alpha versions. - if ( typeof options !== "object" ) { - var opts = null; - - // Map old-style call signature for form submit to the new options object format. - if ( typeof toPage === "object" && toPage.url && toPage.type ) { - opts = { - type: toPage.type, - data: toPage.data, - forcePageLoad: true - }; - toPage = toPage.url; - } - - // The arguments passed into the function need to be re-mapped - // to the new options object format. - var len = arguments.length; - if ( len > 1 ) { - var argNames = [ "transition", "reverse", "changeHash", "fromHashChange" ], i; - for ( i = 1; i < len; i++ ) { - var a = arguments[ i ]; - if ( typeof a !== "undefined" ) { - opts = opts || {}; - opts[ argNames[ i - 1 ] ] = a; - } - } - } - - // If an options object was created, then we know changePage() was called - // with an old signature. - if ( opts ) { - return $.mobile.changePage( toPage, opts ); - } - } - // XXX: REMOVE_BEFORE_SHIPPING_1.0 - // If we are in the midst of a transition, queue the current request. // We'll call changePage() once we're done with the current transition to // service the request. @@ -2769,8 +2873,6 @@ // Let listeners know we're about to change the current page. mpc.trigger( pbcEvent, triggerData ); - mpc.trigger( "beforechangepage", triggerData ); // XXX: DEPRECATED for 1.0 - // If the default behavior is prevented, stop here! if( pbcEvent.isDefaultPrevented() ){ return; @@ -2799,7 +2901,6 @@ $.mobile.changePage( newPage, options ); }) .fail(function( url, options ) { - // XXX_jblas: Fire off changepagefailed notificaiton. isPageTransitioning = false; //clear out the active button state @@ -2808,7 +2909,6 @@ //release transition lock so navigation is free again releasePageTransitionLock(); settings.pageContainer.trigger( "pagechangefailed", triggerData ); - settings.pageContainer.trigger( "changepagefailed", triggerData ); // XXX: DEPRECATED for 1.0 }); return; } @@ -2826,16 +2926,19 @@ pageTitle = document.title, isDialog = settings.role === "dialog" || toPage.jqmData( "role" ) === "dialog"; - // 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 toPage is not an array or object (those are allowed to be "same") - // - // XXX_jblas: We need to remove this at some point when we allow for transitions - // to the same page. - if( fromPage && fromPage[0] === toPage[0] ) { + // By default, we prevent changePage requests when the fromPage and toPage + // are the same element, but folks that generate content manually/dynamically + // and reuse pages want to be able to transition to the same page. To allow + // this, they will need to change the default value of allowSamePageTransition + // to true, *OR*, pass it in as an option when they manually call changePage(). + // It should be noted that our default transition animations assume that the + // formPage and toPage are different elements, so they may behave unexpectedly. + // It is up to the developer that turns on the allowSamePageTransitiona option + // to either turn off transition animations, or make sure that an appropriate + // animation transition is used. + if( fromPage && fromPage[0] === toPage[0] && !settings.allowSamePageTransition ) { isPageTransitioning = false; mpc.trigger( "pagechange", triggerData ); - mpc.trigger( "changepage", triggerData ); // XXX: DEPRECATED for 1.0 return; } @@ -2923,8 +3026,6 @@ // Let listeners know we're all done changing the current page. mpc.trigger( "pagechange", triggerData ); - - mpc.trigger( "changepage", triggerData ); // XXX: DEPRECATED for 1.0 }); }; @@ -2938,7 +3039,8 @@ pageContainer: undefined, showLoadMsg: true, //loading message shows by default when pages are being fetched during changePage dataUrl: undefined, - fromPage: undefined + fromPage: undefined, + allowSamePageTransition: false }; /* Event Bindings - hashchange, submit, and click */ @@ -3023,6 +3125,12 @@ //add active state on vclick $( document ).bind( "vclick", function( event ) { + // if this isn't a left click we don't care. Its important to note + // that when the virtual event is generated it will create + if ( event.which > 1 ){ + return; + } + var link = findClosestLink( event.target ); if ( link ) { if ( path.parseUrl( link.getAttribute( "href" ) || "#" ).hash !== "#" ) { @@ -3037,7 +3145,10 @@ // click routing - direct to HTTP or Ajax, accordingly $( document ).bind( "click", function( event ) { var link = findClosestLink( event.target ); - if ( !link ) { + + // If there is no link associated with the click or its not a left + // click we want to ignore the click + if ( !link || event.which > 1) { return; } @@ -3053,17 +3164,17 @@ return false; } + var baseUrl = getClosestBaseUrl( $link ), + + //get href, if defined, otherwise default to empty hash + href = path.makeUrlAbsolute( $link.attr( "href" ) || "#", baseUrl ); + //if ajax is disabled, exit early - if( !$.mobile.ajaxEnabled ){ + if( !$.mobile.ajaxEnabled && !path.isEmbeddedPage( href ) ){ 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 @@ -3172,7 +3283,7 @@ isForward: function() { window.history.forward(); } }); - // prevent changepage + // prevent changePage() return; } else { // if the current active page is a dialog and we're navigating @@ -3201,7 +3312,12 @@ //if to is defined, load it if ( to ) { - to = ( typeof to === "string" && !path.isPath( to ) ) ? ( '#' + to ) : to; + // At this point, 'to' can be one of 3 things, a cached page element from + // a history stack entry, an id, or site-relative/absolute URL. If 'to' is + // an id, we need to resolve it against the documentBase, not the location.href, + // since the hashchange could've been the result of a forward/backward navigation + // that crosses from an external page/dialog to an internal page/dialog. + to = ( typeof to === "string" && !path.isPath( to ) ) ? ( path.makeUrlAbsolute( '#' + to, documentBase ) ) : to; $.mobile.changePage( to, changePageOptions ); } else { //there's no hash, go to the first page in the dom @@ -3271,28 +3387,47 @@ return url; }, + // TODO sort out a single barrier to hashchange functionality + nextHashChangePrevented: function( value ) { + $.mobile.urlHistory.ignoreNextHashChange = value; + self.onHashChangeDisabled = value; + }, + // on hash change we want to clean up the url // NOTE this takes place *after* the vanilla navigation hash change // handling has taken place and set the state of the DOM onHashChange: function( e ) { - var href, state; - - self.hashchangeFired = true; - - // only replaceState when the hash doesn't represent an embeded page - if( $.mobile.path.isPath(location.hash) ) { - - // propulate the hash when its not available - state = self.state(); - - // make the hash abolute with the current href - href = $.mobile.path.makeUrlAbsolute( state.hash.replace("#", ""), location.href ); - + // disable this hash change + if( self.onHashChangeDisabled ){ + return; + } + + var href, state, + hash = location.hash, + isPath = $.mobile.path.isPath( hash ); + hash = isPath ? hash.replace( "#", "" ) : hash; + + // propulate the hash when its not available + state = self.state(); + + // make the hash abolute with the current href + href = $.mobile.path.makeUrlAbsolute( hash, location.href ); + + if ( isPath ) { href = self.resetUIKeys( href ); - - // replace the current url with the new href and store the state - history.replaceState( state, document.title, href ); - } + } + + // replace the current url with the new href and store the state + // Note that in some cases we might be replacing an url with the + // same url. We do this anyways because we need to make sure that + // all of our history entries have a state object associated with + // them. This allows us to work around the case where window.history.back() + // is called to transition from an external page to an embedded page. + // In that particular case, a hashchange event is *NOT* generated by the browser. + // Ensuring each history entry has a state object means that onPopState() + // will always trigger our hashchange callback even when a hashchange event + // is not fired. + history.replaceState( state, document.title, href ); }, // on popstate (ie back or forward) we need to replace the hash that was there previously @@ -3304,14 +3439,14 @@ // or forward popstate if( poppedState ) { // disable any hashchange triggered by the browser - $.mobile.urlHistory.ignoreNextHashChange = true; + self.nextHashChangePrevented( true ); // defer our manual hashchange until after the browser fired // version has come and gone setTimeout(function() { // make sure that the manual hash handling takes place - $.mobile.urlHistory.ignoreNextHashChange = false; - + self.nextHashChangePrevented( false ); + // change the page based on the hash $.mobile._handleHashChange( poppedState.hash ); }, 100); @@ -3356,7 +3491,7 @@ $to.add( $from ).removeClass( "out in reverse " + name ); - if ( $from ) { + if ( $from && $from[ 0 ] !== $to[ 0 ] ) { $from.removeClass( $.mobile.activePageClass ); } @@ -3427,10 +3562,13 @@ optType = o.degradeInputs[ type ] || "text"; if ( o.degradeInputs[ type ] ) { - $this.replaceWith( - $( "
" ).html( $this.clone() ).html() - .replace( /\s+type=["']?\w+['"]?/, " type=\"" + optType + "\" data-" + $.mobile.ns + "type=\"" + type + "\" " ) - ); + var html = $( "
" ).html( $this.clone() ).html(), + // In IE browsers, the type sometimes doesn't exist in the cloned markup, so we replace the closing tag instead + hasType = html.indexOf( " type=" ) > -1, + findstr = hasType ? /\s+type=["']?\w+['"]?/ : /\/?>/, + repstr = " type=\"" + optType + "\" data-" + $.mobile.ns + "type=\"" + type + "\"" + ( hasType ? "" : ">" ); + + $this.replaceWith( html.replace( findstr, repstr ) ); } }); @@ -3451,26 +3589,35 @@ initSelector : ":jqmData(role='dialog')" }, _create: function() { - var $el = this.element, - pageTheme = $el.attr( "class" ).match( /ui-body-[a-z]/ ); - + var self = this, + $el = this.element, + pageTheme = $el.attr( "class" ).match( /ui-body-[a-z]/ ), + headerCloseButton = $( ""+ this.options.closeBtnText + "" ); + if( pageTheme.length ){ $el.removeClass( pageTheme[ 0 ] ); - } - + } + $el.addClass( "ui-body-" + this.options.theme ); - + // Class the markup for dialog styling // Set aria role $el.attr( "role", "dialog" ) .addClass( "ui-dialog" ) .find( ":jqmData(role='header')" ) .addClass( "ui-corner-top ui-overlay-shadow" ) - .prepend( ""+ this.options.closeBtnText + "" ) + .prepend( headerCloseButton ) .end() .find( ":jqmData(role='content'),:jqmData(role='footer')" ) .last() .addClass( "ui-corner-bottom ui-overlay-shadow" ); + + // this must be an anonymous function so that select menu dialogs can replace + // the close method. This is a change from previously just defining data-rel=back + // on the button and letting nav handle it + headerCloseButton.bind( "vclick", function() { + self.close(); + }); /* bind events - clicks and submits should use the closing transition that the dialog opened with @@ -3613,6 +3760,7 @@ collapsed: true, heading: ">:header,>legend", theme: null, + contentTheme: null, iconTheme: "d", initSelector: ":jqmData(role='collapsible')" }, @@ -3620,16 +3768,31 @@ var $el = this.element, o = this.options, - collapsibleContain = $el.addClass( "ui-collapsible-contain" ), + collapsible = $el.addClass( "ui-collapsible" ), collapsibleHeading = $el.find( o.heading ).eq( 0 ), - collapsibleContent = collapsibleContain.wrapInner( "
" ).find( ".ui-collapsible-content" ), - collapsibleParent = $el.closest( ":jqmData(role='collapsible-set')" ).addClass( "ui-collapsible-set" ); + collapsibleContent = collapsible.wrapInner( "
" ).find( ".ui-collapsible-content" ), + collapsibleSet = $el.closest( ":jqmData(role='collapsible-set')" ).addClass( "ui-collapsible-set" ), + colllapsiblesInSet = collapsibleSet.children( ":jqmData(role='collapsible')" ); // Replace collapsibleHeading if it's a legend if ( collapsibleHeading.is( "legend" ) ) { collapsibleHeading = $( "
"+ collapsibleHeading.html() +"
" ).insertBefore( collapsibleHeading ); collapsibleHeading.next().remove(); } + + // If we are in a collapsible set + if ( collapsibleSet.length ) { + // Inherit the theme from collapsible-set + if ( !o.theme ) { + o.theme = collapsibleSet.jqmData( "theme" ); + } + // Inherit the content-theme from collapsible-set + if ( !o.contentTheme ) { + o.contentTheme = collapsibleSet.jqmData( "content-theme" ); + } + } + + collapsibleContent.addClass( ( o.contentTheme ) ? ( "ui-body-" + o.contentTheme ) : ""); collapsibleHeading //drop heading in before content @@ -3640,117 +3803,96 @@ .wrapInner( "" ) .find( "a:eq(0)" ) .buttonMarkup({ - shadow: !collapsibleParent.length, + shadow: false, corners: false, iconPos: "left", icon: "plus", theme: o.theme - }) - .find( ".ui-icon" ) - .removeAttr( "class" ) - .buttonMarkup({ - shadow: true, - corners: true, - iconPos: "notext", - icon: "plus", - theme: o.iconTheme + }); + + if ( !collapsibleSet.length ) { + collapsibleHeading + .find( "a:eq(0), .ui-btn-inner" ) + .addClass( "ui-corner-top ui-corner-bottom" ); + } else { + // If we are in a collapsible set + + // Initialize the collapsible set if it's not already initialized + if ( !collapsibleSet.jqmData( "collapsiblebound" ) ) { + + collapsibleSet + .jqmData( "collapsiblebound", true ) + .bind( "expand", function( event ) { + + $( event.target ) + .closest( ".ui-collapsible" ) + .siblings( ".ui-collapsible" ) + .trigger( "collapse" ); + }); - - if ( !collapsibleParent.length ) { + } + + colllapsiblesInSet.first() + .find( "a:eq(0)" ) + .addClass( "ui-corner-top" ) + .find( ".ui-btn-inner" ) + .addClass( "ui-corner-top" ); + + colllapsiblesInSet.last() + .jqmData( "collapsible-last", true ) + .find( "a:eq(0)" ) + .addClass( "ui-corner-bottom" ) + .find( ".ui-btn-inner" ) + .addClass( "ui-corner-bottom" ); + + + if ( collapsible.jqmData( "collapsible-last" ) ) { collapsibleHeading - .find( "a:eq(0)" ) - .addClass( "ui-corner-all" ) - .find( ".ui-btn-inner" ) - .addClass( "ui-corner-all" ); - } else { - if ( collapsibleContain.jqmData( "collapsible-last" ) ) { + .find( "a:eq(0), .ui-btn-inner" ) + .addClass( "ui-corner-bottom" ); + } + } + + //events + collapsible + .bind( "expand collapse", function( event ) { + if ( !event.isDefaultPrevented() ) { + + event.preventDefault(); + + var $this = $( this ), + isCollapse = ( event.type === "collapse" ), + contentTheme = o.contentTheme; + collapsibleHeading - .find( "a:eq(0), .ui-btn-inner" ) - .addClass( "ui-corner-bottom" ); - } - } - - //events - collapsibleContain - .bind( "collapse", function( event ) { - if ( ! event.isDefaultPrevented() && - $( event.target ).closest( ".ui-collapsible-contain" ).is( collapsibleContain ) ) { - - event.preventDefault(); - - collapsibleHeading - .addClass( "ui-collapsible-heading-collapsed" ) + .toggleClass( "ui-collapsible-heading-collapsed", isCollapse) .find( ".ui-collapsible-heading-status" ) .text( o.expandCueText ) .end() .find( ".ui-icon" ) - .removeClass( "ui-icon-minus" ) - .addClass( "ui-icon-plus" ); - - collapsibleContent.addClass( "ui-collapsible-content-collapsed" ).attr( "aria-hidden", true ); - - if ( collapsibleContain.jqmData( "collapsible-last" ) ) { + .toggleClass( "ui-icon-minus", !isCollapse ) + .toggleClass( "ui-icon-plus", isCollapse ); + + $this.toggleClass( "ui-collapsible-collapsed", isCollapse ); + collapsibleContent.toggleClass( "ui-collapsible-content-collapsed", isCollapse ).attr( "aria-hidden", isCollapse ); + + if ( contentTheme && ( !collapsibleSet.length || collapsible.jqmData( "collapsible-last" ) ) ) { collapsibleHeading .find( "a:eq(0), .ui-btn-inner" ) - .addClass( "ui-corner-bottom" ); - } - } - }) - .bind( "expand", function( event ) { - if ( !event.isDefaultPrevented() ) { - - event.preventDefault(); - - collapsibleHeading - .removeClass( "ui-collapsible-heading-collapsed" ) - .find( ".ui-collapsible-heading-status" ).text( o.collapseCueText ); - - collapsibleHeading.find( ".ui-icon" ).removeClass( "ui-icon-plus" ).addClass( "ui-icon-minus" ); - - collapsibleContent.removeClass( "ui-collapsible-content-collapsed" ).attr( "aria-hidden", false ); - - if ( collapsibleContain.jqmData( "collapsible-last" ) ) { - - collapsibleHeading - .find( "a:eq(0), .ui-btn-inner" ) - .removeClass( "ui-corner-bottom" ); + .toggleClass( "ui-corner-bottom", isCollapse ); + collapsibleContent.toggleClass( "ui-corner-bottom", !isCollapse ); } } }) .trigger( o.collapsed ? "collapse" : "expand" ); - // Close others in a set - if ( collapsibleParent.length && !collapsibleParent.jqmData( "collapsiblebound" ) ) { - - collapsibleParent - .jqmData( "collapsiblebound", true ) - .bind( "expand", function( event ) { - - $( event.target ) - .closest( ".ui-collapsible-contain" ) - .siblings( ".ui-collapsible-contain" ) - .trigger( "collapse" ); - - }); - - var set = collapsibleParent.children( ":jqmData(role='collapsible')" ); - - set.first() - .find( "a:eq(0)" ) - .addClass( "ui-corner-top" ) - .find( ".ui-btn-inner" ) - .addClass( "ui-corner-top" ); - - set.last().jqmData( "collapsible-last", true ); - } - collapsibleHeading - .bind( "vclick", function( event ) { + .bind( "click", function( event ) { var type = collapsibleHeading.is( ".ui-collapsible-heading-collapsed" ) ? "expand" : "collapse"; - collapsibleContain.trigger( type ); + collapsible.trigger( type ); event.preventDefault(); }); @@ -3965,7 +4107,7 @@ $li = this.element.children( "li" ); // at create time the li are not visible yet so we need to rely on .ui-screen-hidden $visibleli = create?$li.not( ".ui-screen-hidden" ):$li.filter( ":visible" ); - + this._removeCorners( $li ); // Select the first visible li element @@ -4092,7 +4234,7 @@ self._itemApply( $list, item ); } - + this._refreshCorners( create ); }, @@ -4155,8 +4297,12 @@ }).listview(); - //on pagehide, remove any nested pages along with the parent page, as long as they aren't active - if( hasSubPages && parentPage.data("page").options.domCache === false ){ + // on pagehide, remove any nested pages along with the parent page, as long as they aren't active + // and aren't embedded + if( hasSubPages && + parentPage.is( ":jqmData(external-page='true')" ) && + parentPage.data("page").options.domCache === false ) { + var newRemove = function( e, ui ){ var nextPage = ui.nextPage, npURL; @@ -4308,7 +4454,7 @@ }); })( jQuery );/* -* jQuery Mobile Framework : "fieldcontain" plugin - simple class additions to make form row separators +* jQuery Mobile Framework : "nojs" plugin - class to make elements hidden to A grade browsers * Copyright (c) jQuery Project * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license @@ -4538,7 +4684,9 @@ _create: function() { var $el = this.element, o = this.options, - type; + type, + name, + $buttonPlaceholder; // Add ARIA role this.button = $( "
" ) @@ -4555,25 +4703,27 @@ .insertBefore( $el ) .append( $el.addClass( "ui-btn-hidden" ) ); - // Add hidden input during submit type = $el.attr( "type" ); - - if ( type !== "button" && type !== "reset" ) { - - $el.bind( "vclick", function() { - - var $buttonPlaceholder = $( "", { - type: "hidden", - name: $el.attr( "name" ), - value: $el.attr( "value" ) - }) - .insertBefore( $el ); - - // Bind to doc to remove after submit handling - $( document ).submit(function(){ - $buttonPlaceholder.remove(); + name = $el.attr( "name" ); + + // Add hidden input during submit if input type="submit" has a name. + if ( type !== "button" && type !== "reset" && name ) { + $el.bind( "vclick", function() { + // Add hidden input if it doesn’t already exist. + if( $buttonPlaceholder === undefined ) { + $buttonPlaceholder = $( "", { + type: "hidden", + name: $el.attr( "name" ), + value: $el.attr( "value" ) + }) + .insertBefore( $el ); + + // Bind to doc to remove after submit handling + $( document ).submit(function(){ + $buttonPlaceholder.remove(); + }); + } }); - }); } this.refresh(); @@ -4893,13 +5043,17 @@ } if ( !preventInputUpdate ) { + var valueChanged = false; + // update control"s value if ( cType === "input" ) { + valueChanged = control.val() !== newval; control.val( newval ); } else { + valueChanged = control[ 0 ].selectedIndex !== newval; control[ 0 ].selectedIndex = newval; } - if ( !isfromControl ) { + if ( !isfromControl && valueChanged ) { control.trigger( "change" ); } } @@ -4941,7 +5095,7 @@ $.widget( "mobile.textinput", $.mobile.widget, { options: { theme: null, - initSelector: "input[type='text'], input[type='search'], :jqmData(type='search'), input[type='number'], :jqmData(type='number'), input[type='password'], input[type='email'], input[type='url'], input[type='tel'], textarea" + initSelector: "input[type='text'], input[type='search'], :jqmData(type='search'), input[type='number'], :jqmData(type='number'), input[type='password'], input[type='email'], input[type='url'], input[type='tel'], textarea, input:not([type])" }, _create: function() { @@ -4964,18 +5118,21 @@ input.addClass("ui-input-text ui-body-"+ o.theme ); focusedEl = input; - - // XXX: Temporary workaround for issue 785. Turn off autocorrect and - // autocomplete since the popup they use can't be dismissed by - // the user. Note that we test for the presence of the feature - // by looking for the autocorrect property on the input element. - if ( typeof input[0].autocorrect !== "undefined" ) { + + // XXX: Temporary workaround for issue 785 (Apple bug 8910589). + // Turn off autocorrect and autocomplete on non-iOS 5 devices + // since the popup they use can't be dismissed by the user. Note + // that we test for the presence of the feature by looking for + // the autocorrect property on the input element. We currently + // have no test for iOS 5 or newer so we're temporarily using + // the touchOverflow support flag for jQM 1.0. Yes, I feel dirty. - jblas + if ( typeof input[0].autocorrect !== "undefined" && !$.support.touchOverflow ) { // Set the attribute instead of the property just in case there // is code that attempts to make modifications via HTML. input[0].setAttribute( "autocorrect", "off" ); input[0].setAttribute( "autocomplete", "off" ); } - + //"search" input widget if ( input.is( "[type='search'],:jqmData(type='search')" ) ) { @@ -5056,11 +5213,11 @@ //auto self-init widgets $( document ).bind( "pagecreate create", function( e ){ - + $( $.mobile.textinput.prototype.options.initSelector, e.target ) .not( ":jqmData(role='none'), :jqmData(role='nojs')" ) .textinput(); - + }); })( jQuery ); @@ -5079,13 +5236,13 @@ label = widget.label, thisPage = widget.select.closest( ".ui-page" ), screen = $( "
", {"class": "ui-selectmenu-screen ui-screen-hidden"} ).appendTo( thisPage ), - selectOptions = widget.select.find("option"), + selectOptions = widget._selectOptions(), isMultiple = widget.isMultiple = widget.select[ 0 ].multiple, buttonId = selectID + "-button", menuId = selectID + "-menu", menuPage = $( "
" + "
" + - "
" + label.text() + "
"+ + "
" + label.getEncodedText() + "
"+ "
"+ "
"+ "
" ).appendTo( $.mobile.pageContainer ).page(), @@ -5174,7 +5331,7 @@ // index of option tag to be selected var oldIndex = self.select[ 0 ].selectedIndex, newIndex = self.list.find( "li:not(.ui-li-divider)" ).index( this ), - option = self.selectOptions.eq( newIndex )[ 0 ]; + option = self._selectOptions().eq( newIndex )[ 0 ]; // toggle selected status on the tag for multi selects option.selected = self.isMultiple ? !option.selected : true; @@ -5252,6 +5409,20 @@ self.menuPage.bind( "pagehide", function() { self.list.appendTo( self.listbox ); self._focusButton(); + + // TODO centralize page removal binding / handling in the page plugin. + // Suggestion from @jblas to do refcounting + // + // TODO extremely confusing dependency on the open method where the pagehide.remove + // bindings are stripped to prevent the parent page from disappearing. The way + // we're keeping pages in the DOM right now sucks + // + // rebind the page remove that was unbound in the open function + // to allow for the parent page removal from actions other than the use + // of a dialog sized custom select + // + // doing this here provides for the back button on the custom select dialog + $.mobile._bindPageRemove.call( self.thisPage ); }); // Events on "screen" overlay @@ -5266,18 +5437,32 @@ return false; } }); + + // track this dependency so that when the parent page + // is removed on pagehide it will also remove the menupage + self.thisPage.addDependents( this.menuPage ); }, - refresh: function( forceRebuild ){ + _isRebuildRequired: function() { + var list = this.list.find( "li" ), + options = this._selectOptions(); + + // TODO exceedingly naive method to determine difference + // ignores value changes etc in favor of a forcedRebuild + // from the user in the refresh method + return options.text() !== list.text(); + }, + + refresh: function( forceRebuild , foo ){ var self = this, select = this.element, isMultiple = this.isMultiple, - options = this.selectOptions = select.find( "option" ), + options = this._selectOptions(), selected = this.selected(), // return an array of all selected index's indicies = this.selectedIndices(); - if ( forceRebuild || select[0].options.length != self.list.find( "li" ).length ) { + if ( forceRebuild || this._isRebuildRequired() ) { self._buildList(); } @@ -5313,18 +5498,6 @@ var self = this; if ( self.menuType == "page" ) { - // TODO centralize page removal binding / handling in the page plugin. - // Suggestion from @jblas to do refcounting - // - // rebind the page remove that was unbound in the open function - // to allow for the parent page removal from actions other than the use - // of a dialog sized custom select - if( !self.thisPage.data("page").options.domCache ){ - self.thisPage.bind( "pagehide.remove", function() { - $(self).remove(); - }); - } - // doesn't solve the possible issue with calling change page // where the objects don't define data urls which prevents dialog key // stripping - changePage has incoming refactor @@ -5348,7 +5521,10 @@ var self = this, menuHeight = self.list.parent().outerHeight(), menuWidth = self.list.parent().outerWidth(), - scrollTop = $( window ).scrollTop(), + activePage = $( ".ui-page-active" ), + tOverflow = $.support.touchOverflow && $.mobile.touchOverflowEnabled, + tScrollElem = activePage.is( ".ui-native-fixed" ) ? activePage.find( ".ui-content" ) : activePage; + scrollTop = tOverflow ? tScrollElem.scrollTop() : $( window ).scrollTop(), btnOffset = self.button.offset().top, screenHeight = window.innerHeight, screenWidth = window.innerWidth; @@ -5461,7 +5637,7 @@ self.select.find( "option" ).each( function( i ) { var $this = $( this ), $parent = $this.parent(), - text = $this.text(), + text = $this.getEncodedText(), anchor = ""+ text +"", classes = [], extraAttrs = []; @@ -5572,6 +5748,10 @@ }, _theme: function(){ + if ( this.options.theme ){ + return this.options.theme; + } + var themedParent, theme; // if no theme is defined, try to find closest theme container // TODO move to core as something like findCurrentTheme @@ -5597,6 +5777,10 @@ }, 40); }, + _selectOptions: function() { + return this.select.find( "option" ); + }, + // setup items that are generally necessary for select menu extension _preExtension: function(){ this.select = this.element.wrap( "
" ); @@ -5604,7 +5788,6 @@ this.label = $( "label[for='"+ this.selectID +"']" ).addClass( "ui-select" ); this.isMultiple = this.select[ 0 ].multiple; this.options.theme = this._theme(); - this.selectOptions = this.select.find( "option" ); }, _create: function() { @@ -5653,7 +5836,7 @@ this.buttonCount = $( "" ) .addClass( "ui-li-count ui-btn-up-c ui-btn-corner-all" ) .hide() - .appendTo( button ); + .appendTo( button.addClass('ui-li-has-count') ); } // Disable if specified @@ -5695,14 +5878,14 @@ }, selected: function() { - return this.selectOptions.filter( ":selected" ); + return this._selectOptions().filter( ":selected" ); }, selectedIndices: function() { var self = this; return this.selected().map( function() { - return self.selectOptions.index( this ); + return self._selectOptions().index( this ); }).get(); }, @@ -5733,6 +5916,11 @@ this.setButtonText(); this.setButtonCount(); }, + + // open and close preserved in native selects + // to simplify users code when looping over selects + open: $.noop, + close: $.noop, disable: function() { this._setDisabled( true ); @@ -5763,7 +5951,12 @@ $.fn.buttonMarkup = function( options ) { return this.each( function() { var el = $( this ), - o = $.extend( {}, $.fn.buttonMarkup.defaults, el.jqmData(), options ), + o = $.extend( {}, $.fn.buttonMarkup.defaults, { + icon: el.jqmData( "icon" ), + iconpos: el.jqmData( "iconpos" ), + theme: el.jqmData( "theme" ), + inline: el.jqmData( "inline" ) + }, options ), // Classes Defined innerClass = "ui-btn-inner", @@ -5831,6 +6024,7 @@ corners: true, shadow: true, iconshadow: true, + inline: false, wrapperEls: "span" }; @@ -5957,7 +6151,7 @@ }); })(jQuery);/* -* jQuery Mobile Framework : "fieldcontain" plugin - simple class additions to make form row separators +* jQuery Mobile Framework : "links" plugin - simple class additions for links * Copyright (c) jQuery Project * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license @@ -5992,7 +6186,7 @@ $.fn.fixHeaderFooter = function( options ) { - if ( !$.support.scrollTop ) { + if ( !$.support.scrollTop || ( $.support.touchOverflow && $.mobile.touchOverflowEnabled ) ) { return this; } @@ -6014,7 +6208,7 @@ // single controller for all showing,hiding,toggling $.mobile.fixedToolbars = (function() { - if ( !$.support.scrollTop || $.support.touchOverflow ) { + if ( !$.support.scrollTop || ( $.support.touchOverflow && $.mobile.touchOverflowEnabled ) ) { return; } @@ -6328,9 +6522,6 @@ }; })(); -// TODO - Deprecated namepace on $. Remove in a later release -$.fixedToolbars = $.mobile.fixedToolbars; - //auto self-init widgets $( document ).bind( "pagecreate create", function( event ) { @@ -6338,7 +6529,7 @@ $( event.target ).each(function() { - if ( !$.support.scrollTop || $.support.touchOverflow ) { + if ( !$.support.scrollTop || ( $.support.touchOverflow && $.mobile.touchOverflowEnabled ) ) { return this; } @@ -6369,7 +6560,11 @@ (function( $, undefined ) { +// Enable touch overflow scrolling when it's natively supported $.mobile.touchOverflowEnabled = false; + +// Enabled zoom when touch overflow is enabled. Can cause usability issues, unfortunately +$.mobile.touchOverflowZoomEnabled = false; $( document ).bind( "pagecreate", function( event ) { if( $.support.touchOverflow && $.mobile.touchOverflowEnabled ){ @@ -6419,108 +6614,7 @@ }); })( jQuery ); -/* -* jQuery Mobile Framework : resolution and CSS media query related helpers and behavior -* Copyright (c) jQuery Project -* Dual licensed under the MIT or GPL Version 2 licenses. -* http://jquery.org/license -*/ -(function( $, undefined ) { - -var $window = $( window ), - $html = $( "html" ), - - //media-query-like width breakpoints, which are translated to classes on the html element - resolutionBreakpoints = [ 320, 480, 768, 1024 ]; - -/* - private function for adding/removing breakpoint classes to HTML element for faux media-query support - It does not require media query support, instead using JS to detect screen width > cross-browser support - This function is called on orientationchange, resize, and mobileinit, and is bound via the 'htmlclass' event namespace -*/ -function detectResolutionBreakpoints() { - var currWidth = $window.width(), - minPrefix = "min-width-", - maxPrefix = "max-width-", - minBreakpoints = [], - maxBreakpoints = [], - unit = "px", - breakpointClasses; - - $html.removeClass( minPrefix + resolutionBreakpoints.join(unit + " " + minPrefix) + unit + " " + - maxPrefix + resolutionBreakpoints.join( unit + " " + maxPrefix) + unit ); - - $.each( resolutionBreakpoints, function( i, breakPoint ) { - if( currWidth >= breakPoint ) { - minBreakpoints.push( minPrefix + breakPoint + unit ); - } - if( currWidth <= breakPoint ) { - maxBreakpoints.push( maxPrefix + breakPoint + unit ); - } - }); - - if ( minBreakpoints.length ) { - breakpointClasses = minBreakpoints.join(" "); - } - if ( maxBreakpoints.length ) { - breakpointClasses += " " + maxBreakpoints.join(" "); - } - - $html.addClass( breakpointClasses ); -}; - -/* $.mobile.addResolutionBreakpoints method: - pass either a number or an array of numbers and they'll be added to the min/max breakpoint classes - Examples: - $.mobile.addResolutionBreakpoints( 500 ); - $.mobile.addResolutionBreakpoints( [500, 1200] ); -*/ -$.mobile.addResolutionBreakpoints = function( newbps ) { - if( $.type( newbps ) === "array" ){ - resolutionBreakpoints = resolutionBreakpoints.concat( newbps ); - } else { - resolutionBreakpoints.push( newbps ); - } - - resolutionBreakpoints.sort(function( a, b ) { - return a - b; - }); - - detectResolutionBreakpoints(); -}; - -/* on mobileinit, add classes to HTML element - and set handlers to update those on orientationchange and resize -*/ -$( document ).bind( "mobileinit.htmlclass", function() { - // bind to orientationchange and resize - // to add classes to HTML element for min/max breakpoints and orientation - - var ev = $.support.orientation; - - $window.bind( "orientationchange.htmlclass throttledresize.htmlclass", function( event ) { - - // add orientation class to HTML element on flip/resize. - if ( event.orientation ) { - $html.removeClass( "portrait landscape" ).addClass( event.orientation ); - } - - // add classes to HTML element for min/max breakpoints - detectResolutionBreakpoints(); - }); -}); - -/* Manually trigger an orientationchange event when the dom ready event fires. - This will ensure that any viewport meta tag that may have been injected - has taken effect already, allowing us to properly calculate the width of the - document. -*/ -$(function() { - //trigger event manually - $window.trigger( "orientationchange.htmlclass" ); -}); - -})(jQuery);/*! +/*! * jQuery Mobile v@VERSION * http://jquerymobile.com/ * @@ -6582,15 +6676,6 @@ $html.removeClass( "ui-loading" ); }, - // XXX: deprecate for 1.0 - pageLoading: function ( done ) { - if ( done ) { - $.mobile.hidePageLoadingMsg(); - } else { - $.mobile.showPageLoadingMsg(); - } - }, - // find and enhance the pages in the dom and transition to the first page. initializePage: function() { // find present pages @@ -6617,6 +6702,10 @@ // define page container $.mobile.pageContainer = $pages.first().parent().addClass( "ui-mobile-viewport" ); + // alert listeners that the pagecontainer has been determined for binding + // to events triggered on it + $window.trigger( "pagecontainercreate" ); + // cue page loading message $.mobile.showPageLoadingMsg(); @@ -6630,6 +6719,24 @@ } } }); + + // This function injects a meta viewport tag to prevent scaling. Off by default, on by default when touchOverflow scrolling is enabled + function disableZoom() { + var cont = "user-scalable=no", + meta = $( "meta[name='viewport']" ); + + if( meta.length ){ + meta.attr( "content", meta.attr( "content" ) + ", " + cont ); + } + else{ + $( "head" ).prepend( "", { "name": "viewport", "content": cont } ); + } + } + + // if touch-overflow is enabled, disable user scaling, as it creates usability issues + if( $.support.touchOverflow && $.mobile.touchOverflowEnabled && !$.mobile.touchOverflowZoomEnabled ){ + disableZoom(); + } // initialize events now, after mobileinit has occurred $.mobile._registerInternalEvents();