--- a/js/jquery.mobile-1.0b3.js +++ b/js/jquery.mobile-1.0b3.js @@ -1,5 +1,5 @@ /*! - * jQuery Mobile v1.0b2 + * jQuery Mobile v1.0b3 * http://jquerymobile.com/ * * Copyright 2010, jQuery Project @@ -356,7 +356,7 @@ var fakeBody = $( "<body>" ).prependTo( "html" ), fbCSS = fakeBody[ 0 ].style, - vendors = [ "webkit", "moz", "o" ], + vendors = [ "Webkit", "Moz", "O" ], webos = "palmGetResource" in window, //only used to rule out scrollTop bb = window.blackberry; //only used to rule out box shadow, as it's filled opaque on BB @@ -366,7 +366,7 @@ props = ( prop + " " + vendors.join( uc_prop + " " ) + uc_prop ).split( " " ); for ( var v in props ){ - if ( fbCSS[ v ] !== undefined ) { + if ( fbCSS[ props[ v ] ] !== undefined ) { return true; } } @@ -415,9 +415,10 @@ orientation: "orientation" in window, touch: "ontouchend" in document, cssTransitions: "WebKitTransitionEvent" in window, - pushState: !!history.pushState, + pushState: "pushState" in history && "replaceState" in history, mediaquery: $.mobile.media( "only all" ), cssPseudoElement: !!propExists( "content" ), + touchOverflow: !!propExists( "overflowScrolling" ), boxShadow: !!propExists( "boxShadow" ) && !bb, scrollTop: ( "pageXOffset" in window || "scrollTop" in document.documentElement || "scrollTop" in fakeBody[ 0 ] ) && !webos, dynamicBaseTag: baseTagTest(), @@ -631,8 +632,7 @@ } function triggerVirtualEvent( eventType, event, flags ) { - var defaultPrevented = false, - ve; + var ve; if ( ( flags && flags[ eventType ] ) || ( !flags && getClosestElementWithVirtualBinding( event.target, eventType ) ) ) { @@ -640,18 +640,27 @@ ve = createVirtualEvent( event, eventType ); $( event.target).trigger( ve ); - - defaultPrevented = ve.isDefaultPrevented(); } - return defaultPrevented; + return ve; } function mouseEventCallback( event ) { var touchID = $.data(event.target, touchTargetPropertyName); if ( !blockMouseTriggers && ( !lastTouchID || lastTouchID !== touchID ) ){ - triggerVirtualEvent( "v" + event.type, event ); + var ve = triggerVirtualEvent( "v" + event.type, event ); + if ( ve ) { + if ( ve.isDefaultPrevented() ) { + event.preventDefault(); + } + if ( ve.isPropagationStopped() ) { + event.stopPropagation(); + } + if ( ve.isImmediatePropagationStopped() ) { + event.stopImmediatePropagation(); + } + } } } @@ -731,7 +740,8 @@ triggerVirtualEvent( "vmouseup", event, flags ); if ( !didScroll ) { - if ( triggerVirtualEvent( "vclick", event, flags ) ) { + var ve = triggerVirtualEvent( "vclick", event, flags ); + if ( ve && ve.isDefaultPrevented() ) { // The target of the mouse events that follow the touchend // event don't necessarily match the target used during the // touch. This means we need to rely on coordinates for blocking @@ -1028,16 +1038,19 @@ return false; } - var touching = true, - origTarget = event.target, + var origTarget = event.target, origEvent = event.originalEvent, timer; + function clearTapTimer() { + clearTimeout( timer ); + } + function clearTapHandlers() { - touching = false; - clearTimeout(timer); + clearTapTimer(); $this.unbind( "vclick", clickHandler ) + .unbind( "vmouseup", clearTapTimer ) .unbind( "vmousecancel", clearTapHandlers ); } @@ -1052,12 +1065,11 @@ } $this.bind( "vmousecancel", clearTapHandlers ) + .bind( "vmouseup", clearTapTimer ) .bind( "vclick", clickHandler ); timer = setTimeout(function() { - if ( touching ) { - triggerCustomEvent( thisObject, "taphold", event ); - } + triggerCustomEvent( thisObject, "taphold", $.Event( "taphold" ) ); }, 750 ); }); } @@ -1657,14 +1669,12 @@ }, _create: function() { - var $elem = this.element, - o = this.options; - - if ( this._trigger( "beforeCreate" ) === false ) { - return; - } - - $elem.addClass( "ui-page ui-body-" + o.theme ); + + this._trigger( "beforecreate" ); + + this.element + .attr( "tabindex", "0" ) + .addClass( "ui-page ui-body-" + this.options.theme ); } }); @@ -1707,7 +1717,7 @@ defaultPageTransition: "slide", // Minimum scroll distance that will be remembered when returning to a page - minScrollBack: screen.height / 2, + minScrollBack: 250, // Set default dialog transition - 'none' for no transitions defaultDialogTransition: "pop", @@ -1718,9 +1728,11 @@ // Error response message - appears when an Ajax page request fails pageLoadErrorMessage: "Error Loading Page", - + //automatically initialize the DOM when it's ready autoInitializePage: true, + + pushStateEnabled: true, // Support conditions that must be met in order to proceed // default enhanced qualifications are media query support OR IE 7+ @@ -1835,6 +1847,7 @@ return $.find( expr, null, null, [ node ] ).length > 0; }; })( jQuery, this ); + /* * jQuery Mobile Framework : core utilities for auto ajax navigation, base tag mgmt, * Copyright (c) jQuery Project @@ -1875,7 +1888,7 @@ // [15]: ?msg=1234&type=unread // [16]: #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. @@ -2053,6 +2066,26 @@ return ( /^(:?\w+:)/ ).test( url ); }, + //check if the specified url refers to the first page in the main application document. + isFirstPageUrl: function( url ) { + // We only deal with absolute paths. + var u = path.parseUrl( path.makeUrlAbsolute( url, documentBase ) ), + + // Does the url have the same path as the document? + samePath = u.hrefNoHash === documentUrl.hrefNoHash || ( documentBaseDiffers && u.hrefNoHash === documentBase.hrefNoHash ), + + // Get the first page element. + fp = $.mobile.firstPage, + + // Get the id of the first page element if it has one. + fpId = fp && fp[0] ? fp[0].id : undefined; + + // The url refers to the first page if the path matches the document and + // it either has no hash value, or the hash is exactly equal to the id of the + // first page element. + return samePath && ( !u.hash || u.hash === "#" || ( fpId && u.hash.replace( /^#/, "" ) === fpId ) ); + }, + isEmbeddedPage: function( url ) { var u = path.parseUrl( url ); @@ -2074,7 +2107,7 @@ //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. + // Array of pages that are visited during a single page load. // Each has a url and optional transition, title, and pageUrl (which represents the file path, in cases where URL is obscured, such as dialogs) stack: [], @@ -2095,13 +2128,13 @@ }, // addNew is used whenever a new page is added - addNew: function( url, transition, title, pageUrl ) { + addNew: function( url, transition, title, pageUrl, role ) { //if there's forward history, wipe it if( urlHistory.getNext() ) { urlHistory.clearForward(); } - urlHistory.stack.push( {url : url, transition: transition, title: title, pageUrl: pageUrl } ); + urlHistory.stack.push( {url : url, transition: transition, title: title, pageUrl: pageUrl, role: role } ); urlHistory.activeIndex = urlHistory.stack.length - 1; }, @@ -2112,7 +2145,7 @@ }, directHashChange: function( opts ) { - var back , forward, newActiveIndex; + var back , forward, newActiveIndex, prev = this.getActive(); // check if url isp in history and if it's ahead or behind current page $.each( urlHistory.stack, function( i, historyEntry ) { @@ -2130,9 +2163,9 @@ this.activeIndex = newActiveIndex !== undefined ? newActiveIndex : this.activeIndex; if( back ) { - opts.isBack(); + ( opts.either || opts.isBack )( true ); } else if( forward ) { - opts.isForward(); + ( opts.either || opts.isForward )( false ); } }, @@ -2191,20 +2224,13 @@ //direct focus to the page title, or otherwise first focusable element function reFocus( page ) { - var lastClicked = page.jqmData( "lastClicked" ); - - if( lastClicked && lastClicked.length ) { - lastClicked.focus(); - } - else { - var pageTitle = page.find( ".ui-title:eq(0)" ); - - if( pageTitle.length ) { - pageTitle.focus(); - } - else{ - page.find( focusable ).eq( 0 ).focus(); - } + var pageTitle = page.find( ".ui-title:eq(0)" ); + + if( pageTitle.length ) { + pageTitle.focus(); + } + else{ + page.focus(); } } @@ -2222,41 +2248,94 @@ $.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 ); + + // Make the iOS clock quick-scroll work again if we're using native overflow scrolling + /* + if( $.support.touchOverflow ){ + if( $.mobile.touchOverflowEnabled ){ + $( window ).bind( "scrollstop", function(){ + if( $( this ).scrollTop() === 0 ){ + $.mobile.activePage.scrollTop( 0 ); + } + }); + } + } + */ //function for transitioning between two existing pages function transitionPages( toPage, fromPage, transition, reverse ) { //get current scroll distance - var currScroll = $.support.scrollTop ? $window.scrollTop() : true, - toScroll = toPage.data( "lastScroll" ) || $.mobile.defaultHomeScroll, + var active = $.mobile.urlHistory.getActive(), + touchOverflow = $.support.touchOverflow && $.mobile.touchOverflowEnabled, + toScroll = active.lastScroll || ( touchOverflow ? 0 : $.mobile.defaultHomeScroll ), screenHeight = getScreenHeight(); - - //if scrolled down, scroll to top - if( currScroll ){ - window.scrollTo( 0, $.mobile.defaultHomeScroll ); - } - - //if the Y location we're scrolling to is less than 10px, let it go for sake of smoothness - if( toScroll < $.mobile.minScrollBack ){ - toScroll = 0; - } + + // Scroll to top, hide addr bar + window.scrollTo( 0, $.mobile.defaultHomeScroll ); if( fromPage ) { - //set as data for returning to that spot - fromPage - .height( screenHeight + currScroll ) - .jqmData( "lastScroll", currScroll ) - .jqmData( "lastClicked", $activeClickedLink ); - //trigger before show/hide events fromPage.data( "page" )._trigger( "beforehide", null, { nextPage: toPage } ); } - toPage - .height( screenHeight + toScroll ) - .data( "page" )._trigger( "beforeshow", null, { prevPage: fromPage || $( "" ) } ); + + 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 ); + } + else{ + toPage.scrollTop( toScroll ); + } + } //find the transition handler for the specified transition. If there //isn't one in our transitionHandlers dictionary, use the default one. @@ -2265,21 +2344,25 @@ promise = th( transition, reverse, toPage, fromPage ); promise.done(function() { - //reset toPage height bac - toPage.height( "" ); - - //jump to top or prev scroll, sometimes on iOS the page has not rendered yet. - if( toScroll ){ + //reset toPage height back + if( !touchOverflow ){ + toPage.height( "" ); + // 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 ); - $( document ).one( "silentscroll", function() { reFocus( toPage ); } ); - } - else{ - reFocus( toPage ); } //trigger show/hide events if( fromPage ) { - fromPage.height("").data( "page" )._trigger( "hide", null, { nextPage: toPage } ); + if( !touchOverflow ){ + fromPage.height( "" ); + } + + fromPage.data( "page" )._trigger( "hide", null, { nextPage: toPage } ); } //trigger pageshow, define prevPage as either fromPage or empty jQuery obj @@ -2300,6 +2383,8 @@ return pageMin; } + + $.mobile.getScreenHeight = getScreenHeight; //simply set the active page's minimum height to screen height, depending on orientation function resetActivePageHeight(){ @@ -2349,6 +2434,8 @@ //history stack $.mobile.urlHistory = urlHistory; + $.mobile.dialogHashKey = dialogHashKey; + //default non-animation transition handler $.mobile.noneTransitionHandler = function( name, reverse, $toPage, $fromPage ) { if ( $fromPage ) { @@ -2433,6 +2520,12 @@ // Check to see if the page already exists in the DOM. page = settings.pageContainer.children( ":jqmData(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. + if ( page.length === 0 && $.mobile.firstPage && path.isFirstPageUrl( absUrl ) ) { + page = $( $.mobile.firstPage ); + } // Reset base to the default document base. if ( base ) { @@ -2453,18 +2546,18 @@ } if ( settings.showLoadMsg ) { - + // This configurable timeout allows cached pages a brief delay to load without showing a message var loadMsgDelay = setTimeout(function(){ $.mobile.showPageLoadingMsg(); }, settings.loadMsgDelay ), - + // Shared logic for clearing timeout and removing message. hideMsg = function(){ - + // Stop message show timer clearTimeout( loadMsgDelay ); - + // Hide loading message $.mobile.hidePageLoadingMsg(); }; @@ -2582,7 +2675,7 @@ // Remove loading message. if ( settings.showLoadMsg ) { - + // Remove loading message. hideMsg(); @@ -2661,16 +2754,38 @@ return; } + var settings = $.extend( {}, $.mobile.changePage.defaults, options ); + + // Make sure we have a pageContainer to work with. + settings.pageContainer = settings.pageContainer || $.mobile.pageContainer; + + // Make sure we have a fromPage. + settings.fromPage = settings.fromPage || $.mobile.activePage; + + var mpc = settings.pageContainer, + pbcEvent = new $.Event( "pagebeforechange" ), + triggerData = { toPage: toPage, options: settings }; + + // 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; + } + + // We allow "pagebeforechange" observers to modify the toPage in the trigger + // data to allow for redirects. Make sure our toPage is updated. + + toPage = triggerData.toPage; + // Set the isPageTransitioning flag to prevent any requests from // entering this method while we are in the midst of loading a page // or transitioning. isPageTransitioning = true; - - var settings = $.extend( {}, $.mobile.changePage.defaults, options ); - - // Make sure we have a pageContainer to work with. - settings.pageContainer = settings.pageContainer || $.mobile.pageContainer; // If the caller passed us a url, call loadPage() // to make sure it is loaded into the DOM. We'll listen @@ -2692,16 +2807,16 @@ //release transition lock so navigation is free again releasePageTransitionLock(); - settings.pageContainer.trigger("changepagefailed"); + settings.pageContainer.trigger( "pagechangefailed", triggerData ); + settings.pageContainer.trigger( "changepagefailed", triggerData ); // XXX: DEPRECATED for 1.0 }); return; } // The caller passed us a real page DOM element. Update our // internal state and then trigger a transition to the page. - var mpc = settings.pageContainer, - fromPage = $.mobile.activePage, - url = toPage.jqmData( "url" ), + var fromPage = settings.fromPage, + url = ( settings.dataUrl && path.convertUrlToDataUrl( settings.dataUrl ) ) || toPage.jqmData( "url" ), // The pageUrl var is usually the same as url, except when url is obscured as a dialog url. pageUrl always contains the file path pageUrl = url, fileUrl = path.getFilePath( url ), @@ -2711,9 +2826,6 @@ pageTitle = document.title, isDialog = settings.role === "dialog" || toPage.jqmData( "role" ) === "dialog"; - // Let listeners know we're about to change the current page. - mpc.trigger( "beforechangepage" ); - // 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") @@ -2722,7 +2834,8 @@ // to the same page. if( fromPage && fromPage[0] === toPage[0] ) { isPageTransitioning = false; - mpc.trigger( "changepage" ); + mpc.trigger( "pagechange", triggerData ); + mpc.trigger( "changepage", triggerData ); // XXX: DEPRECATED for 1.0 return; } @@ -2754,7 +2867,11 @@ // for the dialog content to be used in the hash. Instead, we want // to append the dialogHashKey to the url of the current page. if ( isDialog && active ) { - url = active.url + dialogHashKey; + // on the initial page load active.url is undefined and in that case should + // be an empty string. Moving the undefined -> empty string back into + // urlHistory.addNew seemed imprudent given undefined better represents + // the url state + url = ( active.url || "" ) + dialogHashKey; } // Set the location hash. @@ -2773,7 +2890,7 @@ //add page to history stack if it's not back or forward if( !historyDir ) { - urlHistory.addNew( url, settings.transition, pageTitle, pageUrl ); + urlHistory.addNew( url, settings.transition, pageTitle, pageUrl, settings.role ); } //set page title @@ -2805,7 +2922,9 @@ releasePageTransitionLock(); // Let listeners know we're all done changing the current page. - mpc.trigger( "changepage" ); + mpc.trigger( "pagechange", triggerData ); + + mpc.trigger( "changepage", triggerData ); // XXX: DEPRECATED for 1.0 }); }; @@ -2817,7 +2936,9 @@ role: undefined, // By default we rely on the role defined by the @data-role attribute. duplicateCachedPage: undefined, pageContainer: undefined, - showLoadMsg: true //loading message shows by default when pages are being fetched during changePage + showLoadMsg: true, //loading message shows by default when pages are being fetched during changePage + dataUrl: undefined, + fromPage: undefined }; /* Event Bindings - hashchange, submit, and click */ @@ -2905,7 +3026,9 @@ var link = findClosestLink( event.target ); if ( link ) { if ( path.parseUrl( link.getAttribute( "href" ) || "#" ).hash !== "#" ) { - $( link ).closest( ".ui-btn" ).not( ".ui-disabled" ).addClass( $.mobile.activeBtnClass ); + removeActiveLinkClass( true ); + $activeClickedLink = $( link ).closest( ".ui-btn" ).not( ".ui-disabled" ); + $activeClickedLink.addClass( $.mobile.activeBtnClass ); $( "." + $.mobile.activePageClass + " .ui-btn" ).not( link ).blur(); } } @@ -2983,8 +3106,6 @@ // moved into more comprehensive isExternalLink isExternal = useDefaultUrlHandling || ( path.isExternal( href ) && !isCrossDomainPageLoad ); - $activeClickedLink = $link.closest( ".ui-btn" ); - if( isExternal ) { httpCleanup(); //use default click handling @@ -3017,12 +3138,19 @@ }); } ); - //hashchange event handler - $window.bind( "hashchange", function( e, triggered ) { + $.mobile._handleHashChange = function( hash ) { //find first page via hash - var to = path.stripHash( location.hash ), + var to = path.stripHash( 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; + transition = $.mobile.urlHistory.stack.length === 0 ? "none" : undefined, + + // default options for the changPage calls made after examining the current state + // of the page and the hash + changePageOptions = { + transition: transition, + changeHash: false, + fromHashChange: true + }; //if listening is disabled (either globally or temporarily), or it's a dialog hash if( !$.mobile.hashListeningEnabled || urlHistory.ignoreNextHashChange ) { @@ -3031,8 +3159,7 @@ } // special case for dialogs - if( urlHistory.stack.length > 1 && - to.indexOf( dialogHashKey ) > -1 ) { + 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 @@ -3048,22 +3175,43 @@ // prevent changepage return; } else { - var setTo = function() { to = $.mobile.urlHistory.getActive().pageUrl; }; // 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 }); - } - } - + urlHistory.directHashChange({ + currentUrl: to, + + // regardless of the direction of the history change + // do the following + either: function( isBack ) { + var active = $.mobile.urlHistory.getActive(); + + to = active.pageUrl; + + // make sure to set the role, transition and reversal + // as most of this is lost by the domCache cleaning + $.extend( changePageOptions, { + role: active.role, + transition: active.transition, + reverse: isBack + }); + } + }); + } + } + //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 } ); - } + $.mobile.changePage( to, changePageOptions ); + } else { + //there's no hash, go to the first page in the dom + $.mobile.changePage( $.mobile.firstPage, changePageOptions ); + } + }; + + //hashchange event handler + $window.bind( "hashchange", function( e, triggered ) { + $.mobile._handleHashChange( location.hash ); }); //set page min-heights to be device specific @@ -3073,7 +3221,122 @@ };//_registerInternalEvents callback })( jQuery ); -/*! +/* +* jQuery Mobile Framework : history.pushState support, layered on top of hashchange +* Copyright (c) jQuery Project +* Dual licensed under the MIT or GPL Version 2 licenses. +* http://jquery.org/license +*/ +( function( $, window ) { + // For now, let's Monkeypatch this onto the end of $.mobile._registerInternalEvents + // Scope self to pushStateHandler so we can reference it sanely within the + // methods handed off as event handlers + var pushStateHandler = {}, + self = pushStateHandler, + $win = $( window ), + url = $.mobile.path.parseUrl( location.href ); + + $.extend( pushStateHandler, { + // TODO move to a path helper, this is rather common functionality + initialFilePath: (function() { + return url.pathname + url.search; + })(), + + initialHref: url.hrefNoHash, + + // Flag for tracking if a Hashchange naturally occurs after each popstate + replace + hashchangeFired: false, + + state: function() { + return { + hash: location.hash || "#" + self.initialFilePath, + title: document.title, + + // persist across refresh + initialHref: self.initialHref + }; + }, + + resetUIKeys: function( url ) { + var dialog = $.mobile.dialogHashKey, + subkey = "&" + $.mobile.subPageUrlKey, + dialogIndex = url.indexOf( dialog ); + + if( dialogIndex > -1 ) { + url = url.slice( 0, dialogIndex ) + "#" + url.slice( dialogIndex ); + } else if( url.indexOf( subkey ) > -1 ) { + url = url.split( subkey ).join( "#" + subkey ); + } + + return url; + }, + + // 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 ); + + href = self.resetUIKeys( href ); + + // replace the current url with the new href and store the state + history.replaceState( state, document.title, href ); + } + }, + + // on popstate (ie back or forward) we need to replace the hash that was there previously + // cleaned up by the additional hash handling + onPopState: function( e ) { + var poppedState = e.originalEvent.state, holdnexthashchange = false; + + // if there's no state its not a popstate we care about, ie chrome's initial popstate + // or forward popstate + if( poppedState ) { + // disable any hashchange triggered by the browser + $.mobile.urlHistory.ignoreNextHashChange = 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; + + // change the page based on the hash + $.mobile._handleHashChange( poppedState.hash ); + }, 100); + } + }, + + init: function() { + $win.bind( "hashchange", self.onHashChange ); + + // Handle popstate events the occur through history changes + $win.bind( "popstate", self.onPopState ); + + // if there's no hash, we need to replacestate for returning to home + if ( location.hash === "" ) { + history.replaceState( self.state(), document.title, location.href ); + } + } + }); + + $( function() { + if( $.mobile.pushStateEnabled && $.support.pushState ){ + pushStateHandler.init(); + } + }); +})( jQuery, this );/*! * jQuery Mobile v@VERSION * http://jquerymobile.com/ * @@ -3141,7 +3404,7 @@ month: false, number: false, range: "number", - search: true, + search: "text", tel: false, time: false, url: false, @@ -3324,7 +3587,9 @@ } else if ( role === "content" ) { - $this.addClass( "ui-body-" + ( theme || pageTheme || o.contentTheme ) ); + if (theme || o.contentTheme) { + $this.addClass( "ui-body-" + ( theme || o.contentTheme ) ); + } // Add ARIA role $this.attr( "role", "main" ); @@ -3345,7 +3610,7 @@ options: { expandCueText: " click to expand contents", collapseCueText: " click to collapse contents", - collapsed: false, + collapsed: true, heading: ">:header,>legend", theme: null, iconTheme: "d", @@ -3653,22 +3918,26 @@ return orig + " ui-listview " + ( t.options.inset ? " ui-listview-inset ui-corner-all ui-shadow " : "" ); }); - t.refresh(); + t.refresh( true ); }, _itemApply: function( $list, item ) { + var $countli = item.find( ".ui-li-count" ); + if ( $countli.length ) { + item.addClass( "ui-li-has-count" ); + } + $countli.addClass( "ui-btn-up-" + ( $list.jqmData( "counttheme" ) || this.options.countTheme ) + " ui-btn-corner-all" ); + // TODO class has to be defined in markup - item.find( ".ui-li-count" ) - .addClass( "ui-btn-up-" + ( $list.jqmData( "counttheme" ) || this.options.countTheme ) + " ui-btn-corner-all" ).end() - .find( "h1, h2, h3, h4, h5, h6" ).addClass( "ui-li-heading" ).end() - .find( "p, dl" ).addClass( "ui-li-desc" ).end() - .find( ">img:eq(0), .ui-link-inherit>img:eq(0)" ).addClass( "ui-li-thumb" ).each(function() { - item.addClass( $(this).is( ".ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" ); - }).end() - .find( ".ui-li-aside" ).each(function() { - var $this = $(this); - $this.prependTo( $this.parent() ); //shift aside to front for css float - }); + item.find( "h1, h2, h3, h4, h5, h6" ).addClass( "ui-li-heading" ).end() + .find( "p, dl" ).addClass( "ui-li-desc" ).end() + .find( ">img:eq(0), .ui-link-inherit>img:eq(0)" ).addClass( "ui-li-thumb" ).each(function() { + item.addClass( $(this).is( ".ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" ); + }).end() + .find( ".ui-li-aside" ).each(function() { + var $this = $(this); + $this.prependTo( $this.parent() ); //shift aside to front for css float + }); }, _removeCorners: function( li, which ) { @@ -3683,6 +3952,43 @@ li.removeClass( bot ); } else { li.removeClass( top + " " + bot ); + } + }, + + _refreshCorners: function( create ) { + var $li, + $visibleli, + $topli, + $bottomli; + + if ( this.options.inset ) { + $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 + $topli = $visibleli.first() + .addClass( "ui-corner-top" ); + + $topli.add( $topli.find( ".ui-btn-inner" ) ) + .find( ".ui-li-link-alt" ) + .addClass( "ui-corner-tr" ) + .end() + .find( ".ui-li-thumb" ) + .addClass( "ui-corner-tl" ); + + // Select the last visible li element + $bottomli = $visibleli.last() + .addClass( "ui-corner-bottom" ); + + $bottomli.add( $bottomli.find( ".ui-btn-inner" ) ) + .find( ".ui-li-link-alt" ) + .addClass( "ui-corner-br" ) + .end() + .find( ".ui-li-thumb" ) + .addClass( "ui-corner-bl" ); } }, @@ -3725,6 +4031,10 @@ icon: a.length > 1 || icon === false ? false : icon || "arrow-r", theme: itemTheme }); + + if ( ( icon != false ) && ( a.length == 1 ) ) { + item.addClass( "ui-li-has-arrow" ); + } a.first().addClass( "ui-link-inherit" ); @@ -3771,40 +4081,6 @@ } } - if ( o.inset ) { - if ( pos === 0 ) { - itemClass += " ui-corner-top"; - - item.add( item.find( ".ui-btn-inner" ) ) - .find( ".ui-li-link-alt" ) - .addClass( "ui-corner-tr" ) - .end() - .find( ".ui-li-thumb" ) - .addClass( "