Added: ofbiz/trunk/framework/images/webapp/images/jquery/jquery.mobile/jquery.mobile-1.4.0.js
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/images/webapp/images/jquery/jquery.mobile/jquery.mobile-1.4.0.js?rev=1559769&view=auto ============================================================================== --- ofbiz/trunk/framework/images/webapp/images/jquery/jquery.mobile/jquery.mobile-1.4.0.js (added) +++ ofbiz/trunk/framework/images/webapp/images/jquery/jquery.mobile/jquery.mobile-1.4.0.js Mon Jan 20 16:27:09 2014 @@ -0,0 +1,14736 @@ +/*! +* jQuery Mobile 1.4.0 +* Git HEAD hash: f09aae0e035d6805e461a7be246d04a0dbc98f69 <> Date: Thu Dec 19 2013 17:34:22 UTC +* http://jquerymobile.com +* +* Copyright 2010, 2013 jQuery Foundation, Inc. and other contributors +* Released under the MIT license. +* http://jquery.org/license +* +*/ + + +(function ( root, doc, factory ) { + if ( typeof define === "function" && define.amd ) { + // AMD. Register as an anonymous module. + define( [ "jquery" ], function ( $ ) { + factory( $, root, doc ); + return $.mobile; + }); + } else { + // Browser globals + factory( root.jQuery, root, doc ); + } +}( this, document, function ( jQuery, window, document, undefined ) { +(function( $ ) { + $.mobile = {}; +}( jQuery )); + +(function( $, window, undefined ) { + $.extend( $.mobile, { + + // Version of the jQuery Mobile Framework + version: "1.4.0", + + // Deprecated and no longer used in 1.4 remove in 1.5 + // Define the url parameter used for referencing widget-generated sub-pages. + // Translates to example.html&ui-page=subpageIdentifier + // hash segment before &ui-page= is used to make Ajax request + subPageUrlKey: "ui-page", + + hideUrlBar: true, + + // Keepnative Selector + keepNative: ":jqmData(role='none'), :jqmData(role='nojs')", + + // Deprecated in 1.4 remove in 1.5 + // Class assigned to page currently in view, and during transitions + activePageClass: "ui-page-active", + + // Deprecated in 1.4 remove in 1.5 + // Class used for "active" button state, from CSS framework + activeBtnClass: "ui-btn-active", + + // Deprecated in 1.4 remove in 1.5 + // Class used for "focus" form element state, from CSS framework + focusClass: "ui-focus", + + // Automatically handle clicks and form submissions through Ajax, when same-domain + ajaxEnabled: true, + + // Automatically load and show pages based on location.hash + hashListeningEnabled: true, + + // disable to prevent jquery from bothering with links + linkBindingEnabled: true, + + // Set default page transition - 'none' for no transitions + defaultPageTransition: "fade", + + // Set maximum window width for transitions to apply - 'false' for no limit + maxTransitionWidth: false, + + // Minimum scroll distance that will be remembered when returning to a page + // Deprecated remove in 1.5 + minScrollBack: 0, + + // Set default dialog transition - 'none' for no transitions + defaultDialogTransition: "pop", + + // Error response message - appears when an Ajax page request fails + pageLoadErrorMessage: "Error Loading Page", + + // For error messages, which theme does the box uses? + pageLoadErrorMessageTheme: "a", + + // replace calls to window.history.back with phonegaps navigation helper + // where it is provided on the window object + phonegapNavigationEnabled: false, + + //automatically initialize the DOM when it's ready + autoInitializePage: true, + + pushStateEnabled: true, + + // allows users to opt in to ignoring content by marking a parent element as + // data-ignored + ignoreContentEnabled: false, + + buttonMarkup: { + hoverDelay: 200 + }, + + // disable the alteration of the dynamic base tag or links in the case + // that a dynamic base tag isn't supported + dynamicBaseEnabled: true, + + // default the property to remove dependency on assignment in init module + pageContainer: $(), + + //enable cross-domain page support + allowCrossDomainPages: false, + + dialogHashKey: "&ui-state=dialog" + }); +})( jQuery, this ); + +(function( $, window, undefined ) { + var nsNormalizeDict = {}, + oldFind = $.find, + rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, + jqmDataRE = /:jqmData\(([^)]*)\)/g; + + $.extend( $.mobile, { + + // Namespace used framework-wide for data-attrs. Default is no namespace + + ns: "", + + // Retrieve an attribute from an element and perform some massaging of the value + + getAttribute: function( element, key ) { + var data; + + element = element.jquery ? element[0] : element; + + if ( element && element.getAttribute ) { + data = element.getAttribute( "data-" + $.mobile.ns + key ); + } + + // Copied from core's src/data.js:dataAttr() + // Convert from a string to a proper data type + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? JSON.parse( data ) : + data; + } catch( err ) {} + + return data; + }, + + // Expose our cache for testing purposes. + nsNormalizeDict: nsNormalizeDict, + + // Take a data attribute property, prepend the namespace + // and then camel case the attribute string. Add the result + // to our nsNormalizeDict so we don't have to do this again. + nsNormalize: function( prop ) { + return nsNormalizeDict[ prop ] || + ( nsNormalizeDict[ prop ] = $.camelCase( $.mobile.ns + prop ) ); + }, + + // Find the closest javascript page element to gather settings data jsperf test + // http://jsperf.com/single-complex-selector-vs-many-complex-selectors/edit + // possibly naive, but it shows that the parsing overhead for *just* the page selector vs + // the page and dialog selector is negligable. This could probably be speed up by + // doing a similar parent node traversal to the one found in the inherited theme code above + closestPageData: function( $target ) { + return $target + .closest( ":jqmData(role='page'), :jqmData(role='dialog')" ) + .data( "mobile-page" ); + } + + }); + + // Mobile version of data and removeData and hasData methods + // ensures all data is set and retrieved using jQuery Mobile's data namespace + $.fn.jqmData = function( prop, value ) { + var result; + if ( typeof prop !== "undefined" ) { + if ( prop ) { + prop = $.mobile.nsNormalize( prop ); + } + + // undefined is permitted as an explicit input for the second param + // in this case it returns the value and does not set it to undefined + if ( arguments.length < 2 || value === undefined ) { + result = this.data( prop ); + } else { + result = this.data( prop, value ); + } + } + return result; + }; + + $.jqmData = function( elem, prop, value ) { + var result; + if ( typeof prop !== "undefined" ) { + result = $.data( elem, prop ? $.mobile.nsNormalize( prop ) : prop, value ); + } + return result; + }; + + $.fn.jqmRemoveData = function( prop ) { + return this.removeData( $.mobile.nsNormalize( prop ) ); + }; + + $.jqmRemoveData = function( elem, prop ) { + return $.removeData( elem, $.mobile.nsNormalize( prop ) ); + }; + + $.find = function( selector, context, ret, extra ) { + if ( selector.indexOf( ":jqmData" ) > -1 ) { + selector = selector.replace( jqmDataRE, "[data-" + ( $.mobile.ns || "" ) + "$1]" ); + } + + return oldFind.call( this, selector, context, ret, extra ); + }; + + $.extend( $.find, oldFind ); + +})( jQuery, this ); + +/*! + * jQuery UI Core c0ab71056b936627e8a7821f03c044aec6280a40 + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/category/ui-core/ + */ +(function( $, undefined ) { + +var uuid = 0, + runiqueId = /^ui-id-\d+$/; + +// $.ui might exist from components with no dependencies, e.g., $.ui.position +$.ui = $.ui || {}; + +$.extend( $.ui, { + version: "c0ab71056b936627e8a7821f03c044aec6280a40", + + keyCode: { + BACKSPACE: 8, + COMMA: 188, + DELETE: 46, + DOWN: 40, + END: 35, + ENTER: 13, + ESCAPE: 27, + HOME: 36, + LEFT: 37, + PAGE_DOWN: 34, + PAGE_UP: 33, + PERIOD: 190, + RIGHT: 39, + SPACE: 32, + TAB: 9, + UP: 38 + } +}); + +// plugins +$.fn.extend({ + focus: (function( orig ) { + return function( delay, fn ) { + return typeof delay === "number" ? + this.each(function() { + var elem = this; + setTimeout(function() { + $( elem ).focus(); + if ( fn ) { + fn.call( elem ); + } + }, delay ); + }) : + orig.apply( this, arguments ); + }; + })( $.fn.focus ), + + scrollParent: function() { + var scrollParent; + if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) { + scrollParent = this.parents().filter(function() { + return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x")); + }).eq(0); + } else { + scrollParent = this.parents().filter(function() { + return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x")); + }).eq(0); + } + + return ( /fixed/ ).test( this.css( "position") ) || !scrollParent.length ? $( this[ 0 ].ownerDocument || document ) : scrollParent; + }, + + uniqueId: function() { + return this.each(function() { + if ( !this.id ) { + this.id = "ui-id-" + (++uuid); + } + }); + }, + + removeUniqueId: function() { + return this.each(function() { + if ( runiqueId.test( this.id ) ) { + $( this ).removeAttr( "id" ); + } + }); + } +}); + +// selectors +function focusable( element, isTabIndexNotNaN ) { + var map, mapName, img, + nodeName = element.nodeName.toLowerCase(); + if ( "area" === nodeName ) { + map = element.parentNode; + mapName = map.name; + 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 $.expr.filters.visible( element ) && + !$( element ).parents().addBack().filter(function() { + return $.css( this, "visibility" ) === "hidden"; + }).length; +} + +$.extend( $.expr[ ":" ], { + data: $.expr.createPseudo ? + $.expr.createPseudo(function( dataName ) { + return function( elem ) { + return !!$.data( elem, dataName ); + }; + }) : + // support: jQuery <1.8 + 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: jQuery <1.8 +if ( !$( "<a>" ).outerWidth( 1 ).jquery ) { + $.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( $.css( elem, "padding" + this ) ) || 0; + if ( border ) { + size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0; + } + if ( margin ) { + size -= parseFloat( $.css( elem, "margin" + this ) ) || 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" ); + }); + }; + }); +} + +// support: jQuery <1.8 +if ( !$.fn.addBack ) { + $.fn.addBack = function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + }; +} + +// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413) +if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) { + $.fn.removeData = (function( removeData ) { + return function( key ) { + if ( arguments.length ) { + return removeData.call( this, $.camelCase( key ) ); + } else { + return removeData.call( this ); + } + }; + })( $.fn.removeData ); +} + + + + + +// deprecated +$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); + +$.support.selectstart = "onselectstart" in document.createElement( "div" ); +$.fn.extend({ + disableSelection: function() { + return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) + + ".ui-disableSelection", function( event ) { + event.preventDefault(); + }); + }, + + enableSelection: function() { + return this.unbind( ".ui-disableSelection" ); + }, + + zIndex: function( zIndex ) { + if ( zIndex !== undefined ) { + return this.css( "zIndex", zIndex ); + } + + if ( this.length ) { + var elem = $( this[ 0 ] ), position, value; + while ( elem.length && elem[ 0 ] !== document ) { + // Ignore z-index if position is set to a value where z-index is ignored by the browser + // This makes behavior of this function consistent across browsers + // WebKit always returns auto if the element is positioned + position = elem.css( "position" ); + if ( position === "absolute" || position === "relative" || position === "fixed" ) { + // IE returns 0 when zIndex is not specified + // other browsers return a string + // we ignore the case of nested elements with an explicit value of 0 + // <div style="z-index: -10;"><div style="z-index: 0;"></div></div> + value = parseInt( elem.css( "zIndex" ), 10 ); + if ( !isNaN( value ) && value !== 0 ) { + return value; + } + } + elem = elem.parent(); + } + } + + return 0; + } +}); + +// $.ui.plugin is deprecated. Use $.widget() extensions instead. +$.ui.plugin = { + add: function( module, option, set ) { + var i, + proto = $.ui[ module ].prototype; + for ( i in set ) { + proto.plugins[ i ] = proto.plugins[ i ] || []; + proto.plugins[ i ].push( [ option, set[ i ] ] ); + } + }, + call: function( instance, name, args, allowDisconnected ) { + var i, + set = instance.plugins[ name ]; + + if ( !set ) { + return; + } + + if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) ) { + return; + } + + for ( i = 0; i < set.length; i++ ) { + if ( instance.options[ set[ i ][ 0 ] ] ) { + set[ i ][ 1 ].apply( instance.element, args ); + } + } + } +}; + +})( jQuery ); +(function( $, window, undefined ) { + + $.extend( $.mobile, { + // define the window and the document objects + window: $( window ), + document: $( document ), + + // TODO: Remove and use $.ui.keyCode directly + keyCode: $.ui.keyCode, + + // Place to store various widget extensions + behaviors: {}, + + // Scroll page vertically: scroll to 0 to hide iOS address bar, or pass a Y value + silentScroll: function( ypos ) { + if ( $.type( ypos ) !== "number" ) { + ypos = $.mobile.defaultHomeScroll; + } + + // prevent scrollstart and scrollstop events + $.event.special.scrollstart.enabled = false; + + setTimeout(function() { + window.scrollTo( 0, ypos ); + $.mobile.document.trigger( "silentscroll", { x: 0, y: ypos }); + }, 20 ); + + setTimeout(function() { + $.event.special.scrollstart.enabled = true; + }, 150 ); + }, + + getClosestBaseUrl: function( ele ) { + // Find the closest page and extract out its url. + var url = $( ele ).closest( ".ui-page" ).jqmData( "url" ), + base = $.mobile.path.documentBase.hrefNoHash; + + if ( !$.mobile.dynamicBaseEnabled || !url || !$.mobile.path.isPath( url ) ) { + url = base; + } + + return $.mobile.path.makeUrlAbsolute( url, base ); + }, + removeActiveLinkClass: function( forceRemoval ) { + if ( !!$.mobile.activeClickedLink && + ( !$.mobile.activeClickedLink.closest( "." + $.mobile.activePageClass ).length || + forceRemoval ) ) { + + $.mobile.activeClickedLink.removeClass( $.mobile.activeBtnClass ); + } + $.mobile.activeClickedLink = null; + }, + + // DEPRECATED in 1.4 + // Find the closest parent with a theme class on it. Note that + // we are not using $.fn.closest() on purpose here because this + // method gets called quite a bit and we need it to be as fast + // as possible. + getInheritedTheme: function( el, defaultTheme ) { + var e = el[ 0 ], + ltr = "", + re = /ui-(bar|body|overlay)-([a-z])\b/, + c, m; + while ( e ) { + c = e.className || ""; + if ( c && ( m = re.exec( c ) ) && ( ltr = m[ 2 ] ) ) { + // We found a parent with a theme class + // on it so bail from this loop. + break; + } + + e = e.parentNode; + } + // Return the theme letter we found, if none, return the + // specified default. + return ltr || defaultTheme || "a"; + }, + + enhanceable: function( elements ) { + return this.haveParents( elements, "enhance" ); + }, + + hijackable: function( elements ) { + return this.haveParents( elements, "ajax" ); + }, + + haveParents: function( elements, attr ) { + if ( !$.mobile.ignoreContentEnabled ) { + return elements; + } + + var count = elements.length, + $newSet = $(), + e, $element, excluded, + i, c; + + for ( i = 0; i < count; i++ ) { + $element = elements.eq( i ); + excluded = false; + e = elements[ i ]; + + while ( e ) { + c = e.getAttribute ? e.getAttribute( "data-" + $.mobile.ns + attr ) : ""; + + if ( c === "false" ) { + excluded = true; + break; + } + + e = e.parentNode; + } + + if ( !excluded ) { + $newSet = $newSet.add( $element ); + } + } + + return $newSet; + }, + + getScreenHeight: function() { + // 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 || $.mobile.window.height(); + }, + + //simply set the active page's minimum height to screen height, depending on orientation + resetActivePageHeight: function( height ) { + var page = $( "." + $.mobile.activePageClass ), + pageHeight = page.height(), + pageOuterHeight = page.outerHeight( true ); + + height = ( typeof height === "number" ) ? height : $.mobile.getScreenHeight(); + + page.css( "min-height", height - ( pageOuterHeight - pageHeight ) ); + }, + + loading: function() { + // If this is the first call to this function, instantiate a loader widget + var loader = this.loading._widget || $( $.mobile.loader.prototype.defaultHtml ).loader(), + + // Call the appropriate method on the loader + returnValue = loader.loader.apply( loader, arguments ); + + // Make sure the loader is retained for future calls to this function. + this.loading._widget = loader; + + return returnValue; + } + }); + + $.addDependents = function( elem, newDependents ) { + var $elem = $( elem ), + dependents = $elem.jqmData( "dependents" ) || $(); + + $elem.jqmData( "dependents", $( dependents ).add( newDependents ) ); + }; + + // plugins + $.fn.extend({ + removeWithDependents: function() { + $.removeWithDependents( this ); + }, + + // Enhance child elements + enhanceWithin: function() { + var index, + widgetElements = {}, + keepNative = $.mobile.page.prototype.keepNativeSelector(), + that = this; + + // Add no js class to elements + if ( $.mobile.nojs ) { + $.mobile.nojs( this ); + } + + // Bind links for ajax nav + if ( $.mobile.links ) { + $.mobile.links( this ); + } + + // Degrade inputs for styleing + if ( $.mobile.degradeInputsWithin ) { + $.mobile.degradeInputsWithin( this ); + } + + // Run buttonmarkup + if ( $.fn.buttonMarkup ) { + this.find( $.fn.buttonMarkup.initSelector ).not( keepNative ) + .jqmEnhanceable().buttonMarkup(); + } + + // Add classes for fieldContain + if ( $.fn.fieldcontain ) { + this.find( ":jqmData(role='fieldcontain')" ).not( keepNative ) + .jqmEnhanceable().fieldcontain(); + } + + // Enhance widgets + $.each( $.mobile.widgets, function( name, constructor ) { + + // If initSelector not false find elements + if ( constructor.initSelector ) { + + // Filter elements that should not be enhanced based on parents + var elements = $.mobile.enhanceable( that.find( constructor.initSelector ) ); + + // If any matching elements remain filter ones with keepNativeSelector + if ( elements.length > 0 ) { + + // $.mobile.page.prototype.keepNativeSelector is deprecated this is just for backcompat + // Switch to $.mobile.keepNative in 1.5 which is just a value not a function + elements = elements.not( keepNative ); + } + + // Enhance whatever is left + if ( elements.length > 0 ) { + widgetElements[ constructor.prototype.widgetName ] = elements; + } + } + }); + + for ( index in widgetElements ) { + widgetElements[ index ][ index ](); + } + + return this; + }, + + addDependents: function( newDependents ) { + $.addDependents( this, newDependents ); + }, + + // note that this helper doesn't attempt to handle the callback + // or setting of an html element's text, its only purpose is + // to return the html encoded version of the text in all cases. (thus the name) + getEncodedText: function() { + return $( "<a>" ).text( this.text() ).html(); + }, + + // fluent helper function for the mobile namespaced equivalent + jqmEnhanceable: function() { + return $.mobile.enhanceable( this ); + }, + + jqmHijackable: function() { + return $.mobile.hijackable( this ); + } + }); + + $.removeWithDependents = function( nativeElement ) { + var element = $( nativeElement ); + + ( element.jqmData( "dependents" ) || $() ).remove(); + element.remove(); + }; + $.addDependents = function( nativeElement, newDependents ) { + var element = $( nativeElement ), + dependents = element.jqmData( "dependents" ) || $(); + + element.jqmData( "dependents", $( dependents ).add( newDependents ) ); + }; + + $.find.matches = function( expr, set ) { + return $.find( expr, null, null, set ); + }; + + $.find.matchesSelector = function( node, expr ) { + return $.find( expr, null, null, [ node ] ).length > 0; + }; + +})( jQuery, this ); + + +/*! + * jQuery UI Widget c0ab71056b936627e8a7821f03c044aec6280a40N + * http://jqueryui.com + * + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/jQuery.widget/ + */ +(function( $, undefined ) { + +var uuid = 0, + slice = Array.prototype.slice, + _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 ); +}; + +$.widget = function( name, base, prototype ) { + var fullName, existingConstructor, constructor, basePrototype, + // proxiedPrototype allows the provided prototype to remain unmodified + // so that it can be used as a mixin for multiple widgets (#8876) + proxiedPrototype = {}, + namespace = name.split( "." )[ 0 ]; + + name = name.split( "." )[ 1 ]; + fullName = namespace + "-" + name; + + if ( !prototype ) { + prototype = base; + base = $.Widget; + } + + // create selector for plugin + $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { + return !!$.data( elem, fullName ); + }; + + $[ namespace ] = $[ namespace ] || {}; + existingConstructor = $[ namespace ][ name ]; + constructor = $[ namespace ][ name ] = function( options, element ) { + // allow instantiation without "new" keyword + if ( !this._createWidget ) { + return new constructor( options, element ); + } + + // allow instantiation without initializing for simple inheritance + // must use "new" keyword (the code above always passes args) + if ( arguments.length ) { + this._createWidget( options, element ); + } + }; + // extend with the existing constructor to carry over any static properties + $.extend( constructor, existingConstructor, { + version: prototype.version, + // copy the object used to create the prototype in case we need to + // redefine the widget later + _proto: $.extend( {}, prototype ), + // track widgets that inherit from this widget in case this widget is + // redefined after a widget inherits from it + _childConstructors: [] + }); + + 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 + basePrototype.options = $.widget.extend( {}, basePrototype.options ); + $.each( prototype, function( prop, value ) { + if ( !$.isFunction( value ) ) { + proxiedPrototype[ prop ] = value; + return; + } + proxiedPrototype[ prop ] = (function() { + var _super = function() { + return base.prototype[ prop ].apply( this, arguments ); + }, + _superApply = function( args ) { + return base.prototype[ prop ].apply( this, args ); + }; + return function() { + var __super = this._super, + __superApply = this._superApply, + returnValue; + + this._super = _super; + this._superApply = _superApply; + + returnValue = value.apply( this, arguments ); + + this._super = __super; + this._superApply = __superApply; + + return returnValue; + }; + })(); + }); + constructor.prototype = $.widget.extend( basePrototype, { + // TODO: remove support for widgetEventPrefix + // always use the name + a colon as the prefix, e.g., draggable:start + // don't prefix for widgets that aren't DOM-based + widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name + }, proxiedPrototype, { + constructor: constructor, + namespace: namespace, + widgetName: name, + widgetFullName: fullName + }); + + // If this widget is being redefined then we need to find all widgets that + // are inheriting from it and redefine all of them so that they inherit from + // the new version of this widget. We're essentially trying to replace one + // level in the prototype chain. + if ( existingConstructor ) { + $.each( existingConstructor._childConstructors, function( i, child ) { + var childPrototype = child.prototype; + + // redefine the child widget using the same prototype that was + // originally used, but inherit from the new version of the base + $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); + }); + // remove the list of existing child constructors from the old constructor + // so the old child constructors can be garbage collected + delete existingConstructor._childConstructors; + } else { + base._childConstructors.push( constructor ); + } + + $.widget.bridge( name, constructor ); + + return constructor; +}; + +$.widget.extend = function( target ) { + var input = slice.call( arguments, 1 ), + inputIndex = 0, + inputLength = input.length, + key, + value; + for ( ; inputIndex < inputLength; inputIndex++ ) { + for ( key in input[ inputIndex ] ) { + value = input[ inputIndex ][ key ]; + if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { + // Clone objects + if ( $.isPlainObject( value ) ) { + target[ key ] = $.isPlainObject( target[ key ] ) ? + $.widget.extend( {}, target[ key ], value ) : + // Don't extend strings, arrays, etc. with objects + $.widget.extend( {}, value ); + // Copy everything else by reference + } else { + target[ key ] = value; + } + } + } + } + return target; +}; + +$.widget.bridge = function( name, object ) { + var fullName = object.prototype.widgetFullName || name; + $.fn[ name ] = function( options ) { + var isMethodCall = typeof options === "string", + args = slice.call( arguments, 1 ), + returnValue = this; + + // allow multiple hashes to be passed on init + options = !isMethodCall && args.length ? + $.widget.extend.apply( null, [ options ].concat(args) ) : + options; + + if ( isMethodCall ) { + this.each(function() { + var methodValue, + instance = $.data( this, fullName ); + if ( options === "instance" ) { + returnValue = instance; + return false; + } + if ( !instance ) { + return $.error( "cannot call methods on " + name + " prior to initialization; " + + "attempted to call method '" + options + "'" ); + } + if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { + return $.error( "no such method '" + options + "' for " + name + " widget instance" ); + } + methodValue = instance[ options ].apply( instance, args ); + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue && methodValue.jquery ? + returnValue.pushStack( methodValue.get() ) : + methodValue; + return false; + } + }); + } else { + this.each(function() { + var instance = $.data( this, fullName ); + if ( instance ) { + instance.option( options || {} )._init(); + } else { + $.data( this, fullName, new object( options, this ) ); + } + }); + } + + return returnValue; + }; +}; + +$.Widget = function( /* options, element */ ) {}; +$.Widget._childConstructors = []; + +$.Widget.prototype = { + widgetName: "widget", + widgetEventPrefix: "", + defaultElement: "<div>", + options: { + disabled: false, + + // callbacks + create: null + }, + _createWidget: function( options, element ) { + element = $( element || this.defaultElement || this )[ 0 ]; + this.element = $( element ); + this.uuid = uuid++; + this.eventNamespace = "." + this.widgetName + this.uuid; + this.options = $.widget.extend( {}, + this.options, + this._getCreateOptions(), + options ); + + this.bindings = $(); + this.hoverable = $(); + this.focusable = $(); + + if ( element !== this ) { + $.data( element, this.widgetFullName, this ); + this._on( true, this.element, { + remove: function( event ) { + if ( event.target === element ) { + this.destroy(); + } + } + }); + this.document = $( element.style ? + // element within the document + element.ownerDocument : + // element is window or document + element.document || element ); + this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); + } + + this._create(); + this._trigger( "create", null, this._getCreateEventData() ); + this._init(); + }, + _getCreateOptions: $.noop, + _getCreateEventData: $.noop, + _create: $.noop, + _init: $.noop, + + destroy: function() { + this._destroy(); + // we can probably remove the unbind calls in 2.0 + // all event bindings should go through this._on() + this.element + .unbind( this.eventNamespace ) + .removeData( this.widgetFullName ) + // support: jquery <1.6.3 + // http://bugs.jquery.com/ticket/9413 + .removeData( $.camelCase( this.widgetFullName ) ); + this.widget() + .unbind( this.eventNamespace ) + .removeAttr( "aria-disabled" ) + .removeClass( + this.widgetFullName + "-disabled " + + "ui-state-disabled" ); + + // clean up events and states + this.bindings.unbind( this.eventNamespace ); + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); + }, + _destroy: $.noop, + + widget: function() { + return this.element; + }, + + option: function( key, value ) { + var options = key, + parts, + curOption, + i; + + if ( arguments.length === 0 ) { + // don't return a reference to the internal hash + return $.widget.extend( {}, this.options ); + } + + if ( typeof key === "string" ) { + // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } + options = {}; + parts = key.split( "." ); + key = parts.shift(); + if ( parts.length ) { + curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); + for ( i = 0; i < parts.length - 1; i++ ) { + curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; + curOption = curOption[ parts[ i ] ]; + } + key = parts.pop(); + if ( value === undefined ) { + return curOption[ key ] === undefined ? null : curOption[ key ]; + } + curOption[ key ] = value; + } else { + if ( value === undefined ) { + return this.options[ key ] === undefined ? null : this.options[ key ]; + } + options[ key ] = value; + } + } + + this._setOptions( options ); + + return this; + }, + _setOptions: function( options ) { + var key; + + for ( key in options ) { + this._setOption( key, options[ key ] ); + } + + return this; + }, + _setOption: function( key, value ) { + this.options[ key ] = value; + + if ( key === "disabled" ) { + this.widget() + .toggleClass( this.widgetFullName + "-disabled", !!value ); + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); + } + + return this; + }, + + enable: function() { + return this._setOptions({ disabled: false }); + }, + disable: function() { + return this._setOptions({ disabled: true }); + }, + + _on: function( suppressDisabledCheck, element, handlers ) { + var delegateElement, + instance = this; + + // no suppressDisabledCheck flag, shuffle arguments + if ( typeof suppressDisabledCheck !== "boolean" ) { + handlers = element; + element = suppressDisabledCheck; + suppressDisabledCheck = false; + } + + // no element argument, shuffle and use this.element + if ( !handlers ) { + handlers = element; + element = this.element; + delegateElement = this.widget(); + } else { + // accept selectors, DOM elements + element = delegateElement = $( element ); + this.bindings = this.bindings.add( element ); + } + + $.each( handlers, function( event, handler ) { + function handlerProxy() { + // allow widgets to customize the disabled handling + // - disabled as an array instead of boolean + // - disabled class as method for disabling individual parts + if ( !suppressDisabledCheck && + ( instance.options.disabled === true || + $( this ).hasClass( "ui-state-disabled" ) ) ) { + return; + } + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + + // copy the guid so direct unbinding works + if ( typeof handler !== "string" ) { + handlerProxy.guid = handler.guid = + handler.guid || handlerProxy.guid || $.guid++; + } + + var match = event.match( /^(\w+)\s*(.*)$/ ), + eventName = match[1] + instance.eventNamespace, + selector = match[2]; + if ( selector ) { + delegateElement.delegate( selector, eventName, handlerProxy ); + } else { + element.bind( eventName, handlerProxy ); + } + }); + }, + + _off: function( element, eventName ) { + eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; + element.unbind( eventName ).undelegate( eventName ); + }, + + _delay: function( handler, delay ) { + function handlerProxy() { + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + var instance = this; + return setTimeout( handlerProxy, delay || 0 ); + }, + + _hoverable: function( element ) { + this.hoverable = this.hoverable.add( element ); + this._on( element, { + mouseenter: function( event ) { + $( event.currentTarget ).addClass( "ui-state-hover" ); + }, + mouseleave: function( event ) { + $( event.currentTarget ).removeClass( "ui-state-hover" ); + } + }); + }, + + _focusable: function( element ) { + this.focusable = this.focusable.add( element ); + this._on( element, { + focusin: function( event ) { + $( event.currentTarget ).addClass( "ui-state-focus" ); + }, + focusout: function( event ) { + $( event.currentTarget ).removeClass( "ui-state-focus" ); + } + }); + }, + + _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.apply( this.element[0], [ event ].concat( data ) ) === false || + event.isDefaultPrevented() ); + } +}; + +$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { + $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { + if ( typeof options === "string" ) { + options = { effect: options }; + } + var hasOptions, + effectName = !options ? + method : + options === true || typeof options === "number" ? + defaultEffect : + options.effect || defaultEffect; + options = options || {}; + if ( typeof options === "number" ) { + options = { duration: options }; + } + hasOptions = !$.isEmptyObject( options ); + options.complete = callback; + if ( options.delay ) { + element.delay( options.delay ); + } + if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { + element[ method ]( options ); + } else if ( effectName !== method && element[ effectName ] ) { + element[ effectName ]( options.duration, options.easing, callback ); + } else { + element.queue(function( next ) { + $( this )[ method ](); + if ( callback ) { + callback.call( element[ 0 ] ); + } + next(); + }); + } + }; +}); + +})( jQuery ); + +(function( $, undefined ) { + +var rcapitals = /[A-Z]/g, + replaceFunction = function( c ) { + return "-" + c.toLowerCase(); + }; + +$.extend( $.Widget.prototype, { + _getCreateOptions: function() { + var option, value, + elem = this.element[ 0 ], + options = {}; + + // + if ( !$.mobile.getAttribute( elem, "defaults" ) ) { + for ( option in this.options ) { + value = $.mobile.getAttribute( elem, option.replace( rcapitals, replaceFunction ) ); + + if ( value != null ) { + options[ option ] = value; + } + } + } + + return options; + } +}); + +//TODO: Remove in 1.5 for backcompat only +$.mobile.widget = $.Widget; + +})( jQuery ); + + +(function( $ ) { + // TODO move loader class down into the widget settings + var loaderClass = "ui-loader", $html = $( "html" ); + + $.widget( "mobile.loader", { + // NOTE if the global config settings are defined they will override these + // options + options: { + // the theme for the loading message + theme: "a", + + // whether the text in the loading message is shown + textVisible: false, + + // custom html for the inner content of the loading message + html: "", + + // the text to be displayed when the popup is shown + text: "loading" + }, + + defaultHtml: "<div class='" + loaderClass + "'>" + + "<span class='ui-icon-loading'></span>" + + "<h1></h1>" + + "</div>", + + // For non-fixed supportin browsers. Position at y center (if scrollTop supported), above the activeBtn (if defined), or just 100px from top + fakeFixLoader: function() { + var activeBtn = $( "." + $.mobile.activeBtnClass ).first(); + + this.element + .css({ + top: $.support.scrollTop && this.window.scrollTop() + this.window.height() / 2 || + activeBtn.length && activeBtn.offset().top || 100 + }); + }, + + // check position of loader to see if it appears to be "fixed" to center + // if not, use abs positioning + checkLoaderPosition: function() { + var offset = this.element.offset(), + scrollTop = this.window.scrollTop(), + screenHeight = $.mobile.getScreenHeight(); + + if ( offset.top < scrollTop || ( offset.top - scrollTop ) > screenHeight ) { + this.element.addClass( "ui-loader-fakefix" ); + this.fakeFixLoader(); + this.window + .unbind( "scroll", this.checkLoaderPosition ) + .bind( "scroll", $.proxy( this.fakeFixLoader, this ) ); + } + }, + + resetHtml: function() { + this.element.html( $( this.defaultHtml ).html() ); + }, + + // Turn on/off page loading message. Theme doubles as an object argument + // with the following shape: { theme: '', text: '', html: '', textVisible: '' } + // NOTE that the $.mobile.loading* settings and params past the first are deprecated + // TODO sweet jesus we need to break some of this out + show: function( theme, msgText, textonly ) { + var textVisible, message, loadSettings; + + this.resetHtml(); + + // use the prototype options so that people can set them globally at + // mobile init. Consistency, it's what's for dinner + if ( $.type( theme ) === "object" ) { + loadSettings = $.extend( {}, this.options, theme ); + + theme = loadSettings.theme; + } else { + loadSettings = this.options; + + // here we prefer the theme value passed as a string argument, then + // we prefer the global option because we can't use undefined default + // prototype options, then the prototype option + theme = theme || loadSettings.theme; + } + + // set the message text, prefer the param, then the settings object + // then loading message + message = msgText || ( loadSettings.text === false ? "" : loadSettings.text ); + + // prepare the dom + $html.addClass( "ui-loading" ); + + textVisible = loadSettings.textVisible; + + // add the proper css given the options (theme, text, etc) + // Force text visibility if the second argument was supplied, or + // if the text was explicitly set in the object args + this.element.attr("class", loaderClass + + " ui-corner-all ui-body-" + theme + + " ui-loader-" + ( textVisible || msgText || theme.text ? "verbose" : "default" ) + + ( loadSettings.textonly || textonly ? " ui-loader-textonly" : "" ) ); + + // TODO verify that jquery.fn.html is ok to use in both cases here + // this might be overly defensive in preventing unknowing xss + // if the html attribute is defined on the loading settings, use that + // otherwise use the fallbacks from above + if ( loadSettings.html ) { + this.element.html( loadSettings.html ); + } else { + this.element.find( "h1" ).text( message ); + } + + // attach the loader to the DOM + this.element.appendTo( $.mobile.pageContainer ); + + // check that the loader is visible + this.checkLoaderPosition(); + + // on scroll check the loader position + this.window.bind( "scroll", $.proxy( this.checkLoaderPosition, this ) ); + }, + + hide: function() { + $html.removeClass( "ui-loading" ); + + if ( this.options.text ) { + this.element.removeClass( "ui-loader-fakefix" ); + } + + $.mobile.window.unbind( "scroll", this.fakeFixLoader ); + $.mobile.window.unbind( "scroll", this.checkLoaderPosition ); + } + }); + +})(jQuery, this); + + +// Script: jQuery hashchange event +// +// *Version: 1.3, Last updated: 7/21/2010* +// +// Project Home - http://benalman.com/projects/jquery-hashchange-plugin/ +// GitHub - http://github.com/cowboy/jquery-hashchange/ +// Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js +// (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (0.8kb gzipped) +// +// About: License +// +// Copyright (c) 2010 "Cowboy" Ben Alman, +// Dual licensed under the MIT and GPL licenses. +// http://benalman.com/about/license/ +// +// About: Examples +// +// These working examples, complete with fully commented code, illustrate a few +// ways in which this plugin can be used. +// +// hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/ +// document.domain - http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/ +// +// About: Support and Testing +// +// Information about what version or versions of jQuery this plugin has been +// tested with, what browsers it has been tested in, and where the unit tests +// reside (so you can test it yourself). +// +// jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2 +// Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5, +// Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5. +// Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/ +// +// About: Known issues +// +// While this jQuery hashchange event implementation is quite stable and +// robust, there are a few unfortunate browser bugs surrounding expected +// hashchange event-based behaviors, independent of any JavaScript +// window.onhashchange abstraction. See the following examples for more +// information: +// +// Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/ +// Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/ +// WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/ +// Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/ +// +// Also note that should a browser natively support the window.onhashchange +// event, but not report that it does, the fallback polling loop will be used. +// +// About: Release History +// +// 1.3 - (7/21/2010) Reorganized IE6/7 Iframe code to make it more +// "removable" for mobile-only development. Added IE6/7 document.title +// support. Attempted to make Iframe as hidden as possible by using +// techniques from http://www.paciellogroup.com/blog/?p=604. Added +// support for the "shortcut" format $(window).hashchange( fn ) and +// $(window).hashchange() like jQuery provides for built-in events. +// Renamed jQuery.hashchangeDelay to <jQuery.fn.hashchange.delay> and +// lowered its default value to 50. Added <jQuery.fn.hashchange.domain> +// and <jQuery.fn.hashchange.src> properties plus document-domain.html +// file to address access denied issues when setting document.domain in +// IE6/7. +// 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin +// from a page on another domain would cause an error in Safari 4. Also, +// IE6/7 Iframe is now inserted after the body (this actually works), +// which prevents the page from scrolling when the event is first bound. +// Event can also now be bound before DOM ready, but it won't be usable +// before then in IE6/7. +// 1.1 - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug +// where browser version is incorrectly reported as 8.0, despite +// inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag. +// 1.0 - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special +// window.onhashchange functionality into a separate plugin for users +// who want just the basic event & back button support, without all the +// extra awesomeness that BBQ provides. This plugin will be included as +// part of jQuery BBQ, but also be available separately. + +(function( $, window, undefined ) { + // Reused string. + var str_hashchange = 'hashchange', + + // Method / object references. + doc = document, + fake_onhashchange, + special = $.event.special, + + // Does the browser support window.onhashchange? Note that IE8 running in + // IE7 compatibility mode reports true for 'onhashchange' in window, even + // though the event isn't supported, so also test document.documentMode. + doc_mode = doc.documentMode, + supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 ); + + // Get location.hash (or what you'd expect location.hash to be) sans any + // leading #. Thanks for making this necessary, Firefox! + function get_fragment( url ) { + url = url || location.href; + return '#' + url.replace( /^[^#]*#?(.*)$/, '$1' ); + }; + + // Method: jQuery.fn.hashchange + // + // Bind a handler to the window.onhashchange event or trigger all bound + // window.onhashchange event handlers. This behavior is consistent with + // jQuery's built-in event handlers. + // + // Usage: + // + // > jQuery(window).hashchange( [ handler ] ); + // + // Arguments: + // + // handler - (Function) Optional handler to be bound to the hashchange + // event. This is a "shortcut" for the more verbose form: + // jQuery(window).bind( 'hashchange', handler ). If handler is omitted, + // all bound window.onhashchange event handlers will be triggered. This + // is a shortcut for the more verbose + // jQuery(window).trigger( 'hashchange' ). These forms are described in + // the <hashchange event> section. + // + // Returns: + // + // (jQuery) The initial jQuery collection of elements. + + // Allow the "shortcut" format $(elem).hashchange( fn ) for binding and + // $(elem).hashchange() for triggering, like jQuery does for built-in events. + $.fn[ str_hashchange ] = function( fn ) { + return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange ); + }; + + // Property: jQuery.fn.hashchange.delay + // + // The numeric interval (in milliseconds) at which the <hashchange event> + // polling loop executes. Defaults to 50. + + // Property: jQuery.fn.hashchange.domain + // + // If you're setting document.domain in your JavaScript, and you want hash + // history to work in IE6/7, not only must this property be set, but you must + // also set document.domain BEFORE jQuery is loaded into the page. This + // property is only applicable if you are supporting IE6/7 (or IE8 operating + // in "IE7 compatibility" mode). + // + // In addition, the <jQuery.fn.hashchange.src> property must be set to the + // path of the included "document-domain.html" file, which can be renamed or + // modified if necessary (note that the document.domain specified must be the + // same in both your main JavaScript as well as in this file). + // + // Usage: + // + // jQuery.fn.hashchange.domain = document.domain; + + // Property: jQuery.fn.hashchange.src + // + // If, for some reason, you need to specify an Iframe src file (for example, + // when setting document.domain as in <jQuery.fn.hashchange.domain>), you can + // do so using this property. Note that when using this property, history + // won't be recorded in IE6/7 until the Iframe src file loads. This property + // is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7 + // compatibility" mode). + // + // Usage: + // + // jQuery.fn.hashchange.src = 'path/to/file.html'; + + $.fn[ str_hashchange ].delay = 50; + /* + $.fn[ str_hashchange ].domain = null; + $.fn[ str_hashchange ].src = null; + */ + + // Event: hashchange event + // + // Fired when location.hash changes. In browsers that support it, the native + // HTML5 window.onhashchange event is used, otherwise a polling loop is + // initialized, running every <jQuery.fn.hashchange.delay> milliseconds to + // see if the hash has changed. In IE6/7 (and IE8 operating in "IE7 + // compatibility" mode), a hidden Iframe is created to allow the back button + // and hash-based history to work. + // + // Usage as described in <jQuery.fn.hashchange>: + // + // > // Bind an event handler. + // > jQuery(window).hashchange( function(e) { + // > var hash = location.hash; + // > ... + // > }); + // > + // > // Manually trigger the event handler. + // > jQuery(window).hashchange(); + // + // A more verbose usage that allows for event namespacing: + // + // > // Bind an event handler. + // > jQuery(window).bind( 'hashchange', function(e) { + // > var hash = location.hash; + // > ... + // > }); + // > + // > // Manually trigger the event handler. + // > jQuery(window).trigger( 'hashchange' ); + // + // Additional Notes: + // + // * The polling loop and Iframe are not created until at least one handler + // is actually bound to the 'hashchange' event. + // * If you need the bound handler(s) to execute immediately, in cases where + // a location.hash exists on page load, via bookmark or page refresh for + // example, use jQuery(window).hashchange() or the more verbose + // jQuery(window).trigger( 'hashchange' ). + // * The event can be bound before DOM ready, but since it won't be usable + // before then in IE6/7 (due to the necessary Iframe), recommended usage is + // to bind it inside a DOM ready handler. + + // Override existing $.event.special.hashchange methods (allowing this plugin + // to be defined after jQuery BBQ in BBQ's source code). + special[ str_hashchange ] = $.extend( special[ str_hashchange ], { + + // Called only when the first 'hashchange' event is bound to window. + setup: function() { + // If window.onhashchange is supported natively, there's nothing to do.. + if ( supports_onhashchange ) { return false; } + + // Otherwise, we need to create our own. And we don't want to call this + // until the user binds to the event, just in case they never do, since it + // will create a polling loop and possibly even a hidden Iframe. + $( fake_onhashchange.start ); + }, + + // Called only when the last 'hashchange' event is unbound from window. + teardown: function() { + // If window.onhashchange is supported natively, there's nothing to do.. + if ( supports_onhashchange ) { return false; } + + // Otherwise, we need to stop ours (if possible). + $( fake_onhashchange.stop ); + } + + }); + + // fake_onhashchange does all the work of triggering the window.onhashchange + // event for browsers that don't natively support it, including creating a + // polling loop to watch for hash changes and in IE 6/7 creating a hidden + // Iframe to enable back and forward. + fake_onhashchange = (function() { + var self = {}, + timeout_id, + + // Remember the initial hash so it doesn't get triggered immediately. + last_hash = get_fragment(), + + fn_retval = function( val ) { return val; }, + history_set = fn_retval, + history_get = fn_retval; + + // Start the polling loop. + self.start = function() { + timeout_id || poll(); + }; + + // Stop the polling loop. + self.stop = function() { + timeout_id && clearTimeout( timeout_id ); + timeout_id = undefined; + }; + + // This polling loop checks every $.fn.hashchange.delay milliseconds to see + // if location.hash has changed, and triggers the 'hashchange' event on + // window when necessary. + function poll() { + var hash = get_fragment(), + history_hash = history_get( last_hash ); + + if ( hash !== last_hash ) { + history_set( last_hash = hash, history_hash ); + + $(window).trigger( str_hashchange ); + + } else if ( history_hash !== last_hash ) { + location.href = location.href.replace( /#.*/, '' ) + history_hash; + } + + timeout_id = setTimeout( poll, $.fn[ str_hashchange ].delay ); + }; + + // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + // vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv + // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + window.attachEvent && !window.addEventListener && !supports_onhashchange && (function() { + // Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8 + // when running in "IE7 compatibility" mode. + + var iframe, + iframe_src; + + // When the event is bound and polling starts in IE 6/7, create a hidden + // Iframe for history handling. + self.start = function() { + if ( !iframe ) { + iframe_src = $.fn[ str_hashchange ].src; + iframe_src = iframe_src && iframe_src + get_fragment(); + + // Create hidden Iframe. Attempt to make Iframe as hidden as possible + // by using techniques from http://www.paciellogroup.com/blog/?p=604. + iframe = $('<iframe tabindex="-1" title="empty"/>').hide() + + // When Iframe has completely loaded, initialize the history and + // start polling. + .one( 'load', function() { + iframe_src || history_set( get_fragment() ); + poll(); + }) + + // Load Iframe src if specified, otherwise nothing. + .attr( 'src', iframe_src || 'javascript:0' ) + + // Append Iframe after the end of the body to prevent unnecessary + // initial page scrolling (yes, this works). + .insertAfter( 'body' )[0].contentWindow; + + // Whenever `document.title` changes, update the Iframe's title to + // prettify the back/next history menu entries. Since IE sometimes + // errors with "Unspecified error" the very first time this is set + // (yes, very useful) wrap this with a try/catch block. + doc.onpropertychange = function() { + try { + if ( event.propertyName === 'title' ) { + iframe.document.title = doc.title; + } + } catch(e) {} + }; + + } + }; + + // Override the "stop" method since an IE6/7 Iframe was created. Even + // if there are no longer any bound event handlers, the polling loop + // is still necessary for back/next to work at all! + self.stop = fn_retval; + + // Get history by looking at the hidden Iframe's location.hash. + history_get = function() { + return get_fragment( iframe.location.href ); + }; + + // Set a new history item by opening and then closing the Iframe + // document, *then* setting its location.hash. If document.domain has + // been set, update that as well. + history_set = function( hash, history_hash ) { + var iframe_doc = iframe.document, + domain = $.fn[ str_hashchange ].domain; + + if ( hash !== history_hash ) { + // Update Iframe with any initial `document.title` that might be set. + iframe_doc.title = doc.title; + + // Opening the Iframe's document after it has been closed is what + // actually adds a history entry. + iframe_doc.open(); + + // Set document.domain for the Iframe document as well, if necessary. + domain && iframe_doc.write( '<script>document.domain="' + domain + '"<\/script>' ); + + iframe_doc.close(); + + // Update the Iframe's hash, for great justice. + iframe.location.hash = hash; + } + }; + + })(); + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // ^^^^^^^^^^^^^^^^^^^ REMOVE IF NOT SUPPORTING IE6/7/8 ^^^^^^^^^^^^^^^^^^^ + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + return self; + })(); + +})(jQuery,this); + +(function( $, undefined ) { + + /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ + window.matchMedia = window.matchMedia || (function( doc, undefined ) { + + + + var bool, + docElem = doc.documentElement, + refNode = docElem.firstElementChild || docElem.firstChild, + // fakeBody required for <FF4 when executed in <head> + fakeBody = doc.createElement( "body" ), + div = doc.createElement( "div" ); + + div.id = "mq-test-1"; + div.style.cssText = "position:absolute;top:-100em"; + fakeBody.style.background = "none"; + fakeBody.appendChild(div); + + return function(q){ + + div.innerHTML = "­<style media=\"" + q + "\"> #mq-test-1 { width: 42px; }</style>"; + + docElem.insertBefore( fakeBody, refNode ); + bool = div.offsetWidth === 42; + docElem.removeChild( fakeBody ); + + return { + matches: bool, + media: q + }; + + }; + + }( document )); + + // $.mobile.media uses matchMedia to return a boolean. + $.mobile.media = function( q ) { + return window.matchMedia( q ).matches; + }; + +})(jQuery); + + (function( $, undefined ) { + var support = { + touch: "ontouchend" in document + }; + + $.mobile.support = $.mobile.support || {}; + $.extend( $.support, support ); + $.extend( $.mobile.support, support ); + }( jQuery )); + + (function( $, undefined ) { + $.extend( $.support, { + orientation: "orientation" in window && "onorientationchange" in window + }); + }( jQuery )); + +(function( $, undefined ) { + +// thx Modernizr +function propExists( prop ) { + var uc_prop = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ), + props = ( prop + " " + vendors.join( uc_prop + " " ) + uc_prop ).split( " " ), + v; + + for ( v in props ) { + if ( fbCSS[ props[ v ] ] !== undefined ) { + return true; + } + } +} + +var fakeBody = $( "<body>" ).prependTo( "html" ), + fbCSS = fakeBody[ 0 ].style, + vendors = [ "Webkit", "Moz", "O" ], + webos = "palmGetResource" in window, //only used to rule out scrollTop + opera = window.opera, + operamini = window.operamini && ({}).toString.call( window.operamini ) === "[object OperaMini]", + bb = window.blackberry && !propExists( "-webkit-transform" ), //only used to rule out box shadow, as it's filled opaque on BB 5 and lower + nokiaLTE7_3; + +function validStyle( prop, value, check_vend ) { + var div = document.createElement( "div" ), + uc = function( txt ) { + return txt.charAt( 0 ).toUpperCase() + txt.substr( 1 ); + }, + vend_pref = function( vend ) { + if ( vend === "" ) { + return ""; + } else { + return "-" + vend.charAt( 0 ).toLowerCase() + vend.substr( 1 ) + "-"; + } + }, + check_style = function( vend ) { + var vend_prop = vend_pref( vend ) + prop + ": " + value + ";", + uc_vend = uc( vend ), + propStyle = uc_vend + ( uc_vend === "" ? prop : uc( prop ) ); + + div.setAttribute( "style", vend_prop ); + + if ( !!div.style[ propStyle ] ) { + ret = true; + } + }, + check_vends = check_vend ? check_vend : vendors, + i, ret; + + for( i = 0; i < check_vends.length; i++ ) { + check_style( check_vends[i] ); + } + return !!ret; +} + +// inline SVG support test +function inlineSVG() { + // Thanks Modernizr & Erik Dahlstrom + var w = window, + svg = !!w.document.createElementNS && !!w.document.createElementNS( "http://www.w3.org/2000/svg", "svg" ).createSVGRect && !( w.opera && navigator.userAgent.indexOf( "Chrome" ) === -1 ), + support = function( data ) { + if ( !( data && svg ) ) { + $( "html" ).addClass( "ui-nosvg" ); + } + }, + img = new w.Image(); + + img.onerror = function() { + support( false ); + }; + img.onload = function() { + support( img.width === 1 && img.height === 1 ); + }; + img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="; +} + +function transform3dTest() { + var mqProp = "transform-3d", + // Because the `translate3d` test below throws false positives in Android: + ret = $.mobile.media( "(-" + vendors.join( "-" + mqProp + "),(-" ) + "-" + mqProp + "),(" + mqProp + ")" ), + el, transforms, t; + + if ( ret ) { + return !!ret; + } + + el = document.createElement( "div" ); + transforms = { + // Weâre omitting Opera for the time being; MS uses unprefixed. + "MozTransform": "-moz-transform", + "transform": "transform" + }; + + fakeBody.append( el ); + + for ( t in transforms ) { + if ( el.style[ t ] !== undefined ) { + el.style[ t ] = "translate3d( 100px, 1px, 1px )"; + ret = window.getComputedStyle( el ).getPropertyValue( transforms[ t ] ); + } + } + return ( !!ret && ret !== "none" ); +} + +// Test for dynamic-updating base tag support ( allows us to avoid href,src attr rewriting ) +function baseTagTest() { + var fauxBase = location.protocol + "//" + location.host + location.pathname + "ui-dir/", + base = $( "head base" ), + fauxEle = null, + href = "", + link, rebase; + + if ( !base.length ) { + base = fauxEle = $( "<base>", { "href": fauxBase }).appendTo( "head" ); + } else { + href = base.attr( "href" ); + } + + link = $( "<a href='testurl' />" ).prependTo( fakeBody ); + rebase = link[ 0 ].href; + base[ 0 ].href = href || location.pathname; + + if ( fauxEle ) { + fauxEle.remove(); + } + return rebase.indexOf( fauxBase ) === 0; +} + +// Thanks Modernizr +function cssPointerEventsTest() { + var element = document.createElement( "x" ), + documentElement = document.documentElement, + getComputedStyle = window.getComputedStyle, + supports; + + if ( !( "pointerEvents" in element.style ) ) { + return false; + } + + element.style.pointerEvents = "auto"; + element.style.pointerEvents = "x"; + documentElement.appendChild( element ); + supports = getComputedStyle && + getComputedStyle( element, "" ).pointerEvents === "auto"; + documentElement.removeChild( element ); + return !!supports; +} + +function boundingRect() { + var div = document.createElement( "div" ); + return typeof div.getBoundingClientRect !== "undefined"; +} + +// non-UA-based IE version check by James Padolsey, modified by jdalton - from http://gist.github.com/527683 +// allows for inclusion of IE 6+, including Windows Mobile 7 +$.extend( $.mobile, { browser: {} } ); +$.mobile.browser.oldIE = (function() { + var v = 3, + div = document.createElement( "div" ), + a = div.all || []; + + do { + div.innerHTML = "<!--[if gt IE " + ( ++v ) + "]><br><![endif]-->"; + } while( a[0] ); + + return v > 4 ? v : !v; +})(); + +function fixedPosition() { + var w = window, + ua = navigator.userAgent, + platform = navigator.platform, + // Rendering engine is Webkit, and capture major version + wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ ), + wkversion = !!wkmatch && wkmatch[ 1 ], + ffmatch = ua.match( /Fennec\/([0-9]+)/ ), + ffversion = !!ffmatch && ffmatch[ 1 ], + operammobilematch = ua.match( /Opera Mobi\/([0-9]+)/ ), + omversion = !!operammobilematch && operammobilematch[ 1 ]; + + if ( + // iOS 4.3 and older : Platform is iPhone/Pad/Touch and Webkit version is less than 534 (ios5) + ( ( platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1 || platform.indexOf( "iPod" ) > -1 ) && wkversion && wkversion < 534 ) || + // Opera Mini + ( w.operamini && ({}).toString.call( w.operamini ) === "[object OperaMini]" ) || + ( operammobilematch && omversion < 7458 ) || + //Android lte 2.1: Platform is Android and Webkit version is less than 533 (Android 2.2) + ( ua.indexOf( "Android" ) > -1 && wkversion && wkversion < 533 ) || + // Firefox Mobile before 6.0 - + ( ffversion && ffversion < 6 ) || + // WebOS less than 3 + ( "palmGetResource" in window && wkversion && wkversion < 534 ) || + // MeeGo + ( ua.indexOf( "MeeGo" ) > -1 && ua.indexOf( "NokiaBrowser/8.5.0" ) > -1 ) ) { + return false; + } + + return true; +} + +$.extend( $.support, { + cssTransitions: "WebKitTransitionEvent" in window || + validStyle( "transition", "height 100ms linear", [ "Webkit", "Moz", "" ] ) && + !$.mobile.browser.oldIE && !opera, + + // Note, Chrome for iOS has an extremely quirky implementation of popstate. + // We've chosen to take the shortest path to a bug fix here for issue #5426 + // See the following link for information about the regex chosen + // https://developers.google.com/chrome/mobile/docs/user-agent#chrome_for_ios_user-agent + pushState: "pushState" in history && + "replaceState" in history && + // When running inside a FF iframe, calling replaceState causes an error + !( window.navigator.userAgent.indexOf( "Firefox" ) >= 0 && window.top !== window ) && + ( window.navigator.userAgent.search(/CriOS/) === -1 ), + + mediaquery: $.mobile.media( "only all" ), + cssPseudoElement: !!propExists( "content" ), + touchOverflow: !!propExists( "overflowScrolling" ), + cssTransform3d: transform3dTest(), + cssAnimations: !!propExists( "animationName" ), + boxShadow: !!propExists( "boxShadow" ) && !bb, + fixedPosition: fixedPosition(), + scrollTop: ("pageXOffset" in window || + "scrollTop" in document.documentElement || + "scrollTop" in fakeBody[ 0 ]) && !webos && !operamini, + + dynamicBaseTag: baseTagTest(), + cssPointerEvents: cssPointerEventsTest(), + boundingRect: boundingRect(), + inlineSVG: inlineSVG +}); + +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 +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 && $.support.cssPseudoElement ) || $.mobile.browser.oldIE && $.mobile.browser.oldIE >= 8 ) && ( $.support.boundingRect || $.fn.jquery.match(/1\.[0-7+]\.[0-9+]?/) !== null ); +}; + +$.mobile.ajaxBlacklist = + // BlackBerry browsers, pre-webkit + window.blackberry && !window.WebKitPoint || + // Opera Mini + operamini || + // Symbian webkits pre 7.3 + nokiaLTE7_3; + +// Lastly, this workaround is the only way we've found so far to get pre 7.3 Symbian webkit devices +// to render the stylesheets when they're referenced before this script, as we'd recommend doing. +// This simply reappends the CSS in place, which for some reason makes it apply +if ( nokiaLTE7_3 ) { + $(function() { + $( "head link[rel='stylesheet']" ).attr( "rel", "alternate stylesheet" ).attr( "rel", "stylesheet" ); + }); +} + +// For ruling out shadows via css +if ( !$.support.boxShadow ) { + $( "html" ).addClass( "ui-noboxshadow" ); +} + +})( jQuery ); + + +(function( $, undefined ) { + var $win = $.mobile.window, self, + dummyFnToInitNavigate = function() { + }; + + $.event.special.beforenavigate = { + setup: function() { + $win.on( "navigate", dummyFnToInitNavigate ); + }, + + teardown: function() { + $win.off( "navigate", dummyFnToInitNavigate ); + } + }; + + $.event.special.navigate = self = { + bound: false, + + pushStateEnabled: true, + + originalEventName: undefined, + + // If pushstate support is present and push state support is defined to + // be true on the mobile namespace. + isPushStateEnabled: function() { + return $.support.pushState && + $.mobile.pushStateEnabled === true && + this.isHashChangeEnabled(); + }, + + // !! assumes mobile namespace is present + isHashChangeEnabled: function() { + return $.mobile.hashListeningEnabled === true; + }, + + // TODO a lot of duplication between popstate and hashchange + popstate: function( event ) { + var newEvent = new $.Event( "navigate" ), + beforeNavigate = new $.Event( "beforenavigate" ), + state = event.originalEvent.state || {}; + + beforeNavigate.originalEvent = event; + $win.trigger( beforeNavigate ); + + if ( beforeNavigate.isDefaultPrevented() ) { + return; + } + + if ( event.historyState ) { + $.extend(state, event.historyState); + } + + // Make sure the original event is tracked for the end + // user to inspect incase they want to do something special + newEvent.originalEvent = event; + + // NOTE we let the current stack unwind because any assignment to + // location.hash will stop the world and run this event handler. By + // doing this we create a similar behavior to hashchange on hash + // assignment + setTimeout(function() { + $win.trigger( newEvent, { + state: state + }); + }, 0); + }, + + hashchange: function( event /*, data */ ) { + var newEvent = new $.Event( "navigate" ), + beforeNavigate = new $.Event( "beforenavigate" ); + + beforeNavigate.originalEvent = event; + $win.trigger( beforeNavigate ); + + if ( beforeNavigate.isDefaultPrevented() ) { + return; + } + + // Make sure the original event is tracked for the end + // user to inspect incase they want to do something special + newEvent.originalEvent = event; + + // Trigger the hashchange with state provided by the user + // that altered the hash + $win.trigger( newEvent, { + // Users that want to fully normalize the two events + // will need to do history management down the stack and + // add the state to the event before this binding is fired + // TODO consider allowing for the explicit addition of callbacks + // to be fired before this value is set to avoid event timing issues + state: event.hashchangeState || {} + }); + }, + + // TODO We really only want to set this up once + // but I'm not clear if there's a beter way to achieve + // this with the jQuery special event structure + setup: function( /* data, namespaces */ ) { + if ( self.bound ) { + return; + } + + self.bound = true; + + if ( self.isPushStateEnabled() ) { + self.originalEventName = "popstate"; + $win.bind( "popstate.navigate", self.popstate ); + } else if ( self.isHashChangeEnabled() ) { + self.originalEventName = "hashchange"; + $win.bind( "hashchange.navigate", self.hashchange ); + } + } + }; +})( jQuery ); + + + +(function( $, undefined ) { + var path, $base, dialogHashKey = "&ui-state=dialog"; + + $.mobile.path = path = { + uiStateKey: "&ui-state", + + // This scary looking regular expression parses an absolute URL or its relative + // variants (protocol, site, document, query, and hash), into the various + // components (protocol, host, path, query, fragment, etc that make up the + // URL as well as some other commonly used sub-parts. When used with RegExp.exec() + // or String.match, it parses the URL into a results array that looks like this: + // + // [0]: http://jblas:password@...:8080/mail/inbox?msg=1234&type=unread#msg-content + // [1]: http://jblas:password@...:8080/mail/inbox?msg=1234&type=unread + // [2]: http://jblas:password@...:8080/mail/inbox + // [3]: http://jblas:password@...:8080 + // [4]: http: + // [5]: // + // [6]: jblas:[hidden email]:8080 + // [7]: jblas:password + // [8]: jblas + // [9]: password + // [10]: mycompany.com:8080 + // [11]: mycompany.com + // [12]: 8080 + // [13]: /mail/inbox + // [14]: /mail/ + // [15]: inbox + // [16]: ?msg=1234&type=unread + // [17]: #msg-content + // + urlParseRE: /^\s*(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/, + + // Abstraction to address xss (Issue #4787) by removing the authority in + // browsers that auto decode it. All references to location.href should be + // replaced with a call to this method so that it can be dealt with properly here + getLocation: function( url ) { + var uri = url ? this.parseUrl( url ) : location, + hash = this.parseUrl( url || location.href ).hash; + + // mimic the browser with an empty string when the hash is empty + hash = hash === "#" ? "" : hash; + + // Make sure to parse the url or the location object for the hash because using location.hash + // is autodecoded in firefox, the rest of the url should be from the object (location unless + // we're testing) to avoid the inclusion of the authority + return uri.protocol + "//" + uri.host + uri.pathname + uri.search + hash; + }, + + //return the original document url + getDocumentUrl: function( asParsedObject ) { + return asParsedObject ? $.extend( {}, path.documentUrl ) : path.documentUrl.href; + }, + + parseLocation: function() { + return this.parseUrl( this.getLocation() ); + }, + + //Parse a URL into a structure that allows easy access to + //all of the URL components by name. + parseUrl: function( url ) { + // If we're passed an object, we'll assume that it is + // a parsed url object and just return it back to the caller. + if ( $.type( url ) === "object" ) { + return url; + } + + var matches = path.urlParseRE.exec( url || "" ) || []; + + // Create an object that allows the caller to access the sub-matches + // by name. Note that IE returns an empty string instead of undefined, + // like all other browsers do, so we normalize everything so its consistent + // no matter what browser we're running on. + return { + href: matches[ 0 ] || "", + hrefNoHash: matches[ 1 ] || "", + hrefNoSearch: matches[ 2 ] || "", + domain: matches[ 3 ] || "", + protocol: matches[ 4 ] || "", + doubleSlash: matches[ 5 ] || "", + authority: matches[ 6 ] || "", + username: matches[ 8 ] || "", + password: matches[ 9 ] || "", + host: matches[ 10 ] || "", + hostname: matches[ 11 ] || "", + port: matches[ 12 ] || "", + pathname: matches[ 13 ] || "", + directory: matches[ 14 ] || "", + filename: matches[ 15 ] || "", + search: matches[ 16 ] || "", + hash: matches[ 17 ] || "" + }; + }, + + //Turn relPath into an asbolute path. absPath is + //an optional absolute path which describes what + //relPath is relative to. + makePathAbsolute: function( relPath, absPath ) { + var absStack, + relStack, + i, d; + + if ( relPath && relPath.charAt( 0 ) === "/" ) { + return relPath; + } + + relPath = relPath || ""; + absPath = absPath ? absPath.replace( /^\/|(\/[^\/]*|[^\/]+)$/g, "" ) : ""; + + absStack = absPath ? absPath.split( "/" ) : []; + relStack = relPath.split( "/" ); + + for ( i = 0; i < relStack.length; i++ ) { + d = relStack[ i ]; + switch ( d ) { + case ".": + break; + case "..": + if ( absStack.length ) { + absStack.pop(); + } + break; + default: + absStack.push( d ); + break; + } + } + return "/" + absStack.join( "/" ); + }, + + //Returns true if both urls have the same domain. + isSameDomain: function( absUrl1, absUrl2 ) { + return path.parseUrl( absUrl1 ).domain === path.parseUrl( absUrl2 ).domain; + }, + + //Returns true for any relative variant. + isRelativeUrl: function( url ) { + // All relative Url variants have one thing in common, no protocol. + return path.parseUrl( url ).protocol === ""; + }, + + //Returns true for an absolute url. + isAbsoluteUrl: function( url ) { + return path.parseUrl( url ).protocol !== ""; + }, + + //Turn the specified realtive URL into an absolute one. This function + //can handle all relative variants (protocol, site, document, query, fragment). + makeUrlAbsolute: function( relUrl, absUrl ) { + if ( !path.isRelativeUrl( relUrl ) ) { + return relUrl; + } + + if ( absUrl === undefined ) { + absUrl = this.documentBase; + } + + var relObj = path.parseUrl( relUrl ), + absObj = path.parseUrl( absUrl ), + protocol = relObj.protocol || absObj.protocol, + doubleSlash = relObj.protocol ? relObj.doubleSlash : ( relObj.doubleSlash || absObj.doubleSlash ), + authority = relObj.authority || absObj.authority, + hasPath = relObj.pathname !== "", + pathname = path.makePathAbsolute( relObj.pathname || absObj.filename, absObj.pathname ), + search = relObj.search || ( !hasPath && absObj.search ) || "", + hash = relObj.hash; + + return protocol + doubleSlash + authority + pathname + search + hash; + }, + + //Add search (aka query) params to the specified url. + addSearchParams: function( url, params ) { + var u = path.parseUrl( url ), + p = ( typeof params === "object" ) ? $.param( params ) : params, + s = u.search || "?"; + return u.hrefNoSearch + s + ( s.charAt( s.length - 1 ) !== "?" ? "&" : "" ) + p + ( u.hash || "" ); + }, + + convertUrlToDataUrl: function( absUrl ) { + var u = path.parseUrl( absUrl ); + if ( path.isEmbeddedPage( u ) ) { + // For embedded pages, remove the dialog hash key as in getFilePath(), + // and remove otherwise the Data Url won't match the id of the embedded Page. + return u.hash + .split( dialogHashKey )[0] + .replace( /^#/, "" ) + .replace( /\?.*$/, "" ); + } else if ( path.isSameDomain( u, this.documentBase ) ) { + return u.hrefNoHash.replace( this.documentBase.domain, "" ).split( dialogHashKey )[0]; + } + + return window.decodeURIComponent(absUrl); + }, + + //get path from current hash, or from a file path + get: function( newPath ) { + if ( newPath === undefined ) { + newPath = path.parseLocation().hash; + } + return path.stripHash( newPath ).replace( /[^\/]*\.[^\/*]+$/, "" ); + }, + + //set location hash to path + set: function( path ) { + location.hash = path; + }, + + //test if a given url (string) is a path + //NOTE might be exceptionally naive + isPath: function( url ) { + return ( /\// ).test( url ); + }, + + //return a url path with the window's location protocol/hostname/pathname removed + clean: function( url ) { + return url.replace( this.documentBase.domain, "" ); + }, + + //just return the url without an initial # + stripHash: function( url ) { + return url.replace( /^#/, "" ); + }, + + stripQueryParams: function( url ) { + return url.replace( /\?.*$/, "" ); + }, + + //remove the preceding hash, any query params, and dialog notations + cleanHash: function( hash ) { + return path.stripHash( hash.replace( /\?.*$/, "" ).replace( dialogHashKey, "" ) ); + }, + + isHashValid: function( hash ) { + return ( /^#[^#]+$/ ).test( hash ); + }, + + //check whether a url is referencing the same domain, or an external domain or different protocol + //could be mailto, etc + isExternal: function( url ) { + var u = path.parseUrl( url ); + return u.protocol && u.domain !== this.documentUrl.domain ? true : false; + }, + + hasProtocol: function( url ) { + return ( /^(:?\w+:)/ ).test( url ); + }, + + isEmbeddedPage: function( url ) { + var u = path.parseUrl( url ); + + //if the path is absolute, then we need to compare the url against + //both the this.documentUrl and the documentBase. The main reason for this + //is that links embedded within external documents will refer to the + //application document, whereas links embedded within the application + //document will be resolved against the document base. + if ( u.protocol !== "" ) { + return ( !this.isPath(u.hash) && u.hash && ( u.hrefNoHash === this.documentUrl.hrefNoHash || ( this.documentBaseDiffers && u.hrefNoHash === this.documentBase.hrefNoHash ) ) ); + } + return ( /^#/ ).test( u.href ); + }, + + squash: function( url, resolutionUrl ) { + var href, cleanedUrl, search, stateIndex, + isPath = this.isPath( url ), + uri = this.parseUrl( url ), + preservedHash = uri.hash, + uiState = ""; + + // produce a url against which we can resole the provided path + resolutionUrl = resolutionUrl || (path.isPath(url) ? path.getLocation() : path.getDocumentUrl()); + + // If the url is anything but a simple string, remove any preceding hash + // eg #foo/bar -> foo/bar + // #foo -> #foo + cleanedUrl = isPath ? path.stripHash( url ) : url; + + // If the url is a full url with a hash check if the parsed hash is a path + // if it is, strip the #, and use it otherwise continue without change + cleanedUrl = path.isPath( uri.hash ) ? path.stripHash( uri.hash ) : cleanedUrl; + + // Split the UI State keys off the href + stateIndex = cleanedUrl.indexOf( this.uiStateKey ); + + // store the ui state keys for use + if ( stateIndex > -1 ) { + uiState = cleanedUrl.slice( stateIndex ); + cleanedUrl = cleanedUrl.slice( 0, stateIndex ); + } + + // make the cleanedUrl absolute relative to the resolution url + href = path.makeUrlAbsolute( cleanedUrl, resolutionUrl ); + + // grab the search from the resolved url since parsing from + // the passed url may not yield the correct result + search = this.parseUrl( href ).search; + + // TODO all this crap is terrible, clean it up + if ( isPath ) { + // reject the hash if it's a path or it's just a dialog key + if ( path.isPath( preservedHash ) || preservedHash.replace("#", "").indexOf( this.uiStateKey ) === 0) { + preservedHash = ""; + } + + // Append the UI State keys where it exists and it's been removed + // from the url + if ( uiState && preservedHash.indexOf( this.uiStateKey ) === -1) { + preservedHash += uiState; + } + + // make sure that pound is on the front of the hash + if ( preservedHash.indexOf( "#" ) === -1 && preservedHash !== "" ) { + preservedHash = "#" + preservedHash; + } + + // reconstruct each of the pieces with the new search string and hash + href = path.parseUrl( href ); + href = href.protocol + "//" + href.host + href.pathname + search + preservedHash; + } else { + href += href.indexOf( "#" ) > -1 ? uiState : "#" + uiState; + } + + return href; + }, + + isPreservableHash: function( hash ) { + return hash.replace( "#", "" ).indexOf( this.uiStateKey ) === 0; + }, + + // Escape weird characters in the hash if it is to be used as a selector + hashToSelector: function( hash ) { + var hasHash = ( hash.substring( 0, 1 ) === "#" ); + if ( hasHash ) { + hash = hash.substring( 1 ); + } + return ( hasHash ? "#" : "" ) + hash.replace( /([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g, "\\$1" ); + }, + + // return the substring of a filepath before the sub-page key, for making + // a server request + getFilePath: function( path ) { + var splitkey = "&" + $.mobile.subPageUrlKey; + return path && path.split( splitkey )[0].split( dialogHashKey )[0]; + }, + + // check if the specified url refers to the first page in the main + // application document. + isFirstPageUrl: function( url ) { + // We only deal with absolute paths. + var u = path.parseUrl( path.makeUrlAbsolute( url, this.documentBase ) ), + + // Does the url have the same path as the document? + samePath = u.hrefNoHash === this.documentUrl.hrefNoHash || + ( this.documentBaseDiffers && + u.hrefNoHash === this.documentBase.hrefNoHash ), + + // Get the first page element. + fp = $.mobile.firstPage, + + // Get the id of the first page element if it has one. + fpId = fp && fp[0] ? fp[0].id : undefined; + + // The url refers to the first page if the path matches the document and + // it either has no hash value, or the hash is exactly equal to the id + // of the first page element. + return samePath && + ( !u.hash || + u.hash === "#" || + ( fpId && u.hash.replace( /^#/, "" ) === fpId ) ); + }, + + // Some embedded browsers, like the web view in Phone Gap, allow + // cross-domain XHR requests if the document doing the request was loaded + // via the file:// protocol. This is usually to allow the application to + // "phone home" and fetch app specific data. We normally let the browser + // handle external/cross-domain urls, but if the allowCrossDomainPages + // option is true, we will allow cross-domain http/https requests to go + // through our page loading logic. + isPermittedCrossDomainRequest: function( docUrl, reqUrl ) { + return $.mobile.allowCrossDomainPages && + (docUrl.protocol === "file:" || docUrl.protocol === "content:") && + reqUrl.search( /^https?:/ ) !== -1; + } + }; + + path.documentUrl = path.parseLocation(); + + $base = $( "head" ).find( "base" ); + + path.documentBase = $base.length ? + path.parseUrl( path.makeUrlAbsolute( $base.attr( "href" ), path.documentUrl.href ) ) : + path.documentUrl; + [... 12023 lines stripped ...] |
Free forum by Nabble | Edit this page |