From 856f5652ea6800aa747f1b4d0337401c6a841f3e Mon Sep 17 00:00:00 2001 From: Antonio Gallo Date: Fri, 8 Jun 2012 06:35:20 +0000 Subject: added Javascript license information file (for LibreJS) and replaced the minified version of each Javascript with the full version to improve clearness (also updating to the last release) --- h-source/Public/Js/functions.js | 6 + h-source/Public/Js/jquery/jquery.mobile-1.0.css | 1847 ----- h-source/Public/Js/jquery/jquery.mobile-1.0.min.js | 172 - h-source/Public/Js/jquery/jquery.mobile-1.1.0.css | 2053 ++++++ h-source/Public/Js/jquery/jquery.mobile-1.1.0.js | 7551 ++++++++++++++++++++ .../Js/jquery/ui/js/jquery-ui-1.8.14.custom.min.js | 141 - .../Js/jquery/ui/js/jquery-ui-1.8.21.custom.js | 1937 +++++ 7 files changed, 11547 insertions(+), 2160 deletions(-) delete mode 100644 h-source/Public/Js/jquery/jquery.mobile-1.0.css delete mode 100644 h-source/Public/Js/jquery/jquery.mobile-1.0.min.js create mode 100644 h-source/Public/Js/jquery/jquery.mobile-1.1.0.css create mode 100644 h-source/Public/Js/jquery/jquery.mobile-1.1.0.js delete mode 100644 h-source/Public/Js/jquery/ui/js/jquery-ui-1.8.14.custom.min.js create mode 100644 h-source/Public/Js/jquery/ui/js/jquery-ui-1.8.21.custom.js (limited to 'h-source/Public') diff --git a/h-source/Public/Js/functions.js b/h-source/Public/Js/functions.js index 9111ba4..5b426d2 100644 --- a/h-source/Public/Js/functions.js +++ b/h-source/Public/Js/functions.js @@ -1,5 +1,8 @@ ", a[ 0 ] ){}; + + return v > 4 ? v : !v; +})(); + + +$.extend( $.support, { + orientation: "orientation" in window && "onorientationchange" in window, + touch: "ontouchend" in document, + cssTransitions: "WebKitTransitionEvent" in window || validStyle( 'transition', 'height 100ms linear' ), + pushState: "pushState" in history && "replaceState" in history, + mediaquery: $.mobile.media( "only all" ), + cssPseudoElement: !!propExists( "content" ), + touchOverflow: !!propExists( "overflowScrolling" ), + cssTransform3d: transform3dTest(), + boxShadow: !!propExists( "boxShadow" ) && !bb, + scrollTop: ( "pageXOffset" in window || "scrollTop" in document.documentElement || "scrollTop" in fakeBody[ 0 ] ) && !webos && !operamini, + dynamicBaseTag: baseTagTest() +}); + +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.ie && $.mobile.browser.ie >= 7; +}; + +$.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( $, window, undefined ) { + +// add new event shortcuts +$.each( ( "touchstart touchmove touchend orientationchange throttledresize " + + "tap taphold swipe swipeleft swiperight scrollstart scrollstop" ).split( " " ), function( i, name ) { + + $.fn[ name ] = function( fn ) { + return fn ? this.bind( name, fn ) : this.trigger( name ); + }; + + $.attrFn[ name ] = true; +}); + +var supportTouch = $.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.handle.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 = { + 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 } ) ); + }, 750 ); + }); + } +}; + +// also handles swipeleft, swiperight +$.event.special.swipe = { + scrollSupressionThreshold: 10, // 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. + + setup: function() { + var thisObject = this, + $this = $( thisObject ); + + $this.bind( touchStartEvent, function( event ) { + var data = event.originalEvent.touches ? + event.originalEvent.touches[ 0 ] : event, + start = { + time: ( new Date() ).getTime(), + coords: [ data.pageX, data.pageY ], + origin: $( event.target ) + }, + stop; + + function moveHandler( event ) { + + if ( !start ) { + return; + } + + var data = event.originalEvent.touches ? + event.originalEvent.touches[ 0 ] : event; + + stop = { + time: ( new Date() ).getTime(), + coords: [ data.pageX, data.pageY ] + }; + + // prevent scrolling + if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.scrollSupressionThreshold ) { + event.preventDefault(); + } + } + + $this.bind( touchMoveEvent, moveHandler ) + .one( touchStopEvent, function( event ) { + $this.unbind( touchMoveEvent, moveHandler ); + + if ( 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" ); + } + } + start = stop = undefined; + }); + }); + } +}; + +(function( $, window ) { + // "Cowboy" Ben Alman + + var win = $( window ), + 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 || $( window ).width(), + wh = window.innerHeight || $( window ).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 = special_event = { + setup: function() { + // If the event is supported natively, return false so that jQuery + // will bind to the event using DOM methods. + if ( $.support.orientation && $.mobile.orientationChangeEnabled ) { + 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 && $.mobile.orientationChangeEnabled ) { + 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( "orientationchange" ); + } + } + + // 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"; + }; + +})( jQuery, window ); + + +// 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; +})(); + + +$.each({ + scrollstop: "scrollstart", + taphold: "tap", + swipeleft: "swipe", + swiperight: "swipe" +}, function( event, sourceEvent ) { + + $.event.special[ event ] = { + setup: function() { + $( this ).bind( sourceEvent, $.noop ); + } + }; +}); + +})( jQuery, this ); + +(function( $, undefined ) { + +$.widget( "mobile.page", $.mobile.widget, { + options: { + theme: "c", + domCache: false, + keepNativeDefault: ":jqmData(role='none'), :jqmData(role='nojs')" + }, + + _create: function() { + + var self = this; + + // if false is returned by the callbacks do not create the page + if( self._trigger( "beforecreate" ) === false ){ + return false; + } + + self.element + .attr( "tabindex", "0" ) + .addClass( "ui-page ui-body-" + self.options.theme ) + .bind( "pagebeforehide", function(){ + self.removeContainerBackground(); + } ) + .bind( "pagebeforeshow", function(){ + self.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 && $( window ).width() > $.mobile.maxTransitionWidth, + none = !$.support.cssTransitions || maxTransitionOverride || !name || name === "none", + 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 + $(window ).scrollTop() ) + .addClass( name + " out" + reverseClass ); + }, + + doneOut = function() { + + if ( $from && sequential ) { + cleanFrom(); + } + + startIn(); + }, + + startIn = function(){ + + $to.addClass( $.mobile.activePageClass ); + + // Send focus to page as it is now display: block + $.mobile.focusPage( $to ); + + // Set to page height + $to.height( screenHeight + toScroll ); + + scrollPage(); + + if( !none ){ + $to.animationComplete( doneIn ); + } + + $to.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( $( 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 ); + +// 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 = {}; + +})( jQuery, this ); + +( function( $, undefined ) { + + //define vars for interal use + var $window = $( window ), + $html = $( 'html' ), + $head = $( 'head' ), + + //url path helpers for use in relative url management + path = { + + // 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: /^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/, + + //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; + } + + 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(), + // otherwise the Data Url won't match the id of the embedded Page. + return u.hash.split( dialogHashKey )[0].replace( /^#/, "" ); + } else if ( path.isSameDomain( u, documentBase ) ) { + return u.hrefNoHash.replace( documentBase.domain, "" ); + } + return absUrl; + }, + + //get path from current hash, or from a file path + get: function( newPath ) { + if( newPath === undefined ) { + newPath = location.hash; + } + return path.stripHash( newPath ).replace( /[^\/]*\.[^\/*]+$/, '' ); + }, + + //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]; + }, + + //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( documentBase.domain, "" ); + }, + + //just return the url without an initial # + stripHash: 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, "" ) ); + }, + + //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 !== documentUrl.domain ? true : false; + }, + + hasProtocol: function( url ) { + return ( /^(:?\w+:)/ ).test( url ); + }, + + //check if the specified url refers to the first page in the main application document. + isFirstPageUrl: function( url ) { + // We only deal with absolute paths. + var u = path.parseUrl( path.makeUrlAbsolute( url, documentBase ) ), + + // Does the url have the same path as the document? + samePath = u.hrefNoHash === documentUrl.hrefNoHash || ( documentBaseDiffers && u.hrefNoHash === documentBase.hrefNoHash ), + + // Get the first page element. + fp = $.mobile.firstPage, + + // Get the id of the first page element if it has one. + fpId = fp && fp[0] ? fp[0].id : undefined; + + // The url refers to the first page if the path matches the document and + // it either has no hash value, or the hash is exactly equal to the id of the + // first page element. + return samePath && ( !u.hash || u.hash === "#" || ( fpId && u.hash.replace( /^#/, "" ) === fpId ) ); + }, + + isEmbeddedPage: function( url ) { + var u = path.parseUrl( url ); + + //if the path is absolute, then we need to compare the url against + //both the 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 ( u.hash && ( u.hrefNoHash === documentUrl.hrefNoHash || ( documentBaseDiffers && u.hrefNoHash === documentBase.hrefNoHash ) ) ); + } + return (/^#/).test( u.href ); + } + }, + + //will be defined when a link is clicked and given an active class + $activeClickedLink = null, + + //urlHistory is purely here to make guesses at whether the back or forward button was clicked + //and provide an appropriate transition + urlHistory = { + // Array of pages that are visited during a single page load. + // Each has a url and optional transition, title, and pageUrl (which represents the file path, in cases where URL is obscured, such as dialogs) + stack: [], + + //maintain an index number for the active page in the stack + activeIndex: 0, + + //get active + getActive: function() { + return urlHistory.stack[ urlHistory.activeIndex ]; + }, + + getPrev: function() { + return urlHistory.stack[ urlHistory.activeIndex - 1 ]; + }, + + getNext: function() { + return urlHistory.stack[ urlHistory.activeIndex + 1 ]; + }, + + // addNew is used whenever a new page is added + addNew: function( url, transition, title, pageUrl, role ) { + //if there's forward history, wipe it + if( urlHistory.getNext() ) { + urlHistory.clearForward(); + } + + urlHistory.stack.push( {url : url, transition: transition, title: title, pageUrl: pageUrl, role: role } ); + + urlHistory.activeIndex = urlHistory.stack.length - 1; + }, + + //wipe urls ahead of active index + clearForward: function() { + urlHistory.stack = urlHistory.stack.slice( 0, urlHistory.activeIndex + 1 ); + }, + + directHashChange: function( opts ) { + var back , forward, newActiveIndex, prev = this.getActive(); + + // check if url isp in history and if it's ahead or behind current page + $.each( urlHistory.stack, function( i, historyEntry ) { + + //if the url is in the stack, it's a forward or a back + if( opts.currentUrl === historyEntry.url ) { + //define back and forward by whether url is older or newer than current page + back = i < urlHistory.activeIndex; + forward = !back; + newActiveIndex = i; + } + }); + + // save new page index, null check to prevent falsey 0 result + this.activeIndex = newActiveIndex !== undefined ? newActiveIndex : this.activeIndex; + + if( back ) { + ( opts.either || opts.isBack )( true ); + } else if( forward ) { + ( opts.either || opts.isForward )( false ); + } + }, + + //disable hashchange event listener internally to ignore one change + //toggled internally when location.hash is updated to match the url of a successful page load + ignoreNextHashChange: false + }, + + //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.parseUrl( location.href ), + + //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 = $base.length ? path.parseUrl( path.makeUrlAbsolute( $base.attr( "href" ), documentUrl.href ) ) : documentUrl, + + //cache the comparison once. + documentBaseDiffers = ( documentUrl.hrefNoHash !== documentBase.hrefNoHash ); + + //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 ) { + 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.hrefNoHash ); + } + + } : undefined; + +/* + internal utility functions +--------------------------------------*/ + + + //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( '.ui-page-active' ).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 ); + + //function for transitioning between two existing pages + function transitionPages( toPage, fromPage, transition, reverse ) { + + if( fromPage ) { + //trigger before show/hide events + fromPage.data( "page" )._trigger( "beforehide", null, { nextPage: toPage } ); + } + + toPage.data( "page" )._trigger( "beforeshow", null, { prevPage: fromPage || $( "" ) } ); + + //clear page loader + $.mobile.hidePageLoadingMsg(); + + // If transition is defined, check if css 3D transforms are supported, and if not, if a fallback is specified + if( transition && !$.support.cssTransform3d && $.mobile.transitionFallbacks[ transition ] ){ + transition = $.mobile.transitionFallbacks[ 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( "page" )._trigger( "hide", null, { nextPage: toPage } ); + } + + //trigger pageshow, define prevPage as either fromPage or empty jQuery obj + toPage.data( "page" )._trigger( "show", null, { prevPage: fromPage || $( "" ) } ); + }); + + return promise; + } + + //simply set the active page's minimum height to screen height, depending on orientation + function getScreenHeight(){ + // Native innerHeight returns more accurate value for this across platforms, + // jQuery version is here as a normalized fallback for platforms like Symbian + return window.innerHeight || $( window ).height(); + } + + $.mobile.getScreenHeight = getScreenHeight; + + //simply set the active page's minimum height to screen height, depending on orientation + function resetActivePageHeight(){ + var aPage = $( "." + $.mobile.activePageClass ), + aPagePadT = parseFloat( aPage.css( "padding-top" ) ), + aPagePadB = parseFloat( aPage.css( "padding-bottom" ) ); + + aPage.css( "min-height", getScreenHeight() - aPagePadT - aPagePadB ); + } + + //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(); + } + +/* 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; + + //return the original document url + $.mobile.getDocumentUrl = function(asParsedObject) { + return asParsedObject ? $.extend( {}, documentUrl ) : documentUrl.href; + }; + + //return the original document base url + $.mobile.getDocumentBase = function(asParsedObject) { + return asParsedObject ? $.extend( {}, documentBase ) : documentBase.href; + }; + + $.mobile._bindPageRemove = function() { + var page = $(this); + + // when dom caching is not enabled or the page is embedded bind to remove the page on hide + if( !page.data("page").options.domCache + && page.is(":jqmData(external-page='true')") ) { + + page.bind( 'pagehide.remove', function() { + var $this = $( this ), + prEvent = new $.Event( "pageremove" ); + + $this.trigger( prEvent ); + + if( !prEvent.isDefaultPrevented() ){ + $this.removeWithDependents(); + } + }); + } + }; + + // Load a page into the DOM. + $.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, + + // determine the current base url + findBaseWithDefault = function(){ + var closestBase = ( $.mobile.activePage && getClosestBaseUrl( $.mobile.activePage ) ); + return closestBase || documentBase.hrefNoHash; + }, + + // 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. + page = settings.pageContainer.children( ":jqmData(url='" + dataUrl + "')" ); + + // If we failed to find the page, check to see if the url is a + // reference to an embedded page. If so, it may have been dynamically + // injected by a developer, in which case it would be lacking a data-url + // attribute and in need of enhancement. + if ( page.length === 0 && dataUrl && !path.isPath( dataUrl ) ) { + page = settings.pageContainer.children( "#" + dataUrl ) + .attr( "data-" + $.mobile.ns + "url", dataUrl ); + } + + // If we failed to find a page in the DOM, check the URL to see if it + // refers to the first page in the application. 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(); + } + } + + // Reset base to the default document base. + if ( base ) { + base.reset(); + } + + // 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 ); + 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(); + }; + } + + 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, + 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 ); + } + + if ( base ) { + 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( ":jqmData(url='" + dataUrl + "')" ); + } + + //bind pageHide to removePage after it's hidden, if the page options specify to do so + + // 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 ); + + // Make sure we have a pageContainer to work with. + settings.pageContainer = settings.pageContainer || $.mobile.pageContainer; + + // Make sure we have a fromPage. + settings.fromPage = settings.fromPage || $.mobile.activePage; + + var mpc = settings.pageContainer, + pbcEvent = new $.Event( "pagebeforechange" ), + triggerData = { toPage: toPage, options: settings }; + + // Let listeners know we're about to change the current page. + mpc.trigger( pbcEvent, triggerData ); + + // If the default behavior is prevented, stop here! + if( pbcEvent.isDefaultPrevented() ){ + return; + } + + // We allow "pagebeforechange" observers to modify the toPage in the trigger + // data to allow for redirects. Make sure our toPage is updated. + + toPage = triggerData.toPage; + + // Set the isPageTransitioning flag to prevent any requests from + // entering this method while we are in the midst of loading a page + // or transitioning. + + isPageTransitioning = true; + + // 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 ( typeof toPage == "string" ) { + $.mobile.loadPage( toPage, settings ) + .done(function( url, options, newPage, dupCachedPage ) { + isPageTransitioning = false; + options.duplicateCachedPage = dupCachedPage; + $.mobile.changePage( newPage, options ); + }) + .fail(function( url, options ) { + isPageTransitioning = false; + + //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 ); + 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 ) { + urlHistory.directHashChange({ + currentUrl: url, + isBack: function() { historyDir = -1; }, + isForward: function() { historyDir = 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) {} + + // 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 + url = ( active.url || "" ) + dialogHashKey; + } + + // Set the location hash. + if( settings.changeHash !== false && url ) { + //disable hash listening temporarily + urlHistory.ignoreNextHashChange = true; + //update hash and history + path.set( url ); + } + + // 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" ).getEncodedText(); + 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 ) { + urlHistory.addNew( url, settings.transition, pageTitle, pageUrl, settings.role ); + } + + //set page title + document.title = urlHistory.getActive().title; + + //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(); + + // Let listeners know we're all done changing the current page. + 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 function is called in the init file + $.mobile._registerInternalEvents = function(){ + + //bind to form submit events, handle with Ajax + $( document ).delegate( "form", "submit", function( event ) { + var $this = $( this ); + + if( !$.mobile.ajaxEnabled || + // test that the form is, itself, ajax false + $this.is(":jqmData(ajax='false')") || + // test that $.mobile.ignoreContentEnabled is set and + // the form or one of it's parents is ajax=false + !$this.jqmHijackable().length ) { + return; + } + + var type = $this.attr( "method" ), + target = $this.attr( "target" ), + url = $this.attr( "action" ); + + // 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( $this ); + 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($this) ); + + //external submits use regular HTTP + if( path.isExternal( url ) || target ) { + return; + } + + $.mobile.changePage( + url, + { + type: type && type.length && type.toLowerCase() || "get", + data: $this.serialize(), + transition: $this.jqmData( "transition" ), + direction: $this.jqmData( "direction" ), + reloadPage: true + } + ); + event.preventDefault(); + }); + + //add active state on vclick + $( document ).bind( "vclick", function( event ) { + // if this isn't a left click we don't care. Its important to note + // that when the virtual event is generated it will create the which attr + if ( event.which > 1 || !$.mobile.linkBindingEnabled ) { + return; + } + + var link = findClosestLink( event.target ); + + // split from the previous return logic to avoid find closest where possible + // TODO teach $.mobile.hijackable to operate on raw dom elements so the link wrapping + // can be avoided + if ( !$(link).jqmHijackable().length ) { + return; + } + + if ( link ) { + if ( path.parseUrl( link.getAttribute( "href" ) || "#" ).hash !== "#" ) { + removeActiveLinkClass( true ); + $activeClickedLink = $( link ).closest( ".ui-btn" ).not( ".ui-disabled" ); + $activeClickedLink.addClass( $.mobile.activeBtnClass ); + $( "." + $.mobile.activePageClass + " .ui-btn" ).not( link ).blur(); + + // By caching the href value to data and switching the href to a #, we can avoid address bar showing in iOS. The click handler resets the href during its initial steps if this data is present + $( link ) + .jqmData( "href", $( link ).attr( "href" ) ) + .attr( "href", "#" ); + } + } + }); + + // click routing - direct to HTTP or Ajax, accordingly + $( document ).bind( "click", function( event ) { + if( !$.mobile.linkBindingEnabled ){ + 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 data cached for the real href value, set the link's href back to it again. This pairs with an address bar workaround from the vclick handler + if( $link.jqmData( "href" ) ){ + $link.attr( "href", $link.jqmData( "href" ) ); + } + + //if there's a data-rel=back attr, go back in history + if( $link.is( ":jqmData(rel='back')" ) ) { + window.history.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. + isCrossDomainPageLoad = ( $.mobile.allowCrossDomainPages && documentUrl.protocol === "file:" && href.search( /^https?:/ ) != -1 ), + + //check for protocol or rel and its not an embedded page + //TODO overlap in logic from isExternal, rel=external check should be + // moved into more comprehensive isExternalLink + isExternal = useDefaultUrlHandling || ( path.isExternal( href ) && !isCrossDomainPageLoad ); + + if( isExternal ) { + httpCleanup(); + //use default click handling + return; + } + + //use ajax + var transition = $link.jqmData( "transition" ), + direction = $link.jqmData( "direction" ), + reverse = ( direction && direction === "reverse" ) || + // deprecated - remove by 1.0 + $link.jqmData( "back" ), + + //this may need to be more specific as we use data-rel more + role = $link.attr( "data-" + $.mobile.ns + "rel" ) || undefined; + + $.mobile.changePage( href, { transition: transition, reverse: reverse, role: role } ); + event.preventDefault(); + }); + + //prefetch pages when anchors with data-prefetch are encountered + $( 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")} ); + } + }); + }); + + $.mobile._handleHashChange = function( hash ) { + //find first page via hash + var to = path.stripHash( hash ), + //transition is false if it's the first page, undefined otherwise (and may be overridden by default) + transition = $.mobile.urlHistory.stack.length === 0 ? "none" : undefined, + + // default options for the changPage calls made after examining the current state + // of the page and the hash + changePageOptions = { + transition: transition, + changeHash: false, + fromHashChange: true + }; + + //if listening is disabled (either globally or temporarily), or it's a dialog hash + if( !$.mobile.hashListeningEnabled || urlHistory.ignoreNextHashChange ) { + urlHistory.ignoreNextHashChange = false; + return; + } + + // special case for dialogs + if( urlHistory.stack.length > 1 && to.indexOf( dialogHashKey ) > -1 ) { + + // If current active page is not a dialog skip the dialog and continue + // in the same direction + if(!$.mobile.activePage.is( ".ui-dialog" )) { + //determine if we're heading forward or backward and continue accordingly past + //the current dialog + urlHistory.directHashChange({ + currentUrl: to, + isBack: function() { window.history.back(); }, + isForward: function() { window.history.forward(); } + }); + + // prevent changePage() + return; + } else { + // if the current active page is a dialog and we're navigating + // to a dialog use the dialog objected saved in the stack + urlHistory.directHashChange({ + currentUrl: to, + + // regardless of the direction of the history change + // do the following + either: function( isBack ) { + var active = $.mobile.urlHistory.getActive(); + + to = active.pageUrl; + + // make sure to set the role, transition and reversal + // as most of this is lost by the domCache cleaning + $.extend( changePageOptions, { + role: active.role, + transition: active.transition, + reverse: isBack + }); + } + }); + } + } + + //if to is defined, load it + if ( to ) { + // At this point, 'to' can be one of 3 things, a cached page element from + // a history stack entry, an id, or site-relative/absolute URL. If 'to' is + // an id, we need to resolve it against the documentBase, not the location.href, + // since the hashchange could've been the result of a forward/backward navigation + // that crosses from an external page/dialog to an internal page/dialog. + to = ( typeof to === "string" && !path.isPath( to ) ) ? ( path.makeUrlAbsolute( '#' + to, documentBase ) ) : to; + $.mobile.changePage( to, changePageOptions ); + } else { + //there's no hash, go to the first page in the dom + $.mobile.changePage( $.mobile.firstPage, changePageOptions ); + } + }; + + //hashchange event handler + $window.bind( "hashchange", function( e, triggered ) { + $.mobile._handleHashChange( location.hash ); + }); + + //set page min-heights to be device specific + $( document ).bind( "pageshow", resetActivePageHeight ); + $( window ).bind( "throttledresize", resetActivePageHeight ); + + };//_registerInternalEvents callback + +})( jQuery ); + +( function( $, window ) { + // For now, let's Monkeypatch this onto the end of $.mobile._registerInternalEvents + // Scope self to pushStateHandler so we can reference it sanely within the + // methods handed off as event handlers + var pushStateHandler = {}, + self = pushStateHandler, + $win = $( window ), + url = $.mobile.path.parseUrl( location.href ); + + $.extend( pushStateHandler, { + // TODO move to a path helper, this is rather common functionality + initialFilePath: (function() { + return url.pathname + url.search; + })(), + + initialHref: url.hrefNoHash, + + state: function() { + return { + hash: location.hash || "#" + self.initialFilePath, + title: document.title, + + // persist across refresh + initialHref: self.initialHref + }; + }, + + resetUIKeys: function( url ) { + var dialog = $.mobile.dialogHashKey, + subkey = "&" + $.mobile.subPageUrlKey, + dialogIndex = url.indexOf( dialog ); + + if( dialogIndex > -1 ) { + url = url.slice( 0, dialogIndex ) + "#" + url.slice( dialogIndex ); + } else if( url.indexOf( subkey ) > -1 ) { + url = url.split( subkey ).join( "#" + subkey ); + } + + return url; + }, + + hashValueAfterReset: function( url ) { + var resetUrl = self.resetUIKeys( url ); + return $.mobile.path.parseUrl( resetUrl ).hash; + }, + + // TODO sort out a single barrier to hashchange functionality + nextHashChangePrevented: function( value ) { + $.mobile.urlHistory.ignoreNextHashChange = value; + self.onHashChangeDisabled = value; + }, + + // on hash change we want to clean up the url + // NOTE this takes place *after* the vanilla navigation hash change + // handling has taken place and set the state of the DOM + onHashChange: function( e ) { + // disable this hash change + if( self.onHashChangeDisabled ){ + return; + } + + var href, state, + hash = location.hash, + isPath = $.mobile.path.isPath( hash ), + resolutionUrl = isPath ? location.href : $.mobile.getDocumentUrl(); + + hash = isPath ? hash.replace( "#", "" ) : hash; + + + // propulate the hash when its not available + state = self.state(); + + // make the hash abolute with the current href + href = $.mobile.path.makeUrlAbsolute( hash, resolutionUrl ); + + if ( isPath ) { + href = self.resetUIKeys( href ); + } + + // replace the current url with the new href and store the state + // Note that in some cases we might be replacing an url with the + // same url. We do this anyways because we need to make sure that + // all of our history entries have a state object associated with + // them. This allows us to work around the case where window.history.back() + // is called to transition from an external page to an embedded page. + // In that particular case, a hashchange event is *NOT* generated by the browser. + // Ensuring each history entry has a state object means that onPopState() + // will always trigger our hashchange callback even when a hashchange event + // is not fired. + history.replaceState( state, document.title, href ); + }, + + // on popstate (ie back or forward) we need to replace the hash that was there previously + // cleaned up by the additional hash handling + onPopState: function( e ) { + var poppedState = e.originalEvent.state, + timeout, fromHash, toHash, hashChanged; + + // if there's no state its not a popstate we care about, eg chrome's initial popstate + if( poppedState ) { + // the active url in the history stack will still be from the previous state + // so we can use it to verify if a hashchange will be fired from the popstate + fromHash = self.hashValueAfterReset( $.mobile.urlHistory.getActive().url ); + + // the hash stored in the state popped off the stack will be our currenturl or + // the url to which we wish to navigate + toHash = self.hashValueAfterReset( poppedState.hash.replace("#", "") ); + + // if the hashes of the urls are different we must assume that the browser + // will fire a hashchange + hashChanged = fromHash !== toHash; + + // unlock hash handling once the hashchange caused be the popstate has fired + if( hashChanged ) { + $win.one( "hashchange.pushstate", function() { + self.nextHashChangePrevented( false ); + }); + } + + // enable hash handling for the the _handleHashChange call + self.nextHashChangePrevented( false ); + + // change the page based on the hash + $.mobile._handleHashChange( poppedState.hash ); + + // only prevent another hash change handling if a hash change will be fired + // by the browser + if( hashChanged ) { + // disable hash handling until one of the above timers fires + self.nextHashChangePrevented( true ); + } + } + }, + + init: function() { + $win.bind( "hashchange", self.onHashChange ); + + // Handle popstate events the occur through history changes + $win.bind( "popstate", self.onPopState ); + + // if there's no hash, we need to replacestate for returning to home + if ( location.hash === "" ) { + history.replaceState( self.state(), document.title, location.href ); + } + } + }); + + $( function() { + if( $.mobile.pushStateEnabled && $.support.pushState ){ + pushStateHandler.init(); + } + }); +})( jQuery, this ); + +/* +* 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 transition handler for slide transitions +$.mobile.transitionHandlers.slide = $.mobile.transitionHandlers.simultaneous; + +// Set the slide transition'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 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 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 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 +$( 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: { + closeBtnText : "Close", + overlayTheme : "a", + initSelector : ":jqmData(role='dialog')" + }, + _create: function() { + var self = this, + $el = this.element, + headerCloseButton = $( ""+ this.options.closeBtnText + "" ), + dialogWrap = $("
", { + "role" : "dialog", + "class" : "ui-dialog-contain ui-corner-all ui-overlay-shadow" + }); + + $el.addClass( "ui-dialog ui-overlay-" + this.options.overlayTheme ); + + // Class the markup for dialog styling + // Set aria role + $el + .wrapInner( dialogWrap ) + .children() + .find( ":jqmData(role='header')" ) + .prepend( headerCloseButton ) + .end() + .children( ':first-child') + .addClass( "ui-corner-top" ) + .end() + .children( ":last-child" ) + .addClass( "ui-corner-bottom" ); + + // 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. + headerCloseButton.bind( "click", function() { + self.close(); + }); + + /* 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" ); + } + }) + .bind( "pagehide", function( e, ui ) { + $( this ).find( "." + $.mobile.activeBtnClass ).removeClass( $.mobile.activeBtnClass ); + }) + // Override the theme set by the page plugin on pageshow + .bind( "pagebeforeshow", function(){ + if( self.options.overlayTheme ){ + self.element + .page( "removeContainerBackground" ) + .page( "setContainerBackground", self.options.overlayTheme ); + } + }); + }, + + // Close method goes back in history + close: function() { + window.history.back(); + } +}); + +//auto self-init widgets +$( document ).delegate( $.mobile.dialog.prototype.options.initSelector, "pagecreate", function(){ + $.mobile.dialog.prototype.enhance( this ); +}); + +})( jQuery, this ); + +(function( $, undefined ) { + +$.fn.fieldcontain = function( options ) { + return this.addClass( "ui-field-contain ui-body ui-br" ); +}; + +//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"; + } + } + 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(3n+3)" ).addClass( "ui-block-c" ); + } + if ( iterator > 3 ) { + $kids.filter( ":nth-child(4n+4)" ).addClass( "ui-block-d" ); + } + if ( iterator > 4 ) { + $kids.filter( ":nth-child(5n+5)" ).addClass( "ui-block-e" ); + } + }); +}; +})( jQuery ); + +(function( $, undefined ) { + +$( document ).bind( "pagecreate create", function( e ){ + $( ":jqmData(role='nojs')", e.target ).addClass( "ui-nojs" ); + +}); + +})( jQuery ); + +( function( $, undefined ) { + +$.fn.buttonMarkup = function( options ) { + var $workingSet = this; + + // 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 : el.jqmData( "icon" ), + iconpos: options.iconpos !== undefined ? options.iconpos : el.jqmData( "iconpos" ), + theme: options.theme !== undefined ? options.theme : el.jqmData( "theme" ) || $.mobile.getInheritedTheme( el, "c" ), + inline: options.inline !== undefined ? options.inline : el.jqmData( "inline" ), + shadow: options.shadow !== undefined ? options.shadow : el.jqmData( "shadow" ), + corners: options.corners !== undefined ? options.corners : el.jqmData( "corners" ), + iconshadow: options.iconshadow !== undefined ? options.iconshadow : el.jqmData( "iconshadow" ), + mini: options.mini !== undefined ? options.mini : el.jqmData( "mini" ) + }, options ), + + // Classes Defined + innerClass = "ui-btn-inner", + textClass = "ui-btn-text", + buttonClass, iconClass, + // Button inner markup + buttonInner, + buttonText, + buttonIcon, + buttonElements; + + $.each(o, function(key, value) { + e.setAttribute( "data-" + $.mobile.ns + key, value ); + el.jqmData(key, value); + }); + + // 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; + } + 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 ui-btn-up-" + o.theme; + buttonClass += o.inline ? " ui-btn-inline" : ""; + 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 ? " ui-mini" : " ui-fullsize"; + } + + if ( o.inline !== undefined ) { + // Used to control styling in headers/footers, where buttons default to `mini` style. + buttonClass += o.inline === false ? " ui-btn-block" : " ui-btn-inline"; + } + + + 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() ); + } + } + + innerClass += o.corners ? " ui-btn-corner-all" : ""; + + if ( o.iconpos && 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.appendChild( document.createTextNode("\u00a0") ); + 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 = { + 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; +} + +var attachEvents = function() { + var hoverDelay = $.mobile.buttonMarkup.hoverDelay, hov, foc; + + $( document ).bind( { + "vmousedown vmousecancel vmouseup vmouseover vmouseout focus blur scrollstart": function( event ) { + var theme, + $btn = $( closestEnabledButton( event.target ) ), + evt = event.type; + + if ( $btn.length ) { + theme = $btn.attr( "data-" + $.mobile.ns + "theme" ); + + if ( evt === "vmousedown" ) { + if ( $.support.touch ) { + hov = setTimeout(function() { + $btn.removeClass( "ui-btn-up-" + theme ).addClass( "ui-btn-down-" + theme ); + }, hoverDelay ); + } else { + $btn.removeClass( "ui-btn-up-" + theme ).addClass( "ui-btn-down-" + theme ); + } + } else if ( evt === "vmousecancel" || evt === "vmouseup" ) { + $btn.removeClass( "ui-btn-down-" + theme ).addClass( "ui-btn-up-" + theme ); + } else if ( evt === "vmouseover" || evt === "focus" ) { + if ( $.support.touch ) { + foc = setTimeout(function() { + $btn.removeClass( "ui-btn-up-" + theme ).addClass( "ui-btn-hover-" + theme ); + }, hoverDelay ); + } else { + $btn.removeClass( "ui-btn-up-" + theme ).addClass( "ui-btn-hover-" + theme ); + } + } else if ( evt === "vmouseout" || evt === "blur" || evt === "scrollstart" ) { + $btn.removeClass( "ui-btn-hover-" + theme + " ui-btn-down-" + theme ).addClass( "ui-btn-up-" + theme ); + 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 +$( 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 ) + .not( ".ui-btn, :jqmData(role='none'), :jqmData(role='nojs')" ) + .buttonMarkup(); +}); + +})( jQuery ); + + +(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; + +$( document ).delegate( ":jqmData(role='page'), :jqmData(role='dialog')", "pagecreate", function( e ) { + + var $page = $( this ), + o = $page.data( "page" ).options, + pageRole = $page.jqmData( "role" ), + pageTheme = o.theme; + + $( ":jqmData(role='header'), :jqmData(role='footer'), :jqmData(role='content')", this ) + .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" ); + 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 ) { + +$.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", + theme: null, + contentTheme: null, + iconTheme: "d", + 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( "
" ).find( ".ui-collapsible-content" ), + collapsibleSet = $el.closest( ":jqmData(role='collapsible-set')" ).addClass( "ui-collapsible-set" ); + + // 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" ); + } + + // Gets the preference icon position in the set + if ( !o.iconPos ) { + o.iconPos = collapsibleSet.jqmData( "iconpos" ); + } + + if( !o.mini ) { + o.mini = collapsibleSet.jqmData( "mini" ); + } + } + collapsibleContent.addClass( ( o.contentTheme ) ? ( "ui-body-" + o.contentTheme ) : ""); + + 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: $el.jqmData( "iconpos" ) || o.iconPos || "left", + icon: "plus", + mini: o.mini, + theme: o.theme + }) + .add( ".ui-btn-inner", $el ) + .addClass( "ui-corner-top ui-corner-bottom" ); + + //events + collapsible + .bind( "expand collapse", function( event ) { + if ( !event.isDefaultPrevented() ) { + + event.preventDefault(); + + var $this = $( this ), + isCollapse = ( event.type === "collapse" ), + contentTheme = o.contentTheme; + + collapsibleHeading + .toggleClass( "ui-collapsible-heading-collapsed", isCollapse) + .find( ".ui-collapsible-heading-status" ) + .text( isCollapse ? o.expandCueText : o.collapseCueText ) + .end() + .find( ".ui-icon" ) + .toggleClass( "ui-icon-minus", !isCollapse ) + .toggleClass( "ui-icon-plus", isCollapse ); + + $this.toggleClass( "ui-collapsible-collapsed", isCollapse ); + collapsibleContent.toggleClass( "ui-collapsible-content-collapsed", isCollapse ).attr( "aria-hidden", isCollapse ); + + if ( contentTheme && ( !collapsibleSet.length || collapsible.jqmData( "collapsible-last" ) ) ) { + collapsibleHeading + .find( "a" ).first().add( collapsibleHeading.find( ".ui-btn-inner" ) ) + .toggleClass( "ui-corner-bottom", isCollapse ); + collapsibleContent.toggleClass( "ui-corner-bottom", !isCollapse ); + } + collapsibleContent.trigger( "updatelayout" ); + } + }) + .trigger( o.collapsed ? "collapse" : "expand" ); + + collapsibleHeading + .bind( "click", function( event ) { + + var type = collapsibleHeading.is( ".ui-collapsible-heading-collapsed" ) ? + "expand" : "collapse"; + + collapsible.trigger( type ); + + event.preventDefault(); + }); + } +}); + +//auto self-init widgets +$( document ).bind( "pagecreate create", function( e ){ + $.mobile.collapsible.prototype.enhanceWithin( e.target ); +}); + +})( jQuery ); + +(function( $, undefined ) { + +$.widget( "mobile.collapsibleset", $.mobile.widget, { + 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" ); + } + + if ( !o.corners ) { + o.corners = $el.jqmData( "corners" ) === undefined ? true : false; + } + + // Initialize the collapsible set if it's not already initialized + if ( !$el.jqmData( "collapsiblebound" ) ) { + $el + .jqmData( "collapsiblebound", true ) + .bind( "expand collapse", function( event ) { + var isCollapse = ( event.type === "collapse" ), + collapsible = $( event.target ).closest( ".ui-collapsible" ), + widget = collapsible.data( "collapsible" ), + contentTheme = widget.options.contentTheme; + if ( contentTheme && collapsible.jqmData( "collapsible-last" ) ) { + collapsible.find( widget.options.heading ).first() + .find( "a" ).first() + .add( ".ui-btn-inner" ) + .toggleClass( "ui-corner-bottom", isCollapse ); + collapsible.find( ".ui-collapsible-content" ).toggleClass( "ui-corner-bottom", !isCollapse ); + } + }) + .bind( "expand", function( event ) { + $( event.target ) + .closest( ".ui-collapsible" ) + .siblings( ".ui-collapsible" ) + .trigger( "collapse" ); + }); + } + }, + + _init: function() { + this.refresh(); + }, + + refresh: function() { + var $el = this.element, + o = this.options, + collapsiblesInSet = $el.children( ":jqmData(role='collapsible')" ); + + $.mobile.collapsible.prototype.enhance( collapsiblesInSet.not( ".ui-collapsible" ) ); + + // clean up borders + collapsiblesInSet.each( function() { + $( this ).find( $.mobile.collapsible.prototype.options.heading ) + .find( "a" ).first() + .add( ".ui-btn-inner" ) + .removeClass( "ui-corner-top ui-corner-bottom" ); + }); + + collapsiblesInSet.first() + .find( "a" ) + .first() + .addClass( o.corners ? "ui-corner-top" : "" ) + .find( ".ui-btn-inner" ) + .addClass( "ui-corner-top" ); + + collapsiblesInSet.last() + .jqmData( "collapsible-last", true ) + .find( "a" ) + .first() + .addClass( o.corners ? "ui-corner-bottom" : "" ) + .find( ".ui-btn-inner" ) + .addClass( "ui-corner-bottom" ); + } +}); + +//auto self-init widgets +$( document ).bind( "pagecreate create", function( e ){ + $.mobile.collapsibleset.prototype.enhanceWithin( e.target ); +}); + +})( 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" ) + .attr( "role","navigation" ) + .find( "ul" ) + .jqmEnhanceable() + .grid({ grid: this.options.grid }); + + if ( !iconpos ) { + $navbar.addClass( "ui-navbar-noicons" ); + } + + $navbtns.buttonMarkup({ + corners: false, + shadow: false, + inline: true, + iconpos: iconpos + }); + + $navbar.delegate( "a", "vclick", function( event ) { + if( !$(event.target).hasClass("ui-disabled") ) { + $navbtns.removeClass( $.mobile.activeBtnClass ); + $( this ).addClass( $.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 +$( 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, { + + options: { + theme: null, + countTheme: "c", + headerTheme: "b", + dividerTheme: "b", + splitIcon: "arrow-r", + splitTheme: "b", + mini: false, + inset: false, + initSelector: ":jqmData(role='listview')" + }, + + _create: function() { + var t = this, + listviewClasses = ""; + + listviewClasses += t.options.inset ? " ui-listview-inset ui-corner-all ui-shadow " : ""; + listviewClasses += t.element.jqmData( "mini" ) || t.options.mini === true ? " ui-mini" : ""; + + // create listview markup + t.element.addClass(function( i, orig ) { + return orig + " ui-listview " + listviewClasses; + }); + + t.refresh( true ); + }, + + _removeCorners: function( li, which ) { + var top = "ui-corner-top ui-corner-tr ui-corner-tl", + bot = "ui-corner-bottom ui-corner-br ui-corner-bl"; + + li = li.add( li.find( ".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb" ) ); + + if ( which === "top" ) { + li.removeClass( top ); + } else if ( which === "bottom" ) { + li.removeClass( bot ); + } else { + li.removeClass( top + " " + bot ); + } + }, + + _refreshCorners: function( create ) { + var $li, + $visibleli, + $topli, + $bottomli; + + if ( this.options.inset ) { + $li = this.element.children( "li" ); + // at create time the li are not visible yet so we need to rely on .ui-screen-hidden + $visibleli = create?$li.not( ".ui-screen-hidden" ):$li.filter( ":visible" ); + + this._removeCorners( $li ); + + // Select the first visible li element + $topli = $visibleli.first() + .addClass( "ui-corner-top" ); + + $topli.add( $topli.find( ".ui-btn-inner" ) + .not( ".ui-li-link-alt span:first-child" ) ) + .addClass( "ui-corner-top" ) + .end() + .find( ".ui-li-link-alt, .ui-li-link-alt span:first-child" ) + .addClass( "ui-corner-tr" ) + .end() + .find( ".ui-li-thumb" ) + .not(".ui-li-icon") + .addClass( "ui-corner-tl" ); + + // Select the last visible li element + $bottomli = $visibleli.last() + .addClass( "ui-corner-bottom" ); + + $bottomli.add( $bottomli.find( ".ui-btn-inner" ) ) + .find( ".ui-li-link-alt" ) + .addClass( "ui-corner-br" ) + .end() + .find( ".ui-li-thumb" ) + .not(".ui-li-icon") + .addClass( "ui-corner-bl" ); + } + if ( !create ) { + this.element.trigger( "updatelayout" ); + } + }, + + // 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" ), + li = this._getChildrenByTagName( $list[ 0 ], "li", "LI" ), + counter = $.support.cssPseudoElement || !$.nodeName( $list[ 0 ], "ol" ) ? 0 : 1, + itemClassDict = {}, + item, itemClass, itemTheme, + a, last, splittheme, countParent, icon, imgParents, img, linkIcon; + + if ( counter ) { + $list.find( ".ui-li-dec" ).remove(); + } + + 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" ); + + if ( a.length ) { + icon = item.jqmData("icon"); + + item.buttonMarkup({ + wrapperEls: "div", + shadow: false, + corners: false, + iconpos: "right", + icon: a.length > 1 || icon === false ? false : icon || "arrow-r", + theme: itemTheme + }); + + if ( ( icon != false ) && ( a.length == 1 ) ) { + item.addClass( "ui-li-has-arrow" ); + } + + a.first().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", last.getEncodedText() ) + .addClass( "ui-li-link-alt" ) + .empty() + .buttonMarkup({ + shadow: false, + corners: false, + theme: itemTheme, + icon: false, + iconpos: false + }) + .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 ( item.jqmData( "role" ) === "list-divider" ) { + + itemClass += " ui-li-divider ui-bar-" + dividertheme; + item.attr( "role", "heading" ); + + //reset counter when a divider heading is encountered + if ( counter ) { + counter = 1; + } + + } else { + itemClass += " ui-li-static ui-body-" + itemTheme; + } + } + + if ( counter && itemClass.indexOf( "ui-li-divider" ) < 0 ) { + countParent = item.is( ".ui-li-static:first" ) ? 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._refreshCorners( create ); + }, + + //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(), + nodeEls = $( list.prevAll().toArray().reverse() ), + nodeEls = nodeEls.length ? nodeEls : $( "" + $.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("page").options.domCache === false ) { + + var newRemove = function( e, ui ){ + var nextPage = ui.nextPage, npURL; + + if( ui.nextPage ){ + npURL = nextPage.jqmData( "url" ); + if( npURL.indexOf( parentUrl + "&" + $.mobile.subPageUrlKey ) !== 0 ){ + self.childPages().remove(); + parentPage.remove(); + } + } + }; + + // 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 +"')"); + } +}); + +//auto self-init widgets +$( document ).bind( "pagecreate create", function( e ){ + $.mobile.listview.prototype.enhanceWithin( e.target ); +}); + +})( jQuery ); + +/* +* "checkboxradio" plugin +*/ + +(function( $, undefined ) { + +$.widget( "mobile.checkboxradio", $.mobile.widget, { + options: { + theme: null, + initSelector: "input[type='checkbox'],input[type='radio']" + }, + _create: function() { + var self = this, + input = this.element, + 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 + "']" ), + inputtype = input[0].type, + mini = inheritAttr( input, "mini" ), + checkedState = inputtype + "-on", + uncheckedState = inputtype + "-off", + icon = input.parents( ":jqmData(type='horizontal')" ).length ? undefined : uncheckedState, + iconpos = inheritAttr( input, "iconpos" ), + activeBtn = icon ? "" : " " + $.mobile.activeBtnClass, + checkedClass = "ui-" + checkedState + activeBtn, + uncheckedClass = "ui-" + uncheckedState, + checkedicon = "ui-icon-" + checkedState, + uncheckedicon = "ui-icon-" + uncheckedState; + + if ( inputtype !== "checkbox" && inputtype !== "radio" ) { + return; + } + + // Expose for other methods + $.extend( this, { + label: label, + inputtype: inputtype, + checkedClass: checkedClass, + uncheckedClass: uncheckedClass, + checkedicon: checkedicon, + uncheckedicon: uncheckedicon + }); + + // If there's no selected theme check the data attr + if( !this.options.theme ) { + this.options.theme = $.mobile.getInheritedTheme( this.element, "c" ); + } + + label.buttonMarkup({ + theme: this.options.theme, + icon: icon, + 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.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,fieldset,:jqmData(role='page')" ) + .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" ); + }, + + refresh: function() { + var input = this.element[0], + label = this.label, + icon = label.find( ".ui-icon" ); + + if ( input.checked ) { + label.addClass( this.checkedClass ).removeClass( this.uncheckedClass ); + icon.addClass( this.checkedicon ).removeClass( this.uncheckedicon ); + } else { + label.removeClass( this.checkedClass ).addClass( this.uncheckedClass ); + icon.removeClass( this.checkedicon ).addClass( 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" ); + } +}); + +//auto self-init widgets +$( 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, + inline: false, + corners: true, + shadow: true, + iconshadow: true, + initSelector: "button, [type='button'], [type='submit'], [type='reset'], [type='image']", + mini: false + }, + _create: function() { + var $el = this.element, + $button, + o = this.options, + type, + name, + 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" ) { + !$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"; + } + + // Add ARIA role + this.button = $( "
" ) + .text( $el.text() || $el.val() ) + .insertBefore( $el ) + .buttonMarkup({ + theme: o.theme, + icon: o.icon, + iconpos: o.iconpos, + inline: o.inline, + corners: o.corners, + shadow: o.shadow, + iconshadow: o.iconshadow, + mini: o.mini + }) + .addClass( classes ) + .append( $el.addClass( "ui-btn-hidden" ) ); + + $button = this.button; + type = $el.attr( "type" ); + name = $el.attr( "name" ); + + // Add hidden input during submit if input type="submit" has a name. + if ( type !== "button" && type !== "reset" && name ) { + $el.bind( "vclick", function() { + // Add hidden input if it doesn’t already exist. + if( $buttonPlaceholder === undefined ) { + $buttonPlaceholder = $( "", { + type: "hidden", + name: $el.attr( "name" ), + value: $el.attr( "value" ) + }).insertBefore( $el ); + + // Bind to doc to remove after submit handling + $( document ).one("submit", function(){ + $buttonPlaceholder.remove(); + + // reset the local var so that the hidden input + // will be re-added on subsequent clicks + $buttonPlaceholder = undefined; + }); + } + }); + } + + $el.bind({ + focus: function() { + $button.addClass( $.mobile.focusClass ); + }, + + blur: function() { + $button.removeClass( $.mobile.focusClass ); + } + }); + + this.refresh(); + }, + + 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 ).text( $el.text() || $el.val() ); + } +}); + +//auto self-init widgets +$( document ).bind( "pagecreate create", function( e ){ + $.mobile.button.prototype.enhanceWithin( e.target, true ); +}); + +})( jQuery ); + +(function( $, undefined ) { + +$.fn.controlgroup = function( options ) { + function flipClasses( els, flCorners ) { + els.removeClass( "ui-btn-corner-all ui-shadow" ) + .eq( 0 ).addClass( flCorners[ 0 ] ) + .end() + .last().addClass( flCorners[ 1 ] ).addClass( "ui-controlgroup-last" ); + } + + return this.each(function() { + var $el = $( this ), + o = $.extend({ + direction: $el.jqmData( "type" ) || "vertical", + shadow: false, + excludeInvisible: true, + mini: $el.jqmData( "mini" ) + }, options ), + groupheading = $el.children( "legend" ), + flCorners = o.direction == "horizontal" ? [ "ui-corner-left", "ui-corner-right" ] : [ "ui-corner-top", "ui-corner-bottom" ], + type = $el.find( "input" ).first().attr( "type" ); + + // Replace legend with more stylable replacement div + if ( groupheading.length ) { + $el.wrapInner( "
" ); + $( "
" + groupheading.html() + "
" ).insertBefore( $el.children(0) ); + groupheading.remove(); + } + + $el.addClass( "ui-corner-all ui-controlgroup ui-controlgroup-" + o.direction ); + + flipClasses( $el.find( ".ui-btn" + ( o.excludeInvisible ? ":visible" : "" ) ).not('.ui-slider-handle'), flCorners ); + flipClasses( $el.find( ".ui-btn-inner" ), flCorners ); + + if ( o.shadow ) { + $el.addClass( "ui-shadow" ); + } + + if ( o.mini ) { + $el.addClass( "ui-mini" ); + } + + }); +}; + +// The pagecreate handler for controlgroup is in jquery.mobile.init because of the soft-dependency on the wrapped widgets + +})(jQuery); + +(function( $, undefined ) { + +$( document ).bind( "pagecreate create", function( e ){ + + //links within content areas, tests included with page + $( e.target ) + .find( "a" ) + .jqmEnhanceable() + .not( ".ui-btn, .ui-link-inherit, :jqmData(role='none'), :jqmData(role='nojs')" ) + .addClass( "ui-link" ); + +}); + +})( 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, + // 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])", + clearSearchButtonText: "clear text" + }, + + _create: function() { + + var input = this.element, + o = this.options, + theme = o.theme || $.mobile.getInheritedTheme( this.element, "c" ), + themeclass = " ui-body-" + theme, + mini = input.jqmData("mini") == true, + miniclass = mini ? " ui-mini" : "", + focusedEl, clearbtn; + + $( "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" input widget + if ( input.is( "[type='search'],:jqmData(type='search')" ) ) { + + focusedEl = input.wrap( "" ).parent(); + clearbtn = $( "
" + o.clearSearchButtonText + "" ) + .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: mini + }); + + function toggleClear() { + setTimeout(function() { + clearbtn.toggleClass( "ui-input-clear-hidden", !input.val() ); + }, 0); + } + + toggleClear(); + + input.bind('paste cut keyup focus change blur', toggleClear); + + } else { + input.addClass( "ui-corner-all ui-shadow-inset" + themeclass + miniclass ); + } + + input.focus(function() { + focusedEl.addClass( $.mobile.focusClass ); + }) + .blur(function(){ + focusedEl.removeClass( $.mobile.focusClass ); + }) + // In many situations, iOS will zoom into the select upon tap, this prevents that from happening + .bind( "focus", function() { + if( o.preventFocusZoom ){ + $.mobile.zoom.disable( true ); + } + }) + .bind( "blur", function() { + if( o.preventFocusZoom ){ + $.mobile.zoom.enable( true ); + } + }); + + // Autogrow + if ( input.is( "textarea" ) ) { + var extraLineHeight = 15, + keyupTimeoutBuffer = 100, + keyup = function() { + var scrollHeight = input[ 0 ].scrollHeight, + clientHeight = input[ 0 ].clientHeight; + + if ( clientHeight < scrollHeight ) { + input.height(scrollHeight + extraLineHeight); + } + }, + keyupTimeout; + + input.keyup(function() { + clearTimeout( keyupTimeout ); + keyupTimeout = setTimeout( keyup, keyupTimeoutBuffer ); + }); + + // binding to pagechange here ensures that for pages loaded via + // ajax the height is recalculated without user input + $( document ).one( "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 + $( window ).load( keyup ); + } + } + }, + + disable: function(){ + ( this.element.attr( "disabled", true ).is( "[type='search'],:jqmData(type='search')" ) ? + this.element.parent() : this.element ).addClass( "ui-disabled" ); + }, + + enable: function(){ + ( this.element.attr( "disabled", false).is( "[type='search'],:jqmData(type='search')" ) ? + this.element.parent() : this.element ).removeClass( "ui-disabled" ); + } +}); + +//auto self-init widgets +$( 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.filterCallback = function( text, searchValue ){ + return text.toLowerCase().indexOf( searchValue ) === -1; +}; + +$( document ).delegate( ":jqmData(role='listview')", "listviewcreate", function() { + + var list = $( this ), + listview = list.data( "listview" ); + + if ( !listview.options.filter ) { + return; + } + + var wrapper = $( "
", { + "class": "ui-listview-filter ui-bar-" + listview.options.filterTheme, + "role": "search" + }), + search = $( "", { + placeholder: listview.options.filterPlaceholder + }) + .attr( "data-" + $.mobile.ns + "type", "search" ) + .jqmData( "lastval", "" ) + .bind( "keyup change", function() { + + var $this = $(this), + val = this.value.toLowerCase(), + listItems = null, + lastval = $this.jqmData( "lastval" ) + "", + childItems = false, + itemtext = "", + item; + + // Change val as lastval for next execution + $this.jqmData( "lastval" , val ); + if ( val.length < lastval.length || val.indexOf(lastval) !== 0 ) { + + // 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 ( 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 ) ) { + + //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", false ); + } + listview._refreshCorners(); + }) + .appendTo( wrapper ) + .textinput(); + + if ( listview.options.inset ) { + wrapper.addClass( "ui-listview-filter-inset" ); + } + + wrapper.bind( "submit", function() { + return false; + }) + .insertBefore( list ); +}); + +})( jQuery ); + +( function( $, undefined ) { + +$.widget( "mobile.slider", $.mobile.widget, { + options: { + theme: null, + trackTheme: null, + disabled: false, + initSelector: "input[type='range'], :jqmData(type='range'), :jqmData(role='slider')", + mini: 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(), + + selectClass = ( cType == "select" ) ? "ui-slider-switch" : "", + + controlID = control.attr( "id" ), + + labelID = controlID + "-label", + + label = $( "[for='"+ controlID +"']" ).attr( "id", labelID ), + + val = function() { + return cType == "input" ? parseFloat( control.val() ) : control[0].selectedIndex; + }, + + min = cType == "input" ? parseFloat( control.attr( "min" ) ) : 0, + + max = cType == "input" ? parseFloat( control.attr( "max" ) ) : control.find( "option" ).length-1, + + step = window.parseFloat( control.attr( "step" ) || 1 ), + + inlineClass = ( this.options.inline || control.jqmData("inline") == true ) ? " ui-slider-inline" : "", + + miniClass = ( this.options.mini || control.jqmData("mini") ) ? " ui-slider-mini" : "", + + + domHandle = document.createElement('a'), + handle = $( domHandle ), + domSlider = document.createElement('div'), + slider = $( domSlider ), + + valuebg = control.jqmData("highlight") && cType != "select" ? (function() { + var bg = document.createElement('div'); + bg.className = 'ui-slider-bg ui-btn-active ui-btn-corner-all'; + return $( bg ).prependTo( slider ); + })() : false, + + options; + + domHandle.setAttribute( 'href', "#" ); + domSlider.setAttribute('role','application'); + domSlider.className = ['ui-slider ',selectClass," ui-btn-down-",trackTheme,' ui-btn-corner-all', inlineClass, 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": val(), + "aria-valuetext": val(), + "title": val(), + "aria-labelledby": labelID + }); + + $.extend( this, { + slider: slider, + handle: handle, + valuebg: valuebg, + dragging: false, + beforeStart: null, + userModified: false, + mouseMoved: false + }); + + if ( cType == "select" ) { + var 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( cType === "input" ? "ui-slider-input" : "ui-slider-switch" ) + .change( function() { + // if the user dragged the handle, the "change" event was triggered from inside refresh(); don't call refresh() again + if (!self.mouseMoved) { + self.refresh( val(), true ); + } + }) + .keyup( function() { // necessary? + self.refresh( val(), true, true ); + }) + .blur( function() { + self.refresh( val(), true ); + }); + + // prevent screen drag when slider activated + $( document ).bind( "vmousemove", function( event ) { + if ( self.dragging ) { + // self.mouseMoved must be updated before refresh() because it will be used in the control "change" event + self.mouseMoved = true; + + if ( cType === "select" ) { + // make the handle move in sync with the mouse + handle.removeClass( "ui-slider-handle-snapping" ); + } + + self.refresh( event ); + + // only after refresh() you can calculate self.userModified + self.userModified = self.beforeStart !== control[0].selectedIndex; + return false; + } + }); + + slider.bind( "vmousedown", function( event ) { + self.dragging = true; + self.userModified = false; + self.mouseMoved = false; + + if ( cType === "select" ) { + self.beforeStart = control[0].selectedIndex; + } + + self.refresh( event ); + return false; + }) + .bind( "vclick", false ); + + slider.add( document ) + .bind( "vmouseup", function() { + if ( self.dragging ) { + + self.dragging = false; + + if ( cType === "select") { + + // make the handle move with a smooth transition + handle.addClass( "ui-slider-handle-snapping" ); + + if ( self.mouseMoved ) { + + // this is a drag, change the value only if user dragged enough + if ( self.userModified ) { + self.refresh( self.beforeStart == 0 ? 1 : 0 ); + } + else { + self.refresh( self.beforeStart ); + } + + } + else { + // this is just a click, change the value + self.refresh( self.beforeStart == 0 ? 1 : 0 ); + } + + } + + self.mouseMoved = false; + + return false; + } + }); + + slider.insertAfter( control ); + + // Only add focus class to toggle switch, sliders get it automatically from ui-btn + if( cType == 'select' ) { + this.handle.bind({ + focus: function() { + slider.addClass( $.mobile.focusClass ); + }, + + blur: function() { + slider.removeClass( $.mobile.focusClass ); + } + }); + } + + this.handle.bind({ + // NOTE force focus on handle + vmousedown: function() { + $( this ).focus(); + }, + + vclick: false, + + keydown: function( event ) { + var index = val(); + + if ( self.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 ( !self._keySliding ) { + self._keySliding = true; + $( this ).addClass( "ui-state-active" ); + } + break; + } + + // move the slider according to the keypress + switch ( event.keyCode ) { + case $.mobile.keyCode.HOME: + self.refresh( min ); + break; + case $.mobile.keyCode.END: + self.refresh( max ); + break; + case $.mobile.keyCode.PAGE_UP: + case $.mobile.keyCode.UP: + case $.mobile.keyCode.RIGHT: + self.refresh( index + step ); + break; + case $.mobile.keyCode.PAGE_DOWN: + case $.mobile.keyCode.DOWN: + case $.mobile.keyCode.LEFT: + self.refresh( index - step ); + break; + } + }, // remove active mark + + keyup: function( event ) { + if ( self._keySliding ) { + self._keySliding = false; + $( this ).removeClass( "ui-state-active" ); + } + } + }); + + this.refresh(undefined, undefined, true); + }, + + refresh: function( val, isfromControl, preventInputUpdate ) { + + if ( this.options.disabled || this.element.attr('disabled')) { + this.disable(); + } + + var control = this.element, percent, + cType = control[0].nodeName.toLowerCase(), + min = cType === "input" ? parseFloat( control.attr( "min" ) ) : 0, + max = cType === "input" ? parseFloat( control.attr( "max" ) ) : control.find( "option" ).length - 1, + step = (cType === "input" && parseFloat( control.attr( "step" ) ) > 0) ? parseFloat(control.attr("step")) : 1; + + if ( typeof val === "object" ) { + var data = val, + // a slight tolerance helped get to the ends of the slider + tol = 8; + if ( !this.dragging || + data.pageX < this.slider.offset().left - tol || + data.pageX > this.slider.offset().left + this.slider.width() + tol ) { + return; + } + percent = Math.round( ( ( data.pageX - this.slider.offset().left ) / this.slider.width() ) * 100 ); + } else { + if ( val == null ) { + val = cType === "input" ? parseFloat( control.val() || 0 ) : control[0].selectedIndex; + } + percent = ( parseFloat( val ) - min ) / ( max - min ) * 100; + } + + if ( isNaN( percent ) ) { + return; + } + + if ( percent < 0 ) { + percent = 0; + } + + if ( percent > 100 ) { + percent = 100; + } + + 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 ); + } + // 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 ( newval < min ) { + newval = min; + } + + if ( newval > max ) { + newval = max; + } + + this.handle.css( "left", percent + "%" ); + this.handle.attr( { + "aria-valuenow": cType === "input" ? newval : control.find( "option" ).eq( newval ).attr( "value" ), + "aria-valuetext": cType === "input" ? newval : control.find( "option" ).eq( newval ).getEncodedText(), + title: cType === "input" ? newval : control.find( "option" ).eq( newval ).getEncodedText() + }); + 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 ( cType === "input" ) { + valueChanged = control.val() !== newval; + control.val( newval ); + } else { + valueChanged = control[ 0 ].selectedIndex !== newval; + control[ 0 ].selectedIndex = newval; + } + 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 ); + } + +}); + +//auto self-init widgets +$( document ).bind( "pagecreate create", function( e ){ + $.mobile.slider.prototype.enhanceWithin( e.target, true ); +}); + +})( jQuery ); + +(function( $, undefined ) { + +$.widget( "mobile.selectmenu", $.mobile.widget, { + options: { + theme: null, + disabled: false, + icon: "arrow-d", + iconpos: "right", + inline: false, + corners: true, + shadow: true, + iconshadow: true, + overlayTheme: "a", + 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.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" ); + } + }, + + _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, + + // 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 + .text( $( this.select[ 0 ].options.item( selectedIndex ) ).text() ) + .insertBefore( this.select ) + .buttonMarkup( { + theme: options.theme, + icon: options.icon, + iconpos: options.iconpos, + inline: options.inline, + corners: options.corners, + shadow: options.shadow, + iconshadow: options.iconshadow, + mini: options.mini + }); + + // 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 ) { + this.select.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(); + }); + + 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 ); + } + }) + .bind( "mouseup", 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(); + + this.button.find( ".ui-btn-text" ).text( function() { + if ( !self.isMultiple ) { + return selected.text(); + } + + return selected.length ? selected.map( function() { + return $( this ).text(); + }).get().join( ", " ) : self.placeholder; + }); + }, + + setButtonCount: function() { + var selected = this.selected(); + + // multiple count inside button + if ( this.isMultiple ) { + this.buttonCount[ selected.length > 1 ? "show" : "hide" ]().text( selected.length ); + } + }, + + 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" ); + } +}); + +//auto self-init widgets +$( document ).bind( "pagecreate create", function( e ){ + $.mobile.selectmenu.prototype.enhanceWithin( e.target, true ); +}); +})( jQuery ); + +/* +* custom "selectmenu" plugin +*/ + +(function( $, undefined ) { + var extendSelect = function( widget ){ + + var select = widget.select, + selectID = widget.selectID, + label = widget.label, + thisPage = widget.select.closest( ".ui-page" ), + screen = $( "
", {"class": "ui-selectmenu-screen ui-screen-hidden"} ).appendTo( thisPage ), + selectOptions = widget._selectOptions(), + isMultiple = widget.isMultiple = widget.select[ 0 ].multiple, + buttonId = selectID + "-button", + menuId = selectID + "-menu", + menuPage = $( "
" + + "
" + + "
" + label.getEncodedText() + "
"+ + "
"+ + "
"+ + "
" ), + + listbox = $("
", { "class": "ui-selectmenu ui-selectmenu-hidden ui-overlay-shadow ui-corner-all ui-body-" + widget.options.overlayTheme + " " + $.mobile.defaultDialogTransition } ).insertAfter(screen), + + list = $( "
",remove:null,select:null,show:null,spinner:"Loading…",tabTemplate:"
  • #{label}
  • "},_create:function(){this._tabify(true)},_setOption:function(b,e){if(b=="selected")this.options.collapsible&& -e==this.options.selected||this.select(e);else{this.options[b]=e;this._tabify()}},_tabId:function(b){return b.title&&b.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF-]/g,"")||this.options.idPrefix+u()},_sanitizeSelector:function(b){return b.replace(/:/g,"\\:")},_cookie:function(){var b=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+w());return d.cookie.apply(null,[b].concat(d.makeArray(arguments)))},_ui:function(b,e){return{tab:b,panel:e,index:this.anchors.index(b)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var b= -d(this);b.html(b.data("label.tabs")).removeData("label.tabs")})},_tabify:function(b){function e(g,f){g.css("display","");!d.support.opacity&&f.opacity&&g[0].style.removeAttribute("filter")}var a=this,c=this.options,h=/^#.+/;this.list=this.element.find("ol,ul").eq(0);this.lis=d(" > li:has(a[href])",this.list);this.anchors=this.lis.map(function(){return d("a",this)[0]});this.panels=d([]);this.anchors.each(function(g,f){var i=d(f).attr("href"),l=i.split("#")[0],q;if(l&&(l===location.toString().split("#")[0]|| -(q=d("base")[0])&&l===q.href)){i=f.hash;f.href=i}if(h.test(i))a.panels=a.panels.add(a.element.find(a._sanitizeSelector(i)));else if(i&&i!=="#"){d.data(f,"href.tabs",i);d.data(f,"load.tabs",i.replace(/#.*$/,""));i=a._tabId(f);f.href="#"+i;f=a.element.find("#"+i);if(!f.length){f=d(c.panelTemplate).attr("id",i).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(a.panels[g-1]||a.list);f.data("destroy.tabs",true)}a.panels=a.panels.add(f)}else c.disabled.push(g)});if(b){this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all"); -this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.lis.addClass("ui-state-default ui-corner-top");this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom");if(c.selected===p){location.hash&&this.anchors.each(function(g,f){if(f.hash==location.hash){c.selected=g;return false}});if(typeof c.selected!=="number"&&c.cookie)c.selected=parseInt(a._cookie(),10);if(typeof c.selected!=="number"&&this.lis.filter(".ui-tabs-selected").length)c.selected= -this.lis.index(this.lis.filter(".ui-tabs-selected"));c.selected=c.selected||(this.lis.length?0:-1)}else if(c.selected===null)c.selected=-1;c.selected=c.selected>=0&&this.anchors[c.selected]||c.selected<0?c.selected:0;c.disabled=d.unique(c.disabled.concat(d.map(this.lis.filter(".ui-state-disabled"),function(g){return a.lis.index(g)}))).sort();d.inArray(c.selected,c.disabled)!=-1&&c.disabled.splice(d.inArray(c.selected,c.disabled),1);this.panels.addClass("ui-tabs-hide");this.lis.removeClass("ui-tabs-selected ui-state-active"); -if(c.selected>=0&&this.anchors.length){a.element.find(a._sanitizeSelector(a.anchors[c.selected].hash)).removeClass("ui-tabs-hide");this.lis.eq(c.selected).addClass("ui-tabs-selected ui-state-active");a.element.queue("tabs",function(){a._trigger("show",null,a._ui(a.anchors[c.selected],a.element.find(a._sanitizeSelector(a.anchors[c.selected].hash))[0]))});this.load(c.selected)}d(window).bind("unload",function(){a.lis.add(a.anchors).unbind(".tabs");a.lis=a.anchors=a.panels=null})}else c.selected=this.lis.index(this.lis.filter(".ui-tabs-selected")); -this.element[c.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible");c.cookie&&this._cookie(c.selected,c.cookie);b=0;for(var j;j=this.lis[b];b++)d(j)[d.inArray(b,c.disabled)!=-1&&!d(j).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");c.cache===false&&this.anchors.removeData("cache.tabs");this.lis.add(this.anchors).unbind(".tabs");if(c.event!=="mouseover"){var k=function(g,f){f.is(":not(.ui-state-disabled)")&&f.addClass("ui-state-"+g)},n=function(g,f){f.removeClass("ui-state-"+ -g)};this.lis.bind("mouseover.tabs",function(){k("hover",d(this))});this.lis.bind("mouseout.tabs",function(){n("hover",d(this))});this.anchors.bind("focus.tabs",function(){k("focus",d(this).closest("li"))});this.anchors.bind("blur.tabs",function(){n("focus",d(this).closest("li"))})}var m,o;if(c.fx)if(d.isArray(c.fx)){m=c.fx[0];o=c.fx[1]}else m=o=c.fx;var r=o?function(g,f){d(g).closest("li").addClass("ui-tabs-selected ui-state-active");f.hide().removeClass("ui-tabs-hide").animate(o,o.duration||"normal", -function(){e(f,o);a._trigger("show",null,a._ui(g,f[0]))})}:function(g,f){d(g).closest("li").addClass("ui-tabs-selected ui-state-active");f.removeClass("ui-tabs-hide");a._trigger("show",null,a._ui(g,f[0]))},s=m?function(g,f){f.animate(m,m.duration||"normal",function(){a.lis.removeClass("ui-tabs-selected ui-state-active");f.addClass("ui-tabs-hide");e(f,m);a.element.dequeue("tabs")})}:function(g,f){a.lis.removeClass("ui-tabs-selected ui-state-active");f.addClass("ui-tabs-hide");a.element.dequeue("tabs")}; -this.anchors.bind(c.event+".tabs",function(){var g=this,f=d(g).closest("li"),i=a.panels.filter(":not(.ui-tabs-hide)"),l=a.element.find(a._sanitizeSelector(g.hash));if(f.hasClass("ui-tabs-selected")&&!c.collapsible||f.hasClass("ui-state-disabled")||f.hasClass("ui-state-processing")||a.panels.filter(":animated").length||a._trigger("select",null,a._ui(this,l[0]))===false){this.blur();return false}c.selected=a.anchors.index(this);a.abort();if(c.collapsible)if(f.hasClass("ui-tabs-selected")){c.selected= --1;c.cookie&&a._cookie(c.selected,c.cookie);a.element.queue("tabs",function(){s(g,i)}).dequeue("tabs");this.blur();return false}else if(!i.length){c.cookie&&a._cookie(c.selected,c.cookie);a.element.queue("tabs",function(){r(g,l)});a.load(a.anchors.index(this));this.blur();return false}c.cookie&&a._cookie(c.selected,c.cookie);if(l.length){i.length&&a.element.queue("tabs",function(){s(g,i)});a.element.queue("tabs",function(){r(g,l)});a.load(a.anchors.index(this))}else throw"jQuery UI Tabs: Mismatching fragment identifier."; -d.browser.msie&&this.blur()});this.anchors.bind("click.tabs",function(){return false})},_getIndex:function(b){if(typeof b=="string")b=this.anchors.index(this.anchors.filter("[href$="+b+"]"));return b},destroy:function(){var b=this.options;this.abort();this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs");this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.anchors.each(function(){var e= -d.data(this,"href.tabs");if(e)this.href=e;var a=d(this).unbind(".tabs");d.each(["href","load","cache"],function(c,h){a.removeData(h+".tabs")})});this.lis.unbind(".tabs").add(this.panels).each(function(){d.data(this,"destroy.tabs")?d(this).remove():d(this).removeClass("ui-state-default ui-corner-top ui-tabs-selected ui-state-active ui-state-hover ui-state-focus ui-state-disabled ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide")});b.cookie&&this._cookie(null,b.cookie);return this},add:function(b, -e,a){if(a===p)a=this.anchors.length;var c=this,h=this.options;e=d(h.tabTemplate.replace(/#\{href\}/g,b).replace(/#\{label\}/g,e));b=!b.indexOf("#")?b.replace("#",""):this._tabId(d("a",e)[0]);e.addClass("ui-state-default ui-corner-top").data("destroy.tabs",true);var j=c.element.find("#"+b);j.length||(j=d(h.panelTemplate).attr("id",b).data("destroy.tabs",true));j.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide");if(a>=this.lis.length){e.appendTo(this.list);j.appendTo(this.list[0].parentNode)}else{e.insertBefore(this.lis[a]); -j.insertBefore(this.panels[a])}h.disabled=d.map(h.disabled,function(k){return k>=a?++k:k});this._tabify();if(this.anchors.length==1){h.selected=0;e.addClass("ui-tabs-selected ui-state-active");j.removeClass("ui-tabs-hide");this.element.queue("tabs",function(){c._trigger("show",null,c._ui(c.anchors[0],c.panels[0]))});this.load(0)}this._trigger("add",null,this._ui(this.anchors[a],this.panels[a]));return this},remove:function(b){b=this._getIndex(b);var e=this.options,a=this.lis.eq(b).remove(),c=this.panels.eq(b).remove(); -if(a.hasClass("ui-tabs-selected")&&this.anchors.length>1)this.select(b+(b+1=b?--h:h});this._tabify();this._trigger("remove",null,this._ui(a.find("a")[0],c[0]));return this},enable:function(b){b=this._getIndex(b);var e=this.options;if(d.inArray(b,e.disabled)!=-1){this.lis.eq(b).removeClass("ui-state-disabled");e.disabled=d.grep(e.disabled,function(a){return a!=b});this._trigger("enable",null, -this._ui(this.anchors[b],this.panels[b]));return this}},disable:function(b){b=this._getIndex(b);var e=this.options;if(b!=e.selected){this.lis.eq(b).addClass("ui-state-disabled");e.disabled.push(b);e.disabled.sort();this._trigger("disable",null,this._ui(this.anchors[b],this.panels[b]))}return this},select:function(b){b=this._getIndex(b);if(b==-1)if(this.options.collapsible&&this.options.selected!=-1)b=this.options.selected;else return this;this.anchors.eq(b).trigger(this.options.event+".tabs");return this}, -load:function(b){b=this._getIndex(b);var e=this,a=this.options,c=this.anchors.eq(b)[0],h=d.data(c,"load.tabs");this.abort();if(!h||this.element.queue("tabs").length!==0&&d.data(c,"cache.tabs"))this.element.dequeue("tabs");else{this.lis.eq(b).addClass("ui-state-processing");if(a.spinner){var j=d("span",c);j.data("label.tabs",j.html()).html(a.spinner)}this.xhr=d.ajax(d.extend({},a.ajaxOptions,{url:h,success:function(k,n){e.element.find(e._sanitizeSelector(c.hash)).html(k);e._cleanup();a.cache&&d.data(c, -"cache.tabs",true);e._trigger("load",null,e._ui(e.anchors[b],e.panels[b]));try{a.ajaxOptions.success(k,n)}catch(m){}},error:function(k,n){e._cleanup();e._trigger("load",null,e._ui(e.anchors[b],e.panels[b]));try{a.ajaxOptions.error(k,n,b,c)}catch(m){}}}));e.element.dequeue("tabs");return this}},abort:function(){this.element.queue([]);this.panels.stop(false,true);this.element.queue("tabs",this.element.queue("tabs").splice(-2,2));if(this.xhr){this.xhr.abort();delete this.xhr}this._cleanup();return this}, -url:function(b,e){this.anchors.eq(b).removeData("cache.tabs").data("load.tabs",e);return this},length:function(){return this.anchors.length}});d.extend(d.ui.tabs,{version:"1.8.14"});d.extend(d.ui.tabs.prototype,{rotation:null,rotate:function(b,e){var a=this,c=this.options,h=a._rotate||(a._rotate=function(j){clearTimeout(a.rotation);a.rotation=setTimeout(function(){var k=c.selected;a.select(++k
    + value = parseInt( elem.css( "zIndex" ), 10 ); + if ( !isNaN( value ) && value !== 0 ) { + return value; + } + } + elem = elem.parent(); + } + } + + return 0; + }, + + disableSelection: function() { + return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) + + ".ui-disableSelection", function( event ) { + event.preventDefault(); + }); + }, + + enableSelection: function() { + return this.unbind( ".ui-disableSelection" ); + } +}); + +$.each( [ "Width", "Height" ], function( i, name ) { + var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], + type = name.toLowerCase(), + orig = { + innerWidth: $.fn.innerWidth, + innerHeight: $.fn.innerHeight, + outerWidth: $.fn.outerWidth, + outerHeight: $.fn.outerHeight + }; + + function reduce( elem, size, border, margin ) { + $.each( side, function() { + size -= parseFloat( $.curCSS( elem, "padding" + this, true) ) || 0; + if ( border ) { + size -= parseFloat( $.curCSS( elem, "border" + this + "Width", true) ) || 0; + } + if ( margin ) { + size -= parseFloat( $.curCSS( elem, "margin" + this, true) ) || 0; + } + }); + return size; + } + + $.fn[ "inner" + name ] = function( size ) { + if ( size === undefined ) { + return orig[ "inner" + name ].call( this ); + } + + return this.each(function() { + $( this ).css( type, reduce( this, size ) + "px" ); + }); + }; + + $.fn[ "outer" + name] = function( size, margin ) { + if ( typeof size !== "number" ) { + return orig[ "outer" + name ].call( this, size ); + } + + return this.each(function() { + $( this).css( type, reduce( this, size, true, margin ) + "px" ); + }); + }; +}); + +// selectors +function focusable( element, isTabIndexNotNaN ) { + var nodeName = element.nodeName.toLowerCase(); + if ( "area" === nodeName ) { + var map = element.parentNode, + mapName = map.name, + img; + if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { + return false; + } + img = $( "img[usemap=#" + mapName + "]" )[0]; + return !!img && visible( img ); + } + return ( /input|select|textarea|button|object/.test( nodeName ) + ? !element.disabled + : "a" == nodeName + ? element.href || isTabIndexNotNaN + : isTabIndexNotNaN) + // the element and all of its ancestors must be visible + && visible( element ); +} + +function visible( element ) { + return !$( element ).parents().andSelf().filter(function() { + return $.curCSS( this, "visibility" ) === "hidden" || + $.expr.filters.hidden( this ); + }).length; +} + +$.extend( $.expr[ ":" ], { + data: function( elem, i, match ) { + return !!$.data( elem, match[ 3 ] ); + }, + + focusable: function( element ) { + return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) ); + }, + + tabbable: function( element ) { + var tabIndex = $.attr( element, "tabindex" ), + isTabIndexNaN = isNaN( tabIndex ); + return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN ); + } +}); + +// support +$(function() { + var body = document.body, + div = body.appendChild( div = document.createElement( "div" ) ); + + // access offsetHeight before setting the style to prevent a layout bug + // in IE 9 which causes the elemnt to continue to take up space even + // after it is removed from the DOM (#8026) + div.offsetHeight; + + $.extend( div.style, { + minHeight: "100px", + height: "auto", + padding: 0, + borderWidth: 0 + }); + + $.support.minHeight = div.offsetHeight === 100; + $.support.selectstart = "onselectstart" in div; + + // set display to none to avoid a layout bug in IE + // http://dev.jquery.com/ticket/4014 + body.removeChild( div ).style.display = "none"; +}); + + + + + +// deprecated +$.extend( $.ui, { + // $.ui.plugin is deprecated. Use the proxy pattern instead. + plugin: { + add: function( module, option, set ) { + var proto = $.ui[ module ].prototype; + for ( var i in set ) { + proto.plugins[ i ] = proto.plugins[ i ] || []; + proto.plugins[ i ].push( [ option, set[ i ] ] ); + } + }, + call: function( instance, name, args ) { + var set = instance.plugins[ name ]; + if ( !set || !instance.element[ 0 ].parentNode ) { + return; + } + + for ( var i = 0; i < set.length; i++ ) { + if ( instance.options[ set[ i ][ 0 ] ] ) { + set[ i ][ 1 ].apply( instance.element, args ); + } + } + } + }, + + // will be deprecated when we switch to jQuery 1.4 - use jQuery.contains() + contains: function( a, b ) { + return document.compareDocumentPosition ? + a.compareDocumentPosition( b ) & 16 : + a !== b && a.contains( b ); + }, + + // only used by resizable + hasScroll: function( el, a ) { + + //If overflow is hidden, the element might have extra content, but the user wants to hide it + if ( $( el ).css( "overflow" ) === "hidden") { + return false; + } + + var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop", + has = false; + + if ( el[ scroll ] > 0 ) { + return true; + } + + // TODO: determine which cases actually cause this to happen + // if the element doesn't have the scroll set, see if it's possible to + // set the scroll + el[ scroll ] = 1; + has = ( el[ scroll ] > 0 ); + el[ scroll ] = 0; + return has; + }, + + // these are odd functions, fix the API or move into individual plugins + isOverAxis: function( x, reference, size ) { + //Determines when x coordinate is over "b" element axis + return ( x > reference ) && ( x < ( reference + size ) ); + }, + isOver: function( y, x, top, left, height, width ) { + //Determines when x, y coordinates is over "b" element + return $.ui.isOverAxis( y, top, height ) && $.ui.isOverAxis( x, left, width ); + } +}); + +})( jQuery ); +/*! + * jQuery UI Widget 1.8.21 + * + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Widget + */ +(function( $, undefined ) { + +// jQuery 1.4+ +if ( $.cleanData ) { + var _cleanData = $.cleanData; + $.cleanData = function( elems ) { + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + try { + $( elem ).triggerHandler( "remove" ); + // http://bugs.jquery.com/ticket/8235 + } catch( e ) {} + } + _cleanData( elems ); + }; +} else { + var _remove = $.fn.remove; + $.fn.remove = function( selector, keepData ) { + return this.each(function() { + if ( !keepData ) { + if ( !selector || $.filter( selector, [ this ] ).length ) { + $( "*", this ).add( [ this ] ).each(function() { + try { + $( this ).triggerHandler( "remove" ); + // http://bugs.jquery.com/ticket/8235 + } catch( e ) {} + }); + } + } + return _remove.call( $(this), selector, keepData ); + }); + }; +} + +$.widget = function( name, base, prototype ) { + var namespace = name.split( "." )[ 0 ], + fullName; + name = name.split( "." )[ 1 ]; + fullName = namespace + "-" + name; + + if ( !prototype ) { + prototype = base; + base = $.Widget; + } + + // create selector for plugin + $.expr[ ":" ][ fullName ] = function( elem ) { + return !!$.data( elem, name ); + }; + + $[ namespace ] = $[ namespace ] || {}; + $[ namespace ][ name ] = function( options, element ) { + // allow instantiation without initializing for simple inheritance + if ( arguments.length ) { + this._createWidget( options, element ); + } + }; + + var basePrototype = new base(); + // we need to make the options hash a property directly on the new instance + // otherwise we'll modify the options hash on the prototype that we're + // inheriting from +// $.each( basePrototype, function( key, val ) { +// if ( $.isPlainObject(val) ) { +// basePrototype[ key ] = $.extend( {}, val ); +// } +// }); + basePrototype.options = $.extend( true, {}, basePrototype.options ); + $[ namespace ][ name ].prototype = $.extend( true, basePrototype, { + namespace: namespace, + widgetName: name, + widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name, + widgetBaseClass: fullName + }, prototype ); + + $.widget.bridge( name, $[ namespace ][ name ] ); +}; + +$.widget.bridge = function( name, object ) { + $.fn[ name ] = function( options ) { + var isMethodCall = typeof options === "string", + args = Array.prototype.slice.call( arguments, 1 ), + returnValue = this; + + // allow multiple hashes to be passed on init + options = !isMethodCall && args.length ? + $.extend.apply( null, [ true, options ].concat(args) ) : + options; + + // prevent calls to internal methods + if ( isMethodCall && options.charAt( 0 ) === "_" ) { + return returnValue; + } + + if ( isMethodCall ) { + this.each(function() { + var instance = $.data( this, name ), + methodValue = instance && $.isFunction( instance[options] ) ? + instance[ options ].apply( instance, args ) : + instance; + // TODO: add this back in 1.9 and use $.error() (see #5972) +// if ( !instance ) { +// throw "cannot call methods on " + name + " prior to initialization; " + +// "attempted to call method '" + options + "'"; +// } +// if ( !$.isFunction( instance[options] ) ) { +// throw "no such method '" + options + "' for " + name + " widget instance"; +// } +// var methodValue = instance[ options ].apply( instance, args ); + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue; + return false; + } + }); + } else { + this.each(function() { + var instance = $.data( this, name ); + if ( instance ) { + instance.option( options || {} )._init(); + } else { + $.data( this, name, new object( options, this ) ); + } + }); + } + + return returnValue; + }; +}; + +$.Widget = function( options, element ) { + // allow instantiation without initializing for simple inheritance + if ( arguments.length ) { + this._createWidget( options, element ); + } +}; + +$.Widget.prototype = { + widgetName: "widget", + widgetEventPrefix: "", + options: { + disabled: false + }, + _createWidget: function( options, element ) { + // $.widget.bridge stores the plugin instance, but we do it anyway + // so that it's stored even before the _create function runs + $.data( element, this.widgetName, this ); + this.element = $( element ); + this.options = $.extend( true, {}, + this.options, + this._getCreateOptions(), + options ); + + var self = this; + this.element.bind( "remove." + this.widgetName, function() { + self.destroy(); + }); + + this._create(); + this._trigger( "create" ); + this._init(); + }, + _getCreateOptions: function() { + return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ]; + }, + _create: function() {}, + _init: function() {}, + + destroy: function() { + this.element + .unbind( "." + this.widgetName ) + .removeData( this.widgetName ); + this.widget() + .unbind( "." + this.widgetName ) + .removeAttr( "aria-disabled" ) + .removeClass( + this.widgetBaseClass + "-disabled " + + "ui-state-disabled" ); + }, + + widget: function() { + return this.element; + }, + + option: function( key, value ) { + var options = key; + + if ( arguments.length === 0 ) { + // don't return a reference to the internal hash + return $.extend( {}, this.options ); + } + + if (typeof key === "string" ) { + if ( value === undefined ) { + return this.options[ key ]; + } + options = {}; + options[ key ] = value; + } + + this._setOptions( options ); + + return this; + }, + _setOptions: function( options ) { + var self = this; + $.each( options, function( key, value ) { + self._setOption( key, value ); + }); + + return this; + }, + _setOption: function( key, value ) { + this.options[ key ] = value; + + if ( key === "disabled" ) { + this.widget() + [ value ? "addClass" : "removeClass"]( + this.widgetBaseClass + "-disabled" + " " + + "ui-state-disabled" ) + .attr( "aria-disabled", value ); + } + + return this; + }, + + enable: function() { + return this._setOption( "disabled", false ); + }, + disable: function() { + return this._setOption( "disabled", true ); + }, + + _trigger: function( type, event, data ) { + var prop, orig, + callback = this.options[ type ]; + + data = data || {}; + event = $.Event( event ); + event.type = ( type === this.widgetEventPrefix ? + type : + this.widgetEventPrefix + type ).toLowerCase(); + // the original event may come from any element + // so we need to reset the target on the new event + event.target = this.element[ 0 ]; + + // copy original event properties over to the new event + orig = event.originalEvent; + if ( orig ) { + for ( prop in orig ) { + if ( !( prop in event ) ) { + event[ prop ] = orig[ prop ]; + } + } + } + + this.element.trigger( event, data ); + + return !( $.isFunction(callback) && + callback.call( this.element[0], event, data ) === false || + event.isDefaultPrevented() ); + } +}; + +})( jQuery ); +/*! + * jQuery UI Mouse 1.8.21 + * + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Mouse + * + * Depends: + * jquery.ui.widget.js + */ +(function( $, undefined ) { + +var mouseHandled = false; +$( document ).mouseup( function( e ) { + mouseHandled = false; +}); + +$.widget("ui.mouse", { + options: { + cancel: ':input,option', + distance: 1, + delay: 0 + }, + _mouseInit: function() { + var self = this; + + this.element + .bind('mousedown.'+this.widgetName, function(event) { + return self._mouseDown(event); + }) + .bind('click.'+this.widgetName, function(event) { + if (true === $.data(event.target, self.widgetName + '.preventClickEvent')) { + $.removeData(event.target, self.widgetName + '.preventClickEvent'); + event.stopImmediatePropagation(); + return false; + } + }); + + this.started = false; + }, + + // TODO: make sure destroying one instance of mouse doesn't mess with + // other instances of mouse + _mouseDestroy: function() { + this.element.unbind('.'+this.widgetName); + $(document) + .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate) + .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate); + }, + + _mouseDown: function(event) { + // don't let more than one widget handle mouseStart + if( mouseHandled ) { return }; + + // we may have missed mouseup (out of window) + (this._mouseStarted && this._mouseUp(event)); + + this._mouseDownEvent = event; + + var self = this, + btnIsLeft = (event.which == 1), + // event.target.nodeName works around a bug in IE 8 with + // disabled inputs (#7620) + elIsCancel = (typeof this.options.cancel == "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false); + if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) { + return true; + } + + this.mouseDelayMet = !this.options.delay; + if (!this.mouseDelayMet) { + this._mouseDelayTimer = setTimeout(function() { + self.mouseDelayMet = true; + }, this.options.delay); + } + + if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { + this._mouseStarted = (this._mouseStart(event) !== false); + if (!this._mouseStarted) { + event.preventDefault(); + return true; + } + } + + // Click event may never have fired (Gecko & Opera) + if (true === $.data(event.target, this.widgetName + '.preventClickEvent')) { + $.removeData(event.target, this.widgetName + '.preventClickEvent'); + } + + // these delegates are required to keep context + this._mouseMoveDelegate = function(event) { + return self._mouseMove(event); + }; + this._mouseUpDelegate = function(event) { + return self._mouseUp(event); + }; + $(document) + .bind('mousemove.'+this.widgetName, this._mouseMoveDelegate) + .bind('mouseup.'+this.widgetName, this._mouseUpDelegate); + + event.preventDefault(); + + mouseHandled = true; + return true; + }, + + _mouseMove: function(event) { + // IE mouseup check - mouseup happened when mouse was out of window + if ($.browser.msie && !(document.documentMode >= 9) && !event.button) { + return this._mouseUp(event); + } + + if (this._mouseStarted) { + this._mouseDrag(event); + return event.preventDefault(); + } + + if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { + this._mouseStarted = + (this._mouseStart(this._mouseDownEvent, event) !== false); + (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event)); + } + + return !this._mouseStarted; + }, + + _mouseUp: function(event) { + $(document) + .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate) + .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate); + + if (this._mouseStarted) { + this._mouseStarted = false; + + if (event.target == this._mouseDownEvent.target) { + $.data(event.target, this.widgetName + '.preventClickEvent', true); + } + + this._mouseStop(event); + } + + return false; + }, + + _mouseDistanceMet: function(event) { + return (Math.max( + Math.abs(this._mouseDownEvent.pageX - event.pageX), + Math.abs(this._mouseDownEvent.pageY - event.pageY) + ) >= this.options.distance + ); + }, + + _mouseDelayMet: function(event) { + return this.mouseDelayMet; + }, + + // These are placeholder methods, to be overriden by extending plugin + _mouseStart: function(event) {}, + _mouseDrag: function(event) {}, + _mouseStop: function(event) {}, + _mouseCapture: function(event) { return true; } +}); + +})(jQuery); +/*! + * jQuery UI Position 1.8.21 + * + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Position + */ +(function( $, undefined ) { + +$.ui = $.ui || {}; + +var horizontalPositions = /left|center|right/, + verticalPositions = /top|center|bottom/, + center = "center", + support = {}, + _position = $.fn.position, + _offset = $.fn.offset; + +$.fn.position = function( options ) { + if ( !options || !options.of ) { + return _position.apply( this, arguments ); + } + + // make a copy, we don't want to modify arguments + options = $.extend( {}, options ); + + var target = $( options.of ), + targetElem = target[0], + collision = ( options.collision || "flip" ).split( " " ), + offset = options.offset ? options.offset.split( " " ) : [ 0, 0 ], + targetWidth, + targetHeight, + basePosition; + + if ( targetElem.nodeType === 9 ) { + targetWidth = target.width(); + targetHeight = target.height(); + basePosition = { top: 0, left: 0 }; + // TODO: use $.isWindow() in 1.9 + } else if ( targetElem.setTimeout ) { + targetWidth = target.width(); + targetHeight = target.height(); + basePosition = { top: target.scrollTop(), left: target.scrollLeft() }; + } else if ( targetElem.preventDefault ) { + // force left top to allow flipping + options.at = "left top"; + targetWidth = targetHeight = 0; + basePosition = { top: options.of.pageY, left: options.of.pageX }; + } else { + targetWidth = target.outerWidth(); + targetHeight = target.outerHeight(); + basePosition = target.offset(); + } + + // force my and at to have valid horizontal and veritcal positions + // if a value is missing or invalid, it will be converted to center + $.each( [ "my", "at" ], function() { + var pos = ( options[this] || "" ).split( " " ); + if ( pos.length === 1) { + pos = horizontalPositions.test( pos[0] ) ? + pos.concat( [center] ) : + verticalPositions.test( pos[0] ) ? + [ center ].concat( pos ) : + [ center, center ]; + } + pos[ 0 ] = horizontalPositions.test( pos[0] ) ? pos[ 0 ] : center; + pos[ 1 ] = verticalPositions.test( pos[1] ) ? pos[ 1 ] : center; + options[ this ] = pos; + }); + + // normalize collision option + if ( collision.length === 1 ) { + collision[ 1 ] = collision[ 0 ]; + } + + // normalize offset option + offset[ 0 ] = parseInt( offset[0], 10 ) || 0; + if ( offset.length === 1 ) { + offset[ 1 ] = offset[ 0 ]; + } + offset[ 1 ] = parseInt( offset[1], 10 ) || 0; + + if ( options.at[0] === "right" ) { + basePosition.left += targetWidth; + } else if ( options.at[0] === center ) { + basePosition.left += targetWidth / 2; + } + + if ( options.at[1] === "bottom" ) { + basePosition.top += targetHeight; + } else if ( options.at[1] === center ) { + basePosition.top += targetHeight / 2; + } + + basePosition.left += offset[ 0 ]; + basePosition.top += offset[ 1 ]; + + return this.each(function() { + var elem = $( this ), + elemWidth = elem.outerWidth(), + elemHeight = elem.outerHeight(), + marginLeft = parseInt( $.curCSS( this, "marginLeft", true ) ) || 0, + marginTop = parseInt( $.curCSS( this, "marginTop", true ) ) || 0, + collisionWidth = elemWidth + marginLeft + + ( parseInt( $.curCSS( this, "marginRight", true ) ) || 0 ), + collisionHeight = elemHeight + marginTop + + ( parseInt( $.curCSS( this, "marginBottom", true ) ) || 0 ), + position = $.extend( {}, basePosition ), + collisionPosition; + + if ( options.my[0] === "right" ) { + position.left -= elemWidth; + } else if ( options.my[0] === center ) { + position.left -= elemWidth / 2; + } + + if ( options.my[1] === "bottom" ) { + position.top -= elemHeight; + } else if ( options.my[1] === center ) { + position.top -= elemHeight / 2; + } + + // prevent fractions if jQuery version doesn't support them (see #5280) + if ( !support.fractions ) { + position.left = Math.round( position.left ); + position.top = Math.round( position.top ); + } + + collisionPosition = { + left: position.left - marginLeft, + top: position.top - marginTop + }; + + $.each( [ "left", "top" ], function( i, dir ) { + if ( $.ui.position[ collision[i] ] ) { + $.ui.position[ collision[i] ][ dir ]( position, { + targetWidth: targetWidth, + targetHeight: targetHeight, + elemWidth: elemWidth, + elemHeight: elemHeight, + collisionPosition: collisionPosition, + collisionWidth: collisionWidth, + collisionHeight: collisionHeight, + offset: offset, + my: options.my, + at: options.at + }); + } + }); + + if ( $.fn.bgiframe ) { + elem.bgiframe(); + } + elem.offset( $.extend( position, { using: options.using } ) ); + }); +}; + +$.ui.position = { + fit: { + left: function( position, data ) { + var win = $( window ), + over = data.collisionPosition.left + data.collisionWidth - win.width() - win.scrollLeft(); + position.left = over > 0 ? position.left - over : Math.max( position.left - data.collisionPosition.left, position.left ); + }, + top: function( position, data ) { + var win = $( window ), + over = data.collisionPosition.top + data.collisionHeight - win.height() - win.scrollTop(); + position.top = over > 0 ? position.top - over : Math.max( position.top - data.collisionPosition.top, position.top ); + } + }, + + flip: { + left: function( position, data ) { + if ( data.at[0] === center ) { + return; + } + var win = $( window ), + over = data.collisionPosition.left + data.collisionWidth - win.width() - win.scrollLeft(), + myOffset = data.my[ 0 ] === "left" ? + -data.elemWidth : + data.my[ 0 ] === "right" ? + data.elemWidth : + 0, + atOffset = data.at[ 0 ] === "left" ? + data.targetWidth : + -data.targetWidth, + offset = -2 * data.offset[ 0 ]; + position.left += data.collisionPosition.left < 0 ? + myOffset + atOffset + offset : + over > 0 ? + myOffset + atOffset + offset : + 0; + }, + top: function( position, data ) { + if ( data.at[1] === center ) { + return; + } + var win = $( window ), + over = data.collisionPosition.top + data.collisionHeight - win.height() - win.scrollTop(), + myOffset = data.my[ 1 ] === "top" ? + -data.elemHeight : + data.my[ 1 ] === "bottom" ? + data.elemHeight : + 0, + atOffset = data.at[ 1 ] === "top" ? + data.targetHeight : + -data.targetHeight, + offset = -2 * data.offset[ 1 ]; + position.top += data.collisionPosition.top < 0 ? + myOffset + atOffset + offset : + over > 0 ? + myOffset + atOffset + offset : + 0; + } + } +}; + +// offset setter from jQuery 1.4 +if ( !$.offset.setOffset ) { + $.offset.setOffset = function( elem, options ) { + // set position first, in-case top/left are set even on static elem + if ( /static/.test( $.curCSS( elem, "position" ) ) ) { + elem.style.position = "relative"; + } + var curElem = $( elem ), + curOffset = curElem.offset(), + curTop = parseInt( $.curCSS( elem, "top", true ), 10 ) || 0, + curLeft = parseInt( $.curCSS( elem, "left", true ), 10) || 0, + props = { + top: (options.top - curOffset.top) + curTop, + left: (options.left - curOffset.left) + curLeft + }; + + if ( 'using' in options ) { + options.using.call( elem, props ); + } else { + curElem.css( props ); + } + }; + + $.fn.offset = function( options ) { + var elem = this[ 0 ]; + if ( !elem || !elem.ownerDocument ) { return null; } + if ( options ) { + if ( $.isFunction( options ) ) { + return this.each(function( i ) { + $( this ).offset( options.call( this, i, $( this ).offset() ) ); + }); + } + return this.each(function() { + $.offset.setOffset( this, options ); + }); + } + return _offset.call( this ); + }; +} + +// fraction support test (older versions of jQuery don't support fractions) +(function () { + var body = document.getElementsByTagName( "body" )[ 0 ], + div = document.createElement( "div" ), + testElement, testElementParent, testElementStyle, offset, offsetTotal; + + //Create a "fake body" for testing based on method used in jQuery.support + testElement = document.createElement( body ? "div" : "body" ); + testElementStyle = { + visibility: "hidden", + width: 0, + height: 0, + border: 0, + margin: 0, + background: "none" + }; + if ( body ) { + $.extend( testElementStyle, { + position: "absolute", + left: "-1000px", + top: "-1000px" + }); + } + for ( var i in testElementStyle ) { + testElement.style[ i ] = testElementStyle[ i ]; + } + testElement.appendChild( div ); + testElementParent = body || document.documentElement; + testElementParent.insertBefore( testElement, testElementParent.firstChild ); + + div.style.cssText = "position: absolute; left: 10.7432222px; top: 10.432325px; height: 30px; width: 201px;"; + + offset = $( div ).offset( function( _, offset ) { + return offset; + }).offset(); + + testElement.innerHTML = ""; + testElementParent.removeChild( testElement ); + + offsetTotal = offset.top + offset.left + ( body ? 2000 : 0 ); + support.fractions = offsetTotal > 21 && offsetTotal < 22; +})(); + +}( jQuery )); +/*! + * jQuery UI Dialog 1.8.21 + * + * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Dialog + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + * jquery.ui.button.js + * jquery.ui.draggable.js + * jquery.ui.mouse.js + * jquery.ui.position.js + * jquery.ui.resizable.js + */ +(function( $, undefined ) { + +var uiDialogClasses = + 'ui-dialog ' + + 'ui-widget ' + + 'ui-widget-content ' + + 'ui-corner-all ', + sizeRelatedOptions = { + buttons: true, + height: true, + maxHeight: true, + maxWidth: true, + minHeight: true, + minWidth: true, + width: true + }, + resizableRelatedOptions = { + maxHeight: true, + maxWidth: true, + minHeight: true, + minWidth: true + }, + // support for jQuery 1.3.2 - handle common attrFn methods for dialog + attrFn = $.attrFn || { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true, + click: true + }; + +$.widget("ui.dialog", { + options: { + autoOpen: true, + buttons: {}, + closeOnEscape: true, + closeText: 'close', + dialogClass: '', + draggable: true, + hide: null, + height: 'auto', + maxHeight: false, + maxWidth: false, + minHeight: 150, + minWidth: 150, + modal: false, + position: { + my: 'center', + at: 'center', + collision: 'fit', + // ensure that the titlebar is never outside the document + using: function(pos) { + var topOffset = $(this).css(pos).offset().top; + if (topOffset < 0) { + $(this).css('top', pos.top - topOffset); + } + } + }, + resizable: true, + show: null, + stack: true, + title: '', + width: 300, + zIndex: 1000 + }, + + _create: function() { + this.originalTitle = this.element.attr('title'); + // #5742 - .attr() might return a DOMElement + if ( typeof this.originalTitle !== "string" ) { + this.originalTitle = ""; + } + + this.options.title = this.options.title || this.originalTitle; + var self = this, + options = self.options, + + title = options.title || ' ', + titleId = $.ui.dialog.getTitleId(self.element), + + uiDialog = (self.uiDialog = $('
    ')) + .appendTo(document.body) + .hide() + .addClass(uiDialogClasses + options.dialogClass) + .css({ + zIndex: options.zIndex + }) + // setting tabIndex makes the div focusable + // setting outline to 0 prevents a border on focus in Mozilla + .attr('tabIndex', -1).css('outline', 0).keydown(function(event) { + if (options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode && + event.keyCode === $.ui.keyCode.ESCAPE) { + + self.close(event); + event.preventDefault(); + } + }) + .attr({ + role: 'dialog', + 'aria-labelledby': titleId + }) + .mousedown(function(event) { + self.moveToTop(false, event); + }), + + uiDialogContent = self.element + .show() + .removeAttr('title') + .addClass( + 'ui-dialog-content ' + + 'ui-widget-content') + .appendTo(uiDialog), + + uiDialogTitlebar = (self.uiDialogTitlebar = $('
    ')) + .addClass( + 'ui-dialog-titlebar ' + + 'ui-widget-header ' + + 'ui-corner-all ' + + 'ui-helper-clearfix' + ) + .prependTo(uiDialog), + + uiDialogTitlebarClose = $('') + .addClass( + 'ui-dialog-titlebar-close ' + + 'ui-corner-all' + ) + .attr('role', 'button') + .hover( + function() { + uiDialogTitlebarClose.addClass('ui-state-hover'); + }, + function() { + uiDialogTitlebarClose.removeClass('ui-state-hover'); + } + ) + .focus(function() { + uiDialogTitlebarClose.addClass('ui-state-focus'); + }) + .blur(function() { + uiDialogTitlebarClose.removeClass('ui-state-focus'); + }) + .click(function(event) { + self.close(event); + return false; + }) + .appendTo(uiDialogTitlebar), + + uiDialogTitlebarCloseText = (self.uiDialogTitlebarCloseText = $('')) + .addClass( + 'ui-icon ' + + 'ui-icon-closethick' + ) + .text(options.closeText) + .appendTo(uiDialogTitlebarClose), + + uiDialogTitle = $('') + .addClass('ui-dialog-title') + .attr('id', titleId) + .html(title) + .prependTo(uiDialogTitlebar); + + //handling of deprecated beforeclose (vs beforeClose) option + //Ticket #4669 http://dev.jqueryui.com/ticket/4669 + //TODO: remove in 1.9pre + if ($.isFunction(options.beforeclose) && !$.isFunction(options.beforeClose)) { + options.beforeClose = options.beforeclose; + } + + uiDialogTitlebar.find("*").add(uiDialogTitlebar).disableSelection(); + + if (options.draggable && $.fn.draggable) { + self._makeDraggable(); + } + if (options.resizable && $.fn.resizable) { + self._makeResizable(); + } + + self._createButtons(options.buttons); + self._isOpen = false; + + if ($.fn.bgiframe) { + uiDialog.bgiframe(); + } + }, + + _init: function() { + if ( this.options.autoOpen ) { + this.open(); + } + }, + + destroy: function() { + var self = this; + + if (self.overlay) { + self.overlay.destroy(); + } + self.uiDialog.hide(); + self.element + .unbind('.dialog') + .removeData('dialog') + .removeClass('ui-dialog-content ui-widget-content') + .hide().appendTo('body'); + self.uiDialog.remove(); + + if (self.originalTitle) { + self.element.attr('title', self.originalTitle); + } + + return self; + }, + + widget: function() { + return this.uiDialog; + }, + + close: function(event) { + var self = this, + maxZ, thisZ; + + if (false === self._trigger('beforeClose', event)) { + return; + } + + if (self.overlay) { + self.overlay.destroy(); + } + self.uiDialog.unbind('keypress.ui-dialog'); + + self._isOpen = false; + + if (self.options.hide) { + self.uiDialog.hide(self.options.hide, function() { + self._trigger('close', event); + }); + } else { + self.uiDialog.hide(); + self._trigger('close', event); + } + + $.ui.dialog.overlay.resize(); + + // adjust the maxZ to allow other modal dialogs to continue to work (see #4309) + if (self.options.modal) { + maxZ = 0; + $('.ui-dialog').each(function() { + if (this !== self.uiDialog[0]) { + thisZ = $(this).css('z-index'); + if(!isNaN(thisZ)) { + maxZ = Math.max(maxZ, thisZ); + } + } + }); + $.ui.dialog.maxZ = maxZ; + } + + return self; + }, + + isOpen: function() { + return this._isOpen; + }, + + // the force parameter allows us to move modal dialogs to their correct + // position on open + moveToTop: function(force, event) { + var self = this, + options = self.options, + saveScroll; + + if ((options.modal && !force) || + (!options.stack && !options.modal)) { + return self._trigger('focus', event); + } + + if (options.zIndex > $.ui.dialog.maxZ) { + $.ui.dialog.maxZ = options.zIndex; + } + if (self.overlay) { + $.ui.dialog.maxZ += 1; + self.overlay.$el.css('z-index', $.ui.dialog.overlay.maxZ = $.ui.dialog.maxZ); + } + + //Save and then restore scroll since Opera 9.5+ resets when parent z-Index is changed. + // http://ui.jquery.com/bugs/ticket/3193 + saveScroll = { scrollTop: self.element.scrollTop(), scrollLeft: self.element.scrollLeft() }; + $.ui.dialog.maxZ += 1; + self.uiDialog.css('z-index', $.ui.dialog.maxZ); + self.element.attr(saveScroll); + self._trigger('focus', event); + + return self; + }, + + open: function() { + if (this._isOpen) { return; } + + var self = this, + options = self.options, + uiDialog = self.uiDialog; + + self.overlay = options.modal ? new $.ui.dialog.overlay(self) : null; + self._size(); + self._position(options.position); + uiDialog.show(options.show); + self.moveToTop(true); + + // prevent tabbing out of modal dialogs + if ( options.modal ) { + uiDialog.bind( "keydown.ui-dialog", function( event ) { + if ( event.keyCode !== $.ui.keyCode.TAB ) { + return; + } + + var tabbables = $(':tabbable', this), + first = tabbables.filter(':first'), + last = tabbables.filter(':last'); + + if (event.target === last[0] && !event.shiftKey) { + first.focus(1); + return false; + } else if (event.target === first[0] && event.shiftKey) { + last.focus(1); + return false; + } + }); + } + + // set focus to the first tabbable element in the content area or the first button + // if there are no tabbable elements, set focus on the dialog itself + $(self.element.find(':tabbable').get().concat( + uiDialog.find('.ui-dialog-buttonpane :tabbable').get().concat( + uiDialog.get()))).eq(0).focus(); + + self._isOpen = true; + self._trigger('open'); + + return self; + }, + + _createButtons: function(buttons) { + var self = this, + hasButtons = false, + uiDialogButtonPane = $('
    ') + .addClass( + 'ui-dialog-buttonpane ' + + 'ui-widget-content ' + + 'ui-helper-clearfix' + ), + uiButtonSet = $( "
    " ) + .addClass( "ui-dialog-buttonset" ) + .appendTo( uiDialogButtonPane ); + + // if we already have a button pane, remove it + self.uiDialog.find('.ui-dialog-buttonpane').remove(); + + if (typeof buttons === 'object' && buttons !== null) { + $.each(buttons, function() { + return !(hasButtons = true); + }); + } + if (hasButtons) { + $.each(buttons, function(name, props) { + props = $.isFunction( props ) ? + { click: props, text: name } : + props; + var button = $('') + .click(function() { + props.click.apply(self.element[0], arguments); + }) + .appendTo(uiButtonSet); + // can't use .attr( props, true ) with jQuery 1.3.2. + $.each( props, function( key, value ) { + if ( key === "click" ) { + return; + } + if ( key in attrFn ) { + button[ key ]( value ); + } else { + button.attr( key, value ); + } + }); + if ($.fn.button) { + button.button(); + } + }); + uiDialogButtonPane.appendTo(self.uiDialog); + } + }, + + _makeDraggable: function() { + var self = this, + options = self.options, + doc = $(document), + heightBeforeDrag; + + function filteredUi(ui) { + return { + position: ui.position, + offset: ui.offset + }; + } + + self.uiDialog.draggable({ + cancel: '.ui-dialog-content, .ui-dialog-titlebar-close', + handle: '.ui-dialog-titlebar', + containment: 'document', + start: function(event, ui) { + heightBeforeDrag = options.height === "auto" ? "auto" : $(this).height(); + $(this).height($(this).height()).addClass("ui-dialog-dragging"); + self._trigger('dragStart', event, filteredUi(ui)); + }, + drag: function(event, ui) { + self._trigger('drag', event, filteredUi(ui)); + }, + stop: function(event, ui) { + options.position = [ui.position.left - doc.scrollLeft(), + ui.position.top - doc.scrollTop()]; + $(this).removeClass("ui-dialog-dragging").height(heightBeforeDrag); + self._trigger('dragStop', event, filteredUi(ui)); + $.ui.dialog.overlay.resize(); + } + }); + }, + + _makeResizable: function(handles) { + handles = (handles === undefined ? this.options.resizable : handles); + var self = this, + options = self.options, + // .ui-resizable has position: relative defined in the stylesheet + // but dialogs have to use absolute or fixed positioning + position = self.uiDialog.css('position'), + resizeHandles = (typeof handles === 'string' ? + handles : + 'n,e,s,w,se,sw,ne,nw' + ); + + function filteredUi(ui) { + return { + originalPosition: ui.originalPosition, + originalSize: ui.originalSize, + position: ui.position, + size: ui.size + }; + } + + self.uiDialog.resizable({ + cancel: '.ui-dialog-content', + containment: 'document', + alsoResize: self.element, + maxWidth: options.maxWidth, + maxHeight: options.maxHeight, + minWidth: options.minWidth, + minHeight: self._minHeight(), + handles: resizeHandles, + start: function(event, ui) { + $(this).addClass("ui-dialog-resizing"); + self._trigger('resizeStart', event, filteredUi(ui)); + }, + resize: function(event, ui) { + self._trigger('resize', event, filteredUi(ui)); + }, + stop: function(event, ui) { + $(this).removeClass("ui-dialog-resizing"); + options.height = $(this).height(); + options.width = $(this).width(); + self._trigger('resizeStop', event, filteredUi(ui)); + $.ui.dialog.overlay.resize(); + } + }) + .css('position', position) + .find('.ui-resizable-se').addClass('ui-icon ui-icon-grip-diagonal-se'); + }, + + _minHeight: function() { + var options = this.options; + + if (options.height === 'auto') { + return options.minHeight; + } else { + return Math.min(options.minHeight, options.height); + } + }, + + _position: function(position) { + var myAt = [], + offset = [0, 0], + isVisible; + + if (position) { + // deep extending converts arrays to objects in jQuery <= 1.3.2 :-( + // if (typeof position == 'string' || $.isArray(position)) { + // myAt = $.isArray(position) ? position : position.split(' '); + + if (typeof position === 'string' || (typeof position === 'object' && '0' in position)) { + myAt = position.split ? position.split(' ') : [position[0], position[1]]; + if (myAt.length === 1) { + myAt[1] = myAt[0]; + } + + $.each(['left', 'top'], function(i, offsetPosition) { + if (+myAt[i] === myAt[i]) { + offset[i] = myAt[i]; + myAt[i] = offsetPosition; + } + }); + + position = { + my: myAt.join(" "), + at: myAt.join(" "), + offset: offset.join(" ") + }; + } + + position = $.extend({}, $.ui.dialog.prototype.options.position, position); + } else { + position = $.ui.dialog.prototype.options.position; + } + + // need to show the dialog to get the actual offset in the position plugin + isVisible = this.uiDialog.is(':visible'); + if (!isVisible) { + this.uiDialog.show(); + } + this.uiDialog + // workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781 + .css({ top: 0, left: 0 }) + .position($.extend({ of: window }, position)); + if (!isVisible) { + this.uiDialog.hide(); + } + }, + + _setOptions: function( options ) { + var self = this, + resizableOptions = {}, + resize = false; + + $.each( options, function( key, value ) { + self._setOption( key, value ); + + if ( key in sizeRelatedOptions ) { + resize = true; + } + if ( key in resizableRelatedOptions ) { + resizableOptions[ key ] = value; + } + }); + + if ( resize ) { + this._size(); + } + if ( this.uiDialog.is( ":data(resizable)" ) ) { + this.uiDialog.resizable( "option", resizableOptions ); + } + }, + + _setOption: function(key, value){ + var self = this, + uiDialog = self.uiDialog; + + switch (key) { + //handling of deprecated beforeclose (vs beforeClose) option + //Ticket #4669 http://dev.jqueryui.com/ticket/4669 + //TODO: remove in 1.9pre + case "beforeclose": + key = "beforeClose"; + break; + case "buttons": + self._createButtons(value); + break; + case "closeText": + // ensure that we always pass a string + self.uiDialogTitlebarCloseText.text("" + value); + break; + case "dialogClass": + uiDialog + .removeClass(self.options.dialogClass) + .addClass(uiDialogClasses + value); + break; + case "disabled": + if (value) { + uiDialog.addClass('ui-dialog-disabled'); + } else { + uiDialog.removeClass('ui-dialog-disabled'); + } + break; + case "draggable": + var isDraggable = uiDialog.is( ":data(draggable)" ); + if ( isDraggable && !value ) { + uiDialog.draggable( "destroy" ); + } + + if ( !isDraggable && value ) { + self._makeDraggable(); + } + break; + case "position": + self._position(value); + break; + case "resizable": + // currently resizable, becoming non-resizable + var isResizable = uiDialog.is( ":data(resizable)" ); + if (isResizable && !value) { + uiDialog.resizable('destroy'); + } + + // currently resizable, changing handles + if (isResizable && typeof value === 'string') { + uiDialog.resizable('option', 'handles', value); + } + + // currently non-resizable, becoming resizable + if (!isResizable && value !== false) { + self._makeResizable(value); + } + break; + case "title": + // convert whatever was passed in o a string, for html() to not throw up + $(".ui-dialog-title", self.uiDialogTitlebar).html("" + (value || ' ')); + break; + } + + $.Widget.prototype._setOption.apply(self, arguments); + }, + + _size: function() { + /* If the user has resized the dialog, the .ui-dialog and .ui-dialog-content + * divs will both have width and height set, so we need to reset them + */ + var options = this.options, + nonContentHeight, + minContentHeight, + isVisible = this.uiDialog.is( ":visible" ); + + // reset content sizing + this.element.show().css({ + width: 'auto', + minHeight: 0, + height: 0 + }); + + if (options.minWidth > options.width) { + options.width = options.minWidth; + } + + // reset wrapper sizing + // determine the height of all the non-content elements + nonContentHeight = this.uiDialog.css({ + height: 'auto', + width: options.width + }) + .height(); + minContentHeight = Math.max( 0, options.minHeight - nonContentHeight ); + + if ( options.height === "auto" ) { + // only needed for IE6 support + if ( $.support.minHeight ) { + this.element.css({ + minHeight: minContentHeight, + height: "auto" + }); + } else { + this.uiDialog.show(); + var autoHeight = this.element.css( "height", "auto" ).height(); + if ( !isVisible ) { + this.uiDialog.hide(); + } + this.element.height( Math.max( autoHeight, minContentHeight ) ); + } + } else { + this.element.height( Math.max( options.height - nonContentHeight, 0 ) ); + } + + if (this.uiDialog.is(':data(resizable)')) { + this.uiDialog.resizable('option', 'minHeight', this._minHeight()); + } + } +}); + +$.extend($.ui.dialog, { + version: "1.8.21", + + uuid: 0, + maxZ: 0, + + getTitleId: function($el) { + var id = $el.attr('id'); + if (!id) { + this.uuid += 1; + id = this.uuid; + } + return 'ui-dialog-title-' + id; + }, + + overlay: function(dialog) { + this.$el = $.ui.dialog.overlay.create(dialog); + } +}); + +$.extend($.ui.dialog.overlay, { + instances: [], + // reuse old instances due to IE memory leak with alpha transparency (see #5185) + oldInstances: [], + maxZ: 0, + events: $.map('focus,mousedown,mouseup,keydown,keypress,click'.split(','), + function(event) { return event + '.dialog-overlay'; }).join(' '), + create: function(dialog) { + if (this.instances.length === 0) { + // prevent use of anchors and inputs + // we use a setTimeout in case the overlay is created from an + // event that we're going to be cancelling (see #2804) + setTimeout(function() { + // handle $(el).dialog().dialog('close') (see #4065) + if ($.ui.dialog.overlay.instances.length) { + $(document).bind($.ui.dialog.overlay.events, function(event) { + // stop events if the z-index of the target is < the z-index of the overlay + // we cannot return true when we don't want to cancel the event (#3523) + if ($(event.target).zIndex() < $.ui.dialog.overlay.maxZ) { + return false; + } + }); + } + }, 1); + + // allow closing by pressing the escape key + $(document).bind('keydown.dialog-overlay', function(event) { + if (dialog.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode && + event.keyCode === $.ui.keyCode.ESCAPE) { + + dialog.close(event); + event.preventDefault(); + } + }); + + // handle window resize + $(window).bind('resize.dialog-overlay', $.ui.dialog.overlay.resize); + } + + var $el = (this.oldInstances.pop() || $('
    ').addClass('ui-widget-overlay')) + .appendTo(document.body) + .css({ + width: this.width(), + height: this.height() + }); + + if ($.fn.bgiframe) { + $el.bgiframe(); + } + + this.instances.push($el); + return $el; + }, + + destroy: function($el) { + var indexOf = $.inArray($el, this.instances); + if (indexOf != -1){ + this.oldInstances.push(this.instances.splice(indexOf, 1)[0]); + } + + if (this.instances.length === 0) { + $([document, window]).unbind('.dialog-overlay'); + } + + $el.remove(); + + // adjust the maxZ to allow other modal dialogs to continue to work (see #4309) + var maxZ = 0; + $.each(this.instances, function() { + maxZ = Math.max(maxZ, this.css('z-index')); + }); + this.maxZ = maxZ; + }, + + height: function() { + var scrollHeight, + offsetHeight; + // handle IE 6 + if ($.browser.msie && $.browser.version < 7) { + scrollHeight = Math.max( + document.documentElement.scrollHeight, + document.body.scrollHeight + ); + offsetHeight = Math.max( + document.documentElement.offsetHeight, + document.body.offsetHeight + ); + + if (scrollHeight < offsetHeight) { + return $(window).height() + 'px'; + } else { + return scrollHeight + 'px'; + } + // handle "good" browsers + } else { + return $(document).height() + 'px'; + } + }, + + width: function() { + var scrollWidth, + offsetWidth; + // handle IE + if ( $.browser.msie ) { + scrollWidth = Math.max( + document.documentElement.scrollWidth, + document.body.scrollWidth + ); + offsetWidth = Math.max( + document.documentElement.offsetWidth, + document.body.offsetWidth + ); + + if (scrollWidth < offsetWidth) { + return $(window).width() + 'px'; + } else { + return scrollWidth + 'px'; + } + // handle "good" browsers + } else { + return $(document).width() + 'px'; + } + }, + + resize: function() { + /* If the dialog is draggable and the user drags it past the + * right edge of the window, the document becomes wider so we + * need to stretch the overlay. If the user then drags the + * dialog back to the left, the document will become narrower, + * so we need to shrink the overlay to the appropriate size. + * This is handled by shrinking the overlay before setting it + * to the full document size. + */ + var $overlays = $([]); + $.each($.ui.dialog.overlay.instances, function() { + $overlays = $overlays.add(this); + }); + + $overlays.css({ + width: 0, + height: 0 + }).css({ + width: $.ui.dialog.overlay.width(), + height: $.ui.dialog.overlay.height() + }); + } +}); + +$.extend($.ui.dialog.overlay.prototype, { + destroy: function() { + $.ui.dialog.overlay.destroy(this.$el); + } +}); + +}(jQuery)); -- cgit v1.2.3