diff --git a/mopidy_musicbox_webclient/static/index.html b/mopidy_musicbox_webclient/static/index.html index fd8197c..928e9f8 100644 --- a/mopidy_musicbox_webclient/static/index.html +++ b/mopidy_musicbox_webclient/static/index.html @@ -8,9 +8,9 @@ ' ); - - iframe_doc.close(); - - // Update the Iframe's hash, for great justice. - iframe.location.hash = hash; - } - }; - - })(); - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - // ^^^^^^^^^^^^^^^^^^^ REMOVE IF NOT SUPPORTING IE6/7/8 ^^^^^^^^^^^^^^^^^^^ - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - return self; - })(); - -})(jQuery,this); - -(function( $, undefined ) { - - /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ - window.matchMedia = window.matchMedia || (function( doc, undefined ) { - - - - var bool, - docElem = doc.documentElement, - refNode = docElem.firstElementChild || docElem.firstChild, - // fakeBody required for - fakeBody = doc.createElement( "body" ), - div = doc.createElement( "div" ); - - div.id = "mq-test-1"; - div.style.cssText = "position:absolute;top:-100em"; - fakeBody.style.background = "none"; - fakeBody.appendChild(div); - - return function(q){ - - div.innerHTML = "­"; - - docElem.insertBefore( fakeBody, refNode ); - bool = div.offsetWidth === 42; - docElem.removeChild( fakeBody ); - - return { - matches: bool, - media: q - }; - - }; - - }( document )); - - // $.mobile.media uses matchMedia to return a boolean. - $.mobile.media = function( q ) { - return window.matchMedia( q ).matches; - }; - -})(jQuery); - - (function( $, undefined ) { - var support = { - touch: "ontouchend" in document - }; - - $.mobile.support = $.mobile.support || {}; - $.extend( $.support, support ); - $.extend( $.mobile.support, support ); - }( jQuery )); - - (function( $, undefined ) { - $.extend( $.support, { - orientation: "orientation" in window && "onorientationchange" in window - }); - }( jQuery )); - -(function( $, undefined ) { - -// thx Modernizr -function propExists( prop ) { - var uc_prop = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ), - props = ( prop + " " + vendors.join( uc_prop + " " ) + uc_prop ).split( " " ); - - for ( var v in props ) { - if ( fbCSS[ props[ v ] ] !== undefined ) { - return true; - } - } -} - -var fakeBody = $( "" ).prependTo( "html" ), - fbCSS = fakeBody[ 0 ].style, - vendors = [ "Webkit", "Moz", "O" ], - webos = "palmGetResource" in window, //only used to rule out scrollTop - opera = window.opera, - operamini = window.operamini && ({}).toString.call( window.operamini ) === "[object OperaMini]", - bb = window.blackberry && !propExists( "-webkit-transform" ); //only used to rule out box shadow, as it's filled opaque on BB 5 and lower - - -function validStyle( prop, value, check_vend ) { - var div = document.createElement( 'div' ), - uc = function( txt ) { - return txt.charAt( 0 ).toUpperCase() + txt.substr( 1 ); - }, - vend_pref = function( vend ) { - if( vend === "" ) { - return ""; - } else { - return "-" + vend.charAt( 0 ).toLowerCase() + vend.substr( 1 ) + "-"; - } - }, - check_style = function( vend ) { - var vend_prop = vend_pref( vend ) + prop + ": " + value + ";", - uc_vend = uc( vend ), - propStyle = uc_vend + ( uc_vend === "" ? prop : uc( prop ) ); - - div.setAttribute( "style", vend_prop ); - - if ( !!div.style[ propStyle ] ) { - ret = true; - } - }, - check_vends = check_vend ? check_vend : vendors, - ret; - - for( var i = 0; i < check_vends.length; i++ ) { - check_style( check_vends[i] ); - } - return !!ret; -} - -function transform3dTest() { - var mqProp = "transform-3d", - // Because the `translate3d` test below throws false positives in Android: - ret = $.mobile.media( "(-" + vendors.join( "-" + mqProp + "),(-" ) + "-" + mqProp + "),(" + mqProp + ")" ); - - if( ret ) { - return !!ret; - } - - var el = document.createElement( "div" ), - transforms = { - // We’re omitting Opera for the time being; MS uses unprefixed. - 'MozTransform':'-moz-transform', - 'transform':'transform' - }; - - fakeBody.append( el ); - - for ( var t in transforms ) { - if( el.style[ t ] !== undefined ){ - el.style[ t ] = 'translate3d( 100px, 1px, 1px )'; - ret = window.getComputedStyle( el ).getPropertyValue( transforms[ t ] ); - } - } - return ( !!ret && ret !== "none" ); -} - -// Test for dynamic-updating base tag support ( allows us to avoid href,src attr rewriting ) -function baseTagTest() { - var fauxBase = location.protocol + "//" + location.host + location.pathname + "ui-dir/", - base = $( "head base" ), - fauxEle = null, - href = "", - link, rebase; - - if ( !base.length ) { - base = fauxEle = $( "", { "href": fauxBase }).appendTo( "head" ); - } else { - href = base.attr( "href" ); - } - - link = $( "" ).prependTo( fakeBody ); - rebase = link[ 0 ].href; - base[ 0 ].href = href || location.pathname; - - if ( fauxEle ) { - fauxEle.remove(); - } - return rebase.indexOf( fauxBase ) === 0; -} - -// Thanks Modernizr -function cssPointerEventsTest() { - var element = document.createElement( 'x' ), - documentElement = document.documentElement, - getComputedStyle = window.getComputedStyle, - supports; - - if ( !( 'pointerEvents' in element.style ) ) { - return false; - } - - element.style.pointerEvents = 'auto'; - element.style.pointerEvents = 'x'; - documentElement.appendChild( element ); - supports = getComputedStyle && - getComputedStyle( element, '' ).pointerEvents === 'auto'; - documentElement.removeChild( element ); - return !!supports; -} - -function boundingRect() { - var div = document.createElement( "div" ); - return typeof div.getBoundingClientRect !== "undefined"; -} - -// non-UA-based IE version check by James Padolsey, modified by jdalton - from http://gist.github.com/527683 -// allows for inclusion of IE 6+, including Windows Mobile 7 -$.extend( $.mobile, { browser: {} } ); -$.mobile.browser.oldIE = (function() { - var v = 3, - div = document.createElement( "div" ), - a = div.all || []; - - do { - div.innerHTML = ""; - } while( a[0] ); - - return v > 4 ? v : !v; -})(); - -function fixedPosition() { - var w = window, - ua = navigator.userAgent, - platform = navigator.platform, - // Rendering engine is Webkit, and capture major version - wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ ), - wkversion = !!wkmatch && wkmatch[ 1 ], - ffmatch = ua.match( /Fennec\/([0-9]+)/ ), - ffversion = !!ffmatch && ffmatch[ 1 ], - operammobilematch = ua.match( /Opera Mobi\/([0-9]+)/ ), - omversion = !!operammobilematch && operammobilematch[ 1 ]; - - if( - // iOS 4.3 and older : Platform is iPhone/Pad/Touch and Webkit version is less than 534 (ios5) - ( ( platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1 || platform.indexOf( "iPod" ) > -1 ) && wkversion && wkversion < 534 ) || - // Opera Mini - ( w.operamini && ({}).toString.call( w.operamini ) === "[object OperaMini]" ) || - ( operammobilematch && omversion < 7458 ) || - //Android lte 2.1: Platform is Android and Webkit version is less than 533 (Android 2.2) - ( ua.indexOf( "Android" ) > -1 && wkversion && wkversion < 533 ) || - // Firefox Mobile before 6.0 - - ( ffversion && ffversion < 6 ) || - // WebOS less than 3 - ( "palmGetResource" in window && wkversion && wkversion < 534 ) || - // MeeGo - ( ua.indexOf( "MeeGo" ) > -1 && ua.indexOf( "NokiaBrowser/8.5.0" ) > -1 ) ) { - return false; - } - - return true; -} - -$.extend( $.support, { - cssTransitions: "WebKitTransitionEvent" in window || - validStyle( 'transition', 'height 100ms linear', [ "Webkit", "Moz", "" ] ) && - !$.mobile.browser.oldIE && !opera, - - // Note, Chrome for iOS has an extremely quirky implementation of popstate. - // We've chosen to take the shortest path to a bug fix here for issue #5426 - // See the following link for information about the regex chosen - // https://developers.google.com/chrome/mobile/docs/user-agent#chrome_for_ios_user-agent - pushState: "pushState" in history && - "replaceState" in history && - // When running inside a FF iframe, calling replaceState causes an error - !( window.navigator.userAgent.indexOf( "Firefox" ) >= 0 && window.top !== window ) && - ( window.navigator.userAgent.search(/CriOS/) === -1 ), - - mediaquery: $.mobile.media( "only all" ), - cssPseudoElement: !!propExists( "content" ), - touchOverflow: !!propExists( "overflowScrolling" ), - cssTransform3d: transform3dTest(), - boxShadow: !!propExists( "boxShadow" ) && !bb, - fixedPosition: fixedPosition(), - scrollTop: ("pageXOffset" in window || - "scrollTop" in document.documentElement || - "scrollTop" in fakeBody[ 0 ]) && !webos && !operamini, - - dynamicBaseTag: baseTagTest(), - cssPointerEvents: cssPointerEventsTest(), - boundingRect: boundingRect() -}); - -fakeBody.remove(); - - -// $.mobile.ajaxBlacklist is used to override ajaxEnabled on platforms that have known conflicts with hash history updates (BB5, Symbian) -// or that generally work better browsing in regular http for full page refreshes (Opera Mini) -// Note: This detection below is used as a last resort. -// We recommend only using these detection methods when all other more reliable/forward-looking approaches are not possible -var nokiaLTE7_3 = (function() { - - var ua = window.navigator.userAgent; - - //The following is an attempt to match Nokia browsers that are running Symbian/s60, with webkit, version 7.3 or older - return ua.indexOf( "Nokia" ) > -1 && - ( ua.indexOf( "Symbian/3" ) > -1 || ua.indexOf( "Series60/5" ) > -1 ) && - ua.indexOf( "AppleWebKit" ) > -1 && - ua.match( /(BrowserNG|NokiaBrowser)\/7\.[0-3]/ ); -})(); - -// Support conditions that must be met in order to proceed -// default enhanced qualifications are media query support OR IE 7+ - -$.mobile.gradeA = function() { - return ( $.support.mediaquery || $.mobile.browser.oldIE && $.mobile.browser.oldIE >= 7 ) && ( $.support.boundingRect || $.fn.jquery.match(/1\.[0-7+]\.[0-9+]?/) !== null ); -}; - -$.mobile.ajaxBlacklist = - // BlackBerry browsers, pre-webkit - window.blackberry && !window.WebKitPoint || - // Opera Mini - operamini || - // Symbian webkits pre 7.3 - nokiaLTE7_3; - -// Lastly, this workaround is the only way we've found so far to get pre 7.3 Symbian webkit devices -// to render the stylesheets when they're referenced before this script, as we'd recommend doing. -// This simply reappends the CSS in place, which for some reason makes it apply -if ( nokiaLTE7_3 ) { - $(function() { - $( "head link[rel='stylesheet']" ).attr( "rel", "alternate stylesheet" ).attr( "rel", "stylesheet" ); - }); -} - -// For ruling out shadows via css -if ( !$.support.boxShadow ) { - $( "html" ).addClass( "ui-mobile-nosupport-boxshadow" ); -} - -})( jQuery ); - - -(function( $, undefined ) { - var $win = $.mobile.window, self, history; - - $.event.special.navigate = self = { - bound: false, - - pushStateEnabled: true, - - originalEventName: undefined, - - // If pushstate support is present and push state support is defined to - // be true on the mobile namespace. - isPushStateEnabled: function() { - return $.support.pushState && - $.mobile.pushStateEnabled === true && - this.isHashChangeEnabled(); - }, - - // !! assumes mobile namespace is present - isHashChangeEnabled: function() { - return $.mobile.hashListeningEnabled === true; - }, - - // TODO a lot of duplication between popstate and hashchange - popstate: function( event ) { - var newEvent = new $.Event( "navigate" ), - beforeNavigate = new $.Event( "beforenavigate" ), - state = event.originalEvent.state || {}, - href = location.href; - - $win.trigger( beforeNavigate ); - - if( beforeNavigate.isDefaultPrevented() ){ - return; - } - - if( event.historyState ){ - $.extend(state, event.historyState); - } - - // Make sure the original event is tracked for the end - // user to inspect incase they want to do something special - newEvent.originalEvent = event; - - // NOTE we let the current stack unwind because any assignment to - // location.hash will stop the world and run this event handler. By - // doing this we create a similar behavior to hashchange on hash - // assignment - setTimeout(function() { - $win.trigger( newEvent, { - state: state - }); - }, 0); - }, - - hashchange: function( event, data ) { - var newEvent = new $.Event( "navigate" ), - beforeNavigate = new $.Event( "beforenavigate" ); - - $win.trigger( beforeNavigate ); - - if( beforeNavigate.isDefaultPrevented() ){ - return; - } - - // Make sure the original event is tracked for the end - // user to inspect incase they want to do something special - newEvent.originalEvent = event; - - // Trigger the hashchange with state provided by the user - // that altered the hash - $win.trigger( newEvent, { - // Users that want to fully normalize the two events - // will need to do history management down the stack and - // add the state to the event before this binding is fired - // TODO consider allowing for the explicit addition of callbacks - // to be fired before this value is set to avoid event timing issues - state: event.hashchangeState || {} - }); - }, - - // TODO We really only want to set this up once - // but I'm not clear if there's a beter way to achieve - // this with the jQuery special event structure - setup: function( data, namespaces ) { - if( self.bound ) { - return; - } - - self.bound = true; - - if( self.isPushStateEnabled() ) { - self.originalEventName = "popstate"; - $win.bind( "popstate.navigate", self.popstate ); - } else if ( self.isHashChangeEnabled() ){ - self.originalEventName = "hashchange"; - $win.bind( "hashchange.navigate", self.hashchange ); - } - } - }; -})( jQuery ); - - - -(function( $, undefined ) { - var path, documentBase, $base, dialogHashKey = "&ui-state=dialog"; - - $.mobile.path = path = { - uiStateKey: "&ui-state", - - // This scary looking regular expression parses an absolute URL or its relative - // variants (protocol, site, document, query, and hash), into the various - // components (protocol, host, path, query, fragment, etc that make up the - // URL as well as some other commonly used sub-parts. When used with RegExp.exec() - // or String.match, it parses the URL into a results array that looks like this: - // - // [0]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread#msg-content - // [1]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread - // [2]: http://jblas:password@mycompany.com:8080/mail/inbox - // [3]: http://jblas:password@mycompany.com:8080 - // [4]: http: - // [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: /^\s*(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/, - - // Abstraction to address xss (Issue #4787) by removing the authority in - // browsers that auto decode it. All references to location.href should be - // replaced with a call to this method so that it can be dealt with properly here - getLocation: function( url ) { - var uri = url ? this.parseUrl( url ) : location, - hash = this.parseUrl( url || location.href ).hash; - - // mimic the browser with an empty string when the hash is empty - hash = hash === "#" ? "" : hash; - - // Make sure to parse the url or the location object for the hash because using location.hash - // is autodecoded in firefox, the rest of the url should be from the object (location unless - // we're testing) to avoid the inclusion of the authority - return uri.protocol + "//" + uri.host + uri.pathname + uri.search + hash; - }, - - parseLocation: function() { - return this.parseUrl( this.getLocation() ); - }, - - //Parse a URL into a structure that allows easy access to - //all of the URL components by name. - parseUrl: function( url ) { - // If we're passed an object, we'll assume that it is - // a parsed url object and just return it back to the caller. - if ( $.type( url ) === "object" ) { - return url; - } - - 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. - 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 ] || "" - }; - }, - - //Turn relPath into an asbolute path. absPath is - //an optional absolute path which describes what - //relPath is relative to. - makePathAbsolute: function( relPath, absPath ) { - if ( relPath && relPath.charAt( 0 ) === "/" ) { - return relPath; - } - - relPath = relPath || ""; - absPath = absPath ? absPath.replace( /^\/|(\/[^\/]*|[^\/]+)$/g, "" ) : ""; - - var absStack = absPath ? absPath.split( "/" ) : [], - relStack = relPath.split( "/" ); - for ( var i = 0; i < relStack.length; i++ ) { - var d = relStack[ i ]; - switch ( d ) { - case ".": - break; - case "..": - if ( absStack.length ) { - absStack.pop(); - } - break; - default: - absStack.push( d ); - break; - } - } - return "/" + absStack.join( "/" ); - }, - - //Returns true if both urls have the same domain. - isSameDomain: function( absUrl1, absUrl2 ) { - return path.parseUrl( absUrl1 ).domain === path.parseUrl( absUrl2 ).domain; - }, - - //Returns true for any relative variant. - isRelativeUrl: function( url ) { - // All relative Url variants have one thing in common, no protocol. - return path.parseUrl( url ).protocol === ""; - }, - - //Returns true for an absolute url. - isAbsoluteUrl: function( url ) { - return path.parseUrl( url ).protocol !== ""; - }, - - //Turn the specified realtive URL into an absolute one. This function - //can handle all relative variants (protocol, site, document, query, fragment). - makeUrlAbsolute: function( relUrl, absUrl ) { - if ( !path.isRelativeUrl( relUrl ) ) { - return relUrl; - } - - if ( absUrl === undefined ) { - absUrl = this.documentBase; - } - - 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 + doubleSlash + authority + pathname + search + hash; - }, - - //Add search (aka query) params to the specified url. - addSearchParams: function( url, params ) { - var u = path.parseUrl( url ), - p = ( typeof params === "object" ) ? $.param( params ) : params, - s = u.search || "?"; - return u.hrefNoSearch + s + ( s.charAt( s.length - 1 ) !== "?" ? "&" : "" ) + p + ( u.hash || "" ); - }, - - convertUrlToDataUrl: function( absUrl ) { - var u = path.parseUrl( absUrl ); - if ( path.isEmbeddedPage( u ) ) { - // For embedded pages, remove the dialog hash key as in getFilePath(), - // and remove otherwise the Data Url won't match the id of the embedded Page. - return u.hash - .split( dialogHashKey )[0] - .replace( /^#/, "" ) - .replace( /\?.*$/, "" ); - } else if ( path.isSameDomain( u, this.documentBase ) ) { - return u.hrefNoHash.replace( this.documentBase.domain, "" ).split( dialogHashKey )[0]; - } - - return window.decodeURIComponent(absUrl); - }, - - //get path from current hash, or from a file path - get: function( newPath ) { - if ( newPath === undefined ) { - newPath = path.parseLocation().hash; - } - return path.stripHash( newPath ).replace( /[^\/]*\.[^\/*]+$/, '' ); - }, - - //set location hash to path - set: function( path ) { - location.hash = path; - }, - - //test if a given url (string) is a path - //NOTE might be exceptionally naive - isPath: function( url ) { - return ( /\// ).test( url ); - }, - - //return a url path with the window's location protocol/hostname/pathname removed - clean: function( url ) { - return url.replace( this.documentBase.domain, "" ); - }, - - //just return the url without an initial # - stripHash: function( url ) { - return url.replace( /^#/, "" ); - }, - - stripQueryParams: function( url ) { - return url.replace( /\?.*$/, "" ); - }, - - //remove the preceding hash, any query params, and dialog notations - cleanHash: function( hash ) { - return path.stripHash( hash.replace( /\?.*$/, "" ).replace( dialogHashKey, "" ) ); - }, - - isHashValid: function( hash ) { - return ( /^#[^#]+$/ ).test( hash ); - }, - - //check whether a url is referencing the same domain, or an external domain or different protocol - //could be mailto, etc - isExternal: function( url ) { - var u = path.parseUrl( url ); - return u.protocol && u.domain !== this.documentUrl.domain ? true : false; - }, - - hasProtocol: function( url ) { - return ( /^(:?\w+:)/ ).test( url ); - }, - - isEmbeddedPage: function( url ) { - var u = path.parseUrl( url ); - - //if the path is absolute, then we need to compare the url against - //both the this.documentUrl and the documentBase. The main reason for this - //is that links embedded within external documents will refer to the - //application document, whereas links embedded within the application - //document will be resolved against the document base. - if ( u.protocol !== "" ) { - return ( !this.isPath(u.hash) && u.hash && ( u.hrefNoHash === this.documentUrl.hrefNoHash || ( this.documentBaseDiffers && u.hrefNoHash === this.documentBase.hrefNoHash ) ) ); - } - return ( /^#/ ).test( u.href ); - }, - - squash: function( url, resolutionUrl ) { - var state, href, cleanedUrl, search, stateIndex, - isPath = this.isPath( url ), - uri = this.parseUrl( url ), - preservedHash = uri.hash, - uiState = ""; - - // produce a url against which we can resole the provided path - resolutionUrl = resolutionUrl || (path.isPath(url) ? path.getLocation() : path.getDocumentUrl()); - - // If the url is anything but a simple string, remove any preceding hash - // eg #foo/bar -> foo/bar - // #foo -> #foo - cleanedUrl = isPath ? path.stripHash( url ) : url; - - // If the url is a full url with a hash check if the parsed hash is a path - // if it is, strip the #, and use it otherwise continue without change - cleanedUrl = path.isPath( uri.hash ) ? path.stripHash( uri.hash ) : cleanedUrl; - - // Split the UI State keys off the href - stateIndex = cleanedUrl.indexOf( this.uiStateKey ); - - // store the ui state keys for use - if( stateIndex > -1 ){ - uiState = cleanedUrl.slice( stateIndex ); - cleanedUrl = cleanedUrl.slice( 0, stateIndex ); - } - - // make the cleanedUrl absolute relative to the resolution url - href = path.makeUrlAbsolute( cleanedUrl, resolutionUrl ); - - // grab the search from the resolved url since parsing from - // the passed url may not yield the correct result - search = this.parseUrl( href ).search; - - // TODO all this crap is terrible, clean it up - if ( isPath ) { - // reject the hash if it's a path or it's just a dialog key - if( path.isPath( preservedHash ) || preservedHash.replace("#", "").indexOf( this.uiStateKey ) === 0) { - preservedHash = ""; - } - - // Append the UI State keys where it exists and it's been removed - // from the url - if( uiState && preservedHash.indexOf( this.uiStateKey ) === -1){ - preservedHash += uiState; - } - - // make sure that pound is on the front of the hash - if( preservedHash.indexOf( "#" ) === -1 && preservedHash !== "" ){ - preservedHash = "#" + preservedHash; - } - - // reconstruct each of the pieces with the new search string and hash - href = path.parseUrl( href ); - href = href.protocol + "//" + href.host + href.pathname + search + preservedHash; - } else { - href += href.indexOf( "#" ) > -1 ? uiState : "#" + uiState; - } - - return href; - }, - - isPreservableHash: function( hash ) { - return hash.replace( "#", "" ).indexOf( this.uiStateKey ) === 0; - } - }; - - path.documentUrl = path.parseLocation(); - - $base = $( "head" ).find( "base" ); - - path.documentBase = $base.length ? - path.parseUrl( path.makeUrlAbsolute( $base.attr( "href" ), path.documentUrl.href ) ) : - path.documentUrl; - - path.documentBaseDiffers = (path.documentUrl.hrefNoHash !== path.documentBase.hrefNoHash); - - //return the original document url - path.getDocumentUrl = function( asParsedObject ) { - return asParsedObject ? $.extend( {}, path.documentUrl ) : path.documentUrl.href; - }; - - //return the original document base url - path.getDocumentBase = function( asParsedObject ) { - return asParsedObject ? $.extend( {}, path.documentBase ) : path.documentBase.href; - }; -})( jQuery ); - - - -(function( $, undefined ) { - var path = $.mobile.path; - - $.mobile.History = function( stack, index ) { - this.stack = stack || []; - this.activeIndex = index || 0; - }; - - $.extend($.mobile.History.prototype, { - getActive: function() { - return this.stack[ this.activeIndex ]; - }, - - getLast: function() { - return this.stack[ this.previousIndex ]; - }, - - getNext: function() { - return this.stack[ this.activeIndex + 1 ]; - }, - - getPrev: function() { - return this.stack[ this.activeIndex - 1 ]; - }, - - // addNew is used whenever a new page is added - add: function( url, data ){ - data = data || {}; - - //if there's forward history, wipe it - if ( this.getNext() ) { - this.clearForward(); - } - - // if the hash is included in the data make sure the shape - // is consistent for comparison - if( data.hash && data.hash.indexOf( "#" ) === -1) { - data.hash = "#" + data.hash; - } - - data.url = url; - this.stack.push( data ); - this.activeIndex = this.stack.length - 1; - }, - - //wipe urls ahead of active index - clearForward: function() { - this.stack = this.stack.slice( 0, this.activeIndex + 1 ); - }, - - find: function( url, stack, earlyReturn ) { - stack = stack || this.stack; - - var entry, i, length = stack.length, index; - - for ( i = 0; i < length; i++ ) { - entry = stack[i]; - - if ( decodeURIComponent(url) === decodeURIComponent(entry.url) || - decodeURIComponent(url) === decodeURIComponent(entry.hash) ) { - index = i; - - if( earlyReturn ) { - return index; - } - } - } - - return index; - }, - - closest: function( url ) { - var closest, a = this.activeIndex; - - // First, take the slice of the history stack before the current index and search - // for a url match. If one is found, we'll avoid avoid looking through forward history - // NOTE the preference for backward history movement is driven by the fact that - // most mobile browsers only have a dedicated back button, and users rarely use - // the forward button in desktop browser anyhow - closest = this.find( url, this.stack.slice(0, a) ); - - // If nothing was found in backward history check forward. The `true` - // value passed as the third parameter causes the find method to break - // on the first match in the forward history slice. The starting index - // of the slice must then be added to the result to get the element index - // in the original history stack :( :( - // - // TODO this is hyper confusing and should be cleaned up (ugh so bad) - if( closest === undefined ) { - closest = this.find( url, this.stack.slice(a), true ); - closest = closest === undefined ? closest : closest + a; - } - - return closest; - }, - - direct: function( opts ) { - var newActiveIndex = this.closest( opts.url ), a = this.activeIndex; - - // save new page index, null check to prevent falsey 0 result - // record the previous index for reference - if( newActiveIndex !== undefined ) { - this.activeIndex = newActiveIndex; - this.previousIndex = a; - } - - // invoke callbacks where appropriate - // - // TODO this is also convoluted and confusing - if ( newActiveIndex < a ) { - ( opts.present || opts.back || $.noop )( this.getActive(), 'back' ); - } else if ( newActiveIndex > a ) { - ( opts.present || opts.forward || $.noop )( this.getActive(), 'forward' ); - } else if ( newActiveIndex === undefined && opts.missing ){ - opts.missing( this.getActive() ); - } - } - }); -})( jQuery ); - - -(function( $, undefined ) { - var path = $.mobile.path, - initialHref = location.href; - - $.mobile.Navigator = function( history ) { - this.history = history; - this.ignoreInitialHashChange = true; - - $.mobile.window.bind({ - "popstate.history": $.proxy( this.popstate, this ), - "hashchange.history": $.proxy( this.hashchange, this ) - }); - }; - - $.extend($.mobile.Navigator.prototype, { - squash: function( url, data ) { - var state, href, hash = path.isPath(url) ? path.stripHash(url) : url; - - href = path.squash( url ); - - // make sure to provide this information when it isn't explicitly set in the - // data object that was passed to the squash method - state = $.extend({ - hash: hash, - url: href - }, data); - - // 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 $.mobile.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. - window.history.replaceState( state, state.title || document.title, href ); - - return state; - }, - - hash: function( url, href ) { - var parsed, loc, hash; - - // Grab the hash for recording. If the passed url is a path - // we used the parsed version of the squashed url to reconstruct, - // otherwise we assume it's a hash and store it directly - parsed = path.parseUrl( url ); - loc = path.parseLocation(); - - if( loc.pathname + loc.search === parsed.pathname + parsed.search ) { - // If the pathname and search of the passed url is identical to the current loc - // then we must use the hash. Otherwise there will be no event - // eg, url = "/foo/bar?baz#bang", location.href = "http://example.com/foo/bar?baz" - hash = parsed.hash ? parsed.hash : parsed.pathname + parsed.search; - } else if ( path.isPath(url) ) { - var resolved = path.parseUrl( href ); - // If the passed url is a path, make it domain relative and remove any trailing hash - hash = resolved.pathname + resolved.search + (path.isPreservableHash( resolved.hash )? resolved.hash.replace( "#", "" ) : ""); - } else { - hash = url; - } - - return hash; - }, - - // TODO reconsider name - go: function( url, data, noEvents ) { - var state, href, hash, popstateEvent, - isPopStateEvent = $.event.special.navigate.isPushStateEnabled(); - - // Get the url as it would look squashed on to the current resolution url - href = path.squash( url ); - - // sort out what the hash sould be from the url - hash = this.hash( url, href ); - - // Here we prevent the next hash change or popstate event from doing any - // history management. In the case of hashchange we don't swallow it - // if there will be no hashchange fired (since that won't reset the value) - // and will swallow the following hashchange - if( noEvents && hash !== path.stripHash(path.parseLocation().hash) ) { - this.preventNextHashChange = noEvents; - } - - // IMPORTANT in the case where popstate is supported the event will be triggered - // directly, stopping further execution - ie, interupting the flow of this - // method call to fire bindings at this expression. Below the navigate method - // there is a binding to catch this event and stop its propagation. - // - // We then trigger a new popstate event on the window with a null state - // so that the navigate events can conclude their work properly - // - // if the url is a path we want to preserve the query params that are available on - // the current url. - this.preventHashAssignPopState = true; - window.location.hash = hash; - - // If popstate is enabled and the browser triggers `popstate` events when the hash - // is set (this often happens immediately in browsers like Chrome), then the - // this flag will be set to false already. If it's a browser that does not trigger - // a `popstate` on hash assignement or `replaceState` then we need avoid the branch - // that swallows the event created by the popstate generated by the hash assignment - // At the time of this writing this happens with Opera 12 and some version of IE - this.preventHashAssignPopState = false; - - state = $.extend({ - url: href, - hash: hash, - title: document.title - }, data); - - if( isPopStateEvent ) { - popstateEvent = new $.Event( "popstate" ); - popstateEvent.originalEvent = { - type: "popstate", - state: null - }; - - this.squash( url, state ); - - // Trigger a new faux popstate event to replace the one that we - // caught that was triggered by the hash setting above. - if( !noEvents ) { - this.ignorePopState = true; - $.mobile.window.trigger( popstateEvent ); - } - } - - // record the history entry so that the information can be included - // in hashchange event driven navigate events in a similar fashion to - // the state that's provided by popstate - this.history.add( state.url, state ); - }, - - - // This binding is intended to catch the popstate events that are fired - // when execution of the `$.navigate` method stops at window.location.hash = url; - // and completely prevent them from propagating. The popstate event will then be - // retriggered after execution resumes - // - // TODO grab the original event here and use it for the synthetic event in the - // second half of the navigate execution that will follow this binding - popstate: function( event ) { - var active, hash, state, closestIndex; - - // Partly to support our test suite which manually alters the support - // value to test hashchange. Partly to prevent all around weirdness - if( !$.event.special.navigate.isPushStateEnabled() ){ - return; - } - - // If this is the popstate triggered by the actual alteration of the hash - // prevent it completely. History is tracked manually - if( this.preventHashAssignPopState ) { - this.preventHashAssignPopState = false; - event.stopImmediatePropagation(); - return; - } - - // if this is the popstate triggered after the `replaceState` call in the go - // method, then simply ignore it. The history entry has already been captured - if( this.ignorePopState ) { - this.ignorePopState = false; - return; - } - - // If there is no state, and the history stack length is one were - // probably getting the page load popstate fired by browsers like chrome - // avoid it and set the one time flag to false. - // TODO: Do we really need all these conditions? Comparing location hrefs - // should be sufficient. - if( !event.originalEvent.state && - this.history.stack.length === 1 && - this.ignoreInitialHashChange ) { - this.ignoreInitialHashChange = false; - - if ( location.href === initialHref ) { - event.preventDefault(); - return; - } - } - - // account for direct manipulation of the hash. That is, we will receive a popstate - // when the hash is changed by assignment, and it won't have a state associated. We - // then need to squash the hash. See below for handling of hash assignment that - // matches an existing history entry - // TODO it might be better to only add to the history stack - // when the hash is adjacent to the active history entry - hash = path.parseLocation().hash; - if( !event.originalEvent.state && hash ) { - // squash the hash that's been assigned on the URL with replaceState - // also grab the resulting state object for storage - state = this.squash( hash ); - - // record the new hash as an additional history entry - // to match the browser's treatment of hash assignment - this.history.add( state.url, state ); - - // pass the newly created state information - // along with the event - event.historyState = state; - - // do not alter history, we've added a new history entry - // so we know where we are - return; - } - - // If all else fails this is a popstate that comes from the back or forward buttons - // make sure to set the state of our history stack properly, and record the directionality - this.history.direct({ - url: (event.originalEvent.state || {}).url || hash, - - // When the url is either forward or backward in history include the entry - // as data on the event object for merging as data in the navigate event - present: function( historyEntry, direction ) { - // make sure to create a new object to pass down as the navigate event data - event.historyState = $.extend({}, historyEntry); - event.historyState.direction = direction; - } - }); - }, - - // NOTE must bind before `navigate` special event hashchange binding otherwise the - // navigation data won't be attached to the hashchange event in time for those - // bindings to attach it to the `navigate` special event - // TODO add a check here that `hashchange.navigate` is bound already otherwise it's - // broken (exception?) - hashchange: function( event ) { - var history, hash; - - // If hashchange listening is explicitly disabled or pushstate is supported - // avoid making use of the hashchange handler. - if(!$.event.special.navigate.isHashChangeEnabled() || - $.event.special.navigate.isPushStateEnabled() ) { - return; - } - - // On occasion explicitly want to prevent the next hash from propogating because we only - // with to alter the url to represent the new state do so here - if( this.preventNextHashChange ){ - this.preventNextHashChange = false; - event.stopImmediatePropagation(); - return; - } - - history = this.history; - hash = path.parseLocation().hash; - - // If this is a hashchange caused by the back or forward button - // make sure to set the state of our history stack properly - this.history.direct({ - url: hash, - - // When the url is either forward or backward in history include the entry - // as data on the event object for merging as data in the navigate event - present: function( historyEntry, direction ) { - // make sure to create a new object to pass down as the navigate event data - event.hashchangeState = $.extend({}, historyEntry); - event.hashchangeState.direction = direction; - }, - - // When we don't find a hash in our history clearly we're aiming to go there - // record the entry as new for future traversal - // - // NOTE it's not entirely clear that this is the right thing to do given that we - // can't know the users intention. It might be better to explicitly _not_ - // support location.hash assignment in preference to $.navigate calls - // TODO first arg to add should be the href, but it causes issues in identifying - // embeded pages - missing: function() { - history.add( hash, { - hash: hash, - title: document.title - }); - } - }); - } - }); -})( jQuery ); - - - -(function( $, undefined ) { - // TODO consider queueing navigation activity until previous activities have completed - // so that end users don't have to think about it. Punting for now - // TODO !! move the event bindings into callbacks on the navigate event - $.mobile.navigate = function( url, data, noEvents ) { - $.mobile.navigate.navigator.go( url, data, noEvents ); - }; - - // expose the history on the navigate method in anticipation of full integration with - // existing navigation functionalty that is tightly coupled to the history information - $.mobile.navigate.history = new $.mobile.History(); - - // instantiate an instance of the navigator for use within the $.navigate method - $.mobile.navigate.navigator = new $.mobile.Navigator( $.mobile.navigate.history ); - - var loc = $.mobile.path.parseLocation(); - $.mobile.navigate.history.add( loc.href, {hash: loc.hash} ); -})( jQuery ); - - -// This plugin is an experiment for abstracting away the touch and mouse -// events so that developers don't have to worry about which method of input -// the device their document is loaded on supports. -// -// The idea here is to allow the developer to register listeners for the -// basic mouse events, such as mousedown, mousemove, mouseup, and click, -// and the plugin will take care of registering the correct listeners -// behind the scenes to invoke the listener at the fastest possible time -// for that device, while still retaining the order of event firing in -// the traditional mouse environment, should multiple handlers be registered -// on the same element for different events. -// -// The current version exposes the following virtual events to jQuery bind methods: -// "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel" - -(function( $, window, document, undefined ) { - -var dataPropertyName = "virtualMouseBindings", - touchTargetPropertyName = "virtualTouchID", - virtualEventNames = "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split( " " ), - touchEventProps = "clientX clientY pageX pageY screenX screenY".split( " " ), - mouseHookProps = $.event.mouseHooks ? $.event.mouseHooks.props : [], - mouseEventProps = $.event.props.concat( mouseHookProps ), - activeDocHandlers = {}, - resetTimerID = 0, - startX = 0, - startY = 0, - didScroll = false, - clickBlockList = [], - blockMouseTriggers = false, - blockTouchTriggers = false, - eventCaptureSupported = "addEventListener" in document, - $document = $( document ), - nextTouchID = 1, - lastTouchID = 0, threshold; - -$.vmouse = { - moveDistanceThreshold: 10, - clickDistanceThreshold: 10, - resetTimerDuration: 1500 -}; - -function getNativeEvent( event ) { - - while ( event && typeof event.originalEvent !== "undefined" ) { - event = event.originalEvent; - } - return event; -} - -function createVirtualEvent( event, eventType ) { - - var t = event.type, - oe, props, ne, prop, ct, touch, i, j, len; - - event = $.Event( event ); - event.type = eventType; - - oe = event.originalEvent; - props = $.event.props; - - // addresses separation of $.event.props in to $.event.mouseHook.props and Issue 3280 - // https://github.com/jquery/jquery-mobile/issues/3280 - if ( t.search( /^(mouse|click)/ ) > -1 ) { - props = mouseEventProps; - } - - // copy original event properties over to the new event - // this would happen if we could call $.event.fix instead of $.Event - // but we don't have a way to force an event to be fixed multiple times - if ( oe ) { - for ( i = props.length, prop; i; ) { - 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 ) { - ne = getNativeEvent( oe ); - t = ne.touches; - ct = ne.changedTouches; - touch = ( t && t.length ) ? t[0] : ( ( ct && ct.length ) ? ct[ 0 ] : undefined ); - - if ( touch ) { - for ( j = 0, len = touchEventProps.length; j < len; j++) { - prop = touchEventProps[ j ]; - event[ prop ] = touch[ prop ]; - } - } - } - - return event; -} - -function getVirtualBindingFlags( element ) { - - var flags = {}, - b, k; - - while ( element ) { - - b = $.data( element, dataPropertyName ); - - for ( k in b ) { - if ( b[ k ] ) { - flags[ k ] = flags.hasVirtualBinding = true; - } - } - element = element.parentNode; - } - return flags; -} - -function getClosestElementWithVirtualBinding( element, eventType ) { - var b; - while ( element ) { - - b = $.data( element, dataPropertyName ); - - if ( b && ( !eventType || b[ eventType ] ) ) { - return element; - } - element = element.parentNode; - } - return null; -} - -function enableTouchBindings() { - blockTouchTriggers = false; -} - -function disableTouchBindings() { - blockTouchTriggers = true; -} - -function enableMouseBindings() { - lastTouchID = 0; - clickBlockList.length = 0; - blockMouseTriggers = false; - - // When mouse bindings are enabled, our - // touch bindings are disabled. - disableTouchBindings(); -} - -function disableMouseBindings() { - // When mouse bindings are disabled, our - // touch bindings are enabled. - enableTouchBindings(); -} - -function startResetTimer() { - clearResetTimer(); - resetTimerID = setTimeout( function() { - resetTimerID = 0; - enableMouseBindings(); - }, $.vmouse.resetTimerDuration ); -} - -function clearResetTimer() { - if ( resetTimerID ) { - clearTimeout( resetTimerID ); - resetTimerID = 0; - } -} - -function triggerVirtualEvent( eventType, event, flags ) { - var ve; - - if ( ( flags && flags[ eventType ] ) || - ( !flags && getClosestElementWithVirtualBinding( event.target, eventType ) ) ) { - - ve = createVirtualEvent( event, eventType ); - - $( event.target).trigger( ve ); - } - - return ve; -} - -function mouseEventCallback( event ) { - var touchID = $.data( event.target, touchTargetPropertyName ); - - if ( !blockMouseTriggers && ( !lastTouchID || lastTouchID !== touchID ) ) { - var ve = triggerVirtualEvent( "v" + event.type, event ); - if ( ve ) { - if ( ve.isDefaultPrevented() ) { - event.preventDefault(); - } - if ( ve.isPropagationStopped() ) { - event.stopPropagation(); - } - if ( ve.isImmediatePropagationStopped() ) { - event.stopImmediatePropagation(); - } - } - } -} - -function handleTouchStart( event ) { - - var touches = getNativeEvent( event ).touches, - target, flags; - - if ( touches && touches.length === 1 ) { - - target = event.target; - flags = getVirtualBindingFlags( target ); - - if ( flags.hasVirtualBinding ) { - - lastTouchID = nextTouchID++; - $.data( target, touchTargetPropertyName, lastTouchID ); - - clearResetTimer(); - - disableMouseBindings(); - didScroll = false; - - var t = getNativeEvent( event ).touches[ 0 ]; - startX = t.pageX; - startY = t.pageY; - - triggerVirtualEvent( "vmouseover", event, flags ); - triggerVirtualEvent( "vmousedown", event, flags ); - } - } -} - -function handleScroll( event ) { - if ( blockTouchTriggers ) { - return; - } - - if ( !didScroll ) { - triggerVirtualEvent( "vmousecancel", event, getVirtualBindingFlags( event.target ) ); - } - - didScroll = true; - startResetTimer(); -} - -function handleTouchMove( event ) { - if ( blockTouchTriggers ) { - return; - } - - var t = getNativeEvent( event ).touches[ 0 ], - didCancel = didScroll, - moveThreshold = $.vmouse.moveDistanceThreshold, - flags = getVirtualBindingFlags( event.target ); - - didScroll = didScroll || - ( Math.abs( t.pageX - startX ) > moveThreshold || - Math.abs( t.pageY - startY ) > moveThreshold ); - - - if ( didScroll && !didCancel ) { - triggerVirtualEvent( "vmousecancel", event, flags ); - } - - triggerVirtualEvent( "vmousemove", event, flags ); - startResetTimer(); -} - -function handleTouchEnd( event ) { - if ( blockTouchTriggers ) { - return; - } - - disableTouchBindings(); - - var flags = getVirtualBindingFlags( event.target ), - t; - triggerVirtualEvent( "vmouseup", event, flags ); - - if ( !didScroll ) { - 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 - // any click that is generated. - t = getNativeEvent( event ).changedTouches[ 0 ]; - clickBlockList.push({ - touchID: lastTouchID, - x: t.clientX, - y: t.clientY - }); - - // Prevent any mouse events that follow from triggering - // virtual event notifications. - blockMouseTriggers = true; - } - } - triggerVirtualEvent( "vmouseout", event, flags); - didScroll = false; - - startResetTimer(); -} - -function hasVirtualBindings( ele ) { - var bindings = $.data( ele, dataPropertyName ), - k; - - if ( bindings ) { - for ( k in bindings ) { - if ( bindings[ k ] ) { - return true; - } - } - } - return false; -} - -function dummyMouseHandler() {} - -function getSpecialEventObject( eventType ) { - var realType = eventType.substr( 1 ); - - return { - setup: function( data, namespace ) { - // If this is the first virtual mouse binding for this element, - // add a bindings object to its data. - - if ( !hasVirtualBindings( this ) ) { - $.data( this, dataPropertyName, {} ); - } - - // If setup is called, we know it is the first binding for this - // eventType, so initialize the count for the eventType to zero. - var bindings = $.data( this, dataPropertyName ); - bindings[ eventType ] = true; - - // If this is the first virtual mouse event for this type, - // register a global handler on the document. - - activeDocHandlers[ eventType ] = ( activeDocHandlers[ eventType ] || 0 ) + 1; - - if ( activeDocHandlers[ eventType ] === 1 ) { - $document.bind( realType, mouseEventCallback ); - } - - // Some browsers, like Opera Mini, won't dispatch mouse/click events - // for elements unless they actually have handlers registered on them. - // To get around this, we register dummy handlers on the elements. - - $( this ).bind( realType, dummyMouseHandler ); - - // For now, if event capture is not supported, we rely on mouse handlers. - if ( eventCaptureSupported ) { - // If this is the first virtual mouse binding for the document, - // register our touchstart handler on the document. - - activeDocHandlers[ "touchstart" ] = ( activeDocHandlers[ "touchstart" ] || 0) + 1; - - if ( activeDocHandlers[ "touchstart" ] === 1 ) { - $document.bind( "touchstart", handleTouchStart ) - .bind( "touchend", handleTouchEnd ) - - // On touch platforms, touching the screen and then dragging your finger - // causes the window content to scroll after some distance threshold is - // exceeded. On these platforms, a scroll prevents a click event from being - // dispatched, and on some platforms, even the touchend is suppressed. To - // mimic the suppression of the click event, we need to watch for a scroll - // event. Unfortunately, some platforms like iOS don't dispatch scroll - // events until *AFTER* the user lifts their finger (touchend). This means - // we need to watch both scroll and touchmove events to figure out whether - // or not a scroll happenens before the touchend event is fired. - - .bind( "touchmove", handleTouchMove ) - .bind( "scroll", handleScroll ); - } - } - }, - - teardown: function( data, namespace ) { - // If this is the last virtual binding for this eventType, - // remove its global handler from the document. - - --activeDocHandlers[ eventType ]; - - if ( !activeDocHandlers[ eventType ] ) { - $document.unbind( realType, mouseEventCallback ); - } - - if ( eventCaptureSupported ) { - // If this is the last virtual mouse binding in existence, - // remove our document touchstart listener. - - --activeDocHandlers[ "touchstart" ]; - - if ( !activeDocHandlers[ "touchstart" ] ) { - $document.unbind( "touchstart", handleTouchStart ) - .unbind( "touchmove", handleTouchMove ) - .unbind( "touchend", handleTouchEnd ) - .unbind( "scroll", handleScroll ); - } - } - - var $this = $( this ), - bindings = $.data( this, dataPropertyName ); - - // teardown may be called when an element was - // removed from the DOM. If this is the case, - // jQuery core may have already stripped the element - // of any data bindings so we need to check it before - // using it. - if ( bindings ) { - bindings[ eventType ] = false; - } - - // Unregister the dummy event handler. - - $this.unbind( realType, dummyMouseHandler ); - - // If this is the last virtual mouse binding on the - // element, remove the binding data from the element. - - if ( !hasVirtualBindings( this ) ) { - $this.removeData( dataPropertyName ); - } - } - }; -} - -// Expose our custom events to the jQuery bind/unbind mechanism. - -for ( var i = 0; i < virtualEventNames.length; i++ ) { - $.event.special[ virtualEventNames[ i ] ] = getSpecialEventObject( virtualEventNames[ i ] ); -} - -// Add a capture click handler to block clicks. -// Note that we require event capture support for this so if the device -// doesn't support it, we punt for now and rely solely on mouse events. -if ( eventCaptureSupported ) { - document.addEventListener( "click", function( e ) { - var cnt = clickBlockList.length, - target = e.target, - x, y, ele, i, o, touchID; - - if ( cnt ) { - x = e.clientX; - y = e.clientY; - threshold = $.vmouse.clickDistanceThreshold; - - // The idea here is to run through the clickBlockList to see if - // the current click event is in the proximity of one of our - // vclick events that had preventDefault() called on it. If we find - // one, then we block the click. - // - // Why do we have to rely on proximity? - // - // Because the target of the touch event that triggered the vclick - // can be different from the target of the click event synthesized - // by the browser. The target of a mouse/click event that is syntehsized - // from a touch event seems to be implementation specific. For example, - // some browsers will fire mouse/click events for a link that is near - // a touch event, even though the target of the touchstart/touchend event - // says the user touched outside the link. Also, it seems that with most - // browsers, the target of the mouse/click event is not calculated until the - // time it is dispatched, so if you replace an element that you touched - // with another element, the target of the mouse/click will be the new - // element underneath that point. - // - // Aside from proximity, we also check to see if the target and any - // of its ancestors were the ones that blocked a click. This is necessary - // because of the strange mouse/click target calculation done in the - // Android 2.1 browser, where if you click on an element, and there is a - // mouse/click handler on one of its ancestors, the target will be the - // innermost child of the touched element, even if that child is no where - // near the point of touch. - - ele = target; - - while ( ele ) { - for ( i = 0; i < cnt; i++ ) { - o = clickBlockList[ i ]; - touchID = 0; - - if ( ( ele === target && Math.abs( o.x - x ) < threshold && Math.abs( o.y - y ) < threshold ) || - $.data( ele, touchTargetPropertyName ) === o.touchID ) { - // XXX: We may want to consider removing matches from the block list - // instead of waiting for the reset timer to fire. - e.preventDefault(); - e.stopPropagation(); - return; - } - } - ele = ele.parentNode; - } - } - }, true); -} -})( jQuery, window, document ); - - -(function( $, window, undefined ) { - var $document = $( document ); - - // add new event shortcuts - $.each( ( "touchstart touchmove touchend " + - "tap taphold " + - "swipe swipeleft swiperight " + - "scrollstart scrollstop" ).split( " " ), function( i, name ) { - - $.fn[ name ] = function( fn ) { - return fn ? this.bind( name, fn ) : this.trigger( name ); - }; - - // jQuery < 1.8 - if ( $.attrFn ) { - $.attrFn[ name ] = true; - } - }); - - var supportTouch = $.mobile.support.touch, - scrollEvent = "touchmove scroll", - touchStartEvent = supportTouch ? "touchstart" : "mousedown", - touchStopEvent = supportTouch ? "touchend" : "mouseup", - touchMoveEvent = supportTouch ? "touchmove" : "mousemove"; - - function triggerCustomEvent( obj, eventType, event ) { - var originalType = event.type; - event.type = eventType; - $.event.dispatch.call( obj, event ); - event.type = originalType; - } - - // also handles scrollstop - $.event.special.scrollstart = { - - enabled: true, - - setup: function() { - - var thisObject = this, - $this = $( thisObject ), - scrolling, - timer; - - function trigger( event, state ) { - scrolling = state; - triggerCustomEvent( thisObject, scrolling ? "scrollstart" : "scrollstop", event ); - } - - // iPhone triggers scroll after a small delay; use touchmove instead - $this.bind( scrollEvent, function( event ) { - - if ( !$.event.special.scrollstart.enabled ) { - return; - } - - if ( !scrolling ) { - trigger( event, true ); - } - - clearTimeout( timer ); - timer = setTimeout( function() { - trigger( event, false ); - }, 50 ); - }); - } - }; - - // also handles taphold - $.event.special.tap = { - tapholdThreshold: 750, - - setup: function() { - var thisObject = this, - $this = $( thisObject ); - - $this.bind( "vmousedown", function( event ) { - - if ( event.which && event.which !== 1 ) { - return false; - } - - var origTarget = event.target, - origEvent = event.originalEvent, - timer; - - function clearTapTimer() { - clearTimeout( timer ); - } - - function clearTapHandlers() { - clearTapTimer(); - - $this.unbind( "vclick", clickHandler ) - .unbind( "vmouseup", clearTapTimer ); - $document.unbind( "vmousecancel", clearTapHandlers ); - } - - function clickHandler( event ) { - clearTapHandlers(); - - // ONLY trigger a 'tap' event if the start target is - // the same as the stop target. - if ( origTarget === event.target ) { - triggerCustomEvent( thisObject, "tap", event ); - } - } - - $this.bind( "vmouseup", clearTapTimer ) - .bind( "vclick", clickHandler ); - $document.bind( "vmousecancel", clearTapHandlers ); - - timer = setTimeout( function() { - triggerCustomEvent( thisObject, "taphold", $.Event( "taphold", { target: origTarget } ) ); - }, $.event.special.tap.tapholdThreshold ); - }); - } - }; - - // also handles swipeleft, swiperight - $.event.special.swipe = { - scrollSupressionThreshold: 30, // More than this horizontal displacement, and we will suppress scrolling. - - durationThreshold: 1000, // More time than this, and it isn't a swipe. - - horizontalDistanceThreshold: 30, // Swipe horizontal displacement must be more than this. - - verticalDistanceThreshold: 75, // Swipe vertical displacement must be less than this. - - start: function( event ) { - var data = event.originalEvent.touches ? - event.originalEvent.touches[ 0 ] : event; - return { - time: ( new Date() ).getTime(), - coords: [ data.pageX, data.pageY ], - origin: $( event.target ) - }; - }, - - stop: function( event ) { - var data = event.originalEvent.touches ? - event.originalEvent.touches[ 0 ] : event; - return { - time: ( new Date() ).getTime(), - coords: [ data.pageX, data.pageY ] - }; - }, - - handleSwipe: function( start, stop ) { - if ( stop.time - start.time < $.event.special.swipe.durationThreshold && - Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.horizontalDistanceThreshold && - Math.abs( start.coords[ 1 ] - stop.coords[ 1 ] ) < $.event.special.swipe.verticalDistanceThreshold ) { - - start.origin.trigger( "swipe" ) - .trigger( start.coords[0] > stop.coords[ 0 ] ? "swipeleft" : "swiperight" ); - } - }, - - setup: function() { - var thisObject = this, - $this = $( thisObject ); - - $this.bind( touchStartEvent, function( event ) { - var start = $.event.special.swipe.start( event ), - stop; - - function moveHandler( event ) { - if ( !start ) { - return; - } - - stop = $.event.special.swipe.stop( event ); - - // prevent scrolling - if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.scrollSupressionThreshold ) { - event.preventDefault(); - } - } - - $this.bind( touchMoveEvent, moveHandler ) - .one( touchStopEvent, function() { - $this.unbind( touchMoveEvent, moveHandler ); - - if ( start && stop ) { - $.event.special.swipe.handleSwipe( start, stop ); - } - start = stop = undefined; - }); - }); - } - }; - $.each({ - scrollstop: "scrollstart", - taphold: "tap", - swipeleft: "swipe", - swiperight: "swipe" - }, function( event, sourceEvent ) { - - $.event.special[ event ] = { - setup: function() { - $( this ).bind( sourceEvent, $.noop ); - } - }; - }); - -})( jQuery, this ); - - - // throttled resize event - (function( $ ) { - $.event.special.throttledresize = { - setup: function() { - $( this ).bind( "resize", handler ); - }, - teardown: function() { - $( this ).unbind( "resize", handler ); - } - }; - - var throttle = 250, - handler = function() { - curr = ( new Date() ).getTime(); - diff = curr - lastCall; - - if ( diff >= throttle ) { - - lastCall = curr; - $( this ).trigger( "throttledresize" ); - - } else { - - if ( heldCall ) { - clearTimeout( heldCall ); - } - - // Promise a held call will still execute - heldCall = setTimeout( handler, throttle - diff ); - } - }, - lastCall = 0, - heldCall, - curr, - diff; - })( jQuery ); - -(function( $, window ) { - var win = $( window ), - event_name = "orientationchange", - special_event, - get_orientation, - last_orientation, - initial_orientation_is_landscape, - initial_orientation_is_default, - portrait_map = { "0": true, "180": true }; - - // It seems that some device/browser vendors use window.orientation values 0 and 180 to - // denote the "default" orientation. For iOS devices, and most other smart-phones tested, - // the default orientation is always "portrait", but in some Android and RIM based tablets, - // the default orientation is "landscape". The following code attempts to use the window - // dimensions to figure out what the current orientation is, and then makes adjustments - // to the to the portrait_map if necessary, so that we can properly decode the - // window.orientation value whenever get_orientation() is called. - // - // Note that we used to use a media query to figure out what the orientation the browser - // thinks it is in: - // - // initial_orientation_is_landscape = $.mobile.media("all and (orientation: landscape)"); - // - // but there was an iPhone/iPod Touch bug beginning with iOS 4.2, up through iOS 5.1, - // where the browser *ALWAYS* applied the landscape media query. This bug does not - // happen on iPad. - - if ( $.support.orientation ) { - - // Check the window width and height to figure out what the current orientation - // of the device is at this moment. Note that we've initialized the portrait map - // values to 0 and 180, *AND* we purposely check for landscape so that if we guess - // wrong, , we default to the assumption that portrait is the default orientation. - // We use a threshold check below because on some platforms like iOS, the iPhone - // form-factor can report a larger width than height if the user turns on the - // developer console. The actual threshold value is somewhat arbitrary, we just - // need to make sure it is large enough to exclude the developer console case. - - var ww = window.innerWidth || win.width(), - wh = window.innerHeight || win.height(), - landscape_threshold = 50; - - initial_orientation_is_landscape = ww > wh && ( ww - wh ) > landscape_threshold; - - - // Now check to see if the current window.orientation is 0 or 180. - initial_orientation_is_default = portrait_map[ window.orientation ]; - - // If the initial orientation is landscape, but window.orientation reports 0 or 180, *OR* - // if the initial orientation is portrait, but window.orientation reports 90 or -90, we - // need to flip our portrait_map values because landscape is the default orientation for - // this device/browser. - if ( ( initial_orientation_is_landscape && initial_orientation_is_default ) || ( !initial_orientation_is_landscape && !initial_orientation_is_default ) ) { - portrait_map = { "-90": true, "90": true }; - } - } - - $.event.special.orientationchange = $.extend( {}, $.event.special.orientationchange, { - setup: function() { - // If the event is supported natively, return false so that jQuery - // will bind to the event using DOM methods. - if ( $.support.orientation && !$.event.special.orientationchange.disabled ) { - return false; - } - - // Get the current orientation to avoid initial double-triggering. - last_orientation = get_orientation(); - - // Because the orientationchange event doesn't exist, simulate the - // event by testing window dimensions on resize. - win.bind( "throttledresize", handler ); - }, - teardown: function() { - // If the event is not supported natively, return false so that - // jQuery will unbind the event using DOM methods. - if ( $.support.orientation && !$.event.special.orientationchange.disabled ) { - return false; - } - - // Because the orientationchange event doesn't exist, unbind the - // resize event handler. - win.unbind( "throttledresize", handler ); - }, - add: function( handleObj ) { - // Save a reference to the bound event handler. - var old_handler = handleObj.handler; - - - handleObj.handler = function( event ) { - // Modify event object, adding the .orientation property. - event.orientation = get_orientation(); - - // Call the originally-bound event handler and return its result. - return old_handler.apply( this, arguments ); - }; - } - }); - - // If the event is not supported natively, this handler will be bound to - // the window resize event to simulate the orientationchange event. - function handler() { - // Get the current orientation. - var orientation = get_orientation(); - - if ( orientation !== last_orientation ) { - // The orientation has changed, so trigger the orientationchange event. - last_orientation = orientation; - win.trigger( event_name ); - } - } - - // Get the current page orientation. This method is exposed publicly, should it - // be needed, as jQuery.event.special.orientationchange.orientation() - $.event.special.orientationchange.orientation = get_orientation = function() { - var isPortrait = true, elem = document.documentElement; - - // prefer window orientation to the calculation based on screensize as - // the actual screen resize takes place before or after the orientation change event - // has been fired depending on implementation (eg android 2.3 is before, iphone after). - // More testing is required to determine if a more reliable method of determining the new screensize - // is possible when orientationchange is fired. (eg, use media queries + element + opacity) - if ( $.support.orientation ) { - // if the window orientation registers as 0 or 180 degrees report - // portrait, otherwise landscape - isPortrait = portrait_map[ window.orientation ]; - } else { - isPortrait = elem && elem.clientWidth / elem.clientHeight < 1.1; - } - - return isPortrait ? "portrait" : "landscape"; - }; - - $.fn[ event_name ] = function( fn ) { - return fn ? this.bind( event_name, fn ) : this.trigger( event_name ); - }; - - // jQuery < 1.8 - if ( $.attrFn ) { - $.attrFn[ event_name ] = true; - } - -}( jQuery, this )); - - - -(function( $, undefined ) { - -$.widget( "mobile.page", $.mobile.widget, { - options: { - theme: "c", - domCache: false, - keepNativeDefault: ":jqmData(role='none'), :jqmData(role='nojs')" - }, - - _create: function() { - // if false is returned by the callbacks do not create the page - if ( this._trigger( "beforecreate" ) === false ) { - return false; - } - - this.element - .attr( "tabindex", "0" ) - .addClass( "ui-page ui-body-" + this.options.theme ); - - this._on( this.element, { - pagebeforehide: "removeContainerBackground", - pagebeforeshow: "_handlePageBeforeShow" - }); - }, - - _handlePageBeforeShow: function( e ) { - this.setContainerBackground(); - }, - - removeContainerBackground: function() { - $.mobile.pageContainer.removeClass( "ui-overlay-" + $.mobile.getInheritedTheme( this.element.parent() ) ); - }, - - // set the page container background to the page theme - setContainerBackground: function( theme ) { - if ( this.options.theme ) { - $.mobile.pageContainer.addClass( "ui-overlay-" + ( theme || this.options.theme ) ); - } - }, - - keepNativeSelector: function() { - var options = this.options, - keepNativeDefined = options.keepNative && $.trim( options.keepNative ); - - if ( keepNativeDefined && options.keepNative !== options.keepNativeDefault ) { - return [options.keepNative, options.keepNativeDefault].join( ", " ); - } - - return options.keepNativeDefault; - } -}); -})( jQuery ); - -(function( $, window, undefined ) { - -var createHandler = function( sequential ) { - - // Default to sequential - if ( sequential === undefined ) { - sequential = true; - } - - return function( name, reverse, $to, $from ) { - - var deferred = new $.Deferred(), - reverseClass = reverse ? " reverse" : "", - active = $.mobile.urlHistory.getActive(), - toScroll = active.lastScroll || $.mobile.defaultHomeScroll, - screenHeight = $.mobile.getScreenHeight(), - maxTransitionOverride = $.mobile.maxTransitionWidth !== false && $.mobile.window.width() > $.mobile.maxTransitionWidth, - none = !$.support.cssTransitions || maxTransitionOverride || !name || name === "none" || Math.max( $.mobile.window.scrollTop(), toScroll ) > $.mobile.getMaxScrollForTransition(), - toPreClass = " ui-page-pre-in", - toggleViewportClass = function() { - $.mobile.pageContainer.toggleClass( "ui-mobile-viewport-transitioning viewport-" + name ); - }, - scrollPage = function() { - // By using scrollTo instead of silentScroll, we can keep things better in order - // Just to be precautios, disable scrollstart listening like silentScroll would - $.event.special.scrollstart.enabled = false; - - window.scrollTo( 0, toScroll ); - - // reenable scrollstart listening like silentScroll would - setTimeout( function() { - $.event.special.scrollstart.enabled = true; - }, 150 ); - }, - cleanFrom = function() { - $from - .removeClass( $.mobile.activePageClass + " out in reverse " + name ) - .height( "" ); - }, - startOut = function() { - // if it's not sequential, call the doneOut transition to start the TO page animating in simultaneously - if ( !sequential ) { - doneOut(); - } - else { - $from.animationComplete( doneOut ); - } - - // Set the from page's height and start it transitioning out - // Note: setting an explicit height helps eliminate tiling in the transitions - $from - .height( screenHeight + $.mobile.window.scrollTop() ) - .addClass( name + " out" + reverseClass ); - }, - - doneOut = function() { - - if ( $from && sequential ) { - cleanFrom(); - } - - startIn(); - }, - - startIn = function() { - - // Prevent flickering in phonegap container: see comments at #4024 regarding iOS - $to.css( "z-index", -10 ); - - $to.addClass( $.mobile.activePageClass + toPreClass ); - - // Send focus to page as it is now display: block - $.mobile.focusPage( $to ); - - // Set to page height - $to.height( screenHeight + toScroll ); - - scrollPage(); - - // Restores visibility of the new page: added together with $to.css( "z-index", -10 ); - $to.css( "z-index", "" ); - - if ( !none ) { - $to.animationComplete( doneIn ); - } - - $to - .removeClass( toPreClass ) - .addClass( name + " in" + reverseClass ); - - if ( none ) { - doneIn(); - } - - }, - - doneIn = function() { - - if ( !sequential ) { - - if ( $from ) { - cleanFrom(); - } - } - - $to - .removeClass( "out in reverse " + name ) - .height( "" ); - - toggleViewportClass(); - - // In some browsers (iOS5), 3D transitions block the ability to scroll to the desired location during transition - // This ensures we jump to that spot after the fact, if we aren't there already. - if ( $.mobile.window.scrollTop() !== toScroll ) { - scrollPage(); - } - - deferred.resolve( name, reverse, $to, $from, true ); - }; - - toggleViewportClass(); - - if ( $from && !none ) { - startOut(); - } - else { - doneOut(); - } - - return deferred.promise(); - }; -}; - -// generate the handlers from the above -var sequentialHandler = createHandler(), - simultaneousHandler = createHandler( false ), - defaultGetMaxScrollForTransition = function() { - return $.mobile.getScreenHeight() * 3; - }; - -// Make our transition handler the public default. -$.mobile.defaultTransitionHandler = sequentialHandler; - -//transition handler dictionary for 3rd party transitions -$.mobile.transitionHandlers = { - "default": $.mobile.defaultTransitionHandler, - "sequential": sequentialHandler, - "simultaneous": simultaneousHandler -}; - -$.mobile.transitionFallbacks = {}; - -// If transition is defined, check if css 3D transforms are supported, and if not, if a fallback is specified -$.mobile._maybeDegradeTransition = function( transition ) { - if ( transition && !$.support.cssTransform3d && $.mobile.transitionFallbacks[ transition ] ) { - transition = $.mobile.transitionFallbacks[ transition ]; - } - - return transition; -}; - -// Set the getMaxScrollForTransition to default if no implementation was set by user -$.mobile.getMaxScrollForTransition = $.mobile.getMaxScrollForTransition || defaultGetMaxScrollForTransition; -})( jQuery, this ); - -(function( $, undefined ) { - - //define vars for interal use - var $window = $.mobile.window, - $html = $( 'html' ), - $head = $( 'head' ), - - // NOTE: path extensions dependent on core attributes. Moved here to remove deps from - // $.mobile.path definition - path = $.extend($.mobile.path, { - - //return the substring of a filepath before the sub-page key, for making a server request - getFilePath: function( path ) { - var splitkey = '&' + $.mobile.subPageUrlKey; - return path && path.split( splitkey )[0].split( dialogHashKey )[0]; - }, - - //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, this.documentBase ) ), - - // Does the url have the same path as the document? - samePath = u.hrefNoHash === this.documentUrl.hrefNoHash || ( this.documentBaseDiffers && u.hrefNoHash === this.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 ) ); - }, - - // Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR - // requests if the document doing the request was loaded via the file:// protocol. - // This is usually to allow the application to "phone home" and fetch app specific - // data. We normally let the browser handle external/cross-domain urls, but if the - // allowCrossDomainPages option is true, we will allow cross-domain http/https - // requests to go through our page loading logic. - isPermittedCrossDomainRequest: function( docUrl, reqUrl ) { - return $.mobile.allowCrossDomainPages && - docUrl.protocol === "file:" && - reqUrl.search( /^https?:/ ) !== -1; - } - }), - - // used to track last vclicked element to make sure its value is added to form data - $lastVClicked = null, - - //will be defined when a link is clicked and given an active class - $activeClickedLink = null, - - // resolved on domready - domreadyDeferred = $.Deferred(), - - //urlHistory is purely here to make guesses at whether the back or forward button was clicked - //and provide an appropriate transition - urlHistory = $.mobile.navigate.history, - - //define first selector to receive focus when a page is shown - focusable = "[tabindex],a,button:visible,select:visible,input", - - //queue to hold simultanious page transitions - pageTransitionQueue = [], - - //indicates whether or not page is in process of transitioning - isPageTransitioning = false, - - //nonsense hash change key for dialogs, so they create a history entry - dialogHashKey = "&ui-state=dialog", - - //existing base tag? - $base = $head.children( "base" ), - - //tuck away the original document URL minus any fragment. - documentUrl = path.documentUrl, - - //if the document has an embedded base tag, documentBase is set to its - //initial value. If a base tag does not exist, then we default to the documentUrl. - documentBase = path.documentBase, - - //cache the comparison once. - documentBaseDiffers = path.documentBaseDiffers, - - getScreenHeight = $.mobile.getScreenHeight; - - //base element management, defined depending on dynamic base tag support - var base = $.support.dynamicBaseTag ? { - - //define base element, for use in routing asset urls that are referenced in Ajax-requested markup - element: ( $base.length ? $base : $( "", { href: documentBase.hrefNoHash } ).prependTo( $head ) ), - - //set the generated BASE element's href attribute to a new page's base path - set: function( href ) { - href = path.parseUrl(href).hrefNoHash; - base.element.attr( "href", path.makeUrlAbsolute( href, documentBase ) ); - }, - - //set the generated BASE element's href attribute to a new page's base path - reset: function() { - base.element.attr( "href", documentBase.hrefNoSearch ); - } - - } : undefined; - - - //return the original document url - $.mobile.getDocumentUrl = path.getDocumentUrl; - - //return the original document base url - $.mobile.getDocumentBase = path.getDocumentBase; - - /* internal utility functions */ - - // NOTE Issue #4950 Android phonegap doesn't navigate back properly - // when a full page refresh has taken place. It appears that hashchange - // and replacestate history alterations work fine but we need to support - // both forms of history traversal in our code that uses backward history - // movement - $.mobile.back = function() { - var nav = window.navigator; - - // if the setting is on and the navigator object is - // available use the phonegap navigation capability - if( this.phonegapNavigationEnabled && - nav && - nav.app && - nav.app.backHistory ){ - nav.app.backHistory(); - } else { - window.history.back(); - } - }; - - //direct focus to the page title, or otherwise first focusable element - $.mobile.focusPage = function ( page ) { - var autofocus = page.find( "[autofocus]" ), - pageTitle = page.find( ".ui-title:eq(0)" ); - - if ( autofocus.length ) { - autofocus.focus(); - return; - } - - if ( pageTitle.length ) { - pageTitle.focus(); - } else{ - page.focus(); - } - }; - - //remove active classes after page transition or error - function removeActiveLinkClass( forceRemoval ) { - if ( !!$activeClickedLink && ( !$activeClickedLink.closest( "." + $.mobile.activePageClass ).length || forceRemoval ) ) { - $activeClickedLink.removeClass( $.mobile.activeBtnClass ); - } - $activeClickedLink = null; - } - - function releasePageTransitionLock() { - isPageTransitioning = false; - if ( pageTransitionQueue.length > 0 ) { - $.mobile.changePage.apply( null, pageTransitionQueue.pop() ); - } - } - - // Save the last scroll distance per page, before it is hidden - var setLastScrollEnabled = true, - setLastScroll, delayedSetLastScroll; - - setLastScroll = function() { - // 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 = $window.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 ); - }; - - // 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() { - - 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 - $window.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 - $window.bind( "scrollstop", delayedSetLastScroll ); - }); - }); - - // bind to scrollstop for the first page as "pagechange" won't be fired in that case - $window.bind( "scrollstop", delayedSetLastScroll ); - - // No-op implementation of transition degradation - $.mobile._maybeDegradeTransition = $.mobile._maybeDegradeTransition || function( transition ) { - return transition; - }; - - //function for transitioning between two existing pages - function transitionPages( toPage, fromPage, transition, reverse ) { - if ( fromPage ) { - //trigger before show/hide events - fromPage.data( "mobile-page" )._trigger( "beforehide", null, { nextPage: toPage } ); - } - - toPage.data( "mobile-page" )._trigger( "beforeshow", null, { prevPage: fromPage || $( "" ) } ); - - //clear page loader - $.mobile.hidePageLoadingMsg(); - - transition = $.mobile._maybeDegradeTransition( transition ); - - //find the transition handler for the specified transition. If there - //isn't one in our transitionHandlers dictionary, use the default one. - //call the handler immediately to kick-off the transition. - var th = $.mobile.transitionHandlers[ transition || "default" ] || $.mobile.defaultTransitionHandler, - promise = th( transition, reverse, toPage, fromPage ); - - promise.done(function() { - //trigger show/hide events - if ( fromPage ) { - fromPage.data( "mobile-page" )._trigger( "hide", null, { nextPage: toPage } ); - } - - //trigger pageshow, define prevPage as either fromPage or empty jQuery obj - toPage.data( "mobile-page" )._trigger( "show", null, { prevPage: fromPage || $( "" ) } ); - }); - - return promise; - } - - //simply set the active page's minimum height to screen height, depending on orientation - $.mobile.resetActivePageHeight = function resetActivePageHeight( height ) { - var aPage = $( "." + $.mobile.activePageClass ), - aPagePadT = parseFloat( aPage.css( "padding-top" ) ), - aPagePadB = parseFloat( aPage.css( "padding-bottom" ) ), - aPageBorderT = parseFloat( aPage.css( "border-top-width" ) ), - aPageBorderB = parseFloat( aPage.css( "border-bottom-width" ) ); - - height = ( typeof height === "number" )? height : getScreenHeight(); - - aPage.css( "min-height", height - aPagePadT - aPagePadB - aPageBorderT - aPageBorderB ); - }; - - //shared page enhancements - function enhancePage( $page, role ) { - // If a role was specified, make sure the data-role attribute - // on the page element is in sync. - if ( role ) { - $page.attr( "data-" + $.mobile.ns + "role", role ); - } - - //run page plugin - $page.page(); - } - - // determine the current base url - function findBaseWithDefault() { - var closestBase = ( $.mobile.activePage && getClosestBaseUrl( $.mobile.activePage ) ); - return closestBase || documentBase.hrefNoHash; - } - - /* exposed $.mobile methods */ - - //animation complete callback - $.fn.animationComplete = function( callback ) { - if ( $.support.cssTransitions ) { - return $( this ).one( 'webkitAnimationEnd animationend', callback ); - } - else{ - // defer execution for consistency between webkit/non webkit - setTimeout( callback, 0 ); - return $( this ); - } - }; - - //expose path object on $.mobile - $.mobile.path = path; - - //expose base object on $.mobile - $.mobile.base = base; - - //history stack - $.mobile.urlHistory = urlHistory; - - $.mobile.dialogHashKey = dialogHashKey; - - //enable cross-domain page support - $.mobile.allowCrossDomainPages = false; - - $.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( "mobile-page" ).options.domCache && - page.is( ":jqmData(external-page='true')" ) ) { - - page.bind( 'pagehide.remove', function( e ) { - var $this = $( this ), - prEvent = new $.Event( "pageremove" ); - - $this.trigger( prEvent ); - - if ( !prEvent.isDefaultPrevented() ) { - $this.removeWithDependents(); - } - }); - } - }; - - // Load a page into the DOM. - $.mobile.loadPage = function( url, options ) { - // This function uses deferred notifications to let callers - // know when the page is done loading, or if an error has occurred. - var deferred = $.Deferred(), - - // The default loadPage options with overrides specified by - // the caller. - settings = $.extend( {}, $.mobile.loadPage.defaults, options ), - - // The DOM element for the page after it has been loaded. - page = null, - - // If the reloadPage option is true, and the page is already - // in the DOM, dupCachedPage will be set to the page element - // so that it can be removed after the new version of the - // page is loaded off the network. - dupCachedPage = null, - - // The absolute version of the URL passed into the function. This - // version of the URL may contain dialog/subpage params in it. - absUrl = path.makeUrlAbsolute( url, findBaseWithDefault() ); - - // If the caller provided data, and we're using "get" request, - // append the data to the URL. - 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. - // In otherwords the real URL of the page to be loaded. - var fileUrl = path.getFilePath( absUrl ), - - // The version of the Url actually stored in the data-url attribute of - // the page. For embedded pages, it is just the id of the page. For pages - // within the same domain as the document base, it is the site relative - // path. For cross-domain pages (Phone Gap only) the entire absolute Url - // used to load the page. - dataUrl = path.convertUrlToDataUrl( absUrl ); - - // Make sure we have a pageContainer to work with. - settings.pageContainer = settings.pageContainer || $.mobile.pageContainer; - - // Check to see if the page already exists in the DOM. - // NOTE do _not_ use the :jqmData psuedo selector because parenthesis - // are a valid url char and it breaks on the first occurence - page = settings.pageContainer.children( "[data-" + $.mobile.ns +"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 && dataUrl && !path.isPath( dataUrl ) ) { - page = settings.pageContainer.children( "#" + dataUrl ) - .attr( "data-" + $.mobile.ns + "url", dataUrl ) - .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 it isn't a reference - // to the first page and refers to non-existent embedded page, error out. - if ( page.length === 0 ) { - if ( $.mobile.firstPage && path.isFirstPageUrl( fileUrl ) ) { - // Check to make sure our cached-first-page is actually - // in the DOM. Some user deployed apps are pruning the first - // page from the DOM for various reasons, we check for this - // case here because we don't want a first-page with an id - // falling through to the non-existent embedded page error - // case. If the first-page is not in the DOM, then we let - // things fall through to the ajax loading code below so - // that it gets reloaded. - if ( $.mobile.firstPage.parent().length ) { - page = $( $.mobile.firstPage ); - } - } else if ( path.isEmbeddedPage( fileUrl ) ) { - deferred.reject( absUrl, options ); - return deferred.promise(); - } - } - - // If the page we are interested in is already in the DOM, - // and the caller did not indicate that we should force a - // reload of the file, we are done. Otherwise, track the - // existing page as a duplicated. - if ( page.length ) { - if ( !settings.reloadPage ) { - enhancePage( page, settings.role ); - deferred.resolve( absUrl, options, page ); - //if we are reloading the page make sure we update the base if its not a prefetch - if( base && !options.prefetch ){ - base.set(url); - } - 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 ) { - - // 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(); - }; - } - // Reset base to the default document base. - // only reset if we are not prefetching - if ( base && ( typeof options === "undefined" || typeof options.prefetch === "undefined" ) ) { - base.reset(); - } - - if ( !( $.mobile.allowCrossDomainPages || path.isSameDomain( documentUrl, absUrl ) ) ) { - deferred.reject( absUrl, options ); - } else { - // Load the new page. - $.ajax({ - url: fileUrl, - type: settings.type, - data: settings.data, - contentType: settings.contentType, - dataType: "html", - success: function( html, textStatus, xhr ) { - //pre-parse html to check for a data-url, - //use it as the new fileUrl, base path, etc - var all = $( "
" ), - - //page title regexp - newPageTitle = html.match( /]*>([^<]*)/ ) && RegExp.$1, - - // TODO handle dialogs again - pageElemRegex = new RegExp( "(<[^>]+\\bdata-" + $.mobile.ns + "role=[\"']?page[\"']?[^>]*>)" ), - dataUrlRegex = new RegExp( "\\bdata-" + $.mobile.ns + "url=[\"']?([^\"'>]*)[\"']?" ); - - - // data-url must be provided for the base tag so resource requests can be directed to the - // correct url. loading into a temprorary element makes these requests immediately - if ( pageElemRegex.test( html ) && - RegExp.$1 && - dataUrlRegex.test( RegExp.$1 ) && - RegExp.$1 ) { - url = fileUrl = path.getFilePath( $( "
" + RegExp.$1 + "
" ).text() ); - } - //dont update the base tag if we are prefetching - if ( base && ( typeof options === "undefined" || typeof options.prefetch === "undefined" )) { - base.set( fileUrl ); - } - - //workaround to allow scripts to execute when included in page divs - all.get( 0 ).innerHTML = html; - page = all.find( ":jqmData(role='page'), :jqmData(role='dialog')" ).first(); - - //if page elem couldn't be found, create one and insert the body element's contents - if ( !page.length ) { - page = $( "
" + ( html.split( /<\/?body[^>]*>/gmi )[1] || "" ) + "
" ); - } - - if ( newPageTitle && !page.jqmData( "title" ) ) { - if ( ~newPageTitle.indexOf( "&" ) ) { - newPageTitle = $( "
" + newPageTitle + "
" ).text(); - } - page.jqmData( "title", newPageTitle ); - } - - //rewrite src and href attrs to use a base url - if ( !$.support.dynamicBaseTag ) { - var newPath = path.get( fileUrl ); - page.find( "[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]" ).each(function() { - var thisAttr = $( this ).is( '[href]' ) ? 'href' : - $( this ).is( '[src]' ) ? 'src' : 'action', - thisUrl = $( this ).attr( thisAttr ); - - // XXX_jblas: We need to fix this so that it removes the document - // base URL, and then prepends with the new page URL. - //if full path exists and is same, chop it - helps IE out - thisUrl = thisUrl.replace( location.protocol + '//' + location.host + location.pathname, '' ); - - if ( !/^(\w+:|#|\/)/.test( thisUrl ) ) { - $( this ).attr( thisAttr, newPath + thisUrl ); - } - }); - } - - //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', $.mobile._bindPageRemove ); - - enhancePage( page, settings.role ); - - // Enhancing the page may result in new dialogs/sub pages being inserted - // into the DOM. If the original absUrl refers to a sub-page, that is the - // real page we are interested in. - if ( absUrl.indexOf( "&" + $.mobile.subPageUrlKey ) > -1 ) { - page = settings.pageContainer.children( "[data-" + $.mobile.ns +"url='" + dataUrl + "']" ); - } - - // Remove loading message. - if ( settings.showLoadMsg ) { - hideMsg(); - } - - // Add the page reference and xhr to our triggerData. - triggerData.xhr = xhr; - triggerData.textStatus = textStatus; - triggerData.page = page; - - // Let listeners know the page loaded successfully. - settings.pageContainer.trigger( "pageload", triggerData ); - - deferred.resolve( absUrl, options, page, dupCachedPage ); - }, - error: function( xhr, textStatus, errorThrown ) { - //set base back to current path - if ( base ) { - base.set( path.get() ); - } - - // Add error info to our triggerData. - triggerData.xhr = xhr; - triggerData.textStatus = textStatus; - triggerData.errorThrown = errorThrown; - - 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. - if ( settings.showLoadMsg ) { - - // Remove loading message. - hideMsg(); - - // show error message - $.mobile.showPageLoadingMsg( $.mobile.pageLoadErrorMessageTheme, $.mobile.pageLoadErrorMessage, true ); - - // hide after delay - setTimeout( $.mobile.hidePageLoadingMsg, 1500 ); - } - - deferred.reject( absUrl, options ); - } - }); - } - - return deferred.promise(); - }; - - $.mobile.loadPage.defaults = { - type: "get", - data: undefined, - reloadPage: false, - role: undefined, // By default we rely on the role defined by the @data-role attribute. - showLoadMsg: false, - pageContainer: undefined, - loadMsgDelay: 50 // This delay allows loads that pull from browser cache to occur without showing the loading message. - }; - - // Show a specific page in the page container. - $.mobile.changePage = function( toPage, options ) { - // 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. - if ( isPageTransitioning ) { - pageTransitionQueue.unshift( arguments ); - return; - } - - var settings = $.extend( {}, $.mobile.changePage.defaults, options ), isToPageString; - - // 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; - - isToPageString = (typeof toPage === "string"); - - var mpc = settings.pageContainer, - pbcEvent = new $.Event( "pagebeforechange" ), - triggerData = { toPage: toPage, options: settings }; - - // NOTE: preserve the original target as the dataUrl value will be simplified - // eg, removing ui-state, and removing query params from the hash - // this is so that users who want to use query params have access to them - // in the event bindings for the page life cycle See issue #5085 - if ( isToPageString ) { - // if the toPage is a string simply convert it - triggerData.absUrl = path.makeUrlAbsolute( toPage, findBaseWithDefault() ); - } else { - // if the toPage is a jQuery object grab the absolute url stored - // in the loadPage callback where it exists - triggerData.absUrl = toPage.data( 'absUrl' ); - } - - // Let listeners know we're about to change the current page. - mpc.trigger( pbcEvent, triggerData ); - - // 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. - // - // We also need to re-evaluate whether it is a string, because an object can - // also be replaced by a string - - toPage = triggerData.toPage; - isToPageString = (typeof toPage === "string"); - - // 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; - - // If the caller passed us a url, call loadPage() - // to make sure it is loaded into the DOM. We'll listen - // to the promise object it returns so we know when - // it is done loading or if an error ocurred. - if ( isToPageString ) { - // preserve the original target as the dataUrl value will be simplified - // eg, removing ui-state, and removing query params from the hash - // this is so that users who want to use query params have access to them - // in the event bindings for the page life cycle See issue #5085 - settings.target = toPage; - - $.mobile.loadPage( toPage, settings ) - .done(function( url, options, newPage, dupCachedPage ) { - isPageTransitioning = false; - options.duplicateCachedPage = dupCachedPage; - - // store the original absolute url so that it can be provided - // to events in the triggerData of the subsequent changePage call - newPage.data( 'absUrl', triggerData.absUrl ); - $.mobile.changePage( newPage, options ); - }) - .fail(function( url, options ) { - - //clear out the active button state - removeActiveLinkClass( true ); - - //release transition lock so navigation is free again - releasePageTransitionLock(); - settings.pageContainer.trigger( "pagechangefailed", triggerData ); - }); - return; - } - - // If we are going to the first-page of the application, we need to make - // sure settings.dataUrl is set to the application document url. This allows - // us to avoid generating a document url with an id hash in the case where the - // first-page of the document has an id attribute specified. - if ( toPage[ 0 ] === $.mobile.firstPage[ 0 ] && !settings.dataUrl ) { - settings.dataUrl = documentUrl.hrefNoHash; - } - - // The caller passed us a real page DOM element. Update our - // internal state and then trigger a transition to the page. - 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 ), - active = urlHistory.getActive(), - activeIsInitialPage = urlHistory.activeIndex === 0, - historyDir = 0, - pageTitle = document.title, - isDialog = settings.role === "dialog" || toPage.jqmData( "role" ) === "dialog"; - - - // 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 ); - - // Even if there is no page change to be done, we should keep the urlHistory in sync with the hash changes - if ( settings.fromHashChange ) { - urlHistory.direct({ url: url }); - } - - return; - } - - // We need to make sure the page we are given has already been enhanced. - enhancePage( toPage, settings.role ); - - // If the changePage request was sent from a hashChange event, check to see if the - // page is already within the urlHistory stack. If so, we'll assume the user hit - // the forward/back button and will try to match the transition accordingly. - if ( settings.fromHashChange ) { - historyDir = options.direction === "back" ? -1 : 1; - } - - // Kill the keyboard. - // XXX_jblas: We need to stop crawling the entire document to kill focus. Instead, - // we should be tracking focus with a delegate() handler so we already have - // the element in hand at this point. - // Wrap this in a try/catch block since IE9 throw "Unspecified error" if document.activeElement - // is undefined when we are in an IFrame. - try { - if ( document.activeElement && document.activeElement.nodeName.toLowerCase() !== 'body' ) { - $( document.activeElement ).blur(); - } else { - $( "input:focus, textarea:focus, select:focus" ).blur(); - } - } catch( e ) {} - - // Record whether we are at a place in history where a dialog used to be - if so, do not add a new history entry and do not change the hash either - var alreadyThere = false; - - // If we're displaying the page as a dialog, we don't want the url - // 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 ) { - // 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 - - // If we are at a place in history that once belonged to a dialog, reuse - // this state without adding to urlHistory and without modifying the hash. - // However, if a dialog is already displayed at this point, and we're - // about to display another dialog, then we must add another hash and - // history entry on top so that one may navigate back to the original dialog - if ( active.url && - active.url.indexOf( dialogHashKey ) > -1 && - $.mobile.activePage && - !$.mobile.activePage.is( ".ui-dialog" ) && - urlHistory.activeIndex > 0 ) { - settings.changeHash = false; - alreadyThere = true; - } - - // Normally, we tack on a dialog hash key, but if this is the location of a stale dialog, - // we reuse the URL from the entry - url = ( active.url || "" ); - - // account for absolute urls instead of just relative urls use as hashes - if( !alreadyThere && url.indexOf("#") > -1 ) { - url += dialogHashKey; - } else { - url += "#" + dialogHashKey; - } - - // tack on another dialogHashKey if this is the same as the initial hash - // this makes sure that a history entry is created for this dialog - if ( urlHistory.activeIndex === 0 && url === urlHistory.initialDst ) { - url += dialogHashKey; - } - } - - // if title element wasn't found, try the page div data attr too - // If this is a deep-link or a reload ( active === undefined ) then just use pageTitle - var newPageTitle = ( !active )? pageTitle : toPage.jqmData( "title" ) || toPage.children( ":jqmData(role='header')" ).find( ".ui-title" ).text(); - if ( !!newPageTitle && pageTitle === document.title ) { - pageTitle = newPageTitle; - } - if ( !toPage.jqmData( "title" ) ) { - toPage.jqmData( "title", pageTitle ); - } - - // Make sure we have a transition defined. - settings.transition = settings.transition || - ( ( historyDir && !activeIsInitialPage ) ? active.transition : undefined ) || - ( isDialog ? $.mobile.defaultDialogTransition : $.mobile.defaultPageTransition ); - - //add page to history stack if it's not back or forward - if ( !historyDir && alreadyThere ) { - urlHistory.getActive().pageUrl = pageUrl; - } - - // Set the location hash. - if ( url && !settings.fromHashChange ) { - var params; - - // rebuilding the hash here since we loose it earlier on - // TODO preserve the originally passed in path - if( !path.isPath( url ) && url.indexOf( "#" ) < 0 ) { - url = "#" + url; - } - - // TODO the property names here are just silly - params = { - transition: settings.transition, - title: pageTitle, - pageUrl: pageUrl, - role: settings.role - }; - - if ( settings.changeHash !== false && $.mobile.hashListeningEnabled ) { - $.mobile.navigate( url, params, true); - } else if ( toPage[ 0 ] !== $.mobile.firstPage[ 0 ] ) { - $.mobile.navigate.history.add( url, params ); - } - } - - //set page title - document.title = pageTitle; - - //set "toPage" as activePage - $.mobile.activePage = toPage; - - // If we're navigating back in the URL history, set reverse accordingly. - settings.reverse = settings.reverse || historyDir < 0; - - transitionPages( toPage, fromPage, settings.transition, settings.reverse ) - .done(function( name, reverse, $to, $from, alreadyFocused ) { - removeActiveLinkClass(); - - //if there's a duplicateCachedPage, remove it from the DOM now that it's hidden - if ( settings.duplicateCachedPage ) { - settings.duplicateCachedPage.remove(); - } - - // Send focus to the newly shown page. Moved from promise .done binding in transitionPages - // itself to avoid ie bug that reports offsetWidth as > 0 (core check for visibility) - // despite visibility: hidden addresses issue #2965 - // https://github.com/jquery/jquery-mobile/issues/2965 - if ( !alreadyFocused ) { - $.mobile.focusPage( toPage ); - } - - releasePageTransitionLock(); - mpc.trigger( "pagechange", triggerData ); - }); - }; - - $.mobile.changePage.defaults = { - transition: undefined, - reverse: false, - changeHash: true, - fromHashChange: false, - 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 - dataUrl: undefined, - fromPage: undefined, - allowSamePageTransition: false - }; - -/* Event Bindings - hashchange, submit, and click */ - function findClosestLink( ele ) - { - while ( ele ) { - // Look for the closest element with a nodeName of "a". - // Note that we are checking if we have a valid nodeName - // before attempting to access it. This is because the - // node we get called with could have originated from within - // an embedded SVG document where some symbol instance elements - // don't have nodeName defined on them, or strings are of type - // SVGAnimatedString. - if ( ( typeof ele.nodeName === "string" ) && ele.nodeName.toLowerCase() === "a" ) { - break; - } - ele = ele.parentNode; - } - return ele; - } - - // The base URL for any given element depends on the page it resides in. - function getClosestBaseUrl( ele ) - { - // Find the closest page and extract out its url. - var url = $( ele ).closest( ".ui-page" ).jqmData( "url" ), - base = documentBase.hrefNoHash; - - if ( !url || !path.isPath( url ) ) { - url = base; - } - - return path.makeUrlAbsolute( url, base); - } - - //The following event bindings should be bound after mobileinit has been triggered - //the following deferred is resolved in the init file - $.mobile.navreadyDeferred = $.Deferred(); - $.mobile._registerInternalEvents = function() { - var getAjaxFormData = function( $form, calculateOnly ) { - var url, ret = true, formData, vclickedName, method; - - if ( !$.mobile.ajaxEnabled || - // test that the form is, itself, ajax false - $form.is( ":jqmData(ajax='false')" ) || - // test that $.mobile.ignoreContentEnabled is set and - // the form or one of it's parents is ajax=false - !$form.jqmHijackable().length || - $form.attr( "target" ) ) { - return false; - } - - url = $form.attr( "action" ); - method = ( $form.attr( "method" ) || "get" ).toLowerCase(); - - // If no action is specified, browsers default to using the - // URL of the document containing the form. Since we dynamically - // pull in pages from external documents, the form should submit - // to the URL for the source document of the page containing - // the form. - if ( !url ) { - // Get the @data-url for the page containing the form. - url = getClosestBaseUrl( $form ); - - // NOTE: If the method is "get", we need to strip off the query string - // because it will get replaced with the new form data. See issue #5710. - if ( method === "get" ) { - url = path.parseUrl( url ).hrefNoSearch; - } - - if ( url === documentBase.hrefNoHash ) { - // The url we got back matches the document base, - // which means the page must be an internal/embedded page, - // so default to using the actual document url as a browser - // would. - url = documentUrl.hrefNoSearch; - } - } - - url = path.makeUrlAbsolute( url, getClosestBaseUrl( $form ) ); - - if ( ( path.isExternal( url ) && !path.isPermittedCrossDomainRequest( documentUrl, url ) ) ) { - return false; - } - - if ( !calculateOnly ) { - formData = $form.serializeArray(); - - if ( $lastVClicked && $lastVClicked[ 0 ].form === $form[ 0 ] ) { - vclickedName = $lastVClicked.attr( "name" ); - if ( vclickedName ) { - // Make sure the last clicked element is included in the form - $.each( formData, function( key, value ) { - if ( value.name === vclickedName ) { - // Unset vclickedName - we've found it in the serialized data already - vclickedName = ""; - return false; - } - }); - if ( vclickedName ) { - formData.push( { name: vclickedName, value: $lastVClicked.attr( "value" ) } ); - } - } - } - - ret = { - url: url, - options: { - type: method, - data: $.param( formData ), - transition: $form.jqmData( "transition" ), - reverse: $form.jqmData( "direction" ) === "reverse", - reloadPage: true - } - }; - } - - return ret; - }; - - //bind to form submit events, handle with Ajax - $.mobile.document.delegate( "form", "submit", function( event ) { - var formData = getAjaxFormData( $( this ) ); - - if ( formData ) { - $.mobile.changePage( formData.url, formData.options ); - event.preventDefault(); - } - }); - - //add active state on vclick - $.mobile.document.bind( "vclick", function( event ) { - var $btn, btnEls, target = event.target, needClosest = false; - // 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 the which attr - if ( event.which > 1 || !$.mobile.linkBindingEnabled ) { - return; - } - - // Record that this element was clicked, in case we need it for correct - // form submission during the "submit" handler above - $lastVClicked = $( target ); - - // Try to find a target element to which the active class will be applied - if ( $.data( target, "mobile-button" ) ) { - // If the form will not be submitted via AJAX, do not add active class - if ( !getAjaxFormData( $( target ).closest( "form" ), true ) ) { - return; - } - // We will apply the active state to this button widget - the parent - // of the input that was clicked will have the associated data - if ( target.parentNode ) { - target = target.parentNode; - } - } else { - target = findClosestLink( target ); - if ( !( target && path.parseUrl( target.getAttribute( "href" ) || "#" ).hash !== "#" ) ) { - return; - } - - // TODO teach $.mobile.hijackable to operate on raw dom elements so the - // link wrapping can be avoided - if ( !$( target ).jqmHijackable().length ) { - return; - } - } - - // Avoid calling .closest by using the data set during .buttonMarkup() - // List items have the button data in the parent of the element clicked - if ( !!~target.className.indexOf( "ui-link-inherit" ) ) { - if ( target.parentNode ) { - btnEls = $.data( target.parentNode, "buttonElements" ); - } - // Otherwise, look for the data on the target itself - } else { - btnEls = $.data( target, "buttonElements" ); - } - // If found, grab the button's outer element - if ( btnEls ) { - target = btnEls.outer; - } else { - needClosest = true; - } - - $btn = $( target ); - // If the outer element wasn't found by the our heuristics, use .closest() - if ( needClosest ) { - $btn = $btn.closest( ".ui-btn" ); - } - - if ( $btn.length > 0 && !$btn.hasClass( "ui-disabled" ) ) { - removeActiveLinkClass( true ); - $activeClickedLink = $btn; - $activeClickedLink.addClass( $.mobile.activeBtnClass ); - } - }); - - // click routing - direct to HTTP or Ajax, accordingly - $.mobile.document.bind( "click", function( event ) { - if ( !$.mobile.linkBindingEnabled || event.isDefaultPrevented() ) { - return; - } - - var link = findClosestLink( event.target ), $link = $( link ), httpCleanup; - - // If there is no link associated with the click or its not a left - // click we want to ignore the click - // TODO teach $.mobile.hijackable to operate on raw dom elements so the link wrapping - // can be avoided - if ( !link || event.which > 1 || !$link.jqmHijackable().length ) { - return; - } - - //remove active link class if external (then it won't be there if you come back) - httpCleanup = function() { - window.setTimeout(function() { removeActiveLinkClass( true ); }, 200 ); - }; - - //if there's a data-rel=back attr, go back in history - if ( $link.is( ":jqmData(rel='back')" ) ) { - $.mobile.back(); - 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 && !path.isEmbeddedPage( href ) ) { - httpCleanup(); - //use default click handling - return; - } - - // XXX_jblas: Ideally links to application pages should be specified as - // an url to the application document with a hash that is either - // the site relative path or id to the page. But some of the - // internal code that dynamically generates sub-pages for nested - // lists and select dialogs, just write a hash in the link they - // create. This means the actual URL path is based on whatever - // the current value of the base tag is at the time this code - // is called. For now we are just assuming that any url with a - // hash in it is an application page reference. - if ( href.search( "#" ) !== -1 ) { - href = href.replace( /[^#]*#/, "" ); - if ( !href ) { - //link was an empty hash meant purely - //for interaction, so we ignore it. - event.preventDefault(); - return; - } else if ( path.isPath( href ) ) { - //we have apath so make it the href we want to load. - href = path.makeUrlAbsolute( href, baseUrl ); - } else { - //we have a simple id so use the documentUrl as its base. - href = path.makeUrlAbsolute( "#" + href, documentUrl.hrefNoHash ); - } - } - - // Should we handle this link, or let the browser deal with it? - var useDefaultUrlHandling = $link.is( "[rel='external']" ) || $link.is( ":jqmData(ajax='false')" ) || $link.is( "[target]" ), - - // Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR - // requests if the document doing the request was loaded via the file:// protocol. - // This is usually to allow the application to "phone home" and fetch app specific - // data. We normally let the browser handle external/cross-domain urls, but if the - // allowCrossDomainPages option is true, we will allow cross-domain http/https - // requests to go through our page loading logic. - - //check for protocol or rel and its not an embedded page - //TODO overlap in logic from isExternal, rel=external check should be - // moved into more comprehensive isExternalLink - isExternal = useDefaultUrlHandling || ( path.isExternal( href ) && !path.isPermittedCrossDomainRequest( documentUrl, href ) ); - - if ( isExternal ) { - httpCleanup(); - //use default click handling - return; - } - - //use ajax - var transition = $link.jqmData( "transition" ), - reverse = $link.jqmData( "direction" ) === "reverse" || - // deprecated - remove by 1.0 - $link.jqmData( "back" ), - - //this may need to be more specific as we use data-rel more - role = $link.attr( "data-" + $.mobile.ns + "rel" ) || undefined; - - $.mobile.changePage( href, { transition: transition, reverse: reverse, role: role, link: $link } ); - event.preventDefault(); - }); - - //prefetch pages when anchors with data-prefetch are encountered - $.mobile.document.delegate( ".ui-page", "pageshow.prefetch", function() { - var urls = []; - $( this ).find( "a:jqmData(prefetch)" ).each(function() { - var $link = $( this ), - url = $link.attr( "href" ); - - if ( url && $.inArray( url, urls ) === -1 ) { - urls.push( url ); - - $.mobile.loadPage( url, { role: $link.attr( "data-" + $.mobile.ns + "rel" ),prefetch: true } ); - } - }); - }); - - $.mobile._handleHashChange = function( url, data ) { - //find first page via hash - var to = path.stripHash(url), - //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, - - // default options for the changPage calls made after examining the current state - // of the page and the hash, NOTE that the transition is derived from the previous - // history entry - changePageOptions = { - changeHash: false, - fromHashChange: true, - reverse: data.direction === "back" - }; - - $.extend( changePageOptions, data, { - transition: (urlHistory.getLast() || {}).transition || transition - }); - - // special case for dialogs - if ( urlHistory.activeIndex > 0 && to.indexOf( dialogHashKey ) > -1 && urlHistory.initialDst !== to ) { - - // If current active page is not a dialog skip the dialog and continue - // in the same direction - if ( $.mobile.activePage && !$.mobile.activePage.is( ".ui-dialog" ) ) { - //determine if we're heading forward or backward and continue accordingly past - //the current dialog - if( data.direction === "back" ) { - $.mobile.back(); - } else { - window.history.forward(); - } - - // prevent changePage call - return; - } else { - // if the current active page is a dialog and we're navigating - // to a dialog use the dialog objected saved in the stack - to = data.pageUrl; - var active = $.mobile.urlHistory.getActive(); - - // 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: data.direction === "back" - }); - } - } - - //if to is defined, load it - if ( 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 = !path.isPath( to ) ? ( path.makeUrlAbsolute( '#' + to, documentBase ) ) : to; - - // If we're about to go to an initial URL that contains a reference to a non-existent - // internal page, go to the first page instead. We know that the initial hash refers to a - // non-existent page, because the initial hash did not end up in the initial urlHistory entry - if ( to === path.makeUrlAbsolute( '#' + urlHistory.initialDst, documentBase ) && - urlHistory.stack.length && urlHistory.stack[0].url !== urlHistory.initialDst.replace( dialogHashKey, "" ) ) { - to = $.mobile.firstPage; - } - - $.mobile.changePage( to, changePageOptions ); - } else { - - //there's no hash, go to the first page in the dom - $.mobile.changePage( $.mobile.firstPage, changePageOptions ); - } - }; - - // TODO roll the logic here into the handleHashChange method - $window.bind( "navigate", function( e, data ) { - var url; - - if ( e.originalEvent && e.originalEvent.isDefaultPrevented() ) { - return; - } - - url = $.event.special.navigate.originalEventName.indexOf( "hashchange" ) > -1 ? data.state.hash : data.state.url; - - if( !url ) { - url = $.mobile.path.parseLocation().hash; - } - - if( !url || url === "#" || url.indexOf( "#" + $.mobile.path.uiStateKey ) === 0 ){ - url = location.href; - } - - $.mobile._handleHashChange( url, data.state ); - }); - - //set page min-heights to be device specific - $.mobile.document.bind( "pageshow", $.mobile.resetActivePageHeight ); - $.mobile.window.bind( "throttledresize", $.mobile.resetActivePageHeight ); - - };//navreadyDeferred done callback - - $( function() { domreadyDeferred.resolve(); } ); - - $.when( domreadyDeferred, $.mobile.navreadyDeferred ).done( function() { $.mobile._registerInternalEvents(); } ); -})( jQuery ); - -/* -* fallback transition for flip in non-3D supporting browsers (which tend to handle complex transitions poorly in general -*/ - -(function( $, window, undefined ) { - -$.mobile.transitionFallbacks.flip = "fade"; - -})( jQuery, this ); -/* -* fallback transition for flow in non-3D supporting browsers (which tend to handle complex transitions poorly in general -*/ - -(function( $, window, undefined ) { - -$.mobile.transitionFallbacks.flow = "fade"; - -})( jQuery, this ); -/* -* fallback transition for pop in non-3D supporting browsers (which tend to handle complex transitions poorly in general -*/ - -(function( $, window, undefined ) { - -$.mobile.transitionFallbacks.pop = "fade"; - -})( jQuery, this ); -/* -* fallback transition for slide in non-3D supporting browsers (which tend to handle complex transitions poorly in general -*/ - -(function( $, window, undefined ) { - -// Use the simultaneous transitions handler for slide transitions -$.mobile.transitionHandlers.slide = $.mobile.transitionHandlers.simultaneous; - -// Set the slide transitions's fallback to "fade" -$.mobile.transitionFallbacks.slide = "fade"; - -})( jQuery, this ); -/* -* fallback transition for slidedown in non-3D supporting browsers (which tend to handle complex transitions poorly in general -*/ - -(function( $, window, undefined ) { - -$.mobile.transitionFallbacks.slidedown = "fade"; - -})( jQuery, this ); -/* -* fallback transition for slidefade in non-3D supporting browsers (which tend to handle complex transitions poorly in general -*/ - -(function( $, window, undefined ) { - -// Set the slide transitions's fallback to "fade" -$.mobile.transitionFallbacks.slidefade = "fade"; - -})( jQuery, this ); -/* -* fallback transition for slideup in non-3D supporting browsers (which tend to handle complex transitions poorly in general -*/ - -(function( $, window, undefined ) { - -$.mobile.transitionFallbacks.slideup = "fade"; - -})( jQuery, this ); -/* -* fallback transition for turn in non-3D supporting browsers (which tend to handle complex transitions poorly in general -*/ - -(function( $, window, undefined ) { - -$.mobile.transitionFallbacks.turn = "fade"; - -})( jQuery, this ); - -(function( $, undefined ) { - -$.mobile.page.prototype.options.degradeInputs = { - color: false, - date: false, - datetime: false, - "datetime-local": false, - email: false, - month: false, - number: false, - range: "number", - search: "text", - tel: false, - time: false, - url: false, - week: false -}; - - -//auto self-init widgets -$.mobile.document.bind( "pagecreate create", function( e ) { - - var page = $.mobile.closestPageData( $( e.target ) ), options; - - if ( !page ) { - return; - } - - options = page.options; - - // degrade inputs to avoid poorly implemented native functionality - $( e.target ).find( "input" ).not( page.keepNativeSelector() ).each(function() { - var $this = $( this ), - type = this.getAttribute( "type" ), - optType = options.degradeInputs[ type ] || "text"; - - if ( options.degradeInputs[ 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 ) ); - } - }); - -}); - -})( jQuery ); - -(function( $, window, undefined ) { - -$.widget( "mobile.dialog", $.mobile.widget, { - options: { - closeBtn: "left", - closeBtnText: "Close", - overlayTheme: "a", - corners: true, - initSelector: ":jqmData(role='dialog')" - }, - - // Override the theme set by the page plugin on pageshow - _handlePageBeforeShow: function() { - this._isCloseable = true; - if ( this.options.overlayTheme ) { - this.element - .page( "removeContainerBackground" ) - .page( "setContainerBackground", this.options.overlayTheme ); - } - }, - - _handlePageBeforeHide: function() { - this._isCloseable = false; - }, - - _create: function() { - var self = this, - $el = this.element, - cornerClass = !!this.options.corners ? " ui-corner-all" : "", - dialogWrap = $( "
", { - "role" : "dialog", - "class" : "ui-dialog-contain ui-overlay-shadow" + cornerClass - }); - - $el.addClass( "ui-dialog ui-overlay-" + this.options.overlayTheme ); - - // Class the markup for dialog styling - // Set aria role - $el.wrapInner( dialogWrap ); - - /* bind events - - clicks and submits should use the closing transition that the dialog opened with - unless a data-transition is specified on the link/form - - if the click was on the close button, or the link has a data-rel="back" it'll go back in history naturally - */ - $el.bind( "vclick submit", function( event ) { - var $target = $( event.target ).closest( event.type === "vclick" ? "a" : "form" ), - active; - - if ( $target.length && !$target.jqmData( "transition" ) ) { - - active = $.mobile.urlHistory.getActive() || {}; - - $target.attr( "data-" + $.mobile.ns + "transition", ( active.transition || $.mobile.defaultDialogTransition ) ) - .attr( "data-" + $.mobile.ns + "direction", "reverse" ); - } - }); - - this._on( $el, { - pagebeforeshow: "_handlePageBeforeShow", - pagebeforehide: "_handlePageBeforeHide" - }); - - $.extend( this, { - _createComplete: false - }); - - this._setCloseBtn( this.options.closeBtn ); - }, - - _setCloseBtn: function( value ) { - var self = this, btn, location; - - if ( this._headerCloseButton ) { - this._headerCloseButton.remove(); - this._headerCloseButton = null; - } - if ( value !== "none" ) { - // Sanitize value - location = ( value === "left" ? "left" : "right" ); - btn = $( ""+ this.options.closeBtnText + "" ); - this.element.children().find( ":jqmData(role='header')" ).first().prepend( btn ); - if ( this._createComplete && $.fn.buttonMarkup ) { - btn.buttonMarkup(); - } - this._createComplete = true; - - // 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 - // - // Use click rather than vclick in order to prevent the possibility of unintentionally - // reopening the dialog if the dialog opening item was directly under the close button. - btn.bind( "click", function() { - self.close(); - }); - - this._headerCloseButton = btn; - } - }, - - _setOption: function( key, value ) { - if ( key === "closeBtn" ) { - this._setCloseBtn( value ); - } - this._super( key, value ); - }, - - // Close method goes back in history - close: function() { - var idx, dst, hist = $.mobile.navigate.history; - - if ( this._isCloseable ) { - this._isCloseable = false; - // If the hash listening is enabled and there is at least one preceding history - // entry it's ok to go back. Initial pages with the dialog hash state are an example - // where the stack check is necessary - if ( $.mobile.hashListeningEnabled && hist.activeIndex > 0 ) { - $.mobile.back(); - } else { - idx = Math.max( 0, hist.activeIndex - 1 ); - dst = hist.stack[ idx ].pageUrl || hist.stack[ idx ].url; - hist.previousIndex = hist.activeIndex; - hist.activeIndex = idx; - if ( !$.mobile.path.isPath( dst ) ) { - dst = $.mobile.path.makeUrlAbsolute( "#" + dst ); - } - - $.mobile.changePage( dst, { direction: "back", changeHash: false, fromHashChange: true } ); - } - } - } -}); - -//auto self-init widgets -$.mobile.document.delegate( $.mobile.dialog.prototype.options.initSelector, "pagecreate", function() { - $.mobile.dialog.prototype.enhance( this ); -}); - -})( jQuery, this ); - -(function( $, undefined ) { - -$.mobile.page.prototype.options.backBtnText = "Back"; -$.mobile.page.prototype.options.addBackBtn = false; -$.mobile.page.prototype.options.backBtnTheme = null; -$.mobile.page.prototype.options.headerTheme = "a"; -$.mobile.page.prototype.options.footerTheme = "a"; -$.mobile.page.prototype.options.contentTheme = null; - -// NOTE bind used to force this binding to run before the buttonMarkup binding -// which expects .ui-footer top be applied in its gigantic selector -// TODO remove the buttonMarkup giant selector and move it to the various modules -// on which it depends -$.mobile.document.bind( "pagecreate", function( e ) { - var $page = $( e.target ), - o = $page.data( "mobile-page" ).options, - pageRole = $page.jqmData( "role" ), - pageTheme = o.theme; - - $( ":jqmData(role='header'), :jqmData(role='footer'), :jqmData(role='content')", $page ) - .jqmEnhanceable() - .each(function() { - - var $this = $( this ), - role = $this.jqmData( "role" ), - theme = $this.jqmData( "theme" ), - contentTheme = theme || o.contentTheme || ( pageRole === "dialog" && pageTheme ), - $headeranchors, - leftbtn, - rightbtn, - backBtn; - - $this.addClass( "ui-" + role ); - - //apply theming and markup modifications to page,header,content,footer - if ( role === "header" || role === "footer" ) { - - var thisTheme = theme || ( role === "header" ? o.headerTheme : o.footerTheme ) || pageTheme; - - $this - //add theme class - .addClass( "ui-bar-" + thisTheme ) - // Add ARIA role - .attr( "role", role === "header" ? "banner" : "contentinfo" ); - - if ( role === "header") { - // Right,left buttons - $headeranchors = $this.children( "a, button" ); - leftbtn = $headeranchors.hasClass( "ui-btn-left" ); - rightbtn = $headeranchors.hasClass( "ui-btn-right" ); - - leftbtn = leftbtn || $headeranchors.eq( 0 ).not( ".ui-btn-right" ).addClass( "ui-btn-left" ).length; - - rightbtn = rightbtn || $headeranchors.eq( 1 ).addClass( "ui-btn-right" ).length; - } - - // Auto-add back btn on pages beyond first view - if ( o.addBackBtn && - role === "header" && - $( ".ui-page" ).length > 1 && - $page.jqmData( "url" ) !== $.mobile.path.stripHash( location.hash ) && - !leftbtn ) { - - backBtn = $( ""+ o.backBtnText +"" ) - // If theme is provided, override default inheritance - .attr( "data-"+ $.mobile.ns +"theme", o.backBtnTheme || thisTheme ) - .prependTo( $this ); - } - - // Page title - $this.children( "h1, h2, h3, h4, h5, h6" ) - .addClass( "ui-title" ) - // Regardless of h element number in src, it becomes h1 for the enhanced page - .attr({ - "role": "heading", - "aria-level": "1" - }); - - } else if ( role === "content" ) { - if ( contentTheme ) { - $this.addClass( "ui-body-" + ( contentTheme ) ); - } - - // Add ARIA role - $this.attr( "role", "main" ); - } - }); -}); - -})( jQuery ); - -(function( $, undefined ) { - -// This function calls getAttribute, which should be safe for data-* attributes -var getAttrFixed = function( e, key ) { - var value = e.getAttribute( key ); - - return value === "true" ? true : - value === "false" ? false : - value === null ? undefined : value; -}; - -$.fn.buttonMarkup = function( options ) { - var $workingSet = this, - nsKey = "data-" + $.mobile.ns, - key; - - // Enforce options to be of type string - options = ( options && ( $.type( options ) === "object" ) )? options : {}; - for ( var i = 0; i < $workingSet.length; i++ ) { - var el = $workingSet.eq( i ), - e = el[ 0 ], - o = $.extend( {}, $.fn.buttonMarkup.defaults, { - icon: options.icon !== undefined ? options.icon : getAttrFixed( e, nsKey + "icon" ), - iconpos: options.iconpos !== undefined ? options.iconpos : getAttrFixed( e, nsKey + "iconpos" ), - theme: options.theme !== undefined ? options.theme : getAttrFixed( e, nsKey + "theme" ) || $.mobile.getInheritedTheme( el, "c" ), - inline: options.inline !== undefined ? options.inline : getAttrFixed( e, nsKey + "inline" ), - shadow: options.shadow !== undefined ? options.shadow : getAttrFixed( e, nsKey + "shadow" ), - corners: options.corners !== undefined ? options.corners : getAttrFixed( e, nsKey + "corners" ), - iconshadow: options.iconshadow !== undefined ? options.iconshadow : getAttrFixed( e, nsKey + "iconshadow" ), - mini: options.mini !== undefined ? options.mini : getAttrFixed( e, nsKey + "mini" ) - }, options ), - - // Classes Defined - innerClass = "ui-btn-inner", - textClass = "ui-btn-text", - buttonClass, iconClass, - hover = false, - state = "up", - // Button inner markup - buttonInner, - buttonText, - buttonIcon, - buttonElements; - - for ( key in o ) { - if ( o[ key ] === undefined || o[ key ] === null ) { - el.removeAttr( nsKey + key ); - } else { - e.setAttribute( nsKey + key, o[ key ] ); - } - } - - // Check if this element is already enhanced - buttonElements = $.data( ( ( e.tagName === "INPUT" || e.tagName === "BUTTON" ) ? e.parentNode : e ), "buttonElements" ); - - if ( buttonElements ) { - e = buttonElements.outer; - el = $( e ); - buttonInner = buttonElements.inner; - buttonText = buttonElements.text; - // We will recreate this icon below - $( buttonElements.icon ).remove(); - buttonElements.icon = null; - hover = buttonElements.hover; - state = buttonElements.state; - } - else { - buttonInner = document.createElement( o.wrapperEls ); - buttonText = document.createElement( o.wrapperEls ); - } - buttonIcon = o.icon ? document.createElement( "span" ) : null; - - if ( attachEvents && !buttonElements ) { - attachEvents(); - } - - // if not, try to find closest theme container - if ( !o.theme ) { - o.theme = $.mobile.getInheritedTheme( el, "c" ); - } - - buttonClass = "ui-btn "; - buttonClass += ( hover ? "ui-btn-hover-" + o.theme : "" ); - buttonClass += ( state ? " ui-btn-" + state + "-" + o.theme : "" ); - buttonClass += o.shadow ? " ui-shadow" : ""; - buttonClass += o.corners ? " ui-btn-corner-all" : ""; - - if ( o.mini !== undefined ) { - // Used to control styling in headers/footers, where buttons default to `mini` style. - buttonClass += o.mini === true ? " ui-mini" : " ui-fullsize"; - } - - if ( o.inline !== undefined ) { - // Used to control styling in headers/footers, where buttons default to `inline` style. - buttonClass += o.inline === true ? " ui-btn-inline" : " ui-btn-block"; - } - - if ( o.icon ) { - o.icon = "ui-icon-" + o.icon; - o.iconpos = o.iconpos || "left"; - - iconClass = "ui-icon " + o.icon; - - if ( o.iconshadow ) { - iconClass += " ui-icon-shadow"; - } - } - - if ( o.iconpos ) { - buttonClass += " ui-btn-icon-" + o.iconpos; - - if ( o.iconpos === "notext" && !el.attr( "title" ) ) { - el.attr( "title", el.getEncodedText() ); - } - } - - if ( buttonElements ) { - el.removeClass( buttonElements.bcls || "" ); - } - el.removeClass( "ui-link" ).addClass( buttonClass ); - - buttonInner.className = innerClass; - buttonText.className = textClass; - if ( !buttonElements ) { - buttonInner.appendChild( buttonText ); - } - if ( buttonIcon ) { - buttonIcon.className = iconClass; - if ( !( buttonElements && buttonElements.icon ) ) { - buttonIcon.innerHTML = " "; - buttonInner.appendChild( buttonIcon ); - } - } - - while ( e.firstChild && !buttonElements ) { - buttonText.appendChild( e.firstChild ); - } - - if ( !buttonElements ) { - e.appendChild( buttonInner ); - } - - // Assign a structure containing the elements of this button to the elements of this button. This - // will allow us to recognize this as an already-enhanced button in future calls to buttonMarkup(). - buttonElements = { - hover : hover, - state : state, - bcls : buttonClass, - outer : e, - inner : buttonInner, - text : buttonText, - icon : buttonIcon - }; - - $.data( e, 'buttonElements', buttonElements ); - $.data( buttonInner, 'buttonElements', buttonElements ); - $.data( buttonText, 'buttonElements', buttonElements ); - if ( buttonIcon ) { - $.data( buttonIcon, 'buttonElements', buttonElements ); - } - } - - return this; -}; - -$.fn.buttonMarkup.defaults = { - corners: true, - shadow: true, - iconshadow: true, - wrapperEls: "span" -}; - -function closestEnabledButton( element ) { - var cname; - - while ( element ) { - // Note that we check for typeof className below because the element we - // handed could be in an SVG DOM where className on SVG elements is defined to - // be of a different type (SVGAnimatedString). We only operate on HTML DOM - // elements, so we look for plain "string". - cname = ( typeof element.className === 'string' ) && ( element.className + ' ' ); - if ( cname && cname.indexOf( "ui-btn " ) > -1 && cname.indexOf( "ui-disabled " ) < 0 ) { - break; - } - - element = element.parentNode; - } - - return element; -} - -function updateButtonClass( $btn, classToRemove, classToAdd, hover, state ) { - var buttonElements = $.data( $btn[ 0 ], "buttonElements" ); - $btn.removeClass( classToRemove ).addClass( classToAdd ); - if ( buttonElements ) { - buttonElements.bcls = $( document.createElement( "div" ) ) - .addClass( buttonElements.bcls + " " + classToAdd ) - .removeClass( classToRemove ) - .attr( "class" ); - if ( hover !== undefined ) { - buttonElements.hover = hover; - } - buttonElements.state = state; - } -} - -var attachEvents = function() { - var hoverDelay = $.mobile.buttonMarkup.hoverDelay, hov, foc; - - $.mobile.document.bind( { - "vmousedown vmousecancel vmouseup vmouseover vmouseout focus blur scrollstart": function( event ) { - var theme, - $btn = $( closestEnabledButton( event.target ) ), - isTouchEvent = event.originalEvent && /^touch/.test( event.originalEvent.type ), - evt = event.type; - - if ( $btn.length ) { - theme = $btn.attr( "data-" + $.mobile.ns + "theme" ); - - if ( evt === "vmousedown" ) { - if ( isTouchEvent ) { - // Use a short delay to determine if the user is scrolling before highlighting - hov = setTimeout( function() { - updateButtonClass( $btn, "ui-btn-up-" + theme, "ui-btn-down-" + theme, undefined, "down" ); - }, hoverDelay ); - } else { - updateButtonClass( $btn, "ui-btn-up-" + theme, "ui-btn-down-" + theme, undefined, "down" ); - } - } else if ( evt === "vmousecancel" || evt === "vmouseup" ) { - updateButtonClass( $btn, "ui-btn-down-" + theme, "ui-btn-up-" + theme, undefined, "up" ); - } else if ( evt === "vmouseover" || evt === "focus" ) { - if ( isTouchEvent ) { - // Use a short delay to determine if the user is scrolling before highlighting - foc = setTimeout( function() { - updateButtonClass( $btn, "ui-btn-up-" + theme, "ui-btn-hover-" + theme, true, "" ); - }, hoverDelay ); - } else { - updateButtonClass( $btn, "ui-btn-up-" + theme, "ui-btn-hover-" + theme, true, "" ); - } - } else if ( evt === "vmouseout" || evt === "blur" || evt === "scrollstart" ) { - updateButtonClass( $btn, "ui-btn-hover-" + theme + " ui-btn-down-" + theme, "ui-btn-up-" + theme, false, "up" ); - if ( hov ) { - clearTimeout( hov ); - } - if ( foc ) { - clearTimeout( foc ); - } - } - } - }, - "focusin focus": function( event ) { - $( closestEnabledButton( event.target ) ).addClass( $.mobile.focusClass ); - }, - "focusout blur": function( event ) { - $( closestEnabledButton( event.target ) ).removeClass( $.mobile.focusClass ); - } - }); - - attachEvents = null; -}; - -//links in bars, or those with data-role become buttons -//auto self-init widgets -$.mobile.document.bind( "pagecreate create", function( e ) { - - $( ":jqmData(role='button'), .ui-bar > a, .ui-header > a, .ui-footer > a, .ui-bar > :jqmData(role='controlgroup') > a", e.target ) - .jqmEnhanceable() - .not( "button, input, .ui-btn, :jqmData(role='none'), :jqmData(role='nojs')" ) - .buttonMarkup(); -}); - -})( jQuery ); - - -(function( $, undefined ) { - -$.widget( "mobile.collapsible", $.mobile.widget, { - options: { - expandCueText: " click to expand contents", - collapseCueText: " click to collapse contents", - collapsed: true, - heading: "h1,h2,h3,h4,h5,h6,legend", - collapsedIcon: "plus", - expandedIcon: "minus", - iconpos: "left", - theme: null, - contentTheme: null, - inset: true, - corners: true, - mini: false, - initSelector: ":jqmData(role='collapsible')" - }, - _create: function() { - - var $el = this.element, - o = this.options, - collapsible = $el.addClass( "ui-collapsible" ), - collapsibleHeading = $el.children( o.heading ).first(), - collapsibleContent = collapsible.wrapInner( "
" ).children( ".ui-collapsible-content" ), - collapsibleSet = $el.closest( ":jqmData(role='collapsible-set')" ).addClass( "ui-collapsible-set" ), - collapsibleClasses = ""; - - // 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" ) || $.mobile.getInheritedTheme( collapsibleSet, "c" ); - } - // Inherit the content-theme from collapsible-set - if ( !o.contentTheme ) { - o.contentTheme = collapsibleSet.jqmData( "content-theme" ); - } - - // Get the preference for collapsed icon in the set, but override with data- attribute on the individual collapsible - o.collapsedIcon = $el.jqmData( "collapsed-icon" ) || collapsibleSet.jqmData( "collapsed-icon" ) || o.collapsedIcon; - - // Get the preference for expanded icon in the set, but override with data- attribute on the individual collapsible - o.expandedIcon = $el.jqmData( "expanded-icon" ) || collapsibleSet.jqmData( "expanded-icon" ) || o.expandedIcon; - - // Gets the preference icon position in the set, but override with data- attribute on the individual collapsible - o.iconpos = $el.jqmData( "iconpos" ) || collapsibleSet.jqmData( "iconpos" ) || o.iconpos; - - // Inherit the preference for inset from collapsible-set or set the default value to ensure equalty within a set - if ( collapsibleSet.jqmData( "inset" ) !== undefined ) { - o.inset = collapsibleSet.jqmData( "inset" ); - } else { - o.inset = true; - } - // Set corners for individual collapsibles to false when in a collapsible-set - o.corners = false; - // Gets the preference for mini in the set - if ( !o.mini ) { - o.mini = collapsibleSet.jqmData( "mini" ); - } - } else { - // get inherited theme if not a set and no theme has been set - if ( !o.theme ) { - o.theme = $.mobile.getInheritedTheme( $el, "c" ); - } - } - - if ( !!o.inset ) { - collapsibleClasses += " ui-collapsible-inset"; - if ( !!o.corners ) { - collapsibleClasses += " ui-corner-all" ; - } - } - if ( o.contentTheme ) { - collapsibleClasses += " ui-collapsible-themed-content"; - collapsibleContent.addClass( "ui-body-" + o.contentTheme ); - } - if ( collapsibleClasses !== "" ) { - collapsible.addClass( collapsibleClasses ); - } - - collapsibleHeading - //drop heading in before content - .insertBefore( collapsibleContent ) - //modify markup & attributes - .addClass( "ui-collapsible-heading" ) - .append( "" ) - .wrapInner( "" ) - .find( "a" ) - .first() - .buttonMarkup({ - shadow: false, - corners: false, - iconpos: o.iconpos, - icon: o.collapsedIcon, - mini: o.mini, - theme: o.theme - }); - - //events - collapsible - .bind( "expand collapse", function( event ) { - if ( !event.isDefaultPrevented() ) { - var $this = $( this ), - isCollapse = ( event.type === "collapse" ); - - event.preventDefault(); - - collapsibleHeading - .toggleClass( "ui-collapsible-heading-collapsed", isCollapse ) - .find( ".ui-collapsible-heading-status" ) - .text( isCollapse ? o.expandCueText : o.collapseCueText ) - .end() - .find( ".ui-icon" ) - .toggleClass( "ui-icon-" + o.expandedIcon, !isCollapse ) - // logic or cause same icon for expanded/collapsed state would remove the ui-icon-class - .toggleClass( "ui-icon-" + o.collapsedIcon, ( isCollapse || o.expandedIcon === o.collapsedIcon ) ) - .end() - .find( "a" ).first().removeClass( $.mobile.activeBtnClass ); - - $this.toggleClass( "ui-collapsible-collapsed", isCollapse ); - collapsibleContent.toggleClass( "ui-collapsible-content-collapsed", isCollapse ).attr( "aria-hidden", isCollapse ); - - collapsibleContent.trigger( "updatelayout" ); - } - }) - .trigger( o.collapsed ? "collapse" : "expand" ); - - collapsibleHeading - .bind( "tap", function( event ) { - collapsibleHeading.find( "a" ).first().addClass( $.mobile.activeBtnClass ); - }) - .bind( "click", function( event ) { - - var type = collapsibleHeading.is( ".ui-collapsible-heading-collapsed" ) ? "expand" : "collapse"; - - collapsible.trigger( type ); - - event.preventDefault(); - event.stopPropagation(); - }); - } -}); - -//auto self-init widgets -$.mobile.document.bind( "pagecreate create", function( e ) { - $.mobile.collapsible.prototype.enhanceWithin( e.target ); -}); - -})( jQuery ); - -(function( $, undefined ) { - -$.mobile.behaviors.addFirstLastClasses = { - _getVisibles: function( $els, create ) { - var visibles; - - if ( create ) { - visibles = $els.not( ".ui-screen-hidden" ); - } else { - visibles = $els.filter( ":visible" ); - if ( visibles.length === 0 ) { - visibles = $els.not( ".ui-screen-hidden" ); - } - } - - return visibles; - }, - - _addFirstLastClasses: function( $els, $visibles, create ) { - $els.removeClass( "ui-first-child ui-last-child" ); - $visibles.eq( 0 ).addClass( "ui-first-child" ).end().last().addClass( "ui-last-child" ); - if ( !create ) { - this.element.trigger( "updatelayout" ); - } - } -}; - -})( jQuery ); - -(function( $, undefined ) { - -$.widget( "mobile.collapsibleset", $.mobile.widget, $.extend( { - options: { - initSelector: ":jqmData(role='collapsible-set')" - }, - _create: function() { - var $el = this.element.addClass( "ui-collapsible-set" ), - o = this.options; - - // Inherit the theme from collapsible-set - if ( !o.theme ) { - o.theme = $.mobile.getInheritedTheme( $el, "c" ); - } - // Inherit the content-theme from collapsible-set - if ( !o.contentTheme ) { - o.contentTheme = $el.jqmData( "content-theme" ); - } - // Inherit the corner styling from collapsible-set - if ( !o.corners ) { - o.corners = $el.jqmData( "corners" ); - } - - if ( $el.jqmData( "inset" ) !== undefined ) { - o.inset = $el.jqmData( "inset" ); - } - o.inset = o.inset !== undefined ? o.inset : true; - o.corners = o.corners !== undefined ? o.corners : true; - - if ( !!o.corners && !!o.inset ) { - $el.addClass( "ui-corner-all" ); - } - - // Initialize the collapsible set if it's not already initialized - if ( !$el.jqmData( "collapsiblebound" ) ) { - $el - .jqmData( "collapsiblebound", true ) - .bind( "expand", function( event ) { - var closestCollapsible = $( event.target ) - .closest( ".ui-collapsible" ); - if ( closestCollapsible.parent().is( ":jqmData(role='collapsible-set')" ) ) { - closestCollapsible - .siblings( ".ui-collapsible" ) - .trigger( "collapse" ); - } - }); - } - }, - - _init: function() { - var $el = this.element, - collapsiblesInSet = $el.children( ":jqmData(role='collapsible')" ), - expanded = collapsiblesInSet.filter( ":jqmData(collapsed='false')" ); - this._refresh( "true" ); - - // Because the corners are handled by the collapsible itself and the default state is collapsed - // That was causing https://github.com/jquery/jquery-mobile/issues/4116 - expanded.trigger( "expand" ); - }, - - _refresh: function( create ) { - var collapsiblesInSet = this.element.children( ":jqmData(role='collapsible')" ); - - $.mobile.collapsible.prototype.enhance( collapsiblesInSet.not( ".ui-collapsible" ) ); - - this._addFirstLastClasses( collapsiblesInSet, this._getVisibles( collapsiblesInSet, create ), create ); - }, - - refresh: function() { - this._refresh( false ); - } -}, $.mobile.behaviors.addFirstLastClasses ) ); - -//auto self-init widgets -$.mobile.document.bind( "pagecreate create", function( e ) { - $.mobile.collapsibleset.prototype.enhanceWithin( e.target ); -}); - -})( jQuery ); - -(function( $, undefined ) { - -// filter function removes whitespace between label and form element so we can use inline-block (nodeType 3 = text) -$.fn.fieldcontain = function( options ) { - return this - .addClass( "ui-field-contain ui-body ui-br" ) - .contents().filter( function() { - return ( this.nodeType === 3 && !/\S/.test( this.nodeValue ) ); - }).remove(); -}; - -//auto self-init widgets -$( document ).bind( "pagecreate create", function( e ) { - $( ":jqmData(role='fieldcontain')", e.target ).jqmEnhanceable().fieldcontain(); -}); - -})( jQuery ); - -(function( $, undefined ) { - -$.fn.grid = function( options ) { - return this.each(function() { - - var $this = $( this ), - o = $.extend({ - grid: null - }, options ), - $kids = $this.children(), - gridCols = { solo:1, a:2, b:3, c:4, d:5 }, - grid = o.grid, - iterator; - - if ( !grid ) { - if ( $kids.length <= 5 ) { - for ( var letter in gridCols ) { - if ( gridCols[ letter ] === $kids.length ) { - grid = letter; - } - } - } else { - grid = "a"; - $this.addClass( "ui-grid-duo" ); - } - } - iterator = gridCols[grid]; - - $this.addClass( "ui-grid-" + grid ); - - $kids.filter( ":nth-child(" + iterator + "n+1)" ).addClass( "ui-block-a" ); - - if ( iterator > 1 ) { - $kids.filter( ":nth-child(" + iterator + "n+2)" ).addClass( "ui-block-b" ); - } - if ( iterator > 2 ) { - $kids.filter( ":nth-child(" + iterator + "n+3)" ).addClass( "ui-block-c" ); - } - if ( iterator > 3 ) { - $kids.filter( ":nth-child(" + iterator + "n+4)" ).addClass( "ui-block-d" ); - } - if ( iterator > 4 ) { - $kids.filter( ":nth-child(" + iterator + "n+5)" ).addClass( "ui-block-e" ); - } - }); -}; -})( jQuery ); - -(function( $, undefined ) { - -$.widget( "mobile.navbar", $.mobile.widget, { - options: { - iconpos: "top", - grid: null, - initSelector: ":jqmData(role='navbar')" - }, - - _create: function() { - - var $navbar = this.element, - $navbtns = $navbar.find( "a" ), - iconpos = $navbtns.filter( ":jqmData(icon)" ).length ? - this.options.iconpos : undefined; - - $navbar.addClass( "ui-navbar ui-mini" ) - .attr( "role", "navigation" ) - .find( "ul" ) - .jqmEnhanceable() - .grid({ grid: this.options.grid }); - - $navbtns.buttonMarkup({ - corners: false, - shadow: false, - inline: true, - iconpos: iconpos - }); - - $navbar.delegate( "a", "vclick", function( event ) { - // ui-btn-inner is returned as target - var target = $( event.target ).is( "a" ) ? $( this ) : $( this ).parent( "a" ); - - if ( !target.is( ".ui-disabled, .ui-btn-active" ) ) { - $navbtns.removeClass( $.mobile.activeBtnClass ); - $( this ).addClass( $.mobile.activeBtnClass ); - - // The code below is a workaround to fix #1181 - var activeBtn = $( this ); - - $( document ).one( "pagehide", function() { - activeBtn.removeClass( $.mobile.activeBtnClass ); - }); - } - }); - - // Buttons in the navbar with ui-state-persist class should regain their active state before page show - $navbar.closest( ".ui-page" ).bind( "pagebeforeshow", function() { - $navbtns.filter( ".ui-state-persist" ).addClass( $.mobile.activeBtnClass ); - }); - } -}); - -//auto self-init widgets -$.mobile.document.bind( "pagecreate create", function( e ) { - $.mobile.navbar.prototype.enhanceWithin( e.target ); -}); - -})( jQuery ); - -(function( $, undefined ) { - -//Keeps track of the number of lists per page UID -//This allows support for multiple nested list in the same page -//https://github.com/jquery/jquery-mobile/issues/1617 -var listCountPerPage = {}; - -$.widget( "mobile.listview", $.mobile.widget, $.extend( { - - options: { - theme: null, - countTheme: "c", - headerTheme: "b", - dividerTheme: "b", - icon: "arrow-r", - splitIcon: "arrow-r", - splitTheme: "b", - corners: true, - shadow: true, - inset: false, - initSelector: ":jqmData(role='listview')" - }, - - _create: function() { - var t = this, - listviewClasses = ""; - - listviewClasses += t.options.inset ? " ui-listview-inset" : ""; - - if ( !!t.options.inset ) { - listviewClasses += t.options.corners ? " ui-corner-all" : ""; - listviewClasses += t.options.shadow ? " ui-shadow" : ""; - } - - // create listview markup - t.element.addClass(function( i, orig ) { - return orig + " ui-listview" + listviewClasses; - }); - - t.refresh( true ); - }, - - // This is a generic utility method for finding the first - // node with a given nodeName. It uses basic DOM traversal - // to be fast and is meant to be a substitute for simple - // $.fn.closest() and $.fn.children() calls on a single - // element. Note that callers must pass both the lowerCase - // and upperCase version of the nodeName they are looking for. - // The main reason for this is that this function will be - // called many times and we want to avoid having to lowercase - // the nodeName from the element every time to ensure we have - // a match. Note that this function lives here for now, but may - // be moved into $.mobile if other components need a similar method. - _findFirstElementByTagName: function( ele, nextProp, lcName, ucName ) { - var dict = {}; - dict[ lcName ] = dict[ ucName ] = true; - while ( ele ) { - if ( dict[ ele.nodeName ] ) { - return ele; - } - ele = ele[ nextProp ]; - } - return null; - }, - _getChildrenByTagName: function( ele, lcName, ucName ) { - var results = [], - dict = {}; - dict[ lcName ] = dict[ ucName ] = true; - ele = ele.firstChild; - while ( ele ) { - if ( dict[ ele.nodeName ] ) { - results.push( ele ); - } - ele = ele.nextSibling; - } - return $( results ); - }, - - _addThumbClasses: function( containers ) { - var i, img, len = containers.length; - for ( i = 0; i < len; i++ ) { - img = $( this._findFirstElementByTagName( containers[ i ].firstChild, "nextSibling", "img", "IMG" ) ); - if ( img.length ) { - img.addClass( "ui-li-thumb" ); - $( this._findFirstElementByTagName( img[ 0 ].parentNode, "parentNode", "li", "LI" ) ).addClass( img.is( ".ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" ); - } - } - }, - - refresh: function( create ) { - this.parentPage = this.element.closest( ".ui-page" ); - this._createSubPages(); - - var o = this.options, - $list = this.element, - self = this, - dividertheme = $list.jqmData( "dividertheme" ) || o.dividerTheme, - listsplittheme = $list.jqmData( "splittheme" ), - listspliticon = $list.jqmData( "spliticon" ), - listicon = $list.jqmData( "icon" ), - li = this._getChildrenByTagName( $list[ 0 ], "li", "LI" ), - ol = !!$.nodeName( $list[ 0 ], "ol" ), - jsCount = !$.support.cssPseudoElement, - start = $list.attr( "start" ), - itemClassDict = {}, - item, itemClass, itemTheme, - a, last, splittheme, counter, startCount, newStartCount, countParent, icon, imgParents, img, linkIcon; - - if ( ol && jsCount ) { - $list.find( ".ui-li-dec" ).remove(); - } - - if ( ol ) { - // Check if a start attribute has been set while taking a value of 0 into account - if ( start || start === 0 ) { - if ( !jsCount ) { - startCount = parseInt( start , 10 ) - 1; - $list.css( "counter-reset", "listnumbering " + startCount ); - } else { - counter = parseInt( start , 10 ); - } - } else if ( jsCount ) { - counter = 1; - } - } - - if ( !o.theme ) { - o.theme = $.mobile.getInheritedTheme( this.element, "c" ); - } - - for ( var pos = 0, numli = li.length; pos < numli; pos++ ) { - item = li.eq( pos ); - itemClass = "ui-li"; - - // If we're creating the element, we update it regardless - if ( create || !item.hasClass( "ui-li" ) ) { - itemTheme = item.jqmData( "theme" ) || o.theme; - a = this._getChildrenByTagName( item[ 0 ], "a", "A" ); - var isDivider = ( item.jqmData( "role" ) === "list-divider" ); - - if ( a.length && !isDivider ) { - icon = item.jqmData( "icon" ); - - item.buttonMarkup({ - wrapperEls: "div", - shadow: false, - corners: false, - iconpos: "right", - icon: a.length > 1 || icon === false ? false : icon || listicon || o.icon, - theme: itemTheme - }); - - if ( ( icon !== false ) && ( a.length === 1 ) ) { - item.addClass( "ui-li-has-arrow" ); - } - - a.first().removeClass( "ui-link" ).addClass( "ui-link-inherit" ); - - if ( a.length > 1 ) { - itemClass += " ui-li-has-alt"; - - last = a.last(); - splittheme = listsplittheme || last.jqmData( "theme" ) || o.splitTheme; - linkIcon = last.jqmData( "icon" ); - - last.appendTo( item ) - .attr( "title", $.trim(last.getEncodedText()) ) - .addClass( "ui-li-link-alt" ) - .empty() - .buttonMarkup({ - shadow: false, - corners: false, - theme: itemTheme, - icon: false, - iconpos: "notext" - }) - .find( ".ui-btn-inner" ) - .append( - $( document.createElement( "span" ) ).buttonMarkup({ - shadow: true, - corners: true, - theme: splittheme, - iconpos: "notext", - // link icon overrides list item icon overrides ul element overrides options - icon: linkIcon || icon || listspliticon || o.splitIcon - }) - ); - } - } else if ( isDivider ) { - - itemClass += " ui-li-divider ui-bar-" + ( item.jqmData( "theme" ) || dividertheme ); - item.attr( "role", "heading" ); - - if ( ol ) { - //reset counter when a divider heading is encountered - if ( start || start === 0 ) { - if ( !jsCount ) { - newStartCount = parseInt( start , 10 ) - 1; - item.css( "counter-reset", "listnumbering " + newStartCount ); - } else { - counter = parseInt( start , 10 ); - } - } else if ( jsCount ) { - counter = 1; - } - } - - } else { - itemClass += " ui-li-static ui-btn-up-" + itemTheme; - } - } - - if ( ol && jsCount && itemClass.indexOf( "ui-li-divider" ) < 0 ) { - countParent = itemClass.indexOf( "ui-li-static" ) > 0 ? item : item.find( ".ui-link-inherit" ); - - countParent.addClass( "ui-li-jsnumbering" ) - .prepend( "" + ( counter++ ) + ". " ); - } - - // Instead of setting item class directly on the list item and its - // btn-inner at this point in time, push the item into a dictionary - // that tells us what class to set on it so we can do this after this - // processing loop is finished. - - if ( !itemClassDict[ itemClass ] ) { - itemClassDict[ itemClass ] = []; - } - - itemClassDict[ itemClass ].push( item[ 0 ] ); - } - - // Set the appropriate listview item classes on each list item - // and their btn-inner elements. The main reason we didn't do this - // in the for-loop above is because we can eliminate per-item function overhead - // by calling addClass() and children() once or twice afterwards. This - // can give us a significant boost on platforms like WP7.5. - - for ( itemClass in itemClassDict ) { - $( itemClassDict[ itemClass ] ).addClass( itemClass ).children( ".ui-btn-inner" ).addClass( itemClass ); - } - - $list.find( "h1, h2, h3, h4, h5, h6" ).addClass( "ui-li-heading" ) - .end() - - .find( "p, dl" ).addClass( "ui-li-desc" ) - .end() - - .find( ".ui-li-aside" ).each(function() { - var $this = $( this ); - $this.prependTo( $this.parent() ); //shift aside to front for css float - }) - .end() - - .find( ".ui-li-count" ).each(function() { - $( this ).closest( "li" ).addClass( "ui-li-has-count" ); - }).addClass( "ui-btn-up-" + ( $list.jqmData( "counttheme" ) || this.options.countTheme) + " ui-btn-corner-all" ); - - // The idea here is to look at the first image in the list item - // itself, and any .ui-link-inherit element it may contain, so we - // can place the appropriate classes on the image and list item. - // Note that we used to use something like: - // - // li.find(">img:eq(0), .ui-link-inherit>img:eq(0)").each( ... ); - // - // But executing a find() like that on Windows Phone 7.5 took a - // really long time. Walking things manually with the code below - // allows the 400 listview item page to load in about 3 seconds as - // opposed to 30 seconds. - - this._addThumbClasses( li ); - this._addThumbClasses( $list.find( ".ui-link-inherit" ) ); - - this._addFirstLastClasses( li, this._getVisibles( li, create ), create ); - // autodividers binds to this to redraw dividers after the listview refresh - this._trigger( "afterrefresh" ); - }, - - //create a string for ID/subpage url creation - _idStringEscape: function( str ) { - return str.replace(/[^a-zA-Z0-9]/g, '-'); - }, - - _createSubPages: function() { - var parentList = this.element, - parentPage = parentList.closest( ".ui-page" ), - parentUrl = parentPage.jqmData( "url" ), - parentId = parentUrl || parentPage[ 0 ][ $.expando ], - parentListId = parentList.attr( "id" ), - o = this.options, - dns = "data-" + $.mobile.ns, - self = this, - persistentFooterID = parentPage.find( ":jqmData(role='footer')" ).jqmData( "id" ), - hasSubPages; - - if ( typeof listCountPerPage[ parentId ] === "undefined" ) { - listCountPerPage[ parentId ] = -1; - } - - parentListId = parentListId || ++listCountPerPage[ parentId ]; - - $( parentList.find( "li>ul, li>ol" ).toArray().reverse() ).each(function( i ) { - var self = this, - list = $( this ), - listId = list.attr( "id" ) || parentListId + "-" + i, - parent = list.parent(), - nodeElsFull = $( list.prevAll().toArray().reverse() ), - nodeEls = nodeElsFull.length ? nodeElsFull : $( "" + $.trim(parent.contents()[ 0 ].nodeValue) + "" ), - title = nodeEls.first().getEncodedText(),//url limits to first 30 chars of text - id = ( parentUrl || "" ) + "&" + $.mobile.subPageUrlKey + "=" + listId, - theme = list.jqmData( "theme" ) || o.theme, - countTheme = list.jqmData( "counttheme" ) || parentList.jqmData( "counttheme" ) || o.countTheme, - newPage, anchor; - - //define hasSubPages for use in later removal - hasSubPages = true; - - newPage = list.detach() - .wrap( "
" ) - .parent() - .before( "
" + title + "
" ) - .after( persistentFooterID ? $( "
" ) : "" ) - .parent() - .appendTo( $.mobile.pageContainer ); - - newPage.page(); - - anchor = parent.find( 'a:first' ); - - if ( !anchor.length ) { - anchor = $( "" ).html( nodeEls || title ).prependTo( parent.empty() ); - } - - anchor.attr( "href", "#" + id ); - - }).listview(); - - // 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( "mobile-page" ).options.domCache === false ) { - - var newRemove = function( e, ui ) { - var nextPage = ui.nextPage, npURL, - prEvent = new $.Event( "pageremove" ); - - if ( ui.nextPage ) { - npURL = nextPage.jqmData( "url" ); - if ( npURL.indexOf( parentUrl + "&" + $.mobile.subPageUrlKey ) !== 0 ) { - self.childPages().remove(); - parentPage.trigger( prEvent ); - if ( !prEvent.isDefaultPrevented() ) { - parentPage.removeWithDependents(); - } - } - } - }; - - // unbind the original page remove and replace with our specialized version - parentPage - .unbind( "pagehide.remove" ) - .bind( "pagehide.remove", newRemove); - } - }, - - // TODO sort out a better way to track sub pages of the listview this is brittle - childPages: function() { - var parentUrl = this.parentPage.jqmData( "url" ); - - return $( ":jqmData(url^='"+ parentUrl + "&" + $.mobile.subPageUrlKey + "')" ); - } -}, $.mobile.behaviors.addFirstLastClasses ) ); - -//auto self-init widgets -$.mobile.document.bind( "pagecreate create", function( e ) { - $.mobile.listview.prototype.enhanceWithin( e.target ); -}); - -})( jQuery ); - -(function( $ ) { - var meta = $( "meta[name=viewport]" ), - initialContent = meta.attr( "content" ), - disabledZoom = initialContent + ",maximum-scale=1, user-scalable=no", - enabledZoom = initialContent + ",maximum-scale=10, user-scalable=yes", - disabledInitially = /(user-scalable[\s]*=[\s]*no)|(maximum-scale[\s]*=[\s]*1)[$,\s]/.test( initialContent ); - - $.mobile.zoom = $.extend( {}, { - enabled: !disabledInitially, - locked: false, - disable: function( lock ) { - if ( !disabledInitially && !$.mobile.zoom.locked ) { - meta.attr( "content", disabledZoom ); - $.mobile.zoom.enabled = false; - $.mobile.zoom.locked = lock || false; - } - }, - enable: function( unlock ) { - if ( !disabledInitially && ( !$.mobile.zoom.locked || unlock === true ) ) { - meta.attr( "content", enabledZoom ); - $.mobile.zoom.enabled = true; - $.mobile.zoom.locked = false; - } - }, - restore: function() { - if ( !disabledInitially ) { - meta.attr( "content", initialContent ); - $.mobile.zoom.enabled = true; - } - } - }); - -}( jQuery )); - -(function( $, undefined ) { - -$.widget( "mobile.textinput", $.mobile.widget, { - options: { - theme: null, - mini: false, - // This option defaults to true on iOS devices. - preventFocusZoom: /iPhone|iPad|iPod/.test( navigator.platform ) && navigator.userAgent.indexOf( "AppleWebKit" ) > -1, - 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[type='time'], input[type='date'], input[type='month'], input[type='week'], input[type='datetime'], input[type='datetime-local'], input[type='color'], input:not([type]), input[type='file']", - clearBtn: false, - clearSearchButtonText: null, //deprecating for 1.3... - clearBtnText: "clear text", - disabled: false - }, - - _create: function() { - - var self = this, - input = this.element, - o = this.options, - theme = o.theme || $.mobile.getInheritedTheme( this.element, "c" ), - themeclass = " ui-body-" + theme, - miniclass = o.mini ? " ui-mini" : "", - isSearch = input.is( "[type='search'], :jqmData(type='search')" ), - focusedEl, - clearbtn, - clearBtnText = o.clearSearchButtonText || o.clearBtnText, - clearBtnBlacklist = input.is( "textarea, :jqmData(type='range')" ), - inputNeedsClearBtn = !!o.clearBtn && !clearBtnBlacklist, - inputNeedsWrap = input.is( "input" ) && !input.is( ":jqmData(type='range')" ); - - function toggleClear() { - setTimeout( function() { - clearbtn.toggleClass( "ui-input-clear-hidden", !input.val() ); - }, 0 ); - } - - $( "label[for='" + input.attr( "id" ) + "']" ).addClass( "ui-input-text" ); - - focusedEl = input.addClass( "ui-input-text ui-body-"+ theme ); - - // 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" and "text" input widgets - if ( isSearch ) { - focusedEl = input.wrap( "" ).parent(); - } else if ( inputNeedsWrap ) { - focusedEl = input.wrap( "
" ).parent(); - } - - if( inputNeedsClearBtn || isSearch ) { - clearbtn = $( "
" + clearBtnText + "" ) - .bind( "click", function( event ) { - input - .val( "" ) - .focus() - .trigger( "change" ); - clearbtn.addClass( "ui-input-clear-hidden" ); - event.preventDefault(); - }) - .appendTo( focusedEl ) - .buttonMarkup({ - icon: "delete", - iconpos: "notext", - corners: true, - shadow: true, - mini: o.mini - }); - - if ( !isSearch ) { - focusedEl.addClass( "ui-input-has-clear" ); - } - - toggleClear(); - - input.bind( "paste cut keyup input focus change blur", toggleClear ); - } - else if ( !inputNeedsWrap && !isSearch ) { - input.addClass( "ui-corner-all ui-shadow-inset" + themeclass + miniclass ); - } - - input.focus(function() { - // In many situations, iOS will zoom into the input upon tap, this prevents that from happening - if ( o.preventFocusZoom ) { - $.mobile.zoom.disable( true ); - } - focusedEl.addClass( $.mobile.focusClass ); - }) - .blur(function() { - focusedEl.removeClass( $.mobile.focusClass ); - if ( o.preventFocusZoom ) { - $.mobile.zoom.enable( true ); - } - }); - - // Autogrow - if ( input.is( "textarea" ) ) { - var extraLineHeight = 15, - keyupTimeoutBuffer = 100, - keyupTimeout; - - this._keyup = function() { - var scrollHeight = input[ 0 ].scrollHeight, - clientHeight = input[ 0 ].clientHeight; - - if ( clientHeight < scrollHeight ) { - var paddingTop = parseFloat( input.css( "padding-top" ) ), - paddingBottom = parseFloat( input.css( "padding-bottom" ) ), - paddingHeight = paddingTop + paddingBottom; - - input.height( scrollHeight - paddingHeight + extraLineHeight ); - } - }; - - input.on( "keyup change input paste", function() { - clearTimeout( keyupTimeout ); - keyupTimeout = setTimeout( self._keyup, keyupTimeoutBuffer ); - }); - - // binding to pagechange here ensures that for pages loaded via - // ajax the height is recalculated without user input - this._on( true, $.mobile.document, { "pagechange": "_keyup" }); - - // Issue 509: the browser is not providing scrollHeight properly until the styles load - if ( $.trim( input.val() ) ) { - // bind to the window load to make sure the height is calculated based on BOTH - // the DOM and CSS - this._on( true, $.mobile.window, {"load": "_keyup"}); - } - } - if ( input.attr( "disabled" ) ) { - this.disable(); - } - }, - - disable: function() { - var $el, - isSearch = this.element.is( "[type='search'], :jqmData(type='search')" ), - inputNeedsWrap = this.element.is( "input" ) && !this.element.is( ":jqmData(type='range')" ), - parentNeedsDisabled = this.element.attr( "disabled", true ) && ( inputNeedsWrap || isSearch ); - - if ( parentNeedsDisabled ) { - $el = this.element.parent(); - } else { - $el = this.element; - } - $el.addClass( "ui-disabled" ); - return this._setOption( "disabled", true ); - }, - - enable: function() { - var $el, - isSearch = this.element.is( "[type='search'], :jqmData(type='search')" ), - inputNeedsWrap = this.element.is( "input" ) && !this.element.is( ":jqmData(type='range')" ), - parentNeedsEnabled = this.element.attr( "disabled", false ) && ( inputNeedsWrap || isSearch ); - - if ( parentNeedsEnabled ) { - $el = this.element.parent(); - } else { - $el = this.element; - } - $el.removeClass( "ui-disabled" ); - return this._setOption( "disabled", false ); - } -}); - -//auto self-init widgets -$.mobile.document.bind( "pagecreate create", function( e ) { - $.mobile.textinput.prototype.enhanceWithin( e.target, true ); -}); - -})( jQuery ); - -(function( $, undefined ) { - -$.mobile.listview.prototype.options.filter = false; -$.mobile.listview.prototype.options.filterPlaceholder = "Filter items..."; -$.mobile.listview.prototype.options.filterTheme = "c"; -$.mobile.listview.prototype.options.filterReveal = false; -// TODO rename callback/deprecate and default to the item itself as the first argument -var defaultFilterCallback = function( text, searchValue, item ) { - return text.toString().toLowerCase().indexOf( searchValue ) === -1; - }; - -$.mobile.listview.prototype.options.filterCallback = defaultFilterCallback; - -$.mobile.document.delegate( "ul, ol", "listviewcreate", function() { - var list = $( this ), - listview = list.data( "mobile-listview" ); - - if ( !listview || !listview.options.filter ) { - return; - } - - if ( listview.options.filterReveal ) { - list.children().addClass( "ui-screen-hidden" ); - } - - var wrapper = $( "
", { - "class": "ui-listview-filter ui-bar-" + listview.options.filterTheme, - "role": "search" - }).submit( function( e ) { - e.preventDefault(); - search.blur(); - }), - onKeyUp = function( e ) { - var $this = $( this ), - val = this.value.toLowerCase(), - listItems = null, - li = list.children(), - lastval = $this.jqmData( "lastval" ) + "", - childItems = false, - itemtext = "", - item, - // Check if a custom filter callback applies - isCustomFilterCallback = listview.options.filterCallback !== defaultFilterCallback; - - if ( lastval && lastval === val ) { - // Execute the handler only once per value change - return; - } - - listview._trigger( "beforefilter", "beforefilter", { input: this } ); - - // Change val as lastval for next execution - $this.jqmData( "lastval" , val ); - if ( isCustomFilterCallback || val.length < lastval.length || val.indexOf( lastval ) !== 0 ) { - - // Custom filter callback applies or removed chars or pasted something totally different, check all items - listItems = list.children(); - } else { - - // Only chars added, not removed, only use visible subset - listItems = list.children( ":not(.ui-screen-hidden)" ); - - if ( !listItems.length && listview.options.filterReveal ) { - listItems = list.children( ".ui-screen-hidden" ); - } - } - - if ( val ) { - - // This handles hiding regular rows without the text we search for - // and any list dividers without regular rows shown under it - - for ( var i = listItems.length - 1; i >= 0; i-- ) { - item = $( listItems[ i ] ); - itemtext = item.jqmData( "filtertext" ) || item.text(); - - if ( item.is( "li:jqmData(role=list-divider)" ) ) { - - item.toggleClass( "ui-filter-hidequeue" , !childItems ); - - // New bucket! - childItems = false; - - } else if ( listview.options.filterCallback( itemtext, val, item ) ) { - - //mark to be hidden - item.toggleClass( "ui-filter-hidequeue" , true ); - } else { - - // There's a shown item in the bucket - childItems = true; - } - } - - // Show items, not marked to be hidden - listItems - .filter( ":not(.ui-filter-hidequeue)" ) - .toggleClass( "ui-screen-hidden", false ); - - // Hide items, marked to be hidden - listItems - .filter( ".ui-filter-hidequeue" ) - .toggleClass( "ui-screen-hidden", true ) - .toggleClass( "ui-filter-hidequeue", false ); - - } else { - - //filtervalue is empty => show all - listItems.toggleClass( "ui-screen-hidden", !!listview.options.filterReveal ); - } - listview._addFirstLastClasses( li, listview._getVisibles( li, false ), false ); - }, - search = $( "", { - placeholder: listview.options.filterPlaceholder - }) - .attr( "data-" + $.mobile.ns + "type", "search" ) - .jqmData( "lastval", "" ) - .bind( "keyup change input", onKeyUp ) - .appendTo( wrapper ) - .textinput(); - - if ( listview.options.inset ) { - wrapper.addClass( "ui-listview-filter-inset" ); - } - - wrapper.bind( "submit", function() { - return false; - }) - .insertBefore( list ); -}); - -})( jQuery ); - -(function( $, undefined ) { - -$.mobile.listview.prototype.options.autodividers = false; -$.mobile.listview.prototype.options.autodividersSelector = function( elt ) { - // look for the text in the given element - var text = $.trim( elt.text() ) || null; - - if ( !text ) { - return null; - } - - // create the text for the divider (first uppercased letter) - text = text.slice( 0, 1 ).toUpperCase(); - - return text; -}; - -$.mobile.document.delegate( "ul,ol", "listviewcreate", function() { - - var list = $( this ), - listview = list.data( "mobile-listview" ); - - if ( !listview || !listview.options.autodividers ) { - return; - } - - var replaceDividers = function () { - list.find( "li:jqmData(role='list-divider')" ).remove(); - - var lis = list.find( 'li' ), - lastDividerText = null, li, dividerText; - - for ( var i = 0; i < lis.length ; i++ ) { - li = lis[i]; - dividerText = listview.options.autodividersSelector( $( li ) ); - - if ( dividerText && lastDividerText !== dividerText ) { - var divider = document.createElement( 'li' ); - divider.appendChild( document.createTextNode( dividerText ) ); - divider.setAttribute( 'data-' + $.mobile.ns + 'role', 'list-divider' ); - li.parentNode.insertBefore( divider, li ); - } - - lastDividerText = dividerText; - } - }; - - var afterListviewRefresh = function () { - list.unbind( 'listviewafterrefresh', afterListviewRefresh ); - replaceDividers(); - listview.refresh(); - list.bind( 'listviewafterrefresh', afterListviewRefresh ); - }; - - afterListviewRefresh(); -}); - -})( jQuery ); - -(function( $, undefined ) { - -$( document ).bind( "pagecreate create", function( e ) { - $( ":jqmData(role='nojs')", e.target ).addClass( "ui-nojs" ); - -}); - -})( jQuery ); - -(function( $, undefined ) { - -$.mobile.behaviors.formReset = { - _handleFormReset: function() { - this._on( this.element.closest( "form" ), { - reset: function() { - this._delay( "_reset" ); - } - }); - } -}; - -})( jQuery ); - -/* -* "checkboxradio" plugin -*/ - -(function( $, undefined ) { - -$.widget( "mobile.checkboxradio", $.mobile.widget, $.extend( { - options: { - theme: null, - mini: false, - initSelector: "input[type='checkbox'],input[type='radio']" - }, - _create: function() { - var self = this, - input = this.element, - o = this.options, - inheritAttr = function( input, dataAttr ) { - return input.jqmData( dataAttr ) || input.closest( "form, fieldset" ).jqmData( dataAttr ); - }, - // NOTE: Windows Phone could not find the label through a selector - // filter works though. - parentLabel = $( input ).closest( "label" ), - label = parentLabel.length ? parentLabel : $( input ).closest( "form, fieldset, :jqmData(role='page'), :jqmData(role='dialog')" ).find( "label" ).filter( "[for='" + input[0].id + "']" ).first(), - inputtype = input[0].type, - mini = inheritAttr( input, "mini" ) || o.mini, - checkedState = inputtype + "-on", - uncheckedState = inputtype + "-off", - iconpos = inheritAttr( input, "iconpos" ), - checkedClass = "ui-" + checkedState, - uncheckedClass = "ui-" + uncheckedState; - - if ( inputtype !== "checkbox" && inputtype !== "radio" ) { - return; - } - - // Expose for other methods - $.extend( this, { - label: label, - inputtype: inputtype, - checkedClass: checkedClass, - uncheckedClass: uncheckedClass, - checkedicon: checkedState, - uncheckedicon: uncheckedState - }); - - // If there's no selected theme check the data attr - if ( !o.theme ) { - o.theme = $.mobile.getInheritedTheme( this.element, "c" ); - } - - label.buttonMarkup({ - theme: o.theme, - icon: uncheckedState, - shadow: false, - mini: mini, - iconpos: iconpos - }); - - // Wrap the input + label in a div - var wrapper = document.createElement('div'); - wrapper.className = 'ui-' + inputtype; - - input.add( label ).wrapAll( wrapper ); - - label.bind({ - vmouseover: function( event ) { - if ( $( this ).parent().is( ".ui-disabled" ) ) { - event.stopPropagation(); - } - }, - - vclick: function( event ) { - if ( input.is( ":disabled" ) ) { - event.preventDefault(); - return; - } - - self._cacheVals(); - - input.prop( "checked", inputtype === "radio" && true || !input.prop( "checked" ) ); - - // trigger click handler's bound directly to the input as a substitute for - // how label clicks behave normally in the browsers - // TODO: it would be nice to let the browser's handle the clicks and pass them - // through to the associate input. we can swallow that click at the parent - // wrapper element level - input.triggerHandler( 'click' ); - - // Input set for common radio buttons will contain all the radio - // buttons, but will not for checkboxes. clearing the checked status - // of other radios ensures the active button state is applied properly - self._getInputSet().not( input ).prop( "checked", false ); - - self._updateAll(); - return false; - } - }); - - input - .bind({ - vmousedown: function() { - self._cacheVals(); - }, - - vclick: function() { - var $this = $( this ); - - // Adds checked attribute to checked input when keyboard is used - if ( $this.is( ":checked" ) ) { - - $this.prop( "checked", true); - self._getInputSet().not( $this ).prop( "checked", false ); - } else { - - $this.prop( "checked", false ); - } - - self._updateAll(); - }, - - focus: function() { - label.addClass( $.mobile.focusClass ); - }, - - blur: function() { - label.removeClass( $.mobile.focusClass ); - } - }); - - this._handleFormReset(); - this.refresh(); - }, - - _cacheVals: function() { - this._getInputSet().each(function() { - $( this ).jqmData( "cacheVal", this.checked ); - }); - }, - - //returns either a set of radios with the same name attribute, or a single checkbox - _getInputSet: function() { - if ( this.inputtype === "checkbox" ) { - return this.element; - } - - return this.element.closest( "form, :jqmData(role='page'), :jqmData(role='dialog')" ) - .find( "input[name='" + this.element[0].name + "'][type='" + this.inputtype + "']" ); - }, - - _updateAll: function() { - var self = this; - - this._getInputSet().each(function() { - var $this = $( this ); - - if ( this.checked || self.inputtype === "checkbox" ) { - $this.trigger( "change" ); - } - }) - .checkboxradio( "refresh" ); - }, - - _reset: function() { - this.refresh(); - }, - - refresh: function() { - var input = this.element[ 0 ], - active = " " + $.mobile.activeBtnClass, - checkedClass = this.checkedClass + ( this.element.parents( ".ui-controlgroup-horizontal" ).length ? active : "" ), - label = this.label; - - if ( input.checked ) { - label.removeClass( this.uncheckedClass + active ).addClass( checkedClass ).buttonMarkup( { icon: this.checkedicon } ); - } else { - label.removeClass( checkedClass ).addClass( this.uncheckedClass ).buttonMarkup( { icon: this.uncheckedicon } ); - } - - if ( input.disabled ) { - this.disable(); - } else { - this.enable(); - } - }, - - disable: function() { - this.element.prop( "disabled", true ).parent().addClass( "ui-disabled" ); - }, - - enable: function() { - this.element.prop( "disabled", false ).parent().removeClass( "ui-disabled" ); - } -}, $.mobile.behaviors.formReset ) ); - -//auto self-init widgets -$.mobile.document.bind( "pagecreate create", function( e ) { - $.mobile.checkboxradio.prototype.enhanceWithin( e.target, true ); -}); - -})( jQuery ); - -(function( $, undefined ) { - -$.widget( "mobile.button", $.mobile.widget, { - options: { - theme: null, - icon: null, - iconpos: null, - corners: true, - shadow: true, - iconshadow: true, - inline: null, - mini: null, - initSelector: "button, [type='button'], [type='submit'], [type='reset']" - }, - _create: function() { - var $el = this.element, - $button, - // create a copy of this.options we can pass to buttonMarkup - o = ( function( tdo ) { - var key, ret = {}; - - for ( key in tdo ) { - if ( tdo[ key ] !== null && key !== "initSelector" ) { - ret[ key ] = tdo[ key ]; - } - } - - return ret; - } )( this.options ), - classes = "", - $buttonPlaceholder; - - // if this is a link, check if it's been enhanced and, if not, use the right function - if ( $el[ 0 ].tagName === "A" ) { - if ( !$el.hasClass( "ui-btn" ) ) { - $el.buttonMarkup(); - } - return; - } - - // get the inherited theme - // TODO centralize for all widgets - if ( !this.options.theme ) { - this.options.theme = $.mobile.getInheritedTheme( this.element, "c" ); - } - - // TODO: Post 1.1--once we have time to test thoroughly--any classes manually applied to the original element should be carried over to the enhanced element, with an `-enhanced` suffix. See https://github.com/jquery/jquery-mobile/issues/3577 - /* if ( $el[0].className.length ) { - classes = $el[0].className; - } */ - if ( !!~$el[0].className.indexOf( "ui-btn-left" ) ) { - classes = "ui-btn-left"; - } - - if ( !!~$el[0].className.indexOf( "ui-btn-right" ) ) { - classes = "ui-btn-right"; - } - - if ( $el.attr( "type" ) === "submit" || $el.attr( "type" ) === "reset" ) { - if ( classes ) { - classes += " ui-submit"; - } else { - classes = "ui-submit"; - } - } - $( "label[for='" + $el.attr( "id" ) + "']" ).addClass( "ui-submit" ); - - // Add ARIA role - this.button = $( "
" ) - [ $el.html() ? "html" : "text" ]( $el.html() || $el.val() ) - .insertBefore( $el ) - .buttonMarkup( o ) - .addClass( classes ) - .append( $el.addClass( "ui-btn-hidden" ) ); - - $button = this.button; - - $el.bind({ - focus: function() { - $button.addClass( $.mobile.focusClass ); - }, - - blur: function() { - $button.removeClass( $.mobile.focusClass ); - } - }); - - this.refresh(); - }, - - _setOption: function( key, value ) { - var op = {}; - - op[ key ] = value; - if ( key !== "initSelector" ) { - this.button.buttonMarkup( op ); - // Record the option change in the options and in the DOM data-* attributes - this.element.attr( "data-" + ( $.mobile.ns || "" ) + ( key.replace( /([A-Z])/, "-$1" ).toLowerCase() ), value ); - } - this._super( "_setOption", key, value ); - }, - - enable: function() { - this.element.attr( "disabled", false ); - this.button.removeClass( "ui-disabled" ).attr( "aria-disabled", false ); - return this._setOption( "disabled", false ); - }, - - disable: function() { - this.element.attr( "disabled", true ); - this.button.addClass( "ui-disabled" ).attr( "aria-disabled", true ); - return this._setOption( "disabled", true ); - }, - - refresh: function() { - var $el = this.element; - - if ( $el.prop("disabled") ) { - this.disable(); - } else { - this.enable(); - } - - // Grab the button's text element from its implementation-independent data item - $( this.button.data( 'buttonElements' ).text )[ $el.html() ? "html" : "text" ]( $el.html() || $el.val() ); - } -}); - -//auto self-init widgets -$.mobile.document.bind( "pagecreate create", function( e ) { - $.mobile.button.prototype.enhanceWithin( e.target, true ); -}); - -})( jQuery ); - -(function( $, undefined ) { - -$.widget( "mobile.slider", $.mobile.widget, $.extend( { - widgetEventPrefix: "slide", - - options: { - theme: null, - trackTheme: null, - disabled: false, - initSelector: "input[type='range'], :jqmData(type='range'), :jqmData(role='slider')", - mini: false, - highlight: false - }, - - _create: function() { - - // TODO: Each of these should have comments explain what they're for - var self = this, - control = this.element, - parentTheme = $.mobile.getInheritedTheme( control, "c" ), - theme = this.options.theme || parentTheme, - trackTheme = this.options.trackTheme || parentTheme, - cType = control[ 0 ].nodeName.toLowerCase(), - isSelect = this.isToggleSwitch = cType === "select", - isRangeslider = control.parent().is( ":jqmData(role='rangeslider')" ), - selectClass = ( this.isToggleSwitch ) ? "ui-slider-switch" : "", - controlID = control.attr( "id" ), - $label = $( "[for='" + controlID + "']" ), - labelID = $label.attr( "id" ) || controlID + "-label", - label = $label.attr( "id", labelID ), - min = !this.isToggleSwitch ? parseFloat( control.attr( "min" ) ) : 0, - max = !this.isToggleSwitch ? parseFloat( control.attr( "max" ) ) : control.find( "option" ).length-1, - step = window.parseFloat( control.attr( "step" ) || 1 ), - miniClass = ( this.options.mini || control.jqmData( "mini" ) ) ? " ui-mini" : "", - domHandle = document.createElement( "a" ), - handle = $( domHandle ), - domSlider = document.createElement( "div" ), - slider = $( domSlider ), - valuebg = this.options.highlight && !this.isToggleSwitch ? (function() { - var bg = document.createElement( "div" ); - bg.className = "ui-slider-bg " + $.mobile.activeBtnClass + " ui-btn-corner-all"; - return $( bg ).prependTo( slider ); - })() : false, - options, - wrapper; - - domHandle.setAttribute( "href", "#" ); - domSlider.setAttribute( "role", "application" ); - domSlider.className = [this.isToggleSwitch ? "ui-slider " : "ui-slider-track ",selectClass," ui-btn-down-",trackTheme," ui-btn-corner-all", miniClass].join( "" ); - domHandle.className = "ui-slider-handle"; - domSlider.appendChild( domHandle ); - - handle.buttonMarkup({ corners: true, theme: theme, shadow: true }) - .attr({ - "role": "slider", - "aria-valuemin": min, - "aria-valuemax": max, - "aria-valuenow": this._value(), - "aria-valuetext": this._value(), - "title": this._value(), - "aria-labelledby": labelID - }); - - $.extend( this, { - slider: slider, - handle: handle, - type: cType, - step: step, - max: max, - min: min, - valuebg: valuebg, - isRangeslider: isRangeslider, - dragging: false, - beforeStart: null, - userModified: false, - mouseMoved: false - }); - - if ( this.isToggleSwitch ) { - wrapper = document.createElement( "div" ); - wrapper.className = "ui-slider-inneroffset"; - - for ( var j = 0, length = domSlider.childNodes.length; j < length; j++ ) { - wrapper.appendChild( domSlider.childNodes[j] ); - } - - domSlider.appendChild( wrapper ); - - // slider.wrapInner( "
" ); - - // make the handle move with a smooth transition - handle.addClass( "ui-slider-handle-snapping" ); - - options = control.find( "option" ); - - for ( var i = 0, optionsCount = options.length; i < optionsCount; i++ ) { - var side = !i ? "b" : "a", - sliderTheme = !i ? " ui-btn-down-" + trackTheme : ( " " + $.mobile.activeBtnClass ), - sliderLabel = document.createElement( "div" ), - sliderImg = document.createElement( "span" ); - - sliderImg.className = ["ui-slider-label ui-slider-label-", side, sliderTheme, " ui-btn-corner-all"].join( "" ); - sliderImg.setAttribute( "role", "img" ); - sliderImg.appendChild( document.createTextNode( options[i].innerHTML ) ); - $( sliderImg ).prependTo( slider ); - } - - self._labels = $( ".ui-slider-label", slider ); - - } - - label.addClass( "ui-slider" ); - - // monitor the input for updated values - control.addClass( this.isToggleSwitch ? "ui-slider-switch" : "ui-slider-input" ); - - this._on( control, { - "change": "_controlChange", - "keyup": "_controlKeyup", - "blur": "_controlBlur", - "vmouseup": "_controlVMouseUp" - }); - - slider.bind( "vmousedown", $.proxy( this._sliderVMouseDown, this ) ) - .bind( "vclick", false ); - - // We have to instantiate a new function object for the unbind to work properly - // since the method itself is defined in the prototype (causing it to unbind everything) - this._on( document, { "vmousemove": "_preventDocumentDrag" }); - this._on( slider.add( document ), { "vmouseup": "_sliderVMouseUp" }); - - slider.insertAfter( control ); - - // wrap in a div for styling purposes - if ( !this.isToggleSwitch && !isRangeslider ) { - wrapper = this.options.mini ? "
" : "
"; - - control.add( slider ).wrapAll( wrapper ); - } - - // Only add focus class to toggle switch, sliders get it automatically from ui-btn - if ( this.isToggleSwitch ) { - this.handle.bind({ - focus: function() { - slider.addClass( $.mobile.focusClass ); - }, - - blur: function() { - slider.removeClass( $.mobile.focusClass ); - } - }); - } - - // bind the handle event callbacks and set the context to the widget instance - this._on( this.handle, { - "vmousedown": "_handleVMouseDown", - "keydown": "_handleKeydown", - "keyup": "_handleKeyup" - }); - - this.handle.bind( "vclick", false ); - - this._handleFormReset(); - - this.refresh( undefined, undefined, true ); - }, - - _controlChange: function( event ) { - // if the user dragged the handle, the "change" event was triggered from inside refresh(); don't call refresh() again - if ( this._trigger( "controlchange", event ) === false ) { - return false; - } - if ( !this.mouseMoved ) { - this.refresh( this._value(), true ); - } - }, - - _controlKeyup: function( event ) { // necessary? - this.refresh( this._value(), true, true ); - }, - - _controlBlur: function( event ) { - this.refresh( this._value(), true ); - }, - - // it appears the clicking the up and down buttons in chrome on - // range/number inputs doesn't trigger a change until the field is - // blurred. Here we check thif the value has changed and refresh - _controlVMouseUp: function( event ) { - this._checkedRefresh(); - }, - - // NOTE force focus on handle - _handleVMouseDown: function( event ) { - this.handle.focus(); - }, - - _handleKeydown: function( event ) { - var index = this._value(); - if ( this.options.disabled ) { - return; - } - - // In all cases prevent the default and mark the handle as active - switch ( event.keyCode ) { - case $.mobile.keyCode.HOME: - case $.mobile.keyCode.END: - case $.mobile.keyCode.PAGE_UP: - case $.mobile.keyCode.PAGE_DOWN: - case $.mobile.keyCode.UP: - case $.mobile.keyCode.RIGHT: - case $.mobile.keyCode.DOWN: - case $.mobile.keyCode.LEFT: - event.preventDefault(); - - if ( !this._keySliding ) { - this._keySliding = true; - this.handle.addClass( "ui-state-active" ); - } - - break; - } - - // move the slider according to the keypress - switch ( event.keyCode ) { - case $.mobile.keyCode.HOME: - this.refresh( this.min ); - break; - case $.mobile.keyCode.END: - this.refresh( this.max ); - break; - case $.mobile.keyCode.PAGE_UP: - case $.mobile.keyCode.UP: - case $.mobile.keyCode.RIGHT: - this.refresh( index + this.step ); - break; - case $.mobile.keyCode.PAGE_DOWN: - case $.mobile.keyCode.DOWN: - case $.mobile.keyCode.LEFT: - this.refresh( index - this.step ); - break; - } - }, // remove active mark - - _handleKeyup: function( event ) { - if ( this._keySliding ) { - this._keySliding = false; - this.handle.removeClass( "ui-state-active" ); - } - }, - - _sliderVMouseDown: function( event ) { - // NOTE: we don't do this in refresh because we still want to - // support programmatic alteration of disabled inputs - if ( this.options.disabled || !( event.which === 1 || event.which === 0 || event.which === undefined ) ) { - return false; - } - if ( this._trigger( "beforestart", event ) === false ) { - return false; - } - this.dragging = true; - this.userModified = false; - this.mouseMoved = false; - - if ( this.isToggleSwitch ) { - this.beforeStart = this.element[0].selectedIndex; - } - - - this.refresh( event ); - this._trigger( "start" ); - return false; - }, - - _sliderVMouseUp: function() { - if ( this.dragging ) { - this.dragging = false; - - if ( this.isToggleSwitch ) { - // make the handle move with a smooth transition - this.handle.addClass( "ui-slider-handle-snapping" ); - - if ( this.mouseMoved ) { - // this is a drag, change the value only if user dragged enough - if ( this.userModified ) { - this.refresh( this.beforeStart === 0 ? 1 : 0 ); - } else { - this.refresh( this.beforeStart ); - } - } else { - // this is just a click, change the value - this.refresh( this.beforeStart === 0 ? 1 : 0 ); - } - } - - this.mouseMoved = false; - this._trigger( "stop" ); - return false; - } - }, - - _preventDocumentDrag: function( event ) { - // NOTE: we don't do this in refresh because we still want to - // support programmatic alteration of disabled inputs - if ( this._trigger( "drag", event ) === false) { - return false; - } - if ( this.dragging && !this.options.disabled ) { - - // this.mouseMoved must be updated before refresh() because it will be used in the control "change" event - this.mouseMoved = true; - - if ( this.isToggleSwitch ) { - // make the handle move in sync with the mouse - this.handle.removeClass( "ui-slider-handle-snapping" ); - } - - this.refresh( event ); - - // only after refresh() you can calculate this.userModified - this.userModified = this.beforeStart !== this.element[0].selectedIndex; - return false; - } - }, - - _checkedRefresh: function() { - if ( this.value !== this._value() ) { - this.refresh( this._value() ); - } - }, - - _value: function() { - return this.isToggleSwitch ? this.element[0].selectedIndex : parseFloat( this.element.val() ) ; - }, - - - _reset: function() { - this.refresh( undefined, false, true ); - }, - - refresh: function( val, isfromControl, preventInputUpdate ) { - // NOTE: we don't return here because we want to support programmatic - // alteration of the input value, which should still update the slider - - var self = this, - parentTheme = $.mobile.getInheritedTheme( this.element, "c" ), - theme = this.options.theme || parentTheme, - trackTheme = this.options.trackTheme || parentTheme, - left, width, data, tol; - - self.slider[0].className = [ this.isToggleSwitch ? "ui-slider ui-slider-switch" : "ui-slider-track"," ui-btn-down-" + trackTheme,' ui-btn-corner-all', ( this.options.mini ) ? " ui-mini":""].join( "" ); - if ( this.options.disabled || this.element.attr( "disabled" ) ) { - this.disable(); - } - - // set the stored value for comparison later - this.value = this._value(); - if ( this.options.highlight && !this.isToggleSwitch && this.slider.find( ".ui-slider-bg" ).length === 0 ) { - this.valuebg = (function() { - var bg = document.createElement( "div" ); - bg.className = "ui-slider-bg " + $.mobile.activeBtnClass + " ui-btn-corner-all"; - return $( bg ).prependTo( self.slider ); - })(); - } - this.handle.buttonMarkup({ corners: true, theme: theme, shadow: true }); - - var pxStep, percent, - control = this.element, - isInput = !this.isToggleSwitch, - optionElements = isInput ? [] : control.find( "option" ), - min = isInput ? parseFloat( control.attr( "min" ) ) : 0, - max = isInput ? parseFloat( control.attr( "max" ) ) : optionElements.length - 1, - step = ( isInput && parseFloat( control.attr( "step" ) ) > 0 ) ? parseFloat( control.attr( "step" ) ) : 1; - - if ( typeof val === "object" ) { - data = val; - // a slight tolerance helped get to the ends of the slider - tol = 8; - - left = this.slider.offset().left; - width = this.slider.width(); - pxStep = width/((max-min)/step); - if ( !this.dragging || - data.pageX < left - tol || - data.pageX > left + width + tol ) { - return; - } - if ( pxStep > 1 ) { - percent = ( ( data.pageX - left ) / width ) * 100; - } else { - percent = Math.round( ( ( data.pageX - left ) / width ) * 100 ); - } - } else { - if ( val == null ) { - val = isInput ? parseFloat( control.val() || 0 ) : control[0].selectedIndex; - } - percent = ( parseFloat( val ) - min ) / ( max - min ) * 100; - } - - if ( isNaN( percent ) ) { - return; - } - - var newval = ( percent / 100 ) * ( max - min ) + min; - - //from jQuery UI slider, the following source will round to the nearest step - var valModStep = ( newval - min ) % step; - var alignValue = newval - valModStep; - - if ( Math.abs( valModStep ) * 2 >= step ) { - alignValue += ( valModStep > 0 ) ? step : ( -step ); - } - - var percentPerStep = 100/((max-min)/step); - // Since JavaScript has problems with large floats, round - // the final value to 5 digits after the decimal point (see jQueryUI: #4124) - newval = parseFloat( alignValue.toFixed(5) ); - - if ( typeof pxStep === "undefined" ) { - pxStep = width / ( (max-min) / step ); - } - if ( pxStep > 1 && isInput ) { - percent = ( newval - min ) * percentPerStep * ( 1 / step ); - } - if ( percent < 0 ) { - percent = 0; - } - - if ( percent > 100 ) { - percent = 100; - } - - if ( newval < min ) { - newval = min; - } - - if ( newval > max ) { - newval = max; - } - - this.handle.css( "left", percent + "%" ); - - this.handle[0].setAttribute( "aria-valuenow", isInput ? newval : optionElements.eq( newval ).attr( "value" ) ); - - this.handle[0].setAttribute( "aria-valuetext", isInput ? newval : optionElements.eq( newval ).getEncodedText() ); - - this.handle[0].setAttribute( "title", isInput ? newval : optionElements.eq( newval ).getEncodedText() ); - - if ( this.valuebg ) { - this.valuebg.css( "width", percent + "%" ); - } - - // drag the label widths - if ( this._labels ) { - var handlePercent = this.handle.width() / this.slider.width() * 100, - aPercent = percent && handlePercent + ( 100 - handlePercent ) * percent / 100, - bPercent = percent === 100 ? 0 : Math.min( handlePercent + 100 - aPercent, 100 ); - - this._labels.each(function() { - var ab = $( this ).is( ".ui-slider-label-a" ); - $( this ).width( ( ab ? aPercent : bPercent ) + "%" ); - }); - } - - if ( !preventInputUpdate ) { - var valueChanged = false; - - // update control"s value - if ( isInput ) { - valueChanged = control.val() !== newval; - control.val( newval ); - } else { - valueChanged = control[ 0 ].selectedIndex !== newval; - control[ 0 ].selectedIndex = newval; - } - if ( this._trigger( "beforechange", val ) === false) { - return false; - } - if ( !isfromControl && valueChanged ) { - control.trigger( "change" ); - } - } - }, - - enable: function() { - this.element.attr( "disabled", false ); - this.slider.removeClass( "ui-disabled" ).attr( "aria-disabled", false ); - return this._setOption( "disabled", false ); - }, - - disable: function() { - this.element.attr( "disabled", true ); - this.slider.addClass( "ui-disabled" ).attr( "aria-disabled", true ); - return this._setOption( "disabled", true ); - } - -}, $.mobile.behaviors.formReset ) ); - -//auto self-init widgets -$.mobile.document.bind( "pagecreate create", function( e ) { - $.mobile.slider.prototype.enhanceWithin( e.target, true ); -}); - -})( jQuery ); - -(function( $, undefined ) { - $.widget( "mobile.rangeslider", $.mobile.widget, { - - options: { - theme: null, - trackTheme: null, - disabled: false, - initSelector: ":jqmData(role='rangeslider')", - mini: false, - highlight: true - }, - - _create: function() { - var secondLabel, - $el = this.element, - elClass = this.options.mini ? "ui-rangeslider ui-mini" : "ui-rangeslider", - _inputFirst = $el.find( "input" ).first(), - _inputLast = $el.find( "input" ).last(), - label = $el.find( "label" ).first(), - _sliderFirst = $.data( _inputFirst.get(0), "mobileSlider" ).slider, - _sliderLast = $.data( _inputLast.get(0), "mobileSlider" ).slider, - firstHandle = $.data( _inputFirst.get(0), "mobileSlider" ).handle, - _sliders = $( "
" ).appendTo( $el ); - - if ( $el.find( "label" ).length > 1 ) { - secondLabel = $el.find( "label" ).last().hide(); - } - - _inputFirst.addClass( "ui-rangeslider-first" ); - _inputLast.addClass( "ui-rangeslider-last" ); - $el.addClass( elClass ); - - _sliderFirst.appendTo( _sliders ); - _sliderLast.appendTo( _sliders ); - label.prependTo( $el ); - firstHandle.prependTo( _sliderLast ); - - $.extend( this, { - _inputFirst: _inputFirst, - _inputLast: _inputLast, - _sliderFirst: _sliderFirst, - _sliderLast: _sliderLast, - _targetVal: null, - _sliderTarget: false, - _sliders: _sliders, - _proxy: false - }); - - this.refresh(); - this._on( this.element.find( "input.ui-slider-input" ), { - "slidebeforestart": "_slidebeforestart", - "slidestop": "_slidestop", - "slidedrag": "_slidedrag", - "slidebeforechange": "_change", - "blur": "_change", - "keyup": "_change" - }); - this._on({ - "mousedown":"_change" - }); - this._on( this.element.closest( "form" ), { - "reset":"_handleReset" - }); - this._on( firstHandle, { - "vmousedown": "_dragFirstHandle" - }); - }, - _handleReset: function(){ - var self = this; - //we must wait for the stack to unwind before updateing other wise sliders will not have updated yet - setTimeout( function(){ - self._updateHighlight(); - },0); - }, - - _dragFirstHandle: function( event ) { - //if the first handle is dragged send the event to the first slider - $.data( this._inputFirst.get(0), "mobileSlider" ).dragging = true; - $.data( this._inputFirst.get(0), "mobileSlider" ).refresh( event ); - return false; - }, - - _slidedrag: function( event ) { - var first = $( event.target ).is( this._inputFirst ), - otherSlider = ( first ) ? this._inputLast : this._inputFirst; - - this._sliderTarget = false; - //if the drag was initiated on an extreme and the other handle is focused send the events to - //the closest handle - if ( ( this._proxy === "first" && first ) || ( this._proxy === "last" && !first ) ) { - $.data( otherSlider.get(0), "mobileSlider" ).dragging = true; - $.data( otherSlider.get(0), "mobileSlider" ).refresh( event ); - return false; - } - }, - - _slidestop: function( event ) { - var first = $( event.target ).is( this._inputFirst ); - - this._proxy = false; - //this stops dragging of the handle and brings the active track to the front - //this makes clicks on the track go the the last handle used - this.element.find( "input" ).trigger( "vmouseup" ); - this._sliderFirst.css( "z-index", first ? 1 : "" ); - }, - - _slidebeforestart: function( event ) { - this._sliderTarget = false; - //if the track is the target remember this and the original value - if ( $( event.originalEvent.target ).hasClass( "ui-slider-track" ) ) { - this._sliderTarget = true; - this._targetVal = $( event.target ).val(); - } - }, - - _setOption: function( options ) { - this._superApply( options ); - this.refresh(); - }, - - refresh: function() { - var $el = this.element, - o = this.options; - - $el.find( "input" ).slider({ - theme: o.theme, - trackTheme: o.trackTheme, - disabled: o.disabled, - mini: o.mini, - highlight: o.highlight - }).slider( "refresh" ); - this._updateHighlight(); - }, - - _change: function( event ) { - if ( event.type === "keyup" ) { - this._updateHighlight(); - return false; - } - - var self = this, - min = parseFloat( this._inputFirst.val(), 10 ), - max = parseFloat( this._inputLast.val(), 10 ), - first = $( event.target ).hasClass( "ui-rangeslider-first" ), - thisSlider = first ? this._inputFirst : this._inputLast, - otherSlider = first ? this._inputLast : this._inputFirst; - - - if( ( this._inputFirst.val() > this._inputLast.val() && event.type === "mousedown" && !$(event.target).hasClass("ui-slider-handle")) ){ - thisSlider.blur(); - } else if( event.type === "mousedown" ){ - return; - } - if ( min > max && !this._sliderTarget ) { - //this prevents min from being greater then max - thisSlider.val( first ? max: min ).slider( "refresh" ); - this._trigger( "normalize" ); - } else if ( min > max ) { - //this makes it so clicks on the target on either extreme go to the closest handle - thisSlider.val( this._targetVal ).slider( "refresh" ); - - //You must wait for the stack to unwind so first slider is updated before updating second - setTimeout( function() { - otherSlider.val( first ? min: max ).slider( "refresh" ); - $.data( otherSlider.get(0), "mobileSlider" ).handle.focus(); - self._sliderFirst.css( "z-index", first ? "" : 1 ); - self._trigger( "normalize" ); - }, 0 ); - this._proxy = ( first ) ? "first" : "last"; - } - //fixes issue where when both _sliders are at min they cannot be adjusted - if ( min === max ) { - $.data( thisSlider.get(0), "mobileSlider" ).handle.css( "z-index", 1 ); - $.data( otherSlider.get(0), "mobileSlider" ).handle.css( "z-index", 0 ); - } else { - $.data( otherSlider.get(0), "mobileSlider" ).handle.css( "z-index", "" ); - $.data( thisSlider.get(0), "mobileSlider" ).handle.css( "z-index", "" ); - } - - this._updateHighlight(); - - if ( min >= max ) { - return false; - } - }, - - _updateHighlight: function() { - var min = parseInt( $.data( this._inputFirst.get(0), "mobileSlider" ).handle.get(0).style.left, 10 ), - max = parseInt( $.data( this._inputLast.get(0), "mobileSlider" ).handle.get(0).style.left, 10 ), - width = (max - min); - - this.element.find( ".ui-slider-bg" ).css({ - "margin-left": min + "%", - "width": width + "%" - }); - }, - - _destroy: function() { - this.element.removeClass( "ui-rangeslider ui-mini" ).find( "label" ).show(); - this._inputFirst.after( this._sliderFirst ); - this._inputLast.after( this._sliderLast ); - this._sliders.remove(); - this.element.find( "input" ).removeClass( "ui-rangeslider-first ui-rangeslider-last" ).slider( "destroy" ); - } - - }); - -$.widget( "mobile.rangeslider", $.mobile.rangeslider, $.mobile.behaviors.formReset ); - -//auto self-init widgets -$( document ).bind( "pagecreate create", function( e ) { - $.mobile.rangeslider.prototype.enhanceWithin( e.target, true ); -}); - -})( jQuery ); - -(function( $, undefined ) { - -$.widget( "mobile.selectmenu", $.mobile.widget, $.extend( { - options: { - theme: null, - disabled: false, - icon: "arrow-d", - iconpos: "right", - inline: false, - corners: true, - shadow: true, - iconshadow: true, - overlayTheme: "a", - dividerTheme: "b", - hidePlaceholderMenuItems: true, - closeText: "Close", - nativeMenu: true, - // This option defaults to true on iOS devices. - preventFocusZoom: /iPhone|iPad|iPod/.test( navigator.platform ) && navigator.userAgent.indexOf( "AppleWebKit" ) > -1, - initSelector: "select:not( :jqmData(role='slider') )", - mini: false - }, - - _button: function() { - return $( "
" ); - }, - - _setDisabled: function( value ) { - this.element.attr( "disabled", value ); - this.button.attr( "aria-disabled", value ); - return this._setOption( "disabled", value ); - }, - - _focusButton : function() { - var self = this; - - setTimeout( function() { - self.button.focus(); - }, 40); - }, - - _selectOptions: function() { - return this.select.find( "option" ); - }, - - // setup items that are generally necessary for select menu extension - _preExtension: function() { - var classes = ""; - // TODO: Post 1.1--once we have time to test thoroughly--any classes manually applied to the original element should be carried over to the enhanced element, with an `-enhanced` suffix. See https://github.com/jquery/jquery-mobile/issues/3577 - /* if ( $el[0].className.length ) { - classes = $el[0].className; - } */ - if ( !!~this.element[0].className.indexOf( "ui-btn-left" ) ) { - classes = " ui-btn-left"; - } - - if ( !!~this.element[0].className.indexOf( "ui-btn-right" ) ) { - classes = " ui-btn-right"; - } - - this.select = this.element.removeClass( "ui-btn-left ui-btn-right" ).wrap( "
" ); - this.selectID = this.select.attr( "id" ); - this.label = $( "label[for='"+ this.selectID +"']" ).addClass( "ui-select" ); - this.isMultiple = this.select[ 0 ].multiple; - if ( !this.options.theme ) { - this.options.theme = $.mobile.getInheritedTheme( this.select, "c" ); - } - }, - - _destroy: function() { - var wrapper = this.element.parents( ".ui-select" ); - if ( wrapper.length > 0 ) { - if ( wrapper.is( ".ui-btn-left, .ui-btn-right" ) ) { - this.element.addClass( wrapper.is( ".ui-btn-left" ) ? "ui-btn-left" : "ui-btn-right" ); - } - this.element.insertAfter( wrapper ); - wrapper.remove(); - } - }, - - _create: function() { - this._preExtension(); - - // Allows for extension of the native select for custom selects and other plugins - // see select.custom for example extension - // TODO explore plugin registration - this._trigger( "beforeCreate" ); - - this.button = this._button(); - - var self = this, - - options = this.options, - - inline = options.inline || this.select.jqmData( "inline" ), - mini = options.mini || this.select.jqmData( "mini" ), - iconpos = options.icon ? ( options.iconpos || this.select.jqmData( "iconpos" ) ) : false, - - // IE throws an exception at options.item() function when - // there is no selected item - // select first in this case - selectedIndex = this.select[ 0 ].selectedIndex === -1 ? 0 : this.select[ 0 ].selectedIndex, - - // TODO values buttonId and menuId are undefined here - button = this.button - .insertBefore( this.select ) - .buttonMarkup( { - theme: options.theme, - icon: options.icon, - iconpos: iconpos, - inline: inline, - corners: options.corners, - shadow: options.shadow, - iconshadow: options.iconshadow, - mini: mini - }); - - this.setButtonText(); - - // Opera does not properly support opacity on select elements - // In Mini, it hides the element, but not its text - // On the desktop,it seems to do the opposite - // for these reasons, using the nativeMenu option results in a full native select in Opera - if ( options.nativeMenu && window.opera && window.opera.version ) { - button.addClass( "ui-select-nativeonly" ); - } - - // Add counter for multi selects - if ( this.isMultiple ) { - this.buttonCount = $( "" ) - .addClass( "ui-li-count ui-btn-up-c ui-btn-corner-all" ) - .hide() - .appendTo( button.addClass('ui-li-has-count') ); - } - - // Disable if specified - if ( options.disabled || this.element.attr('disabled')) { - this.disable(); - } - - // Events on native select - this.select.change(function() { - self.refresh(); - - if ( !!options.nativeMenu ) { - this.blur(); - } - }); - - this._handleFormReset(); - - this.build(); - }, - - build: function() { - var self = this; - - this.select - .appendTo( self.button ) - .bind( "vmousedown", function() { - // Add active class to button - self.button.addClass( $.mobile.activeBtnClass ); - }) - .bind( "focus", function() { - self.button.addClass( $.mobile.focusClass ); - }) - .bind( "blur", function() { - self.button.removeClass( $.mobile.focusClass ); - }) - .bind( "focus vmouseover", function() { - self.button.trigger( "vmouseover" ); - }) - .bind( "vmousemove", function() { - // Remove active class on scroll/touchmove - self.button.removeClass( $.mobile.activeBtnClass ); - }) - .bind( "change blur vmouseout", function() { - self.button.trigger( "vmouseout" ) - .removeClass( $.mobile.activeBtnClass ); - }) - .bind( "change blur", function() { - self.button.removeClass( "ui-btn-down-" + self.options.theme ); - }); - - // In many situations, iOS will zoom into the select upon tap, this prevents that from happening - self.button.bind( "vmousedown", function() { - if ( self.options.preventFocusZoom ) { - $.mobile.zoom.disable( true ); - } - }); - self.label.bind( "click focus", function() { - if ( self.options.preventFocusZoom ) { - $.mobile.zoom.disable( true ); - } - }); - self.select.bind( "focus", function() { - if ( self.options.preventFocusZoom ) { - $.mobile.zoom.disable( true ); - } - }); - self.button.bind( "mouseup", function() { - if ( self.options.preventFocusZoom ) { - setTimeout(function() { - $.mobile.zoom.enable( true ); - }, 0 ); - } - }); - self.select.bind( "blur", function() { - if ( self.options.preventFocusZoom ) { - $.mobile.zoom.enable( true ); - } - }); - - }, - - selected: function() { - return this._selectOptions().filter( ":selected" ); - }, - - selectedIndices: function() { - var self = this; - - return this.selected().map(function() { - return self._selectOptions().index( this ); - }).get(); - }, - - setButtonText: function() { - var self = this, - selected = this.selected(), - text = this.placeholder, - span = $( document.createElement( "span" ) ); - - this.button.find( ".ui-btn-text" ).html(function() { - if ( selected.length ) { - text = selected.map(function() { - return $( this ).text(); - }).get().join( ", " ); - } else { - text = self.placeholder; - } - - // TODO possibly aggregate multiple select option classes - return span.text( text ) - .addClass( self.select.attr( "class" ) ) - .addClass( selected.attr( "class" ) ); - }); - }, - - setButtonCount: function() { - var selected = this.selected(); - - // multiple count inside button - if ( this.isMultiple ) { - this.buttonCount[ selected.length > 1 ? "show" : "hide" ]().text( selected.length ); - } - }, - - _reset: function() { - this.refresh(); - }, - - refresh: function() { - 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 ); - this.button.addClass( "ui-disabled" ); - }, - - enable: function() { - this._setDisabled( false ); - this.button.removeClass( "ui-disabled" ); - } -}, $.mobile.behaviors.formReset ) ); - -//auto self-init widgets -$.mobile.document.bind( "pagecreate create", function( e ) { - $.mobile.selectmenu.prototype.enhanceWithin( e.target, true ); -}); -})( jQuery ); - -(function( $, undefined ) { - -function fitSegmentInsideSegment( winSize, segSize, offset, desired ) { - var ret = desired; - - if ( winSize < segSize ) { - // Center segment if it's bigger than the window - ret = offset + ( winSize - segSize ) / 2; - } else { - // Otherwise center it at the desired coordinate while keeping it completely inside the window - ret = Math.min( Math.max( offset, desired - segSize / 2 ), offset + winSize - segSize ); - } - - return ret; -} - -function windowCoords() { - var $win = $.mobile.window; - - return { - x: $win.scrollLeft(), - y: $win.scrollTop(), - cx: ( window.innerWidth || $win.width() ), - cy: ( window.innerHeight || $win.height() ) - }; -} - -$.widget( "mobile.popup", $.mobile.widget, { - options: { - theme: null, - overlayTheme: null, - shadow: true, - corners: true, - transition: "none", - positionTo: "origin", - tolerance: null, - initSelector: ":jqmData(role='popup')", - closeLinkSelector: "a:jqmData(rel='back')", - closeLinkEvents: "click.popup", - navigateEvents: "navigate.popup", - closeEvents: "navigate.popup pagebeforechange.popup", - dismissible: true, - - // NOTE Windows Phone 7 has a scroll position caching issue that - // requires us to disable popup history management by default - // https://github.com/jquery/jquery-mobile/issues/4784 - // - // NOTE this option is modified in _create! - history: !$.mobile.browser.oldIE - }, - - _eatEventAndClose: function( e ) { - e.preventDefault(); - e.stopImmediatePropagation(); - if ( this.options.dismissible ) { - this.close(); - } - return false; - }, - - // Make sure the screen size is increased beyond the page height if the popup's causes the document to increase in height - _resizeScreen: function() { - var popupHeight = this._ui.container.outerHeight( true ); - - this._ui.screen.removeAttr( "style" ); - if ( popupHeight > this._ui.screen.height() ) { - this._ui.screen.height( popupHeight ); - } - }, - - _handleWindowKeyUp: function( e ) { - if ( this._isOpen && e.keyCode === $.mobile.keyCode.ESCAPE ) { - return this._eatEventAndClose( e ); - } - }, - - _expectResizeEvent: function() { - var winCoords = windowCoords(); - - if ( this._resizeData ) { - if ( winCoords.x === this._resizeData.winCoords.x && - winCoords.y === this._resizeData.winCoords.y && - winCoords.cx === this._resizeData.winCoords.cx && - winCoords.cy === this._resizeData.winCoords.cy ) { - // timeout not refreshed - return false; - } else { - // clear existing timeout - it will be refreshed below - clearTimeout( this._resizeData.timeoutId ); - } - } - - this._resizeData = { - timeoutId: setTimeout( $.proxy( this, "_resizeTimeout" ), 200 ), - winCoords: winCoords - }; - - return true; - }, - - _resizeTimeout: function() { - if ( this._isOpen ) { - if ( !this._expectResizeEvent() ) { - if ( this._ui.container.hasClass( "ui-popup-hidden" ) ) { - // effectively rapid-open the popup while leaving the screen intact - this._ui.container.removeClass( "ui-popup-hidden" ); - this.reposition( { positionTo: "window" } ); - this._ignoreResizeEvents(); - } - - this._resizeScreen(); - this._resizeData = null; - this._orientationchangeInProgress = false; - } - } else { - this._resizeData = null; - this._orientationchangeInProgress = false; - } - }, - - _ignoreResizeEvents: function() { - var self = this; - - if ( this._ignoreResizeTo ) { - clearTimeout( this._ignoreResizeTo ); - } - this._ignoreResizeTo = setTimeout( function() { self._ignoreResizeTo = 0; }, 1000 ); - }, - - _handleWindowResize: function( e ) { - if ( this._isOpen && this._ignoreResizeTo === 0 ) { - if ( ( this._expectResizeEvent() || this._orientationchangeInProgress ) && - !this._ui.container.hasClass( "ui-popup-hidden" ) ) { - // effectively rapid-close the popup while leaving the screen intact - this._ui.container - .addClass( "ui-popup-hidden" ) - .removeAttr( "style" ); - } - } - }, - - _handleWindowOrientationchange: function( e ) { - if ( !this._orientationchangeInProgress && this._isOpen && this._ignoreResizeTo === 0 ) { - this._expectResizeEvent(); - this._orientationchangeInProgress = true; - } - }, - - // When the popup is open, attempting to focus on an element that is not a - // child of the popup will redirect focus to the popup - _handleDocumentFocusIn: function( e ) { - var tgt = e.target, $tgt, ui = this._ui; - - if ( !this._isOpen ) { - return; - } - - if ( tgt !== ui.container[ 0 ] ) { - $tgt = $( e.target ); - if ( 0 === $tgt.parents().filter( ui.container[ 0 ] ).length ) { - $( document.activeElement ).one( "focus", function( e ) { - $tgt.blur(); - }); - ui.focusElement.focus(); - e.preventDefault(); - e.stopImmediatePropagation(); - return false; - } else if ( ui.focusElement[ 0 ] === ui.container[ 0 ] ) { - ui.focusElement = $tgt; - } - } - - this._ignoreResizeEvents(); - }, - - _create: function() { - var ui = { - screen: $( "
" ), - placeholder: $( "
" ), - container: $( "
" ) - }, - thisPage = this.element.closest( ".ui-page" ), - myId = this.element.attr( "id" ), - o = this.options, - key, value; - - // We need to adjust the history option to be false if there's no AJAX nav. - // We can't do it in the option declarations because those are run before - // it is determined whether there shall be AJAX nav. - o.history = o.history && $.mobile.ajaxEnabled && $.mobile.hashListeningEnabled; - - if ( thisPage.length === 0 ) { - thisPage = $( "body" ); - } - - // define the container for navigation event bindings - // TODO this would be nice at the the mobile widget level - o.container = o.container || $.mobile.pageContainer || thisPage; - - // Apply the proto - thisPage.append( ui.screen ); - ui.container.insertAfter( ui.screen ); - // Leave a placeholder where the element used to be - ui.placeholder.insertAfter( this.element ); - if ( myId ) { - ui.screen.attr( "id", myId + "-screen" ); - ui.container.attr( "id", myId + "-popup" ); - ui.placeholder.html( "" ); - } - ui.container.append( this.element ); - ui.focusElement = ui.container; - - // Add class to popup element - this.element.addClass( "ui-popup" ); - - // Define instance variables - $.extend( this, { - _scrollTop: 0, - _page: thisPage, - _ui: ui, - _fallbackTransition: "", - _currentTransition: false, - _prereqs: null, - _isOpen: false, - _tolerance: null, - _resizeData: null, - _ignoreResizeTo: 0, - _orientationchangeInProgress: false - }); - - // This duplicates the code from the various option setters below for - // better performance. It must be kept in sync with those setters. - this._applyTheme( this.element, o.theme, "body" ); - this._applyTheme( this._ui.screen, o.overlayTheme, "overlay" ); - this._applyTransition( o.transition ); - this.element - .toggleClass( "ui-overlay-shadow", o.shadow ) - .toggleClass( "ui-corner-all", o.corners ); - this._setTolerance( o.tolerance ); - - ui.screen.bind( "vclick", $.proxy( this, "_eatEventAndClose" ) ); - - this._on( $.mobile.window, { - orientationchange: $.proxy( this, "_handleWindowOrientationchange" ), - resize: $.proxy( this, "_handleWindowResize" ), - keyup: $.proxy( this, "_handleWindowKeyUp" ) - }); - this._on( $.mobile.document, { - focusin: $.proxy( this, "_handleDocumentFocusIn" ) - }); - }, - - _applyTheme: function( dst, theme, prefix ) { - var classes = ( dst.attr( "class" ) || "").split( " " ), - alreadyAdded = true, - currentTheme = null, - matches, - themeStr = String( theme ); - - while ( classes.length > 0 ) { - currentTheme = classes.pop(); - matches = ( new RegExp( "^ui-" + prefix + "-([a-z])$" ) ).exec( currentTheme ); - if ( matches && matches.length > 1 ) { - currentTheme = matches[ 1 ]; - break; - } else { - currentTheme = null; - } - } - - if ( theme !== currentTheme ) { - dst.removeClass( "ui-" + prefix + "-" + currentTheme ); - if ( ! ( theme === null || theme === "none" ) ) { - dst.addClass( "ui-" + prefix + "-" + themeStr ); - } - } - }, - - _setTheme: function( value ) { - this._applyTheme( this.element, value, "body" ); - }, - - _setOverlayTheme: function( value ) { - this._applyTheme( this._ui.screen, value, "overlay" ); - - if ( this._isOpen ) { - this._ui.screen.addClass( "in" ); - } - }, - - _setShadow: function( value ) { - this.element.toggleClass( "ui-overlay-shadow", value ); - }, - - _setCorners: function( value ) { - this.element.toggleClass( "ui-corner-all", value ); - }, - - _applyTransition: function( value ) { - this._ui.container.removeClass( this._fallbackTransition ); - if ( value && value !== "none" ) { - this._fallbackTransition = $.mobile._maybeDegradeTransition( value ); - if ( this._fallbackTransition === "none" ) { - this._fallbackTransition = ""; - } - this._ui.container.addClass( this._fallbackTransition ); - } - }, - - _setTransition: function( value ) { - if ( !this._currentTransition ) { - this._applyTransition( value ); - } - }, - - _setTolerance: function( value ) { - var tol = { t: 30, r: 15, b: 30, l: 15 }; - - if ( value !== undefined ) { - var ar = String( value ).split( "," ); - - $.each( ar, function( idx, val ) { ar[ idx ] = parseInt( val, 10 ); } ); - - switch( ar.length ) { - // All values are to be the same - case 1: - if ( !isNaN( ar[ 0 ] ) ) { - tol.t = tol.r = tol.b = tol.l = ar[ 0 ]; - } - break; - - // The first value denotes top/bottom tolerance, and the second value denotes left/right tolerance - case 2: - if ( !isNaN( ar[ 0 ] ) ) { - tol.t = tol.b = ar[ 0 ]; - } - if ( !isNaN( ar[ 1 ] ) ) { - tol.l = tol.r = ar[ 1 ]; - } - break; - - // The array contains values in the order top, right, bottom, left - case 4: - if ( !isNaN( ar[ 0 ] ) ) { - tol.t = ar[ 0 ]; - } - if ( !isNaN( ar[ 1 ] ) ) { - tol.r = ar[ 1 ]; - } - if ( !isNaN( ar[ 2 ] ) ) { - tol.b = ar[ 2 ]; - } - if ( !isNaN( ar[ 3 ] ) ) { - tol.l = ar[ 3 ]; - } - break; - - default: - break; - } - } - - this._tolerance = tol; - }, - - _setOption: function( key, value ) { - var setter = "_set" + key.charAt( 0 ).toUpperCase() + key.slice( 1 ); - - if ( this[ setter ] !== undefined ) { - this[ setter ]( value ); - } - - this._super( key, value ); - }, - - // Try and center the overlay over the given coordinates - _placementCoords: function( desired ) { - // rectangle within which the popup must fit - var - winCoords = windowCoords(), - rc = { - x: this._tolerance.l, - y: winCoords.y + this._tolerance.t, - cx: winCoords.cx - this._tolerance.l - this._tolerance.r, - cy: winCoords.cy - this._tolerance.t - this._tolerance.b - }, - menuSize, ret; - - // Clamp the width of the menu before grabbing its size - this._ui.container.css( "max-width", rc.cx ); - menuSize = { - cx: this._ui.container.outerWidth( true ), - cy: this._ui.container.outerHeight( true ) - }; - - // Center the menu over the desired coordinates, while not going outside - // the window tolerances. This will center wrt. the window if the popup is too large. - ret = { - x: fitSegmentInsideSegment( rc.cx, menuSize.cx, rc.x, desired.x ), - y: fitSegmentInsideSegment( rc.cy, menuSize.cy, rc.y, desired.y ) - }; - - // Make sure the top of the menu is visible - ret.y = Math.max( 0, ret.y ); - - // If the height of the menu is smaller than the height of the document - // align the bottom with the bottom of the document - - // fix for $.mobile.document.height() bug in core 1.7.2. - var docEl = document.documentElement, docBody = document.body, - docHeight = Math.max( docEl.clientHeight, docBody.scrollHeight, docBody.offsetHeight, docEl.scrollHeight, docEl.offsetHeight ); - - ret.y -= Math.min( ret.y, Math.max( 0, ret.y + menuSize.cy - docHeight ) ); - - return { left: ret.x, top: ret.y }; - }, - - _createPrereqs: function( screenPrereq, containerPrereq, whenDone ) { - var self = this, prereqs; - - // It is important to maintain both the local variable prereqs and self._prereqs. The local variable remains in - // the closure of the functions which call the callbacks passed in. The comparison between the local variable and - // self._prereqs is necessary, because once a function has been passed to .animationComplete() it will be called - // next time an animation completes, even if that's not the animation whose end the function was supposed to catch - // (for example, if an abort happens during the opening animation, the .animationComplete handler is not called for - // that animation anymore, but the handler remains attached, so it is called the next time the popup is opened - // - making it stale. Comparing the local variable prereqs to the widget-level variable self._prereqs ensures that - // callbacks triggered by a stale .animationComplete will be ignored. - - prereqs = { - screen: $.Deferred(), - container: $.Deferred() - }; - - prereqs.screen.then( function() { - if ( prereqs === self._prereqs ) { - screenPrereq(); - } - }); - - prereqs.container.then( function() { - if ( prereqs === self._prereqs ) { - containerPrereq(); - } - }); - - $.when( prereqs.screen, prereqs.container ).done( function() { - if ( prereqs === self._prereqs ) { - self._prereqs = null; - whenDone(); - } - }); - - self._prereqs = prereqs; - }, - - _animate: function( args ) { - // NOTE before removing the default animation of the screen - // this had an animate callback that would resolve the deferred - // now the deferred is resolved immediately - // TODO remove the dependency on the screen deferred - this._ui.screen - .removeClass( args.classToRemove ) - .addClass( args.screenClassToAdd ); - - args.prereqs.screen.resolve(); - - if ( args.transition && args.transition !== "none" ) { - if ( args.applyTransition ) { - this._applyTransition( args.transition ); - } - if ( this._fallbackTransition ) { - this._ui.container - .animationComplete( $.proxy( args.prereqs.container, "resolve" ) ) - .addClass( args.containerClassToAdd ) - .removeClass( args.classToRemove ); - return; - } - } - this._ui.container.removeClass( args.classToRemove ); - args.prereqs.container.resolve(); - }, - - // The desired coordinates passed in will be returned untouched if no reference element can be identified via - // desiredPosition.positionTo. Nevertheless, this function ensures that its return value always contains valid - // x and y coordinates by specifying the center middle of the window if the coordinates are absent. - // options: { x: coordinate, y: coordinate, positionTo: string: "origin", "window", or jQuery selector - _desiredCoords: function( o ) { - var dst = null, offset, winCoords = windowCoords(), x = o.x, y = o.y, pTo = o.positionTo; - - // Establish which element will serve as the reference - if ( pTo && pTo !== "origin" ) { - if ( pTo === "window" ) { - x = winCoords.cx / 2 + winCoords.x; - y = winCoords.cy / 2 + winCoords.y; - } else { - try { - dst = $( pTo ); - } catch( e ) { - dst = null; - } - if ( dst ) { - dst.filter( ":visible" ); - if ( dst.length === 0 ) { - dst = null; - } - } - } - } - - // If an element was found, center over it - if ( dst ) { - offset = dst.offset(); - x = offset.left + dst.outerWidth() / 2; - y = offset.top + dst.outerHeight() / 2; - } - - // Make sure x and y are valid numbers - center over the window - if ( $.type( x ) !== "number" || isNaN( x ) ) { - x = winCoords.cx / 2 + winCoords.x; - } - if ( $.type( y ) !== "number" || isNaN( y ) ) { - y = winCoords.cy / 2 + winCoords.y; - } - - return { x: x, y: y }; - }, - - _reposition: function( o ) { - // We only care about position-related parameters for repositioning - o = { x: o.x, y: o.y, positionTo: o.positionTo }; - this._trigger( "beforeposition", undefined, o ); - this._ui.container.offset( this._placementCoords( this._desiredCoords( o ) ) ); - }, - - reposition: function( o ) { - if ( this._isOpen ) { - this._reposition( o ); - } - }, - - _openPrereqsComplete: function() { - this._ui.container.addClass( "ui-popup-active" ); - this._isOpen = true; - this._resizeScreen(); - this._ui.container.attr( "tabindex", "0" ).focus(); - this._ignoreResizeEvents(); - this._trigger( "afteropen" ); - }, - - _open: function( options ) { - var o = $.extend( {}, this.options, options ), - // TODO move blacklist to private method - androidBlacklist = ( function() { - var w = window, - ua = navigator.userAgent, - // Rendering engine is Webkit, and capture major version - wkmatch = ua.match( /AppleWebKit\/([0-9\.]+)/ ), - wkversion = !!wkmatch && wkmatch[ 1 ], - androidmatch = ua.match( /Android (\d+(?:\.\d+))/ ), - andversion = !!androidmatch && androidmatch[ 1 ], - chromematch = ua.indexOf( "Chrome" ) > -1; - - // Platform is Android, WebKit version is greater than 534.13 ( Android 3.2.1 ) and not Chrome. - if( androidmatch !== null && andversion === "4.0" && wkversion && wkversion > 534.13 && !chromematch ) { - return true; - } - return false; - }()); - - // Count down to triggering "popupafteropen" - we have two prerequisites: - // 1. The popup window animation completes (container()) - // 2. The screen opacity animation completes (screen()) - this._createPrereqs( - $.noop, - $.noop, - $.proxy( this, "_openPrereqsComplete" ) ); - - this._currentTransition = o.transition; - this._applyTransition( o.transition ); - - if ( !this.options.theme ) { - this._setTheme( this._page.jqmData( "theme" ) || $.mobile.getInheritedTheme( this._page, "c" ) ); - } - - this._ui.screen.removeClass( "ui-screen-hidden" ); - this._ui.container.removeClass( "ui-popup-hidden" ); - - // Give applications a chance to modify the contents of the container before it appears - this._reposition( o ); - - if ( this.options.overlayTheme && androidBlacklist ) { - /* TODO: - The native browser on Android 4.0.X ("Ice Cream Sandwich") suffers from an issue where the popup overlay appears to be z-indexed - above the popup itself when certain other styles exist on the same page -- namely, any element set to `position: fixed` and certain - types of input. These issues are reminiscent of previously uncovered bugs in older versions of Android's native browser: - https://github.com/scottjehl/Device-Bugs/issues/3 - - This fix closes the following bugs ( I use "closes" with reluctance, and stress that this issue should be revisited as soon as possible ): - - https://github.com/jquery/jquery-mobile/issues/4816 - https://github.com/jquery/jquery-mobile/issues/4844 - https://github.com/jquery/jquery-mobile/issues/4874 - */ - - // TODO sort out why this._page isn't working - this.element.closest( ".ui-page" ).addClass( "ui-popup-open" ); - } - this._animate({ - additionalCondition: true, - transition: o.transition, - classToRemove: "", - screenClassToAdd: "in", - containerClassToAdd: "in", - applyTransition: false, - prereqs: this._prereqs - }); - }, - - _closePrereqScreen: function() { - this._ui.screen - .removeClass( "out" ) - .addClass( "ui-screen-hidden" ); - }, - - _closePrereqContainer: function() { - this._ui.container - .removeClass( "reverse out" ) - .addClass( "ui-popup-hidden" ) - .removeAttr( "style" ); - }, - - _closePrereqsDone: function() { - var container = this._ui.container; - - container.removeAttr( "tabindex" ); - - // remove the global mutex for popups - $.mobile.popup.active = undefined; - - // Blur elements inside the container, including the container - $( ":focus", container[ 0 ] ).add( container[ 0 ] ).blur(); - - // alert users that the popup is closed - this._trigger( "afterclose" ); - }, - - _close: function( immediate ) { - this._ui.container.removeClass( "ui-popup-active" ); - this._page.removeClass( "ui-popup-open" ); - - this._isOpen = false; - - // Count down to triggering "popupafterclose" - we have two prerequisites: - // 1. The popup window reverse animation completes (container()) - // 2. The screen opacity animation completes (screen()) - this._createPrereqs( - $.proxy( this, "_closePrereqScreen" ), - $.proxy( this, "_closePrereqContainer" ), - $.proxy( this, "_closePrereqsDone" ) ); - - this._animate( { - additionalCondition: this._ui.screen.hasClass( "in" ), - transition: ( immediate ? "none" : ( this._currentTransition ) ), - classToRemove: "in", - screenClassToAdd: "out", - containerClassToAdd: "reverse out", - applyTransition: true, - prereqs: this._prereqs - }); - }, - - _unenhance: function() { - // Put the element back to where the placeholder was and remove the "ui-popup" class - this._setTheme( "none" ); - this.element - // Cannot directly insertAfter() - we need to detach() first, because - // insertAfter() will do nothing if the payload div was not attached - // to the DOM at the time the widget was created, and so the payload - // will remain inside the container even after we call insertAfter(). - // If that happens and we remove the container a few lines below, we - // will cause an infinite recursion - #5244 - .detach() - .insertAfter( this._ui.placeholder ) - .removeClass( "ui-popup ui-overlay-shadow ui-corner-all" ); - this._ui.screen.remove(); - this._ui.container.remove(); - this._ui.placeholder.remove(); - }, - - _destroy: function() { - if ( $.mobile.popup.active === this ) { - this.element.one( "popupafterclose", $.proxy( this, "_unenhance" ) ); - this.close(); - } else { - this._unenhance(); - } - }, - - _closePopup: function( e, data ) { - var parsedDst, toUrl, o = this.options, immediate = false; - - // restore location on screen - window.scrollTo( 0, this._scrollTop ); - - if ( e && e.type === "pagebeforechange" && data ) { - // Determine whether we need to rapid-close the popup, or whether we can - // take the time to run the closing transition - if ( typeof data.toPage === "string" ) { - parsedDst = data.toPage; - } else { - parsedDst = data.toPage.jqmData( "url" ); - } - parsedDst = $.mobile.path.parseUrl( parsedDst ); - toUrl = parsedDst.pathname + parsedDst.search + parsedDst.hash; - - if ( this._myUrl !== $.mobile.path.makeUrlAbsolute( toUrl ) ) { - // Going to a different page - close immediately - immediate = true; - } else { - e.preventDefault(); - } - } - - // remove nav bindings - o.container.unbind( o.closeEvents ); - // unbind click handlers added when history is disabled - this.element.undelegate( o.closeLinkSelector, o.closeLinkEvents ); - - this._close( immediate ); - }, - - // any navigation event after a popup is opened should close the popup - // NOTE the pagebeforechange is bound to catch navigation events that don't - // alter the url (eg, dialogs from popups) - _bindContainerClose: function() { - this.options.container - .one( this.options.closeEvents, $.proxy( this, "_closePopup" ) ); - }, - - // TODO no clear deliniation of what should be here and - // what should be in _open. Seems to be "visual" vs "history" for now - open: function( options ) { - var self = this, opts = this.options, url, hashkey, activePage, currentIsDialog, hasHash, urlHistory; - - // make sure open is idempotent - if( $.mobile.popup.active ) { - return; - } - - // set the global popup mutex - $.mobile.popup.active = this; - this._scrollTop = $.mobile.window.scrollTop(); - - // if history alteration is disabled close on navigate events - // and leave the url as is - if( !( opts.history ) ) { - self._open( options ); - self._bindContainerClose(); - - // When histoy is disabled we have to grab the data-rel - // back link clicks so we can close the popup instead of - // relying on history to do it for us - self.element - .delegate( opts.closeLinkSelector, opts.closeLinkEvents, function( e ) { - self.close(); - e.preventDefault(); - }); - - return; - } - - // cache some values for min/readability - urlHistory = $.mobile.urlHistory; - hashkey = $.mobile.dialogHashKey; - activePage = $.mobile.activePage; - currentIsDialog = activePage.is( ".ui-dialog" ); - this._myUrl = url = urlHistory.getActive().url; - hasHash = ( url.indexOf( hashkey ) > -1 ) && !currentIsDialog && ( urlHistory.activeIndex > 0 ); - - if ( hasHash ) { - self._open( options ); - self._bindContainerClose(); - return; - } - - // if the current url has no dialog hash key proceed as normal - // otherwise, if the page is a dialog simply tack on the hash key - if ( url.indexOf( hashkey ) === -1 && !currentIsDialog ){ - url = url + (url.indexOf( "#" ) > -1 ? hashkey : "#" + hashkey); - } else { - url = $.mobile.path.parseLocation().hash + hashkey; - } - - // Tack on an extra hashkey if this is the first page and we've just reconstructed the initial hash - if ( urlHistory.activeIndex === 0 && url === urlHistory.initialDst ) { - url += hashkey; - } - - // swallow the the initial navigation event, and bind for the next - $(window).one( "beforenavigate", function( e ) { - e.preventDefault(); - self._open( options ); - self._bindContainerClose(); - }); - - this.urlAltered = true; - $.mobile.navigate( url, {role: "dialog"} ); - }, - - close: function() { - // make sure close is idempotent - if( $.mobile.popup.active !== this ) { - return; - } - - this._scrollTop = $.mobile.window.scrollTop(); - - if( this.options.history && this.urlAltered ) { - $.mobile.back(); - this.urlAltered = false; - } else { - // simulate the nav bindings having fired - this._closePopup(); - } - } -}); - - -// TODO this can be moved inside the widget -$.mobile.popup.handleLink = function( $link ) { - var closestPage = $link.closest( ":jqmData(role='page')" ), - scope = ( ( closestPage.length === 0 ) ? $( "body" ) : closestPage ), - // NOTE make sure to get only the hash, ie7 (wp7) return the absolute href - // in this case ruining the element selection - popup = $( $.mobile.path.parseUrl($link.attr( "href" )).hash, scope[0] ), - offset; - - if ( popup.data( "mobile-popup" ) ) { - offset = $link.offset(); - popup.popup( "open", { - x: offset.left + $link.outerWidth() / 2, - y: offset.top + $link.outerHeight() / 2, - transition: $link.jqmData( "transition" ), - positionTo: $link.jqmData( "position-to" ) - }); - } - - //remove after delay - setTimeout( function() { - // Check if we are in a listview - var $parent = $link.parent().parent(); - if ($parent.hasClass("ui-li")) { - $link = $parent.parent(); - } - $link.removeClass( $.mobile.activeBtnClass ); - }, 300 ); -}; - -// TODO move inside _create -$.mobile.document.bind( "pagebeforechange", function( e, data ) { - if ( data.options.role === "popup" ) { - $.mobile.popup.handleLink( data.options.link ); - e.preventDefault(); - } -}); - -$.mobile.document.bind( "pagecreate create", function( e ) { - $.mobile.popup.prototype.enhanceWithin( e.target, true ); -}); - -})( jQuery ); - -/* -* custom "selectmenu" plugin -*/ - -(function( $, undefined ) { - var extendSelect = function( widget ) { - - var select = widget.select, - origDestroy = widget._destroy, - selectID = widget.selectID, - prefix = ( selectID ? selectID : ( ( $.mobile.ns || "" ) + "uuid-" + widget.uuid ) ), - popupID = prefix + "-listbox", - dialogID = prefix + "-dialog", - label = widget.label, - thisPage = widget.select.closest( ".ui-page" ), - selectOptions = widget._selectOptions(), - isMultiple = widget.isMultiple = widget.select[ 0 ].multiple, - buttonId = selectID + "-button", - menuId = selectID + "-menu", - menuPage = $( "
" + - "
" + - "
" + label.getEncodedText() + "
"+ - "
"+ - "
"+ - "
" ), - - listbox = $( "
" ).insertAfter( widget.select ).popup( { theme: widget.options.overlayTheme } ), - - list = $( "