",
options: {
- disabled: false
+ disabled: false,
+
+ // callbacks
+ create: null
},
_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 );
+ element = $( element || this.defaultElement || this )[ 0 ];
this.element = $( element );
- this.options = $.extend( true, {},
+ this.uuid = uuid++;
+ this.eventNamespace = "." + this.widgetName + this.uuid;
+ this.options = $.widget.extend( {},
this.options,
this._getCreateOptions(),
options );
- var self = this;
- this.element.bind( "remove." + this.widgetName, function() {
- self.destroy();
- });
+ this.bindings = $();
+ this.hoverable = $();
+ this.focusable = $();
+
+ if ( element !== this ) {
+ // 1.9 BC for #7810
+ // TODO remove dual storage
+ $.data( element, this.widgetName, this );
+ $.data( element, this.widgetFullName, this );
+ this._on({ remove: "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" );
+ this._trigger( "create", null, this._getCreateEventData() );
this._init();
},
- _getCreateOptions: function() {
- var options = {};
- if ( $.metadata ) {
- options = $.metadata.get( element )[ this.widgetName ];
- }
- return options;
- },
- _create: function() {},
- _init: function() {},
+ _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.widgetName )
- .removeData( this.widgetName );
+ .unbind( this.eventNamespace )
+ // 1.9 BC for #7810
+ // TODO remove dual storage
+ .removeData( this.widgetName )
+ .removeData( this.widgetFullName )
+ // support: jquery <1.6.3
+ // http://bugs.jquery.com/ticket/9413
+ .removeData( $.camelCase( this.widgetFullName ) );
this.widget()
- .unbind( "." + this.widgetName )
+ .unbind( this.eventNamespace )
.removeAttr( "aria-disabled" )
.removeClass(
- this.widgetBaseClass + "-disabled " +
+ 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;
+ var options = key,
+ parts,
+ curOption,
+ i;
if ( arguments.length === 0 ) {
// don't return a reference to the internal hash
- return $.extend( {}, this.options );
+ return $.widget.extend( {}, this.options );
}
- if (typeof key === "string" ) {
- if ( value === undefined ) {
- return this.options[ key ];
- }
+ if ( typeof key === "string" ) {
+ // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
options = {};
- options[ key ] = value;
+ 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 );
@@ -214,10 +668,11 @@ $.Widget.prototype = {
return this;
},
_setOptions: function( options ) {
- var self = this;
- $.each( options, function( key, value ) {
- self._setOption( key, value );
- });
+ var key;
+
+ for ( key in options ) {
+ this._setOption( key, options[ key ] );
+ }
return this;
},
@@ -226,53 +681,176 @@ $.Widget.prototype = {
if ( key === "disabled" ) {
this.widget()
- [ value ? "addClass" : "removeClass"](
- this.widgetBaseClass + "-disabled" + " " +
- "ui-state-disabled" )
+ .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
.attr( "aria-disabled", value );
+ this.hoverable.removeClass( "ui-state-hover" );
+ this.focusable.removeClass( "ui-state-focus" );
}
- return this;
+ return this;
+ },
+
+ enable: function() {
+ return this._setOption( "disabled", false );
+ },
+ disable: function() {
+ return this._setOption( "disabled", true );
+ },
+
+ _on: function( element, handlers ) {
+ // no element argument, shuffle and use this.element
+ if ( !handlers ) {
+ handlers = element;
+ element = this.element;
+ } else {
+ // accept selectors, DOM elements
+ element = $( element );
+ this.bindings = this.bindings.add( element );
+ }
+
+ var instance = this;
+ $.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 ( 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 ) {
+ instance.widget().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 );
},
- enable: function() {
- return this._setOption( "disabled", false );
+ _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" );
+ }
+ });
},
- disable: function() {
- return this._setOption( "disabled", true );
+
+ _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 callback = this.options[ type ];
+ var prop, orig,
+ callback = this.options[ type ];
+ data = data || {};
event = $.Event( event );
event.type = ( type === this.widgetEventPrefix ?
type :
this.widgetEventPrefix + type ).toLowerCase();
- data = data || {};
+ // 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
- // this would happen if we could call $.event.fix instead of $.Event
- // but we don't have a way to force an event to be fixed multiple times
- if ( event.originalEvent ) {
- for ( var i = $.event.props.length, prop; i; ) {
- prop = $.event.props[ --i ];
- event[ prop ] = event.originalEvent[ prop ];
+ 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 ||
+ 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 ] || $.uiBackCompat !== false && $.effects[ 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();
+ });
+ }
+ };
+});
+
+// DEPRECATED
+if ( $.uiBackCompat !== false ) {
+ $.Widget.prototype._getCreateOptions = function() {
+ return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
+ };
+}
+
})( jQuery );
-/*
-* widget factory extentions for mobile
-*/
(function( $, undefined ) {
@@ -307,184 +885,214 @@ $.widget( "mobile.widget", {
return options;
},
- enhanceWithin: function( target ) {
- // TODO remove dependency on the page widget for the keepNative.
- // Currently the keepNative value is defined on the page prototype so
- // the method is as well
- var page = $(target).closest(":jqmData(role='page')").data( "page" ),
- keepNative = (page && page.keepNativeSelector()) || "";
+ enhanceWithin: function( target, useKeepNative ) {
+ this.enhance( $( this.options.initSelector, $( target )), useKeepNative );
+ },
+
+ enhance: function( targets, useKeepNative ) {
+ var page, keepNative, $widgetElements = $( targets ), self = this;
- $( this.options.initSelector, target ).not( keepNative )[ this.widgetName ]();
+ // if ignoreContentEnabled is set to true the framework should
+ // only enhance the selected elements when they do NOT have a
+ // parent with the data-namespace-ignore attribute
+ $widgetElements = $.mobile.enhanceable( $widgetElements );
+
+ if ( useKeepNative && $widgetElements.length ) {
+ // TODO remove dependency on the page widget for the keepNative.
+ // Currently the keepNative value is defined on the page prototype so
+ // the method is as well
+ page = $.mobile.closestPageData( $widgetElements );
+ keepNative = ( page && page.keepNativeSelector()) || "";
+
+ $widgetElements = $widgetElements.not( keepNative );
+ }
+
+ $widgetElements[ this.widgetName ]();
+ },
+
+ raise: function( msg ) {
+ throw "Widget [" + this.widgetName + "]: " + msg;
}
});
})( jQuery );
-/*
-* a workaround for window.matchMedia
-*/
-(function( $, undefined ) {
-var $window = $( window ),
- $html = $( "html" );
+(function( $, window ) {
+ // DEPRECATED
+ // NOTE global mobile object settings
+ $.extend( $.mobile, {
+ // DEPRECATED Should the text be visble in the loading message?
+ loadingMessageTextVisible: undefined,
-/* $.mobile.media method: pass a CSS media type or query and get a bool return
- note: this feature relies on actual media query support for media queries, though types will work most anywhere
- examples:
- $.mobile.media('screen') //>> tests for screen media type
- $.mobile.media('screen and (min-width: 480px)') //>> tests for screen media type with window width > 480px
- $.mobile.media('@media screen and (-webkit-min-device-pixel-ratio: 2)') //>> tests for webkit 2x pixel ratio (iPhone 4)
-*/
-$.mobile.media = (function() {
- // TODO: use window.matchMedia once at least one UA implements it
- var cache = {},
- testDiv = $( "
" ),
- fakeBody = $( "" ).append( testDiv );
+ // DEPRECATED When the text is visible, what theme does the loading box use?
+ loadingMessageTheme: undefined,
- return function( query ) {
- if ( !( query in cache ) ) {
- var styleBlock = document.createElement( "style" ),
- cssrule = "@media " + query + " { #jquery-mediatest { position:absolute; } }";
+ // DEPRECATED default message setting
+ loadingMessage: undefined,
- //must set type for IE!
- styleBlock.type = "text/css";
+ // DEPRECATED
+ // 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
+ showPageLoadingMsg: function( theme, msgText, textonly ) {
+ $.mobile.loading( 'show', theme, msgText, textonly );
+ },
- if ( styleBlock.styleSheet ){
- styleBlock.styleSheet.cssText = cssrule;
- } else {
- styleBlock.appendChild( document.createTextNode(cssrule) );
- }
+ // DEPRECATED
+ hidePageLoadingMsg: function() {
+ $.mobile.loading( 'hide' );
+ },
- $html.prepend( fakeBody ).prepend( styleBlock );
- cache[ query ] = testDiv.css( "position" ) === "absolute";
- fakeBody.add( styleBlock ).remove();
+ loading: function() {
+ this.loaderWidget.loader.apply( this.loaderWidget, arguments );
}
- return cache[ query ];
- };
-})();
+ });
-})(jQuery);
-/*
-* support tests
-*/
+ // TODO move loader class down into the widget settings
+ var loaderClass = "ui-loader", $html = $( "html" ), $window = $( window );
-(function( $, undefined ) {
+ $.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",
-var fakeBody = $( "" ).prependTo( "html" ),
- fbCSS = fakeBody[ 0 ].style,
- vendors = [ "Webkit", "Moz", "O" ],
- webos = "palmGetResource" in window, //only used to rule out scrollTop
- operamini = window.operamini && ({}).toString.call( window.operamini ) === "[object OperaMini]",
- bb = window.blackberry; //only used to rule out box shadow, as it's filled opaque on BB
+ // whether the text in the loading message is shown
+ textVisible: false,
-// thx Modernizr
-function propExists( prop ) {
- var uc_prop = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),
- props = ( prop + " " + vendors.join( uc_prop + " " ) + uc_prop ).split( " " );
+ // custom html for the inner content of the loading message
+ html: "",
- for ( var v in props ){
- if ( fbCSS[ props[ v ] ] !== undefined ) {
- return true;
- }
- }
-}
+ // the text to be displayed when the popup is shown
+ text: "loading"
+ },
-// 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;
+ defaultHtml: "
" +
+ "" +
+ "
" +
+ "",
- if ( !base.length ) {
- base = fauxEle = $( "
", { "href": fauxBase }).appendTo( "head" );
- } else {
- href = base.attr( "href" );
- }
+ // 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();
- link = $( "
" ).prependTo( fakeBody );
- rebase = link[ 0 ].href;
- base[ 0 ].href = href || location.pathname;
+ this.element
+ .css({
+ top: $.support.scrollTop && $window.scrollTop() + $window.height() / 2 ||
+ activeBtn.length && activeBtn.offset().top || 100
+ });
+ },
- if ( fauxEle ) {
- fauxEle.remove();
- }
- return rebase.indexOf( fauxBase ) === 0;
-}
+ // 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 = $window.scrollTop(),
+ screenHeight = $.mobile.getScreenHeight();
+
+ if ( offset.top < scrollTop || ( offset.top - scrollTop ) > screenHeight ) {
+ this.element.addClass( "ui-loader-fakefix" );
+ this.fakeFixLoader();
+ $window
+ .unbind( "scroll", this.checkLoaderPosition )
+ .bind( "scroll", this.fakeFixLoader );
+ }
+ },
+ resetHtml: function() {
+ this.element.html( $( this.defaultHtml ).html() );
+ },
-// 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
-$.mobile.browser = {};
-$.mobile.browser.ie = (function() {
- var v = 3,
- div = document.createElement( "div" ),
- a = div.all || [];
+ // 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, $header, loadSettings;
- while ( div.innerHTML = "", a[ 0 ] );
+ this.resetHtml();
- return v > 4 ? v : !v;
-})();
+ // 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 );
+ // prefer object property from the param then the old theme setting
+ theme = loadSettings.theme || $.mobile.loadingMessageTheme;
+ } else {
+ loadSettings = this.options;
-$.extend( $.support, {
- orientation: "orientation" in window && "onorientationchange" in window,
- touch: "ontouchend" in document,
- cssTransitions: "WebKitTransitionEvent" in window,
- pushState: "pushState" in history && "replaceState" in history,
- mediaquery: $.mobile.media( "only all" ),
- cssPseudoElement: !!propExists( "content" ),
- touchOverflow: !!propExists( "overflowScrolling" ),
- boxShadow: !!propExists( "boxShadow" ) && !bb,
- scrollTop: ( "pageXOffset" in window || "scrollTop" in document.documentElement || "scrollTop" in fakeBody[ 0 ] ) && !webos && !operamini,
- dynamicBaseTag: baseTagTest()
-});
+ // here we prefer the them 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 || $.mobile.loadingMessageTheme || loadSettings.theme;
+ }
-fakeBody.remove();
+ // set the message text, prefer the param, then the settings object
+ // then loading message
+ message = msgText || $.mobile.loadingMessage || loadSettings.text;
+ // prepare the dom
+ $html.addClass( "ui-loading" );
-// $.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(){
+ if ( $.mobile.loadingMessage !== false || loadSettings.html ) {
+ // boolean values require a bit more work :P, supports object properties
+ // and old settings
+ if ( $.mobile.loadingMessageTextVisible !== undefined ) {
+ textVisible = $.mobile.loadingMessageTextVisible;
+ } else {
+ textVisible = loadSettings.textVisible;
+ }
- var ua = window.navigator.userAgent;
+ // 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 );
+ }
- //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]/ );
-})();
+ // attach the loader to the DOM
+ this.element.appendTo( $.mobile.pageContainer );
-$.mobile.ajaxBlacklist =
- // BlackBerry browsers, pre-webkit
- window.blackberry && !window.WebKitPoint ||
- // Opera Mini
- operamini ||
- // Symbian webkits pre 7.3
- nokiaLTE7_3;
+ // check that the loader is visible
+ this.checkLoaderPosition();
-// 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" );
+ // on scroll check the loader position
+ $window.bind( "scroll", $.proxy( this.checkLoaderPosition, this ) );
+ }
+ },
+
+ hide: function() {
+ $html.removeClass( "ui-loading" );
+
+ if ( $.mobile.loadingMessage ) {
+ this.element.removeClass( "ui-loader-fakefix" );
+ }
+
+ $( window ).unbind( "scroll", $.proxy( this.fakeFixLoader, this) );
+ $( window ).unbind( "scroll", $.proxy( this.checkLoaderPosition, this ) );
+ }
});
-}
-// For ruling out shadows via css
-if ( !$.support.boxShadow ) {
- $( "html" ).addClass( "ui-mobile-nosupport-boxshadow" );
-}
+ $window.bind( 'pagecontainercreate', function() {
+ $.mobile.loaderWidget = $.mobile.loaderWidget || $( $.mobile.loader.prototype.defaultHtml ).loader();
+ });
+})(jQuery, this);
+
-})( jQuery );
-/*
-* "mouse" plugin
-*/
// This plugin is an experiment for abstracting away the touch and mouse
// events so that developers don't have to worry about which method of input
@@ -507,6 +1115,8 @@ var dataPropertyName = "virtualMouseBindings",
touchTargetPropertyName = "virtualTouchID",
virtualEventNames = "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split( " " ),
touchEventProps = "clientX clientY pageX pageY screenX screenY".split( " " ),
+ mouseHookProps = $.event.mouseHooks ? $.event.mouseHooks.props : [],
+ mouseEventProps = $.event.props.concat( mouseHookProps ),
activeDocHandlers = {},
resetTimerID = 0,
startX = 0,
@@ -518,7 +1128,7 @@ var dataPropertyName = "virtualMouseBindings",
eventCaptureSupported = "addEventListener" in document,
$document = $( document ),
nextTouchID = 1,
- lastTouchID = 0;
+ lastTouchID = 0, threshold;
$.vmouse = {
moveDistanceThreshold: 10,
@@ -537,14 +1147,20 @@ function getNativeEvent( event ) {
function createVirtualEvent( event, eventType ) {
var t = event.type,
- oe, props, ne, prop, ct, touch, i, j;
+ oe, props, ne, prop, ct, touch, i, j, len;
- event = $.Event(event);
+ event = $.Event( event );
event.type = eventType;
oe = event.originalEvent;
props = $.event.props;
+ // addresses separation of $.event.props in to $.event.mouseHook.props and Issue 3280
+ // https://github.com/jquery/jquery-mobile/issues/3280
+ if ( t.search( /^(mouse|click)/ ) > -1 ) {
+ props = mouseEventProps;
+ }
+
// copy original event properties over to the new event
// this would happen if we could call $.event.fix instead of $.Event
// but we don't have a way to force an event to be fixed multiple times
@@ -557,7 +1173,7 @@ function createVirtualEvent( event, eventType ) {
// make sure that if the mouse and click virtual events are generated
// without a .which one is defined
- if ( t.search(/mouse(down|up)|click/) > -1 && !event.which ){
+ if ( t.search(/mouse(down|up)|click/) > -1 && !event.which ) {
event.which = 1;
}
@@ -565,10 +1181,10 @@ function createVirtualEvent( event, eventType ) {
ne = getNativeEvent( oe );
t = ne.touches;
ct = ne.changedTouches;
- touch = ( t && t.length ) ? t[0] : ( (ct && ct.length) ? ct[ 0 ] : undefined );
+ touch = ( t && t.length ) ? t[0] : ( ( ct && ct.length ) ? ct[ 0 ] : undefined );
if ( touch ) {
- for ( j = 0, len = touchEventProps.length; j < len; j++){
+ for ( j = 0, len = touchEventProps.length; j < len; j++) {
prop = touchEventProps[ j ];
event[ prop ] = touch[ prop ];
}
@@ -637,14 +1253,14 @@ function disableMouseBindings() {
function startResetTimer() {
clearResetTimer();
- resetTimerID = setTimeout(function(){
+ resetTimerID = setTimeout( function() {
resetTimerID = 0;
enableMouseBindings();
}, $.vmouse.resetTimerDuration );
}
function clearResetTimer() {
- if ( resetTimerID ){
+ if ( resetTimerID ) {
clearTimeout( resetTimerID );
resetTimerID = 0;
}
@@ -665,9 +1281,9 @@ function triggerVirtualEvent( eventType, event, flags ) {
}
function mouseEventCallback( event ) {
- var touchID = $.data(event.target, touchTargetPropertyName);
+ var touchID = $.data( event.target, touchTargetPropertyName );
- if ( !blockMouseTriggers && ( !lastTouchID || lastTouchID !== touchID ) ){
+ if ( !blockMouseTriggers && ( !lastTouchID || lastTouchID !== touchID ) ) {
var ve = triggerVirtualEvent( "v" + event.type, event );
if ( ve ) {
if ( ve.isDefaultPrevented() ) {
@@ -733,12 +1349,14 @@ function handleTouchMove( event ) {
var t = getNativeEvent( event ).touches[ 0 ],
didCancel = didScroll,
- moveThreshold = $.vmouse.moveDistanceThreshold;
- didScroll = didScroll ||
- ( Math.abs(t.pageX - startX) > moveThreshold ||
- Math.abs(t.pageY - startY) > moveThreshold ),
+ moveThreshold = $.vmouse.moveDistanceThreshold,
flags = getVirtualBindingFlags( event.target );
+ didScroll = didScroll ||
+ ( Math.abs( t.pageX - startX ) > moveThreshold ||
+ Math.abs( t.pageY - startY ) > moveThreshold );
+
+
if ( didScroll && !didCancel ) {
triggerVirtualEvent( "vmousecancel", event, flags );
}
@@ -797,7 +1415,7 @@ function hasVirtualBindings( ele ) {
return false;
}
-function dummyMouseHandler(){}
+function dummyMouseHandler() {}
function getSpecialEventObject( eventType ) {
var realType = eventType.substr( 1 );
@@ -808,7 +1426,7 @@ function getSpecialEventObject( eventType ) {
// add a bindings object to its data.
if ( !hasVirtualBindings( this ) ) {
- $.data( this, dataPropertyName, {});
+ $.data( this, dataPropertyName, {} );
}
// If setup is called, we know it is the first binding for this
@@ -838,7 +1456,7 @@ function getSpecialEventObject( eventType ) {
activeDocHandlers[ "touchstart" ] = ( activeDocHandlers[ "touchstart" ] || 0) + 1;
- if (activeDocHandlers[ "touchstart" ] === 1) {
+ if ( activeDocHandlers[ "touchstart" ] === 1 ) {
$document.bind( "touchstart", handleTouchStart )
.bind( "touchend", handleTouchEnd )
@@ -910,7 +1528,7 @@ function getSpecialEventObject( eventType ) {
// Expose our custom events to the jQuery bind/unbind mechanism.
-for ( var i = 0; i < virtualEventNames.length; i++ ){
+for ( var i = 0; i < virtualEventNames.length; i++ ) {
$.event.special[ virtualEventNames[ i ] ] = getSpecialEventObject( virtualEventNames[ i ] );
}
@@ -918,7 +1536,7 @@ for ( var i = 0; i < virtualEventNames.length; i++ ){
// Note that we require event capture support for this so if the device
// doesn't support it, we punt for now and rely solely on mouse events.
if ( eventCaptureSupported ) {
- document.addEventListener( "click", function( e ){
+ document.addEventListener( "click", function( e ) {
var cnt = clickBlockList.length,
target = e.target,
x, y, ele, i, o, touchID;
@@ -955,346 +1573,705 @@ if ( eventCaptureSupported ) {
// innermost child of the touched element, even if that child is no where
// near the point of touch.
- ele = target;
+ ele = target;
+
+ while ( ele ) {
+ for ( i = 0; i < cnt; i++ ) {
+ o = clickBlockList[ i ];
+ touchID = 0;
+
+ if ( ( ele === target && Math.abs( o.x - x ) < threshold && Math.abs( o.y - y ) < threshold ) ||
+ $.data( ele, touchTargetPropertyName ) === o.touchID ) {
+ // XXX: We may want to consider removing matches from the block list
+ // instead of waiting for the reset timer to fire.
+ e.preventDefault();
+ e.stopPropagation();
+ return;
+ }
+ }
+ ele = ele.parentNode;
+ }
+ }
+ }, true);
+}
+})( jQuery, window, document );
+
+ (function( $, undefined ) {
+ var support = {
+ touch: "ontouchend" in document
+ };
+
+ $.mobile = $.mobile || {};
+ $.mobile.support = $.mobile.support || {};
+ $.extend( $.support, support );
+ $.extend( $.mobile.support, support );
+ }( jQuery ));
+
+
+(function( $, window, undefined ) {
+ // add new event shortcuts
+ $.each( ( "touchstart touchmove touchend " +
+ "tap taphold " +
+ "swipe swipeleft swiperight " +
+ "scrollstart scrollstop" ).split( " " ), function( i, name ) {
+
+ $.fn[ name ] = function( fn ) {
+ return fn ? this.bind( name, fn ) : this.trigger( name );
+ };
+
+ // jQuery < 1.8
+ if ( $.attrFn ) {
+ $.attrFn[ name ] = true;
+ }
+ });
+
+ var supportTouch = $.mobile.support.touch,
+ scrollEvent = "touchmove scroll",
+ touchStartEvent = supportTouch ? "touchstart" : "mousedown",
+ touchStopEvent = supportTouch ? "touchend" : "mouseup",
+ touchMoveEvent = supportTouch ? "touchmove" : "mousemove";
+
+ function triggerCustomEvent( obj, eventType, event ) {
+ var originalType = event.type;
+ event.type = eventType;
+ $.event.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 = {
+ tapholdThreshold: 750,
+
+ setup: function() {
+ var thisObject = this,
+ $this = $( thisObject );
+
+ $this.bind( "vmousedown", function( event ) {
+
+ if ( event.which && event.which !== 1 ) {
+ return false;
+ }
+
+ var origTarget = event.target,
+ origEvent = event.originalEvent,
+ timer;
+
+ function clearTapTimer() {
+ clearTimeout( timer );
+ }
+
+ function clearTapHandlers() {
+ clearTapTimer();
- while ( ele ) {
- for ( i = 0; i < cnt; i++ ) {
- o = clickBlockList[ i ];
- touchID = 0;
+ $this.unbind( "vclick", clickHandler )
+ .unbind( "vmouseup", clearTapTimer );
+ $( document ).unbind( "vmousecancel", clearTapHandlers );
+ }
- if ( ( ele === target && Math.abs( o.x - x ) < threshold && Math.abs( o.y - y ) < threshold ) ||
- $.data( ele, touchTargetPropertyName ) === o.touchID ) {
- // XXX: We may want to consider removing matches from the block list
- // instead of waiting for the reset timer to fire.
- e.preventDefault();
- e.stopPropagation();
- return;
+ 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 );
}
}
- ele = ele.parentNode;
- }
+
+ $this.bind( "vmouseup", clearTapTimer )
+ .bind( "vclick", clickHandler );
+ $( document ).bind( "vmousecancel", clearTapHandlers );
+
+ timer = setTimeout( function() {
+ triggerCustomEvent( thisObject, "taphold", $.Event( "taphold", { target: origTarget } ) );
+ }, $.event.special.tap.tapholdThreshold );
+ });
}
- }, true);
-}
-})( jQuery, window, document );
-/*
-* "events" plugin - Handles events
-*/
+ };
-(function( $, window, undefined ) {
+ // also handles swipeleft, swiperight
+ $.event.special.swipe = {
+ scrollSupressionThreshold: 30, // More than this horizontal displacement, and we will suppress scrolling.
-// add new event shortcuts
-$.each( ( "touchstart touchmove touchend orientationchange throttledresize " +
- "tap taphold swipe swipeleft swiperight scrollstart scrollstop" ).split( " " ), function( i, name ) {
+ durationThreshold: 1000, // More time than this, and it isn't a swipe.
- $.fn[ name ] = function( fn ) {
- return fn ? this.bind( name, fn ) : this.trigger( name );
- };
+ horizontalDistanceThreshold: 30, // Swipe horizontal displacement must be more than this.
- $.attrFn[ name ] = true;
-});
+ verticalDistanceThreshold: 75, // Swipe vertical displacement must be less than this.
-var supportTouch = $.support.touch,
- scrollEvent = "touchmove scroll",
- touchStartEvent = supportTouch ? "touchstart" : "mousedown",
- touchStopEvent = supportTouch ? "touchend" : "mouseup",
- touchMoveEvent = supportTouch ? "touchmove" : "mousemove";
+ setup: function() {
+ var thisObject = this,
+ $this = $( thisObject );
-function triggerCustomEvent( obj, eventType, event ) {
- var originalType = event.type;
- event.type = eventType;
- $.event.handle.call( obj, event );
- event.type = originalType;
-}
+ $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;
-// also handles scrollstop
-$.event.special.scrollstart = {
+ function moveHandler( event ) {
- enabled: true,
+ if ( !start ) {
+ return;
+ }
- setup: function() {
+ var data = event.originalEvent.touches ?
+ event.originalEvent.touches[ 0 ] : event;
- var thisObject = this,
- $this = $( thisObject ),
- scrolling,
- timer;
+ stop = {
+ time: ( new Date() ).getTime(),
+ coords: [ data.pageX, data.pageY ]
+ };
- function trigger( event, state ) {
- scrolling = state;
- triggerCustomEvent( thisObject, scrolling ? "scrollstart" : "scrollstop", event );
- }
+ // prevent scrolling
+ if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.scrollSupressionThreshold ) {
+ event.preventDefault();
+ }
+ }
- // iPhone triggers scroll after a small delay; use touchmove instead
- $this.bind( scrollEvent, function( event ) {
+ $this.bind( touchMoveEvent, moveHandler )
+ .one( touchStopEvent, function( event ) {
+ $this.unbind( touchMoveEvent, moveHandler );
- if ( !$.event.special.scrollstart.enabled ) {
- return;
- }
+ 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 ) {
- if ( !scrolling ) {
- trigger( event, true );
+ start.origin.trigger( "swipe" )
+ .trigger( start.coords[0] > stop.coords[ 0 ] ? "swipeleft" : "swiperight" );
+ }
+ }
+ start = stop = undefined;
+ });
+ });
+ }
+ };
+ $.each({
+ scrollstop: "scrollstart",
+ taphold: "tap",
+ swipeleft: "swipe",
+ swiperight: "swipe"
+ }, function( event, sourceEvent ) {
+
+ $.event.special[ event ] = {
+ setup: function() {
+ $( this ).bind( sourceEvent, $.noop );
}
+ };
+ });
- clearTimeout( timer );
- timer = setTimeout(function() {
- trigger( event, false );
- }, 50 );
+})( jQuery, this );
+
+ (function( $, undefined ) {
+ $.extend( $.support, {
+ orientation: "orientation" in window && "onorientationchange" in window
});
- }
-};
+ }( jQuery ));
+
+
+ // 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" );
-// also handles taphold
-$.event.special.tap = {
- setup: function() {
- var thisObject = this,
- $this = $( thisObject );
+ } else {
+
+ if ( heldCall ) {
+ clearTimeout( heldCall );
+ }
+
+ // Promise a held call will still execute
+ heldCall = setTimeout( handler, throttle - diff );
+ }
+ },
+ lastCall = 0,
+ heldCall,
+ curr,
+ diff;
+ })( jQuery );
+
+(function( $, window ) {
+ var win = $( window ),
+ event_name = "orientationchange",
+ special_event,
+ get_orientation,
+ last_orientation,
+ initial_orientation_is_landscape,
+ initial_orientation_is_default,
+ portrait_map = { "0": true, "180": true };
+
+ // It seems that some device/browser vendors use window.orientation values 0 and 180 to
+ // denote the "default" orientation. For iOS devices, and most other smart-phones tested,
+ // the default orientation is always "portrait", but in some Android and RIM based tablets,
+ // the default orientation is "landscape". The following code attempts to use the window
+ // dimensions to figure out what the current orientation is, and then makes adjustments
+ // to the to the portrait_map if necessary, so that we can properly decode the
+ // window.orientation value whenever get_orientation() is called.
+ //
+ // Note that we used to use a media query to figure out what the orientation the browser
+ // thinks it is in:
+ //
+ // initial_orientation_is_landscape = $.mobile.media("all and (orientation: landscape)");
+ //
+ // but there was an iPhone/iPod Touch bug beginning with iOS 4.2, up through iOS 5.1,
+ // where the browser *ALWAYS* applied the landscape media query. This bug does not
+ // happen on iPad.
+
+ if ( $.support.orientation ) {
+
+ // Check the window width and height to figure out what the current orientation
+ // of the device is at this moment. Note that we've initialized the portrait map
+ // values to 0 and 180, *AND* we purposely check for landscape so that if we guess
+ // wrong, , we default to the assumption that portrait is the default orientation.
+ // We use a threshold check below because on some platforms like iOS, the iPhone
+ // form-factor can report a larger width than height if the user turns on the
+ // developer console. The actual threshold value is somewhat arbitrary, we just
+ // need to make sure it is large enough to exclude the developer console case.
- $this.bind( "vmousedown", function( event ) {
+ var ww = window.innerWidth || $( window ).width(),
+ wh = window.innerHeight || $( window ).height(),
+ landscape_threshold = 50;
- if ( event.which && event.which !== 1 ) {
+ initial_orientation_is_landscape = ww > wh && ( ww - wh ) > landscape_threshold;
+
+
+ // Now check to see if the current window.orientation is 0 or 180.
+ initial_orientation_is_default = portrait_map[ window.orientation ];
+
+ // If the initial orientation is landscape, but window.orientation reports 0 or 180, *OR*
+ // if the initial orientation is portrait, but window.orientation reports 90 or -90, we
+ // need to flip our portrait_map values because landscape is the default orientation for
+ // this device/browser.
+ if ( ( initial_orientation_is_landscape && initial_orientation_is_default ) || ( !initial_orientation_is_landscape && !initial_orientation_is_default ) ) {
+ portrait_map = { "-90": true, "90": true };
+ }
+ }
+
+ $.event.special.orientationchange = $.extend( {}, $.event.special.orientationchange, {
+ setup: function() {
+ // If the event is supported natively, return false so that jQuery
+ // will bind to the event using DOM methods.
+ if ( $.support.orientation && !$.event.special.orientationchange.disabled ) {
return false;
}
- var origTarget = event.target,
- origEvent = event.originalEvent,
- timer;
+ // Get the current orientation to avoid initial double-triggering.
+ last_orientation = get_orientation();
- function clearTapTimer() {
- clearTimeout( timer );
+ // Because the orientationchange event doesn't exist, simulate the
+ // event by testing window dimensions on resize.
+ win.bind( "throttledresize", handler );
+ },
+ teardown: function() {
+ // If the event is not supported natively, return false so that
+ // jQuery will unbind the event using DOM methods.
+ if ( $.support.orientation && !$.event.special.orientationchange.disabled ) {
+ return false;
}
- function clearTapHandlers() {
- clearTapTimer();
+ // 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;
- $this.unbind( "vclick", clickHandler )
- .unbind( "vmouseup", clearTapTimer )
- .unbind( "vmousecancel", clearTapHandlers );
- }
- function clickHandler(event) {
- clearTapHandlers();
+ handleObj.handler = function( event ) {
+ // Modify event object, adding the .orientation property.
+ event.orientation = get_orientation();
- // ONLY trigger a 'tap' event if the start target is
- // the same as the stop target.
- if ( origTarget == event.target ) {
- triggerCustomEvent( thisObject, "tap", event );
- }
- }
+ // Call the originally-bound event handler and return its result.
+ return old_handler.apply( this, arguments );
+ };
+ }
+ });
- $this.bind( "vmousecancel", clearTapHandlers )
- .bind( "vmouseup", clearTapTimer )
- .bind( "vclick", clickHandler );
+ // 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();
- timer = setTimeout(function() {
- triggerCustomEvent( thisObject, "taphold", $.Event( "taphold" ) );
- }, 750 );
- });
+ if ( orientation !== last_orientation ) {
+ // The orientation has changed, so trigger the orientationchange event.
+ last_orientation = orientation;
+ win.trigger( event_name );
+ }
}
-};
-// also handles swipeleft, swiperight
-$.event.special.swipe = {
- scrollSupressionThreshold: 10, // More than this horizontal displacement, and we will suppress scrolling.
+ // 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;
- durationThreshold: 1000, // More time than this, and it isn't a swipe.
+ // 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;
+ }
- horizontalDistanceThreshold: 30, // Swipe horizontal displacement must be more than this.
+ return isPortrait ? "portrait" : "landscape";
+ };
- verticalDistanceThreshold: 75, // Swipe vertical displacement must be less than this.
+ $.fn[ event_name ] = function( fn ) {
+ return fn ? this.bind( event_name, fn ) : this.trigger( event_name );
+ };
- setup: function() {
- var thisObject = this,
- $this = $( thisObject );
+ // jQuery < 1.8
+ if ( $.attrFn ) {
+ $.attrFn[ event_name ] = true;
+ }
- $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;
+}( jQuery, this ));
- function moveHandler( event ) {
- if ( !start ) {
- return;
- }
+(function( $, undefined ) {
- var data = event.originalEvent.touches ?
- event.originalEvent.touches[ 0 ] : event;
+var $window = $( window ),
+ $html = $( "html" );
- stop = {
- time: ( new Date() ).getTime(),
- coords: [ data.pageX, data.pageY ]
- };
+/* $.mobile.media method: pass a CSS media type or query and get a bool return
+ note: this feature relies on actual media query support for media queries, though types will work most anywhere
+ examples:
+ $.mobile.media('screen') // tests for screen media type
+ $.mobile.media('screen and (min-width: 480px)') // tests for screen media type with window width > 480px
+ $.mobile.media('@media screen and (-webkit-min-device-pixel-ratio: 2)') // tests for webkit 2x pixel ratio (iPhone 4)
+*/
+$.mobile.media = (function() {
+ // TODO: use window.matchMedia once at least one UA implements it
+ var cache = {},
+ testDiv = $( "
" ),
+ fakeBody = $( "" ).append( testDiv );
- // prevent scrolling
- if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.scrollSupressionThreshold ) {
- event.preventDefault();
- }
+ return function( query ) {
+ if ( !( query in cache ) ) {
+ var styleBlock = document.createElement( "style" ),
+ cssrule = "@media " + query + " { #jquery-mediatest { position:absolute; } }";
+
+ //must set type for IE!
+ styleBlock.type = "text/css";
+
+ if ( styleBlock.styleSheet ) {
+ styleBlock.styleSheet.cssText = cssrule;
+ } else {
+ styleBlock.appendChild( document.createTextNode(cssrule) );
}
- $this.bind( touchMoveEvent, moveHandler )
- .one( touchStopEvent, function( event ) {
- $this.unbind( touchMoveEvent, moveHandler );
+ $html.prepend( fakeBody ).prepend( styleBlock );
+ cache[ query ] = testDiv.css( "position" ) === "absolute";
+ fakeBody.add( styleBlock ).remove();
+ }
+ return cache[ query ];
+ };
+})();
- 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 ) {
+})(jQuery);
- start.origin.trigger( "swipe" )
- .trigger( start.coords[0] > stop.coords[ 0 ] ? "swipeleft" : "swiperight" );
- }
- }
- start = stop = undefined;
- });
- });
+(function( $, undefined ) {
+
+// thx Modernizr
+function propExists( prop ) {
+ var uc_prop = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),
+ props = ( prop + " " + vendors.join( uc_prop + " " ) + uc_prop ).split( " " );
+
+ for ( var v in props ) {
+ if ( fbCSS[ props[ v ] ] !== undefined ) {
+ return true;
+ }
}
-};
+}
-(function( $, window ) {
- // "Cowboy" Ben Alman
+var fakeBody = $( "" ).prependTo( "html" ),
+ fbCSS = fakeBody[ 0 ].style,
+ vendors = [ "Webkit", "Moz", "O" ],
+ webos = "palmGetResource" in window, //only used to rule out scrollTop
+ opera = window.opera,
+ operamini = window.operamini && ({}).toString.call( window.operamini ) === "[object OperaMini]",
+ bb = window.blackberry && !propExists( "-webkit-transform" ); //only used to rule out box shadow, as it's filled opaque on BB 5 and lower
- var win = $( window ),
- special_event,
- get_orientation,
- last_orientation;
- $.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;
+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 ) {
+ 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( prop );
+
+ div.setAttribute( "style", vend_prop );
+
+ if ( !!div.style[ propStyle ] ) {
+ ret = true;
}
+ },
+ check_vends = check_vend ? [ check_vend ] : vendors,
+ ret;
+
+ for( var i = 0; i < check_vends.length; i++ ) {
+ check_style( check_vends[i] );
+ }
+ return !!ret;
+}
+
+// Thanks to Modernizr src for this test idea. `perspective` check is limited to Moz to prevent a false positive for 3D transforms on Android.
+function transform3dTest() {
+ var prop = "transform-3d";
+ return validStyle( 'perspective', '10px', 'moz' ) || $.mobile.media( "(-" + vendors.join( "-" + prop + "),(-" ) + "-" + prop + "),(" + prop + ")" );
+}
+
+// Test for dynamic-updating base tag support ( allows us to avoid href,src attr rewriting )
+function baseTagTest() {
+ var fauxBase = location.protocol + "//" + location.host + location.pathname + "ui-dir/",
+ base = $( "head base" ),
+ fauxEle = null,
+ href = "",
+ link, rebase;
+
+ if ( !base.length ) {
+ base = fauxEle = $( "
", { "href": fauxBase }).appendTo( "head" );
+ } else {
+ href = base.attr( "href" );
+ }
+
+ link = $( "
" ).prependTo( fakeBody );
+ rebase = link[ 0 ].href;
+ base[ 0 ].href = href || location.pathname;
+
+ if ( fauxEle ) {
+ fauxEle.remove();
+ }
+ return rebase.indexOf( fauxBase ) === 0;
+}
+
+// Thanks Modernizr
+function cssPointerEventsTest() {
+ var element = document.createElement( 'x' ),
+ documentElement = document.documentElement,
+ getComputedStyle = window.getComputedStyle,
+ supports;
+
+ if ( !( 'pointerEvents' in element.style ) ) {
+ return false;
+ }
+
+ element.style.pointerEvents = 'auto';
+ element.style.pointerEvents = 'x';
+ documentElement.appendChild( element );
+ supports = getComputedStyle &&
+ getComputedStyle( element, '' ).pointerEvents === 'auto';
+ documentElement.removeChild( element );
+ return !!supports;
+}
- // Get the current orientation to avoid initial double-triggering.
- last_orientation = get_orientation();
+function boundingRect() {
+ var div = document.createElement( "div" );
+ return typeof div.getBoundingClientRect !== "undefined";
+}
- // 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;
- }
+// 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.ie = (function() {
+ var v = 3,
+ div = document.createElement( "div" ),
+ a = div.all || [];
- // 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;
+ do {
+ div.innerHTML = "";
+ } while( a[0] );
+ return v > 4 ? v : !v;
+})();
- 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 );
- };
- }
- };
+$.extend( $.support, {
+ cssTransitions: "WebKitTransitionEvent" in window || validStyle( 'transition', 'height 100ms linear' ) && !opera,
+ 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(),
+ cssPointerEvents: cssPointerEventsTest(),
+ boundingRect: boundingRect()
+});
- // 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();
+fakeBody.remove();
- 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;
+// $.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() {
- // 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 = window.orientation % 180 == 0;
- } else {
- isPortrait = elem && elem.clientWidth / elem.clientHeight < 1.1;
- }
+ var ua = window.navigator.userAgent;
- return isPortrait ? "portrait" : "landscape";
- };
+ //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]/ );
+})();
-})( jQuery, window );
+// 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 ) && ( $.support.boundingRect || $.fn.jquery.match(/1\.[0-7+]\.[0-9+]?/) !== null );
+};
-// throttled resize event
-(function() {
+$.mobile.ajaxBlacklist =
+ // BlackBerry browsers, pre-webkit
+ window.blackberry && !window.WebKitPoint ||
+ // Opera Mini
+ operamini ||
+ // Symbian webkits pre 7.3
+ nokiaLTE7_3;
- $.event.special.throttledresize = {
- setup: function() {
- $( this ).bind( "resize", handler );
- },
- teardown: function(){
- $( this ).unbind( "resize", handler );
- }
- };
+// 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" );
+ });
+}
- var throttle = 250,
- handler = function() {
- curr = ( new Date() ).getTime();
- diff = curr - lastCall;
+// For ruling out shadows via css
+if ( !$.support.boxShadow ) {
+ $( "html" ).addClass( "ui-mobile-nosupport-boxshadow" );
+}
- if ( diff >= throttle ) {
+})( jQuery );
- lastCall = curr;
- $( this ).trigger( "throttledresize" );
+(function( $, undefined ) {
- } else {
+$.widget( "mobile.page", $.mobile.widget, {
+ options: {
+ theme: "c",
+ domCache: false,
+ keepNativeDefault: ":jqmData(role='none'), :jqmData(role='nojs')"
+ },
- if ( heldCall ) {
- clearTimeout( heldCall );
- }
+ _create: function() {
+
+ var self = this;
+
+ // if false is returned by the callbacks do not create the page
+ if ( self._trigger( "beforecreate" ) === false ) {
+ return false;
+ }
- // Promise a held call will still execute
- heldCall = setTimeout( handler, throttle - diff );
- }
- },
- lastCall = 0,
- heldCall,
- curr,
- diff;
-})();
+ 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 ) );
+ }
+ },
-$.each({
- scrollstop: "scrollstart",
- taphold: "tap",
- swipeleft: "swipe",
- swiperight: "swipe"
-}, function( event, sourceEvent ) {
+ keepNativeSelector: function() {
+ var options = this.options,
+ keepNativeDefined = options.keepNative && $.trim( options.keepNative );
- $.event.special[ event ] = {
- setup: function() {
- $( this ).bind( sourceEvent, $.noop );
+ if ( keepNativeDefined && options.keepNative !== options.keepNativeDefault ) {
+ return [options.keepNative, options.keepNativeDefault].join( ", " );
}
- };
+
+ return options.keepNativeDefault;
+ }
});
+})( jQuery );
-})( jQuery, this );
// Script: jQuery hashchange event
//
// *Version: 1.3, Last updated: 7/21/2010*
@@ -1373,9 +2350,7 @@ $.each({
// extra awesomeness that BBQ provides. This plugin will be included as
// part of jQuery BBQ, but also be available separately.
-(function($,window,undefined){
- '$:nomunge'; // Used by YUI compressor.
-
+(function( $, window, undefined ) {
// Reused string.
var str_hashchange = 'hashchange',
@@ -1541,14 +2516,14 @@ $.each({
// 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(){
+ 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; },
+ fn_retval = function( val ) { return val; },
history_set = fn_retval,
history_get = fn_retval;
@@ -1585,7 +2560,7 @@ $.each({
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
- $.browser.msie && !supports_onhashchange && (function(){
+ $.browser.msie && !supports_onhashchange && (function() {
// Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8
// when running in "IE7 compatibility" mode.
@@ -1594,7 +2569,7 @@ $.each({
// When the event is bound and polling starts in IE 6/7, create a hidden
// Iframe for history handling.
- self.start = function(){
+ self.start = function() {
if ( !iframe ) {
iframe_src = $.fn[ str_hashchange ].src;
iframe_src = iframe_src && iframe_src + get_fragment();
@@ -1605,7 +2580,7 @@ $.each({
// When Iframe has completely loaded, initialize the history and
// start polling.
- .one( 'load', function(){
+ .one( 'load', function() {
iframe_src || history_set( get_fragment() );
poll();
})
@@ -1621,7 +2596,7 @@ $.each({
// 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(){
+ doc.onpropertychange = function() {
try {
if ( event.propertyName === 'title' ) {
iframe.document.title = doc.title;
@@ -1676,282 +2651,174 @@ $.each({
})();
})(jQuery,this);
-/*
-* "page" plugin
-*/
-
-(function( $, undefined ) {
-
-$.widget( "mobile.page", $.mobile.widget, {
- options: {
- theme: "c",
- domCache: false,
- keepNativeDefault: ":jqmData(role='none'), :jqmData(role='nojs')"
- },
-
- _create: function() {
-
- this._trigger( "beforecreate" );
-
- this.element
- .attr( "tabindex", "0" )
- .addClass( "ui-page ui-body-" + 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 );
-/*
-* "core" - The base file for jQm
-*/
(function( $, window, undefined ) {
- var nsNormalizeDict = {};
-
- // jQuery.mobile configurable options
- $.extend( $.mobile, {
-
- // Namespace used framework-wide for data-attrs. Default is no namespace
- ns: "",
-
- // Define the url parameter used for referencing widget-generated sub-pages.
- // Translates to to example.html&ui-page=subpageIdentifier
- // hash segment before &ui-page= is used to make Ajax request
- subPageUrlKey: "ui-page",
-
- // Class assigned to page currently in view, and during transitions
- activePageClass: "ui-page-active",
-
- // Class used for "active" button state, from CSS framework
- activeBtnClass: "ui-btn-active",
-
- // 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: "slide",
-
- // Minimum scroll distance that will be remembered when returning to a page
- minScrollBack: 250,
-
- // Set default dialog transition - 'none' for no transitions
- defaultDialogTransition: "pop",
+var createHandler = function( sequential ) {
- // Show loading message during Ajax requests
- // if false, message will not appear, but loading classes will still be toggled on html el
- loadingMessage: "loading",
+ // Default to sequential
+ if ( sequential === undefined ) {
+ sequential = true;
+ }
- // Error response message - appears when an Ajax page request fails
- pageLoadErrorMessage: "Error Loading Page",
+ 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" || Math.max( $( window ).scrollTop(), toScroll ) > $.mobile.getMaxScrollForTransition(),
+ toPreClass = " ui-page-pre-in",
+ toggleViewportClass = function() {
+ $.mobile.pageContainer.toggleClass( "ui-mobile-viewport-transitioning viewport-" + name );
+ },
+ scrollPage = function() {
+ // By using scrollTo instead of silentScroll, we can keep things better in order
+ // Just to be precautios, disable scrollstart listening like silentScroll would
+ $.event.special.scrollstart.enabled = false;
- //automatically initialize the DOM when it's ready
- autoInitializePage: true,
+ window.scrollTo( 0, toScroll );
- pushStateEnabled: true,
+ // 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 );
+ }
- // turn of binding to the native orientationchange due to android orientation behavior
- orientationChangeEnabled: true,
+ // 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 );
+ },
- // Support conditions that must be met in order to proceed
- // default enhanced qualifications are media query support OR IE 7+
- gradeA: function(){
- return $.support.mediaquery || $.mobile.browser.ie && $.mobile.browser.ie >= 7;
- },
+ doneOut = function() {
- // TODO might be useful upstream in jquery itself ?
- keyCode: {
- ALT: 18,
- BACKSPACE: 8,
- CAPS_LOCK: 20,
- COMMA: 188,
- COMMAND: 91,
- COMMAND_LEFT: 91, // COMMAND
- COMMAND_RIGHT: 93,
- CONTROL: 17,
- DELETE: 46,
- DOWN: 40,
- END: 35,
- ENTER: 13,
- ESCAPE: 27,
- HOME: 36,
- INSERT: 45,
- LEFT: 37,
- MENU: 93, // COMMAND_RIGHT
- NUMPAD_ADD: 107,
- NUMPAD_DECIMAL: 110,
- NUMPAD_DIVIDE: 111,
- NUMPAD_ENTER: 108,
- NUMPAD_MULTIPLY: 106,
- NUMPAD_SUBTRACT: 109,
- PAGE_DOWN: 34,
- PAGE_UP: 33,
- PERIOD: 190,
- RIGHT: 39,
- SHIFT: 16,
- SPACE: 32,
- TAB: 9,
- UP: 38,
- WINDOWS: 91 // COMMAND
- },
+ if ( $from && sequential ) {
+ cleanFrom();
+ }
- // 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;
- }
+ startIn();
+ },
- // prevent scrollstart and scrollstop events
- $.event.special.scrollstart.enabled = false;
+ startIn = function() {
- setTimeout(function() {
- window.scrollTo( 0, ypos );
- $( document ).trigger( "silentscroll", { x: 0, y: ypos });
- }, 20 );
+ // Prevent flickering in phonegap container: see comments at #4024 regarding iOS
+ $to.css( "z-index", -10 );
- setTimeout(function() {
- $.event.special.scrollstart.enabled = true;
- }, 150 );
- },
+ $to.addClass( $.mobile.activePageClass + toPreClass );
- // Expose our cache for testing purposes.
- nsNormalizeDict: nsNormalizeDict,
+ // Send focus to page as it is now display: block
+ $.mobile.focusPage( $to );
- // 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 ) {
- if ( !prop ) {
- return;
- }
+ // Set to page height
+ $to.height( screenHeight + toScroll );
- return nsNormalizeDict[ prop ] || ( nsNormalizeDict[ prop ] = $.camelCase( $.mobile.ns + prop ) );
- },
+ scrollPage();
- getInheritedTheme: function( el, defaultTheme ) {
+ // Restores visibility of the new page: added together with $to.css( "z-index", -10 );
+ $to.css( "z-index", "" );
- // 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.
+ if ( !none ) {
+ $to.animationComplete( doneIn );
+ }
- var e = el[ 0 ],
- ltr = "",
- re = /ui-(bar|body)-([a-z])\b/,
- c, m;
+ $to
+ .removeClass( toPreClass )
+ .addClass( name + " in" + reverseClass );
- while ( e ) {
- var c = e.className || "";
- if ( ( m = re.exec( c ) ) && ( ltr = m[ 2 ] ) ) {
- // We found a parent with a theme class
- // on it so bail from this loop.
- break;
+ if ( none ) {
+ doneIn();
}
- e = e.parentNode;
- }
-
- // Return the theme letter we found, if none, return the
- // specified default.
- return ltr || defaultTheme || "a";
- }
- });
+ },
- // 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" ) {
- result = this.data( prop ? $.mobile.nsNormalize( prop ) : prop, value );
- }
- return result;
- };
+ doneIn = function() {
- $.jqmData = function( elem, prop, value ) {
- var result;
- if ( typeof prop != "undefined" ) {
- result = $.data( elem, prop ? $.mobile.nsNormalize( prop ) : prop, value );
- }
- return result;
- };
+ if ( !sequential ) {
- $.fn.jqmRemoveData = function( prop ) {
- return this.removeData( $.mobile.nsNormalize( prop ) );
- };
+ if ( $from ) {
+ cleanFrom();
+ }
+ }
- $.jqmRemoveData = function( elem, prop ) {
- return $.removeData( elem, $.mobile.nsNormalize( prop ) );
- };
+ $to
+ .removeClass( "out in reverse " + name )
+ .height( "" );
- $.fn.removeWithDependents = function() {
- $.removeWithDependents( this );
- };
+ toggleViewportClass();
- $.removeWithDependents = function( elem ) {
- var $elem = $( elem );
+ // 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();
+ }
- ( $elem.jqmData('dependents') || $() ).remove();
- $elem.remove();
- };
+ deferred.resolve( name, reverse, $to, $from, true );
+ };
- $.fn.addDependents = function( newDependents ) {
- $.addDependents( $(this), newDependents );
- };
+ toggleViewportClass();
- $.addDependents = function( elem, newDependents ) {
- var dependents = $(elem).jqmData( 'dependents' ) || $();
+ if ( $from && !none ) {
+ startOut();
+ }
+ else {
+ doneOut();
+ }
- $(elem).jqmData( 'dependents', $.merge(dependents, newDependents) );
+ return deferred.promise();
};
+};
- // note that this helper doesn't attempt to handle the callback
- // or setting of an html elements text, its only purpose is
- // to return the html encoded version of the text in all cases. (thus the name)
- $.fn.getEncodedText = function() {
- return $( "
" ).text( $(this).text() ).html();
+// generate the handlers from the above
+var sequentialHandler = createHandler(),
+ simultaneousHandler = createHandler( false ),
+ defaultGetMaxScrollForTransition = function() {
+ return $.mobile.getScreenHeight() * 3;
};
- // Monkey-patching Sizzle to filter the :jqmData selector
- var oldFind = $.find,
- jqmDataRE = /:jqmData\(([^)]*)\)/g;
+// Make our transition handler the public default.
+$.mobile.defaultTransitionHandler = sequentialHandler;
- $.find = function( selector, context, ret, extra ) {
- selector = selector.replace( jqmDataRE, "[data-" + ( $.mobile.ns || "" ) + "$1]" );
+//transition handler dictionary for 3rd party transitions
+$.mobile.transitionHandlers = {
+ "default": $.mobile.defaultTransitionHandler,
+ "sequential": sequentialHandler,
+ "simultaneous": simultaneousHandler
+};
- return oldFind.call( this, selector, context, ret, extra );
- };
+$.mobile.transitionFallbacks = {};
- $.extend( $.find, oldFind );
+// If transition is defined, check if css 3D transforms are supported, and if not, if a fallback is specified
+$.mobile._maybeDegradeTransition = function( transition ) {
+ if ( transition && !$.support.cssTransform3d && $.mobile.transitionFallbacks[ transition ] ) {
+ transition = $.mobile.transitionFallbacks[ transition ];
+ }
- $.find.matches = function( expr, set ) {
- return $.find( expr, null, null, set );
- };
+ return transition;
+};
- $.find.matchesSelector = function( node, expr ) {
- return $.find( expr, null, null, [ node ] ).length > 0;
- };
+// Set the getMaxScrollForTransition to default if no implementation was set by user
+$.mobile.getMaxScrollForTransition = $.mobile.getMaxScrollForTransition || defaultGetMaxScrollForTransition;
})( jQuery, this );
-/*
-* core utilities for auto ajax navigation, base tag mgmt,
-*/
-
-( function( $, undefined ) {
+(function( $, undefined ) {
//define vars for interal use
var $window = $( window ),
@@ -1988,6 +2855,26 @@ $.widget( "mobile.page", $.mobile.widget, {
//
urlParseRE: /^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,
+ // Abstraction to address xss (Issue #4787) by removing the authority in
+ // browsers that auto decode it. All references to location.href should be
+ // replaced with a call to this method so that it can be dealt with properly here
+ getLocation: function( url ) {
+ var uri = url ? this.parseUrl( url ) : location,
+ hash = this.parseUrl( url || location.href ).hash;
+
+ // mimic the browser with an empty string when the hash is empty
+ hash = hash === "#" ? "" : hash;
+
+ // Make sure to parse the url or the location object for the hash because using location.hash
+ // is autodecoded in firefox, the rest of the url should be from the object (location unless
+ // we're testing) to avoid the inclusion of the authority
+ return uri.protocol + "//" + uri.host + uri.pathname + uri.search + hash;
+ },
+
+ parseLocation: function() {
+ return this.parseUrl( this.getLocation() );
+ },
+
//Parse a URL into a structure that allows easy access to
//all of the URL components by name.
parseUrl: function( url ) {
@@ -2078,6 +2965,10 @@ $.widget( "mobile.page", $.mobile.widget, {
return relUrl;
}
+ if ( absUrl === undefined ) {
+ absUrl = documentBase;
+ }
+
var relObj = path.parseUrl( relUrl ),
absObj = path.parseUrl( absUrl ),
protocol = relObj.protocol || absObj.protocol,
@@ -2102,19 +2993,20 @@ $.widget( "mobile.page", $.mobile.widget, {
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.
+ // 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 u.hrefNoHash.replace( documentBase.domain, "" ).split( dialogHashKey )[0];
}
- return absUrl;
+
+ return window.decodeURIComponent(absUrl);
},
//get path from current hash, or from a file path
get: function( newPath ) {
- if( newPath === undefined ) {
- newPath = location.hash;
+ if ( newPath === undefined ) {
+ newPath = path.parseLocation().hash;
}
return path.stripHash( newPath ).replace( /[^\/]*\.[^\/*]+$/, '' );
},
@@ -2151,6 +3043,10 @@ $.widget( "mobile.page", $.mobile.widget, {
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 ) {
@@ -2193,7 +3089,20 @@ $.widget( "mobile.page", $.mobile.widget, {
if ( u.protocol !== "" ) {
return ( u.hash && ( u.hrefNoHash === documentUrl.hrefNoHash || ( documentBaseDiffers && u.hrefNoHash === documentBase.hrefNoHash ) ) );
}
- return (/^#/).test( u.href );
+ return ( /^#/ ).test( u.href );
+ },
+
+
+ // Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR
+ // requests if the document doing the request was loaded via the file:// protocol.
+ // This is usually to allow the application to "phone home" and fetch app specific
+ // data. We normally let the browser handle external/cross-domain urls, but if the
+ // allowCrossDomainPages option is true, we will allow cross-domain http/https
+ // requests to go through our page loading logic.
+ isPermittedCrossDomainRequest: function( docUrl, reqUrl ) {
+ return $.mobile.allowCrossDomainPages &&
+ docUrl.protocol === "file:" &&
+ reqUrl.search( /^https?:/ ) !== -1;
}
},
@@ -2226,7 +3135,7 @@ $.widget( "mobile.page", $.mobile.widget, {
// 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() ) {
+ if ( urlHistory.getNext() ) {
urlHistory.clearForward();
}
@@ -2243,11 +3152,11 @@ $.widget( "mobile.page", $.mobile.widget, {
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
+ // check if url is 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 ) {
+ if ( decodeURIComponent( opts.currentUrl ) === decodeURIComponent( historyEntry.url ) ) {
//define back and forward by whether url is older or newer than current page
back = i < urlHistory.activeIndex;
forward = !back;
@@ -2258,9 +3167,9 @@ $.widget( "mobile.page", $.mobile.widget, {
// save new page index, null check to prevent falsey 0 result
this.activeIndex = newActiveIndex !== undefined ? newActiveIndex : this.activeIndex;
- if( back ) {
+ if ( back ) {
( opts.either || opts.isBack )( true );
- } else if( forward ) {
+ } else if ( forward ) {
( opts.either || opts.isForward )( false );
}
},
@@ -2286,14 +3195,16 @@ $.widget( "mobile.page", $.mobile.widget, {
$base = $head.children( "base" ),
//tuck away the original document URL minus any fragment.
- documentUrl = path.parseUrl( location.href ),
+ documentUrl = path.parseLocation(),
//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 );
+ documentBaseDiffers = ( documentUrl.hrefNoHash !== documentBase.hrefNoHash ),
+
+ getScreenHeight = $.mobile.getScreenHeight;
//base element management, defined depending on dynamic base tag support
var base = $.support.dynamicBaseTag ? {
@@ -2313,26 +3224,48 @@ $.widget( "mobile.page", $.mobile.widget, {
} : undefined;
-/*
- internal utility functions
---------------------------------------*/
-
+ /* internal utility functions */
+
+ // NOTE Issue #4950 Android phonegap doesn't navigate back properly
+ // when a full page refresh has taken place. It appears that hashchange
+ // and replacestate history alterations work fine but we need to support
+ // both forms of history traversal in our code that uses backward history
+ // movement
+ $.mobile.back = function() {
+ var nav = window.navigator;
+
+ // if the setting is on and the navigator object is
+ // available use the phonegap navigation capability
+ if( this.phonegapNavigationEnabled &&
+ nav &&
+ nav.app &&
+ nav.app.backHistory ){
+ nav.app.backHistory();
+ } else {
+ window.history.back();
+ }
+ };
//direct focus to the page title, or otherwise first focusable element
- function reFocus( page ) {
- var pageTitle = page.find( ".ui-title:eq(0)" );
+ $.mobile.focusPage = function ( page ) {
+ var autofocus = page.find( "[autofocus]" ),
+ pageTitle = page.find( ".ui-title:eq(0)" );
- if( pageTitle.length ) {
- pageTitle.focus();
+ if ( autofocus.length ) {
+ autofocus.focus();
+ return;
}
- else{
+
+ 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 ) ) {
+ if ( !!$activeClickedLink && ( !$activeClickedLink.closest( "." + $.mobile.activePageClass ).length || forceRemoval ) ) {
$activeClickedLink.removeClass( $.mobile.activeBtnClass );
}
$activeClickedLink = null;
@@ -2340,38 +3273,26 @@ $.widget( "mobile.page", $.mobile.widget, {
function releasePageTransitionLock() {
isPageTransitioning = false;
- if( pageTransitionQueue.length > 0 ) {
+ if ( pageTransitionQueue.length > 0 ) {
$.mobile.changePage.apply( null, pageTransitionQueue.pop() );
}
}
// Save the last scroll distance per page, before it is hidden
var setLastScrollEnabled = true,
- firstScrollElem, getScrollElem, setLastScroll, delayedSetLastScroll;
-
- getScrollElem = function() {
- var scrollElem = $window, activePage,
- touchOverflow = $.support.touchOverflow && $.mobile.touchOverflowEnabled;
-
- if( touchOverflow ){
- activePage = $( ".ui-page-active" );
- scrollElem = activePage.is( ".ui-native-fixed" ) ? activePage.find( ".ui-content" ) : activePage;
- }
-
- return scrollElem;
- };
+ setLastScroll, delayedSetLastScroll;
- setLastScroll = function( scrollElem ) {
+ setLastScroll = function() {
// this barrier prevents setting the scroll value based on the browser
// scrolling the window based on a hashchange
- if( !setLastScrollEnabled ) {
+ if ( !setLastScrollEnabled ) {
return;
}
var active = $.mobile.urlHistory.getActive();
- if( active ) {
- var lastScroll = scrollElem && scrollElem.scrollTop();
+ 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.
@@ -2384,14 +3305,14 @@ $.widget( "mobile.page", $.mobile.widget, {
// to the hash targets location (sometimes the top of the page). once pagechange fires
// getLastScroll is again permitted to operate
delayedSetLastScroll = function() {
- setTimeout( setLastScroll, 100, $(this) );
+ 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;
+ setLastScrollEnabled = false;
});
// handle initial hashchange from chrome :(
@@ -2400,106 +3321,56 @@ $.widget( "mobile.page", $.mobile.widget, {
});
// wait until the mobile page container has been determined to bind to pagechange
- $window.one( "pagecontainercreate", function(){
+ $window.one( "pagecontainercreate", function() {
// once the page has changed, re-enable the scroll recording
$.mobile.pageContainer.bind( "pagechange", function() {
- var scrollElem = getScrollElem();
- setLastScrollEnabled = true;
+ setLastScrollEnabled = true;
// remove any binding that previously existed on the get scroll
// which may or may not be different than the scroll element determined for
// this page previously
- scrollElem.unbind( "scrollstop", delayedSetLastScroll );
+ $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
- scrollElem.bind( "scrollstop", delayedSetLastScroll );
+ $window.bind( "scrollstop", delayedSetLastScroll );
});
});
// bind to scrollstop for the first page as "pagechange" won't be fired in that case
- getScrollElem().bind( "scrollstop", delayedSetLastScroll );
-
- // Make the iOS clock quick-scroll work again if we're using native overflow scrolling
- /*
- if( $.support.touchOverflow ){
- if( $.mobile.touchOverflowEnabled ){
- $( window ).bind( "scrollstop", function(){
- if( $( this ).scrollTop() === 0 ){
- $.mobile.activePage.scrollTop( 0 );
- }
- });
- }
- }
- */
+ $window.bind( "scrollstop", delayedSetLastScroll );
+
+ // No-op implementation of transition degradation
+ $.mobile._maybeDegradeTransition = $.mobile._maybeDegradeTransition || function( transition ) {
+ return transition;
+ };
//function for transitioning between two existing pages
function transitionPages( toPage, fromPage, transition, reverse ) {
- //get current scroll distance
- var active = $.mobile.urlHistory.getActive(),
- touchOverflow = $.support.touchOverflow && $.mobile.touchOverflowEnabled,
- toScroll = active.lastScroll || ( touchOverflow ? 0 : $.mobile.defaultHomeScroll ),
- screenHeight = getScreenHeight();
-
- // Scroll to top, hide addr bar
- window.scrollTo( 0, $.mobile.defaultHomeScroll );
-
- if( fromPage ) {
+ if ( fromPage ) {
//trigger before show/hide events
fromPage.data( "page" )._trigger( "beforehide", null, { nextPage: toPage } );
}
- if( !touchOverflow){
- toPage.height( screenHeight + toScroll );
- }
-
toPage.data( "page" )._trigger( "beforeshow", null, { prevPage: fromPage || $( "" ) } );
//clear page loader
$.mobile.hidePageLoadingMsg();
- if( touchOverflow && toScroll ){
-
- toPage.addClass( "ui-mobile-pre-transition" );
- // Send focus to page as it is now display: block
- reFocus( toPage );
-
- //set page's scrollTop to remembered distance
- if( toPage.is( ".ui-native-fixed" ) ){
- toPage.find( ".ui-content" ).scrollTop( toScroll );
- }
- else{
- toPage.scrollTop( toScroll );
- }
- }
+ transition = $.mobile._maybeDegradeTransition( transition );
//find the transition handler for the specified transition. If there
//isn't one in our transitionHandlers dictionary, use the default one.
//call the handler immediately to kick-off the transition.
- var th = $.mobile.transitionHandlers[transition || "none"] || $.mobile.defaultTransitionHandler,
+ var th = $.mobile.transitionHandlers[ transition || "default" ] || $.mobile.defaultTransitionHandler,
promise = th( transition, reverse, toPage, fromPage );
promise.done(function() {
- //reset toPage height back
- if( !touchOverflow ){
- toPage.height( "" );
- // Send focus to the newly shown page
- reFocus( toPage );
- }
-
- // Jump to top or prev scroll, sometimes on iOS the page has not rendered yet.
- if( !touchOverflow ){
- $.mobile.silentScroll( toScroll );
- }
//trigger show/hide events
- if( fromPage ) {
- if( !touchOverflow ){
- fromPage.height( "" );
- }
-
+ if ( fromPage ) {
fromPage.data( "page" )._trigger( "hide", null, { nextPage: toPage } );
}
@@ -2511,33 +3382,21 @@ $.widget( "mobile.page", $.mobile.widget, {
}
//simply set the active page's minimum height to screen height, depending on orientation
- function getScreenHeight(){
- var orientation = $.event.special.orientationchange.orientation(),
- port = orientation === "portrait",
- winMin = port ? 480 : 320,
- screenHeight = port ? screen.availHeight : screen.availWidth,
- winHeight = Math.max( winMin, $( window ).height() ),
- pageMin = Math.min( screenHeight, winHeight );
-
- return pageMin;
- }
-
- $.mobile.getScreenHeight = getScreenHeight;
-
- //simply set the active page's minimum height to screen height, depending on orientation
- function resetActivePageHeight(){
- // Don't apply this height in touch overflow enabled mode
- if( $.support.touchOverflow && $.mobile.touchOverflowEnabled ){
- return;
- }
- $( "." + $.mobile.activePageClass ).css( "min-height", getScreenHeight() );
+ function resetActivePageHeight() {
+ var aPage = $( "." + $.mobile.activePageClass ),
+ aPagePadT = parseFloat( aPage.css( "padding-top" ) ),
+ aPagePadB = parseFloat( aPage.css( "padding-bottom" ) ),
+ aPageBorderT = parseFloat( aPage.css( "border-top-width" ) ),
+ aPageBorderB = parseFloat( aPage.css( "border-bottom-width" ) );
+
+ aPage.css( "min-height", getScreenHeight() - aPagePadT - aPagePadB - aPageBorderT - aPageBorderB );
}
//shared page enhancements
function enhancePage( $page, role ) {
// If a role was specified, make sure the data-role attribute
// on the page element is in sync.
- if( role ) {
+ if ( role ) {
$page.attr( "data-" + $.mobile.ns + "role", role );
}
@@ -2545,12 +3404,12 @@ $.widget( "mobile.page", $.mobile.widget, {
$page.page();
}
-/* exposed $.mobile methods */
+ /* exposed $.mobile methods */
//animation complete callback
$.fn.animationComplete = function( callback ) {
- if( $.support.cssTransitions ) {
- return $( this ).one( 'webkitAnimationEnd', callback );
+ if ( $.support.cssTransitions ) {
+ return $( this ).one( 'webkitAnimationEnd animationend', callback );
}
else{
// defer execution for consistency between webkit/non webkit
@@ -2570,43 +3429,27 @@ $.widget( "mobile.page", $.mobile.widget, {
$.mobile.dialogHashKey = dialogHashKey;
- //default non-animation transition handler
- $.mobile.noneTransitionHandler = function( name, reverse, $toPage, $fromPage ) {
- if ( $fromPage ) {
- $fromPage.removeClass( $.mobile.activePageClass );
- }
- $toPage.addClass( $.mobile.activePageClass );
-
- return $.Deferred().resolve( name, reverse, $toPage, $fromPage ).promise();
- };
-
- //default handler for unknown transitions
- $.mobile.defaultTransitionHandler = $.mobile.noneTransitionHandler;
- //transition handler dictionary for 3rd party transitions
- $.mobile.transitionHandlers = {
- none: $.mobile.defaultTransitionHandler
- };
//enable cross-domain page support
$.mobile.allowCrossDomainPages = false;
//return the original document url
- $.mobile.getDocumentUrl = function(asParsedObject) {
+ $.mobile.getDocumentUrl = function( asParsedObject ) {
return asParsedObject ? $.extend( {}, documentUrl ) : documentUrl.href;
};
//return the original document base url
- $.mobile.getDocumentBase = function(asParsedObject) {
+ $.mobile.getDocumentBase = function( asParsedObject ) {
return asParsedObject ? $.extend( {}, documentBase ) : documentBase.href;
};
$.mobile._bindPageRemove = function() {
- var page = $(this);
+ 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')") ) {
+ if ( !page.data( "page" ).options.domCache &&
+ page.is( ":jqmData(external-page='true')" ) ) {
page.bind( 'pagehide.remove', function() {
var $this = $( this ),
@@ -2614,7 +3457,7 @@ $.widget( "mobile.page", $.mobile.widget, {
$this.trigger( prEvent );
- if( !prEvent.isDefaultPrevented() ){
+ if ( !prEvent.isDefaultPrevented() ) {
$this.removeWithDependents();
}
});
@@ -2641,7 +3484,7 @@ $.widget( "mobile.page", $.mobile.widget, {
dupCachedPage = null,
// determine the current base url
- findBaseWithDefault = function(){
+ findBaseWithDefault = function() {
var closestBase = ( $.mobile.activePage && getClosestBaseUrl( $.mobile.activePage ) );
return closestBase || documentBase.hrefNoHash;
},
@@ -2659,12 +3502,12 @@ $.widget( "mobile.page", $.mobile.widget, {
}
// If the caller is using a "post" request, reloadPage must be true
- if( settings.data && settings.type === "post" ){
+ 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.
+ // 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
@@ -2678,7 +3521,9 @@ $.widget( "mobile.page", $.mobile.widget, {
settings.pageContainer = settings.pageContainer || $.mobile.pageContainer;
// Check to see if the page already exists in the DOM.
- page = settings.pageContainer.children( ":jqmData(url='" + dataUrl + "')" );
+ // NOTE do _not_ use the :jqmData psuedo selector because parenthesis
+ // are a valid url char and it breaks on the first occurence
+ page = settings.pageContainer.children( "[data-" + $.mobile.ns +"url='" + dataUrl + "']" );
// If we failed to find the page, check to see if the url is a
// reference to an embedded page. If so, it may have been dynamically
@@ -2686,7 +3531,8 @@ $.widget( "mobile.page", $.mobile.widget, {
// 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 );
+ .attr( "data-" + $.mobile.ns + "url", dataUrl )
+ .jqmData( "url", dataUrl );
}
// If we failed to find a page in the DOM, check the URL to see if it
@@ -2711,11 +3557,6 @@ $.widget( "mobile.page", $.mobile.widget, {
}
}
- // 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
@@ -2737,19 +3578,19 @@ $.widget( "mobile.page", $.mobile.widget, {
mpc.trigger( pblEvent, triggerData );
// If the default behavior is prevented, stop here!
- if( pblEvent.isDefaultPrevented() ){
+ 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(){
+ var loadMsgDelay = setTimeout(function() {
$.mobile.showPageLoadingMsg();
}, settings.loadMsgDelay ),
// Shared logic for clearing timeout and removing message.
- hideMsg = function(){
+ hideMsg = function() {
// Stop message show timer
clearTimeout( loadMsgDelay );
@@ -2759,6 +3600,11 @@ $.widget( "mobile.page", $.mobile.widget, {
};
}
+ // Reset base to the default document base.
+ if ( base ) {
+ base.reset();
+ }
+
if ( !( $.mobile.allowCrossDomainPages || path.isSameDomain( documentUrl, absUrl ) ) ) {
deferred.reject( absUrl, options );
} else {
@@ -2783,11 +3629,11 @@ $.widget( "mobile.page", $.mobile.widget, {
// 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 ( pageElemRegex.test( html ) &&
+ RegExp.$1 &&
+ dataUrlRegex.test( RegExp.$1 ) &&
+ RegExp.$1 ) {
+ url = fileUrl = path.getFilePath( $( "
" + RegExp.$1 + "
" ).text() );
}
if ( base ) {
@@ -2799,7 +3645,7 @@ $.widget( "mobile.page", $.mobile.widget, {
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 ){
+ if ( !page.length ) {
page = $( "
" + html.split( /<\/?body[^>]*>/gmi )[1] + "
" );
}
@@ -2811,11 +3657,11 @@ $.widget( "mobile.page", $.mobile.widget, {
}
//rewrite src and href attrs to use a base url
- if( !$.support.dynamicBaseTag ) {
+ 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',
+ $( this ).is( '[src]' ) ? 'src' : 'action',
thisUrl = $( this ).attr( thisAttr );
// XXX_jblas: We need to fix this so that it removes the document
@@ -2823,7 +3669,7 @@ $.widget( "mobile.page", $.mobile.widget, {
//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 ) ) {
+ if ( !/^(\w+:|#|\/)/.test( thisUrl ) ) {
$( this ).attr( thisAttr, newPath + thisUrl );
}
});
@@ -2847,7 +3693,7 @@ $.widget( "mobile.page", $.mobile.widget, {
// 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 + "')" );
+ page = settings.pageContainer.children( "[data-" + $.mobile.ns +"url='" + dataUrl + "']" );
}
//bind pageHide to removePage after it's hidden, if the page options specify to do so
@@ -2869,7 +3715,7 @@ $.widget( "mobile.page", $.mobile.widget, {
},
error: function( xhr, textStatus, errorThrown ) {
//set base back to current path
- if( base ) {
+ if ( base ) {
base.set( path.get() );
}
@@ -2887,7 +3733,7 @@ $.widget( "mobile.page", $.mobile.widget, {
// 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() ){
+ if ( plfEvent.isDefaultPrevented() ) {
return;
}
@@ -2897,14 +3743,11 @@ $.widget( "mobile.page", $.mobile.widget, {
// Remove loading message.
hideMsg();
- //show error message
- $( "
"+ $.mobile.pageLoadErrorMessage +"
" )
- .css({ "display": "block", "opacity": 0.96, "top": $window.scrollTop() + 100 })
- .appendTo( settings.pageContainer )
- .delay( 800 )
- .fadeOut( 400, function() {
- $( this ).remove();
- });
+ // show error message
+ $.mobile.showPageLoadingMsg( $.mobile.pageLoadErrorMessageTheme, $.mobile.pageLoadErrorMessage, true );
+
+ // hide after delay
+ setTimeout( $.mobile.hidePageLoadingMsg, 1500 );
}
deferred.reject( absUrl, options );
@@ -2930,7 +3773,7 @@ $.widget( "mobile.page", $.mobile.widget, {
// 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 ) {
+ if ( isPageTransitioning ) {
pageTransitionQueue.unshift( arguments );
return;
}
@@ -2951,7 +3794,7 @@ $.widget( "mobile.page", $.mobile.widget, {
mpc.trigger( pbcEvent, triggerData );
// If the default behavior is prevented, stop here!
- if( pbcEvent.isDefaultPrevented() ){
+ if ( pbcEvent.isDefaultPrevented() ) {
return;
}
@@ -2970,7 +3813,7 @@ $.widget( "mobile.page", $.mobile.widget, {
// 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" ) {
+ if ( typeof toPage === "string" ) {
$.mobile.loadPage( toPage, settings )
.done(function( url, options, newPage, dupCachedPage ) {
isPageTransitioning = false;
@@ -3021,9 +3864,19 @@ $.widget( "mobile.page", $.mobile.widget, {
// 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 ) {
+ if ( fromPage && fromPage[0] === toPage[0] && !settings.allowSamePageTransition ) {
isPageTransitioning = false;
mpc.trigger( "pagechange", triggerData );
+
+ // Even if there is no page change to be done, we should keep the urlHistory in sync with the hash changes
+ if ( settings.fromHashChange ) {
+ urlHistory.directHashChange({
+ currentUrl: url,
+ isBack: function() {},
+ isForward: function() {}
+ });
+ }
+
return;
}
@@ -3033,7 +3886,7 @@ $.widget( "mobile.page", $.mobile.widget, {
// 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 ) {
+ if ( settings.fromHashChange ) {
urlHistory.directHashChange({
currentUrl: url,
isBack: function() { historyDir = -1; },
@@ -3043,17 +3896,20 @@ $.widget( "mobile.page", $.mobile.widget, {
// Kill the keyboard.
// XXX_jblas: We need to stop crawling the entire document to kill focus. Instead,
- // we should be tracking focus with a live() handler so we already have
+ // 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();
+ if ( document.activeElement && document.activeElement.nodeName.toLowerCase() !== 'body' ) {
+ $( document.activeElement ).blur();
} else {
$( "input:focus, textarea:focus, select:focus" ).blur();
}
- } catch(e) {}
+ } catch( e ) {}
+
+ // Record whether we are at a place in history where a dialog used to be - if so, do not add a new history entry and do not change the hash either
+ var alreadyThere = false;
// If we're displaying the page as a dialog, we don't want the url
// for the dialog content to be used in the hash. Instead, we want
@@ -3063,11 +3919,30 @@ $.widget( "mobile.page", $.mobile.widget, {
// 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;
+
+ // If we are at a place in history that once belonged to a dialog, reuse
+ // this state without adding to urlHistory and without modifying the hash.
+ // However, if a dialog is already displayed at this point, and we're
+ // about to display another dialog, then we must add another hash and
+ // history entry on top so that one may navigate back to the original dialog
+ if ( active.url.indexOf( dialogHashKey ) > -1 && !$.mobile.activePage.is( ".ui-dialog" ) ) {
+ settings.changeHash = false;
+ alreadyThere = true;
+ }
+
+ // Normally, we tack on a dialog hash key, but if this is the location of a stale dialog,
+ // we reuse the URL from the entry
+ url = ( active.url || "" ) + ( alreadyThere ? "" : dialogHashKey );
+
+ // tack on another dialogHashKey if this is the same as the initial hash
+ // this makes sure that a history entry is created for this dialog
+ if ( urlHistory.activeIndex === 0 && url === urlHistory.initialDst ) {
+ url += dialogHashKey;
+ }
}
// Set the location hash.
- if( settings.changeHash !== false && url ) {
+ if ( settings.changeHash !== false && url ) {
//disable hash listening temporarily
urlHistory.ignoreNextHashChange = true;
//update hash and history
@@ -3076,8 +3951,8 @@ $.widget( "mobile.page", $.mobile.widget, {
// 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 ) {
+ 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" ) ) {
@@ -3085,12 +3960,16 @@ $.widget( "mobile.page", $.mobile.widget, {
}
// Make sure we have a transition defined.
- settings.transition = settings.transition
- || ( ( historyDir && !activeIsInitialPage ) ? active.transition : undefined )
- || ( isDialog ? $.mobile.defaultDialogTransition : $.mobile.defaultPageTransition );
+ 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 ) {
+ if ( !historyDir ) {
+ // Overwrite the current entry if it's a leftover from a dialog
+ if ( alreadyThere ) {
+ urlHistory.activeIndex = Math.max( 0, urlHistory.activeIndex - 1 );
+ }
urlHistory.addNew( url, settings.transition, pageTitle, pageUrl, settings.role );
}
@@ -3104,7 +3983,7 @@ $.widget( "mobile.page", $.mobile.widget, {
settings.reverse = settings.reverse || historyDir < 0;
transitionPages( toPage, fromPage, settings.transition, settings.reverse )
- .done(function() {
+ .done(function( name, reverse, $to, $from, alreadyFocused ) {
removeActiveLinkClass();
//if there's a duplicateCachedPage, remove it from the DOM now that it's hidden
@@ -3112,8 +3991,13 @@ $.widget( "mobile.page", $.mobile.widget, {
settings.duplicateCachedPage.remove();
}
- //remove initial build class (only present on first pageshow)
- $html.removeClass( "ui-mobile-rendering" );
+ // 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();
@@ -3147,7 +4031,7 @@ $.widget( "mobile.page", $.mobile.widget, {
// 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" ) {
+ if ( ( typeof ele.nodeName === "string" ) && ele.nodeName.toLowerCase() === "a" ) {
break;
}
ele = ele.parentNode;
@@ -3169,18 +4053,22 @@ $.widget( "mobile.page", $.mobile.widget, {
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(){
-
+ //the following deferred is resolved in the init file
+ $.mobile.navreadyDeferred = $.Deferred();
+ $.mobile.navreadyDeferred.done(function() {
//bind to form submit events, handle with Ajax
- $( "form" ).live('submit', function( event ) {
+ $( document ).delegate( "form", "submit", function( event ) {
var $this = $( this );
- if( !$.mobile.ajaxEnabled ||
- $this.is( ":jqmData(ajax='false')" ) ) {
- return;
- }
+
+ 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" ),
@@ -3203,10 +4091,9 @@ $.widget( "mobile.page", $.mobile.widget, {
}
}
- url = path.makeUrlAbsolute( url, getClosestBaseUrl($this) );
+ url = path.makeUrlAbsolute( url, getClosestBaseUrl( $this ) );
- //external submits use regular HTTP
- if( path.isExternal( url ) || target ) {
+ if ( ( path.isExternal( url ) && !path.isPermittedCrossDomainRequest( documentUrl, url ) ) || target ) {
return;
}
@@ -3216,7 +4103,7 @@ $.widget( "mobile.page", $.mobile.widget, {
type: type && type.length && type.toLowerCase() || "get",
data: $this.serialize(),
transition: $this.jqmData( "transition" ),
- direction: $this.jqmData( "direction" ),
+ reverse: $this.jqmData( "direction" ) === "reverse",
reloadPage: true
}
);
@@ -3226,45 +4113,53 @@ $.widget( "mobile.page", $.mobile.widget, {
//add active state on vclick
$( document ).bind( "vclick", function( event ) {
// if this isn't a left click we don't care. Its important to note
- // that when the virtual event is generated it will create
- if ( event.which > 1 || !$.mobile.linkBindingEnabled ){
+ // 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();
}
}
});
// click routing - direct to HTTP or Ajax, accordingly
$( document ).bind( "click", function( event ) {
- if( !$.mobile.linkBindingEnabled ){
+ if ( !$.mobile.linkBindingEnabled ) {
return;
}
- var link = findClosestLink( event.target );
+ 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
- if ( !link || event.which > 1) {
+ // 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;
}
- var $link = $( link ),
- //remove active link class if external (then it won't be there if you come back)
- httpCleanup = function(){
- window.setTimeout( function() { removeActiveLinkClass( true ); }, 200 );
- };
+ //remove active link class if external (then it won't be there if you come back)
+ httpCleanup = function() {
+ window.setTimeout(function() { removeActiveLinkClass( true ); }, 200 );
+ };
//if there's a data-rel=back attr, go back in history
- if( $link.is( ":jqmData(rel='back')" ) ) {
- window.history.back();
+ if ( $link.is( ":jqmData(rel='back')" ) ) {
+ $.mobile.back();
return false;
}
@@ -3274,7 +4169,7 @@ $.widget( "mobile.page", $.mobile.widget, {
href = path.makeUrlAbsolute( $link.attr( "href" ) || "#", baseUrl );
//if ajax is disabled, exit early
- if( !$.mobile.ajaxEnabled && !path.isEmbeddedPage( href ) ){
+ if ( !$.mobile.ajaxEnabled && !path.isEmbeddedPage( href ) ) {
httpCleanup();
//use default click handling
return;
@@ -3289,7 +4184,7 @@ $.widget( "mobile.page", $.mobile.widget, {
// 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 ) {
+ if ( href.search( "#" ) !== -1 ) {
href = href.replace( /[^#]*#/, "" );
if ( !href ) {
//link was an empty hash meant purely
@@ -3314,14 +4209,13 @@ $.widget( "mobile.page", $.mobile.widget, {
// 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 );
+ isExternal = useDefaultUrlHandling || ( path.isExternal( href ) && !path.isPermittedCrossDomainRequest( documentUrl, href ) );
- if( isExternal ) {
+ if ( isExternal ) {
httpCleanup();
//use default click handling
return;
@@ -3329,29 +4223,28 @@ $.widget( "mobile.page", $.mobile.widget, {
//use ajax
var transition = $link.jqmData( "transition" ),
- direction = $link.jqmData( "direction" ),
- reverse = ( direction && direction === "reverse" ) ||
+ reverse = $link.jqmData( "direction" ) === "reverse" ||
// deprecated - remove by 1.0
$link.jqmData( "back" ),
//this may need to be more specific as we use data-rel more
role = $link.attr( "data-" + $.mobile.ns + "rel" ) || undefined;
- $.mobile.changePage( href, { transition: transition, reverse: reverse, role: role } );
+ $.mobile.changePage( href, { transition: transition, reverse: reverse, role: role, link: $link } );
event.preventDefault();
});
//prefetch pages when anchors with data-prefetch are encountered
- $( ".ui-page" ).live( "pageshow.prefetch", function() {
+ $( document ).delegate( ".ui-page", "pageshow.prefetch", function() {
var urls = [];
- $( this ).find( "a:jqmData(prefetch)" ).each(function(){
- var $link = $(this),
+ $( 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.loadPage( url, { role: $link.attr( "data-" + $.mobile.ns + "rel" ) } );
}
});
});
@@ -3362,6 +4255,9 @@ $.widget( "mobile.page", $.mobile.widget, {
//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,
+ // "navigate" event fired to allow others to take advantage of the more robust hashchange handling
+ navEvent = new $.Event( "navigate" ),
+
// default options for the changPage calls made after examining the current state
// of the page and the hash
changePageOptions = {
@@ -3370,23 +4266,34 @@ $.widget( "mobile.page", $.mobile.widget, {
fromHashChange: true
};
+ if ( 0 === urlHistory.stack.length ) {
+ urlHistory.initialDst = to;
+ }
+
+ // We should probably fire the "navigate" event from those places that make calls to _handleHashChange,
+ // and have _handleHashChange hook into the "navigate" event instead of triggering it here
+ $.mobile.pageContainer.trigger( navEvent );
+ if ( navEvent.isDefaultPrevented() ) {
+ return;
+ }
+
//if listening is disabled (either globally or temporarily), or it's a dialog hash
- if( !$.mobile.hashListeningEnabled || urlHistory.ignoreNextHashChange ) {
+ if ( !$.mobile.hashListeningEnabled || urlHistory.ignoreNextHashChange ) {
urlHistory.ignoreNextHashChange = false;
return;
}
// special case for dialogs
- if( urlHistory.stack.length > 1 && to.indexOf( dialogHashKey ) > -1 ) {
+ if ( urlHistory.stack.length > 1 && to.indexOf( dialogHashKey ) > -1 && urlHistory.initialDst !== to ) {
// If current active page is not a dialog skip the dialog and continue
// in the same direction
- if(!$.mobile.activePage.is( ".ui-dialog" )) {
+ 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(); },
+ isBack: function() { $.mobile.back(); },
isForward: function() { window.history.forward(); }
});
@@ -3409,7 +4316,7 @@ $.widget( "mobile.page", $.mobile.widget, {
// as most of this is lost by the domCache cleaning
$.extend( changePageOptions, {
role: active.role,
- transition: active.transition,
+ transition: active.transition,
reverse: isBack
});
}
@@ -3425,6 +4332,14 @@ $.widget( "mobile.page", $.mobile.widget, {
// 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;
+
+ // If we're about to go to an initial URL that contains a reference to a non-existent
+ // internal page, go to the first page instead. We know that the initial hash refers to a
+ // non-existent page, because the initial hash did not end up in the initial urlHistory entry
+ if ( to === path.makeUrlAbsolute( '#' + urlHistory.initialDst, documentBase ) &&
+ urlHistory.stack.length && urlHistory.stack[0].url !== urlHistory.initialDst.replace( dialogHashKey, "" ) ) {
+ to = $.mobile.firstPage;
+ }
$.mobile.changePage( to, changePageOptions );
} else {
//there's no hash, go to the first page in the dom
@@ -3434,28 +4349,33 @@ $.widget( "mobile.page", $.mobile.widget, {
//hashchange event handler
$window.bind( "hashchange", function( e, triggered ) {
- $.mobile._handleHashChange( location.hash );
+ // Firefox auto-escapes the location.hash as for v13 but
+ // leaves the href untouched
+ $.mobile._handleHashChange( path.parseLocation().hash );
});
//set page min-heights to be device specific
$( document ).bind( "pageshow", resetActivePageHeight );
$( window ).bind( "throttledresize", resetActivePageHeight );
- };//_registerInternalEvents callback
+ });//navreadyDeferred done callback
})( jQuery );
-/*
-* history.pushState support, layered on top of hashchange
-*/
-( function( $, window ) {
+(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 );
+ url = $.mobile.path.parseLocation(),
+ mobileinitDeferred = $.Deferred(),
+ domreadyDeferred = $.Deferred();
+
+ $( document ).ready( $.proxy( domreadyDeferred, "resolve" ) );
+
+ $( document ).one( "mobileinit", $.proxy( mobileinitDeferred, "resolve" ) );
$.extend( pushStateHandler, {
// TODO move to a path helper, this is rather common functionality
@@ -3463,14 +4383,16 @@ $.widget( "mobile.page", $.mobile.widget, {
return url.pathname + url.search;
})(),
- initialHref: url.hrefNoHash,
+ hashChangeTimeout: 200,
+
+ hashChangeEnableTimer: undefined,
- // Flag for tracking if a Hashchange naturally occurs after each popstate + replace
- hashchangeFired: false,
+ initialHref: url.hrefNoHash,
state: function() {
return {
- hash: location.hash || "#" + self.initialFilePath,
+ // firefox auto decodes the url when using location.hash but not href
+ hash: $.mobile.path.parseLocation().hash || "#" + self.initialFilePath,
title: document.title,
// persist across refresh
@@ -3483,9 +4405,9 @@ $.widget( "mobile.page", $.mobile.widget, {
subkey = "&" + $.mobile.subPageUrlKey,
dialogIndex = url.indexOf( dialog );
- if( dialogIndex > -1 ) {
+ if ( dialogIndex > -1 ) {
url = url.slice( 0, dialogIndex ) + "#" + url.slice( dialogIndex );
- } else if( url.indexOf( subkey ) > -1 ) {
+ } else if ( url.indexOf( subkey ) > -1 ) {
url = url.split( subkey ).join( "#" + subkey );
}
@@ -3503,16 +4425,19 @@ $.widget( "mobile.page", $.mobile.widget, {
// handling has taken place and set the state of the DOM
onHashChange: function( e ) {
// disable this hash change
- if( self.onHashChangeDisabled ){
+ if ( self.onHashChangeDisabled ) {
return;
}
-
+
var href, state,
- hash = location.hash,
+ // firefox auto decodes the url when using location.hash but not href
+ hash = $.mobile.path.parseLocation().hash,
isPath = $.mobile.path.isPath( hash ),
- resolutionUrl = isPath ? location.href : $.mobile.getDocumentUrl();
+ resolutionUrl = isPath ? $.mobile.path.getLocation() : $.mobile.getDocumentUrl();
+
hash = isPath ? hash.replace( "#", "" ) : hash;
+
// propulate the hash when its not available
state = self.state();
@@ -3527,7 +4452,7 @@ $.widget( "mobile.page", $.mobile.widget, {
// 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()
+ // them. This allows us to work around the case where $.mobile.back()
// is called to transition from an external page to an embedded page.
// In that particular case, a hashchange event is *NOT* generated by the browser.
// Ensuring each history entry has a state object means that onPopState()
@@ -3539,23 +4464,29 @@ $.widget( "mobile.page", $.mobile.widget, {
// on popstate (ie back or forward) we need to replace the hash that was there previously
// cleaned up by the additional hash handling
onPopState: function( e ) {
- var poppedState = e.originalEvent.state, holdnexthashchange = false;
+ var poppedState = e.originalEvent.state,
+ fromHash, toHash, hashChanged;
+
+ // if there's no state its not a popstate we care about, eg chrome's initial popstate
+ if ( poppedState ) {
+ // if we get two pop states in under this.hashChangeTimeout
+ // make sure to clear any timer set for the previous change
+ clearTimeout( self.hashChangeEnableTimer );
+
+ // make sure to enable hash handling for the the _handleHashChange call
+ self.nextHashChangePrevented( false );
- // if there's no state its not a popstate we care about, ie chrome's initial popstate
- // or forward popstate
- if( poppedState ) {
- // disable any hashchange triggered by the browser
+ // change the page based on the hash in the popped state
+ $.mobile._handleHashChange( poppedState.hash );
+
+ // prevent any hashchange in the next self.hashChangeTimeout
self.nextHashChangePrevented( true );
- // defer our manual hashchange until after the browser fired
- // version has come and gone
- setTimeout(function() {
- // make sure that the manual hash handling takes place
+ // re-enable hash change handling after swallowing a possible hash
+ // change event that comes on all popstates courtesy of browsers like Android
+ self.hashChangeEnableTimer = setTimeout( function() {
self.nextHashChangePrevented( false );
-
- // change the page based on the hash
- $.mobile._handleHashChange( poppedState.hash );
- }, 100);
+ }, self.hashChangeTimeout );
}
},
@@ -3567,66 +4498,97 @@ $.widget( "mobile.page", $.mobile.widget, {
// if there's no hash, we need to replacestate for returning to home
if ( location.hash === "" ) {
- history.replaceState( self.state(), document.title, location.href );
+ history.replaceState( self.state(), document.title, $.mobile.path.getLocation() );
}
}
});
- $( function() {
- if( $.mobile.pushStateEnabled && $.support.pushState ){
- pushStateHandler.init();
- }
- });
+ // We need to init when "mobileinit", "domready", and "navready" have all happened
+ $.when( domreadyDeferred, mobileinitDeferred, $.mobile.navreadyDeferred ).done(function() {
+ if ( $.mobile.pushStateEnabled && $.support.pushState ) {
+ pushStateHandler.init();
+ }
+ });
+})( 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 pop in non-3D supporting browsers (which tend to handle complex transitions poorly in general
+*/
+
+(function( $, window, undefined ) {
+
+$.mobile.transitionFallbacks.pop = "fade";
+
})( jQuery, this );
/*
-* "transitions" plugin - Page change tranistions
+* fallback transition for slide in non-3D supporting browsers (which tend to handle complex transitions poorly in general
*/
(function( $, window, undefined ) {
-function css3TransitionHandler( name, reverse, $to, $from ) {
-
- var deferred = new $.Deferred(),
- reverseClass = reverse ? " reverse" : "",
- viewportClass = "ui-mobile-viewport-transitioning viewport-" + name,
- doneFunc = function() {
+// Use the simultaneous transitions handler for slide transitions
+$.mobile.transitionHandlers.slide = $.mobile.transitionHandlers.simultaneous;
- $to.add( $from ).removeClass( "out in reverse " + name );
+// Set the slide transitions's fallback to "fade"
+$.mobile.transitionFallbacks.slide = "fade";
- if ( $from && $from[ 0 ] !== $to[ 0 ] ) {
- $from.removeClass( $.mobile.activePageClass );
- }
+})( jQuery, this );
+/*
+* fallback transition for slidedown in non-3D supporting browsers (which tend to handle complex transitions poorly in general
+*/
- $to.parent().removeClass( viewportClass );
+(function( $, window, undefined ) {
- deferred.resolve( name, reverse, $to, $from );
- };
+$.mobile.transitionFallbacks.slidedown = "fade";
- $to.animationComplete( doneFunc );
+})( jQuery, this );
+/*
+* fallback transition for slidefade in non-3D supporting browsers (which tend to handle complex transitions poorly in general
+*/
- $to.parent().addClass( viewportClass );
+(function( $, window, undefined ) {
- if ( $from ) {
- $from.addClass( name + " out" + reverseClass );
- }
- $to.addClass( $.mobile.activePageClass + " " + name + " in" + reverseClass );
+// Set the slide transitions's fallback to "fade"
+$.mobile.transitionFallbacks.slidefade = "fade";
- return deferred.promise();
-}
+})( jQuery, this );
+/*
+* fallback transition for slideup in non-3D supporting browsers (which tend to handle complex transitions poorly in general
+*/
-// Make our transition handler public.
-$.mobile.css3TransitionHandler = css3TransitionHandler;
+(function( $, window, undefined ) {
-// If the default transition handler is the 'none' handler, replace it with our handler.
-if ( $.mobile.defaultTransitionHandler === $.mobile.noneTransitionHandler ) {
- $.mobile.defaultTransitionHandler = css3TransitionHandler;
-}
+$.mobile.transitionFallbacks.slideup = "fade";
})( jQuery, this );
/*
-* "degradeInputs" plugin - degrades inputs to another type after custom enhancements are made.
+* 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 = {
@@ -3647,11 +4609,11 @@ $.mobile.page.prototype.options.degradeInputs = {
//auto self-init widgets
-$( document ).bind( "pagecreate create", function( e ){
+$( document ).bind( "pagecreate create", function( e ) {
- var page = $(e.target).closest(':jqmData(role="page")').data("page"), options;
+ var page = $.mobile.closestPageData( $( e.target ) ), options;
- if( !page ) {
+ if ( !page ) {
return;
}
@@ -3676,48 +4638,54 @@ $( document ).bind( "pagecreate create", function( e ){
});
-})( jQuery );/*
-* "dialog" plugin.
-*/
+})( jQuery );
(function( $, window, undefined ) {
$.widget( "mobile.dialog", $.mobile.widget, {
options: {
- closeBtnText : "Close",
- overlayTheme : "a",
- initSelector : ":jqmData(role='dialog')"
+ closeBtnText: "Close",
+ overlayTheme: "a",
+ initSelector: ":jqmData(role='dialog')"
},
_create: function() {
var self = this,
$el = this.element,
- headerCloseButton = $( "
"+ this.options.closeBtnText + "" );
+ headerCloseButton = $( "
"+ this.options.closeBtnText + "" ),
+ dialogWrap = $( "
", {
+ "role" : "dialog",
+ "class" : "ui-dialog-contain ui-corner-all ui-overlay-shadow"
+ });
- $el.addClass( "ui-overlay-" + this.options.overlayTheme );
+ $el.addClass( "ui-dialog ui-overlay-" + this.options.overlayTheme );
// Class the markup for dialog styling
// Set aria role
- $el.attr( "role", "dialog" )
- .addClass( "ui-dialog" )
- .find( ":jqmData(role='header')" )
- .addClass( "ui-corner-top ui-overlay-shadow" )
- .prepend( headerCloseButton )
- .end()
- .find( ":jqmData(role='content'),:jqmData(role='footer')" )
- .addClass( "ui-overlay-shadow" )
- .last()
- .addClass( "ui-corner-bottom" );
+ $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
- headerCloseButton.bind( "vclick", function() {
+ //
+ // 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
+ 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 ) {
@@ -3732,112 +4700,462 @@ $.widget( "mobile.dialog", $.mobile.widget, {
.attr( "data-" + $.mobile.ns + "direction", "reverse" );
}
})
- .bind( "pagehide", function() {
- $( this ).find( "." + $.mobile.activeBtnClass ).removeClass( $.mobile.activeBtnClass );
+ .bind( "pagehide", function( e, ui ) {
+ $( this ).find( "." + $.mobile.activeBtnClass ).not( ".ui-slider-bg" ).removeClass( $.mobile.activeBtnClass );
+ })
+ // Override the theme set by the page plugin on pageshow
+ .bind( "pagebeforeshow", function() {
+ self._isCloseable = true;
+ if ( self.options.overlayTheme ) {
+ self.element
+ .page( "removeContainerBackground" )
+ .page( "setContainerBackground", self.options.overlayTheme );
+ }
});
},
// Close method goes back in history
close: function() {
- window.history.back();
+ var dst;
+
+ if ( this._isCloseable ) {
+ this._isCloseable = false;
+ if ( $.mobile.hashListeningEnabled ) {
+ $.mobile.back();
+ } else {
+ dst = $.mobile.urlHistory.getPrev().url;
+ if ( !$.mobile.path.isPath( dst ) ) {
+ dst = $.mobile.path.makeUrlAbsolute( "#" + dst );
+ }
+
+ $.mobile.changePage( dst, { changeHash: false, fromHashChange: true } );
+ }
+ }
}
});
-//auto self-init widgets
-$( $.mobile.dialog.prototype.options.initSelector ).live( "pagecreate", function(){
- $( this ).dialog();
-});
+//auto self-init widgets
+$( document ).delegate( $.mobile.dialog.prototype.options.initSelector, "pagecreate", function() {
+ $.mobile.dialog.prototype.enhance( this );
+});
+
+})( jQuery, this );
+
+(function( $, undefined ) {
+
+$.mobile.page.prototype.options.backBtnText = "Back";
+$.mobile.page.prototype.options.addBackBtn = false;
+$.mobile.page.prototype.options.backBtnTheme = null;
+$.mobile.page.prototype.options.headerTheme = "a";
+$.mobile.page.prototype.options.footerTheme = "a";
+$.mobile.page.prototype.options.contentTheme = null;
+
+// NOTE bind used to force this binding to run before the buttonMarkup binding
+// which expects .ui-footer top be applied in its gigantic selector
+// TODO remove the buttonMarkup giant selector and move it to the various modules
+// on which it depends
+$( document ).bind( "pagecreate", function( e ) {
+ var $page = $( e.target ),
+ o = $page.data( "page" ).options,
+ pageRole = $page.jqmData( "role" ),
+ pageTheme = o.theme;
+
+ $( ":jqmData(role='header'), :jqmData(role='footer'), :jqmData(role='content')", $page )
+ .jqmEnhanceable()
+ .each(function() {
+
+ var $this = $( this ),
+ role = $this.jqmData( "role" ),
+ theme = $this.jqmData( "theme" ),
+ contentTheme = theme || o.contentTheme || ( pageRole === "dialog" && pageTheme ),
+ $headeranchors,
+ leftbtn,
+ rightbtn,
+ backBtn;
+
+ $this.addClass( "ui-" + role );
+
+ //apply theming and markup modifications to page,header,content,footer
+ if ( role === "header" || role === "footer" ) {
+
+ var thisTheme = theme || ( role === "header" ? o.headerTheme : o.footerTheme ) || pageTheme;
+
+ $this
+ //add theme class
+ .addClass( "ui-bar-" + thisTheme )
+ // Add ARIA role
+ .attr( "role", role === "header" ? "banner" : "contentinfo" );
+
+ if ( role === "header") {
+ // Right,left buttons
+ $headeranchors = $this.children( "a, button" );
+ leftbtn = $headeranchors.hasClass( "ui-btn-left" );
+ rightbtn = $headeranchors.hasClass( "ui-btn-right" );
+
+ leftbtn = leftbtn || $headeranchors.eq( 0 ).not( ".ui-btn-right" ).addClass( "ui-btn-left" ).length;
+
+ rightbtn = rightbtn || $headeranchors.eq( 1 ).addClass( "ui-btn-right" ).length;
+ }
+
+ // Auto-add back btn on pages beyond first view
+ if ( o.addBackBtn &&
+ role === "header" &&
+ $( ".ui-page" ).length > 1 &&
+ $page.jqmData( "url" ) !== $.mobile.path.stripHash( location.hash ) &&
+ !leftbtn ) {
+
+ backBtn = $( "
"+ o.backBtnText +"" )
+ // If theme is provided, override default inheritance
+ .attr( "data-"+ $.mobile.ns +"theme", o.backBtnTheme || thisTheme )
+ .prependTo( $this );
+ }
+
+ // Page title
+ $this.children( "h1, h2, h3, h4, h5, h6" )
+ .addClass( "ui-title" )
+ // Regardless of h element number in src, it becomes h1 for the enhanced page
+ .attr({
+ "role": "heading",
+ "aria-level": "1"
+ });
+
+ } else if ( role === "content" ) {
+ if ( contentTheme ) {
+ $this.addClass( "ui-body-" + ( contentTheme ) );
+ }
+
+ // Add ARIA role
+ $this.attr( "role", "main" );
+ }
+ });
+});
+
+})( jQuery );
+
+(function( $, undefined ) {
+
+// filter function removes whitespace between label and form element so we can use inline-block (nodeType 3 = text)
+$.fn.fieldcontain = function( options ) {
+ return this
+ .addClass( "ui-field-contain ui-body ui-br" )
+ .contents().filter( function() {
+ return ( this.nodeType === 3 && !/\S/.test( this.nodeValue ) );
+ }).remove();
+};
+
+//auto self-init widgets
+$( document ).bind( "pagecreate create", function( e ) {
+ $( ":jqmData(role='fieldcontain')", e.target ).jqmEnhanceable().fieldcontain();
+});
+
+})( jQuery );
+
+(function( $, undefined ) {
+
+$.fn.grid = function( options ) {
+ return this.each(function() {
+
+ var $this = $( this ),
+ o = $.extend({
+ grid: null
+ }, options ),
+ $kids = $this.children(),
+ gridCols = { solo:1, a:2, b:3, c:4, d:5 },
+ grid = o.grid,
+ iterator;
+
+ if ( !grid ) {
+ if ( $kids.length <= 5 ) {
+ for ( var letter in gridCols ) {
+ if ( gridCols[ letter ] === $kids.length ) {
+ grid = letter;
+ }
+ }
+ } else {
+ grid = "a";
+ $this.addClass( "ui-grid-duo" );
+ }
+ }
+ iterator = gridCols[grid];
+
+ $this.addClass( "ui-grid-" + grid );
+
+ $kids.filter( ":nth-child(" + iterator + "n+1)" ).addClass( "ui-block-a" );
+
+ if ( iterator > 1 ) {
+ $kids.filter( ":nth-child(" + iterator + "n+2)" ).addClass( "ui-block-b" );
+ }
+ if ( iterator > 2 ) {
+ $kids.filter( ":nth-child(" + iterator + "n+3)" ).addClass( "ui-block-c" );
+ }
+ if ( iterator > 3 ) {
+ $kids.filter( ":nth-child(" + iterator + "n+4)" ).addClass( "ui-block-d" );
+ }
+ if ( iterator > 4 ) {
+ $kids.filter( ":nth-child(" + iterator + "n+5)" ).addClass( "ui-block-e" );
+ }
+ });
+};
+})( jQuery );
+
+(function( $, undefined ) {
+
+$( document ).bind( "pagecreate create", function( e ) {
+ $( ":jqmData(role='nojs')", e.target ).addClass( "ui-nojs" );
+
+});
+
+})( jQuery );
+
+(function( $, undefined ) {
+
+$.fn.buttonMarkup = function( options ) {
+ var $workingSet = this,
+ mapToDataAttr = function( key, value ) {
+ e.setAttribute( "data-" + $.mobile.ns + key, value );
+ el.jqmData( key, value );
+ };
+
+ // 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, mapToDataAttr );
+
+ if ( el.jqmData( "rel" ) === "popup" && el.attr( "href" ) ) {
+ e.setAttribute( "aria-haspopup", true );
+ e.setAttribute( "aria-owns", e.getAttribute( "href" ) );
+ }
+
+ // 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.shadow ? " ui-shadow" : "";
+ buttonClass += o.corners ? " ui-btn-corner-all" : "";
+
+ if ( o.mini !== undefined ) {
+ // Used to control styling in headers/footers, where buttons default to `mini` style.
+ buttonClass += o.mini === true ? " ui-mini" : " ui-fullsize";
+ }
+
+ if ( o.inline !== undefined ) {
+ // Used to control styling in headers/footers, where buttons default to `inline` style.
+ buttonClass += o.inline === true ? " ui-btn-inline" : " ui-btn-block";
+ }
+
+ if ( o.icon ) {
+ o.icon = "ui-icon-" + o.icon;
+ o.iconpos = o.iconpos || "left";
+
+ iconClass = "ui-icon " + o.icon;
+
+ if ( o.iconshadow ) {
+ iconClass += " ui-icon-shadow";
+ }
+ }
+
+ if ( o.iconpos ) {
+ buttonClass += " ui-btn-icon-" + o.iconpos;
+
+ if ( o.iconpos === "notext" && !el.attr( "title" ) ) {
+ el.attr( "title", el.getEncodedText() );
+ }
+ }
+
+ 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.innerHTML = " ";
+ buttonInner.appendChild( buttonIcon );
+ }
+ }
+
+ while ( e.firstChild && !buttonElements ) {
+ buttonText.appendChild( e.firstChild );
+ }
+
+ if ( !buttonElements ) {
+ e.appendChild( buttonInner );
+ }
+
+ // Assign a structure containing the elements of this button to the elements of this button. This
+ // will allow us to recognize this as an already-enhanced button in future calls to buttonMarkup().
+ buttonElements = {
+ bcls : buttonClass,
+ outer : e,
+ inner : buttonInner,
+ text : buttonText,
+ icon : buttonIcon
+ };
-})( jQuery, this );
-/*
-* This plugin handles theming and layout of headers, footers, and content areas
-*/
+ $.data( e, 'buttonElements', buttonElements );
+ $.data( buttonInner, 'buttonElements', buttonElements );
+ $.data( buttonText, 'buttonElements', buttonElements );
+ if ( buttonIcon ) {
+ $.data( buttonIcon, 'buttonElements', buttonElements );
+ }
+ }
-(function( $, undefined ) {
+ return this;
+};
-$.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;
+$.fn.buttonMarkup.defaults = {
+ corners: true,
+ shadow: true,
+ iconshadow: true,
+ wrapperEls: "span"
+};
-$( ":jqmData(role='page'), :jqmData(role='dialog')" ).live( "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 ).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 );
+function closestEnabledButton( element ) {
+ var cname;
- //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;
+ 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;
+ }
- $this
- //add theme class
- .addClass( "ui-bar-" + thisTheme )
- // Add ARIA role
- .attr( "role", role === "header" ? "banner" : "contentinfo" );
+ element = element.parentNode;
+ }
- // Right,left buttons
- $headeranchors = $this.children( "a" );
- leftbtn = $headeranchors.hasClass( "ui-btn-left" );
- rightbtn = $headeranchors.hasClass( "ui-btn-right" );
+ return element;
+}
- 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 &&
- $this.jqmData( "url" ) !== $.mobile.path.stripHash( location.hash ) &&
- !leftbtn ) {
+var attachEvents = function() {
+ var hoverDelay = $.mobile.buttonMarkup.hoverDelay, hov, foc;
- backBtn = $( "
"+ o.backBtnText +"" )
- // If theme is provided, override default inheritance
- .attr( "data-"+ $.mobile.ns +"theme", o.backBtnTheme || thisTheme )
- .prependTo( $this );
- }
+ $( document ).bind( {
+ "vmousedown vmousecancel vmouseup vmouseover vmouseout focus blur scrollstart": function( event ) {
+ var theme,
+ $btn = $( closestEnabledButton( event.target ) ),
+ isTouchEvent = event.originalEvent && /^touch/.test( event.originalEvent.type ),
+ evt = event.type;
- // 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({
- "tabindex": "0",
- "role": "heading",
- "aria-level": "1"
- });
+ if ( $btn.length ) {
+ theme = $btn.attr( "data-" + $.mobile.ns + "theme" );
- } else if ( role === "content" ) {
- if ( contentTheme ) {
- $this.addClass( "ui-body-" + ( contentTheme ) );
+ if ( evt === "vmousedown" ) {
+ if ( isTouchEvent ) {
+ // Use a short delay to determine if the user is scrolling before highlighting
+ 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 ( isTouchEvent ) {
+ // Use a short delay to determine if the user is scrolling before highlighting
+ 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 );
+ }
+ }
}
-
- // Add ARIA role
- $this.attr( "role", "main" );
+ },
+ "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 )
+ .jqmEnhanceable()
+ .not( "button, input, .ui-btn, :jqmData(role='none'), :jqmData(role='nojs')" )
+ .buttonMarkup();
});
-})( jQuery );/*
-* "collapsible" plugin
-*/
+})( jQuery );
+
(function( $, undefined ) {
@@ -3849,7 +5167,8 @@ $.widget( "mobile.collapsible", $.mobile.widget, {
heading: "h1,h2,h3,h4,h5,h6,legend",
theme: null,
contentTheme: null,
- iconTheme: "d",
+ inset: true,
+ mini: false,
initSelector: ":jqmData(role='collapsible')"
},
_create: function() {
@@ -3858,9 +5177,10 @@ $.widget( "mobile.collapsible", $.mobile.widget, {
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" ),
- collapsiblesInSet = collapsibleSet.children( ":jqmData(role='collapsible')" );
+ collapsedIcon = $el.jqmData( "collapsed-icon" ) || o.collapsedIcon,
+ expandedIcon = $el.jqmData( "expanded-icon" ) || o.expandedIcon,
+ collapsibleContent = collapsible.wrapInner( "
" ).children( ".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" ) ) {
@@ -3872,16 +5192,51 @@ $.widget( "mobile.collapsible", $.mobile.widget, {
if ( collapsibleSet.length ) {
// Inherit the theme from collapsible-set
if ( !o.theme ) {
- o.theme = collapsibleSet.jqmData( "theme" );
+ o.theme = collapsibleSet.jqmData( "theme" ) || $.mobile.getInheritedTheme( collapsibleSet, "c" );
}
// Inherit the content-theme from collapsible-set
if ( !o.contentTheme ) {
o.contentTheme = collapsibleSet.jqmData( "content-theme" );
}
- }
+ // Get the preference for collapsed icon in the set
+ if ( !o.collapsedIcon ) {
+ o.collapsedIcon = collapsibleSet.jqmData( "collapsed-icon" );
+ }
+ // Get the preference for expanded icon in the set
+ if ( !o.expandedIcon ) {
+ o.expandedIcon = collapsibleSet.jqmData( "expanded-icon" );
+ }
+ // Gets the preference icon position in the set
+ if ( !o.iconPos ) {
+ o.iconPos = collapsibleSet.jqmData( "iconpos" );
+ }
+ // Inherit the preference for inset from collapsible-set or set the default value to ensure equalty within a set
+ if ( collapsibleSet.jqmData( "inset" ) !== undefined ) {
+ o.inset = collapsibleSet.jqmData( "inset" );
+ } else {
+ o.inset = true;
+ }
+ // Gets the preference for mini in the set
+ if ( !o.mini ) {
+ o.mini = collapsibleSet.jqmData( "mini" );
+ }
+ } else {
+ // get inherited theme if not a set and no theme has been set
+ if ( !o.theme ) {
+ o.theme = $.mobile.getInheritedTheme( $el, "c" );
+ }
+ }
+
+ if ( !!o.inset ) {
+ collapsible.addClass( "ui-collapsible-inset" );
+ }
+
collapsibleContent.addClass( ( o.contentTheme ) ? ( "ui-body-" + o.contentTheme ) : "");
+ collapsedIcon = $el.jqmData( "collapsed-icon" ) || o.collapsedIcon || "plus";
+ expandedIcon = $el.jqmData( "expanded-icon" ) || o.expandedIcon || "minus";
+
collapsibleHeading
//drop heading in before content
.insertBefore( collapsibleContent )
@@ -3894,80 +5249,44 @@ $.widget( "mobile.collapsible", $.mobile.widget, {
.buttonMarkup({
shadow: false,
corners: false,
- iconPos: "left",
- icon: "plus",
+ iconpos: $el.jqmData( "iconpos" ) || o.iconPos || "left",
+ icon: collapsedIcon,
+ mini: o.mini,
theme: o.theme
});
- if ( !collapsibleSet.length ) {
+ if ( !!o.inset ) {
collapsibleHeading
- .find( "a" ).first().add( collapsibleHeading.find( ".ui-btn-inner" ) )
+ .find( "a" ).first().add( ".ui-btn-inner", $el )
.addClass( "ui-corner-top ui-corner-bottom" );
- } else {
- // If we are in a collapsible set
-
- // Initialize the collapsible set if it's not already initialized
- if ( !collapsibleSet.jqmData( "collapsiblebound" ) ) {
-
- collapsibleSet
- .jqmData( "collapsiblebound", true )
- .bind( "expand", function( event ) {
-
- $( event.target )
- .closest( ".ui-collapsible" )
- .siblings( ".ui-collapsible" )
- .trigger( "collapse" );
-
- });
- }
-
- collapsiblesInSet.first()
- .find( "a" )
- .first()
- .addClass( "ui-corner-top" )
- .find( ".ui-btn-inner" )
- .addClass( "ui-corner-top" );
-
- collapsiblesInSet.last()
- .jqmData( "collapsible-last", true )
- .find( "a" )
- .first()
- .addClass( "ui-corner-bottom" )
- .find( ".ui-btn-inner" )
- .addClass( "ui-corner-bottom" );
-
-
- if ( collapsible.jqmData( "collapsible-last" ) ) {
- collapsibleHeading
- .find( "a" ).first().add ( collapsibleHeading.find( ".ui-btn-inner" ) )
- .addClass( "ui-corner-bottom" );
- }
}
//events
collapsible
.bind( "expand collapse", function( event ) {
if ( !event.isDefaultPrevented() ) {
-
- event.preventDefault();
-
var $this = $( this ),
isCollapse = ( event.type === "collapse" ),
- contentTheme = o.contentTheme;
+ contentTheme = o.contentTheme;
+
+ event.preventDefault();
collapsibleHeading
- .toggleClass( "ui-collapsible-heading-collapsed", isCollapse)
+ .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 );
+ .toggleClass( "ui-icon-" + expandedIcon, !isCollapse )
+ // logic or cause same icon for expanded/collapsed state would remove the ui-icon-class
+ .toggleClass( "ui-icon-" + collapsedIcon, ( isCollapse || expandedIcon === collapsedIcon ) )
+ .end()
+ .find( "a" ).first().removeClass( $.mobile.activeBtnClass );
$this.toggleClass( "ui-collapsible-collapsed", isCollapse );
collapsibleContent.toggleClass( "ui-collapsible-content-collapsed", isCollapse ).attr( "aria-hidden", isCollapse );
- if ( contentTheme && ( !collapsibleSet.length || collapsible.jqmData( "collapsible-last" ) ) ) {
+ if ( contentTheme && !!o.inset && ( !collapsibleSet.length || collapsible.jqmData( "collapsible-last" ) ) ) {
collapsibleHeading
.find( "a" ).first().add( collapsibleHeading.find( ".ui-btn-inner" ) )
.toggleClass( "ui-corner-bottom", isCollapse );
@@ -3979,91 +5298,134 @@ $.widget( "mobile.collapsible", $.mobile.widget, {
.trigger( o.collapsed ? "collapse" : "expand" );
collapsibleHeading
+ .bind( "tap", function( event ) {
+ collapsibleHeading.find( "a" ).first().addClass( $.mobile.activeBtnClass );
+ })
.bind( "click", function( event ) {
- var type = collapsibleHeading.is( ".ui-collapsible-heading-collapsed" ) ?
- "expand" : "collapse";
+ var type = collapsibleHeading.is( ".ui-collapsible-heading-collapsed" ) ? "expand" : "collapse";
collapsible.trigger( type );
event.preventDefault();
+ event.stopPropagation();
});
}
});
//auto self-init widgets
-$( document ).bind( "pagecreate create", function( e ){
- $( $.mobile.collapsible.prototype.options.initSelector, e.target ).collapsible();
+$( document ).bind( "pagecreate create", function( e ) {
+ $.mobile.collapsible.prototype.enhanceWithin( e.target );
});
})( jQuery );
-/*
-* "fieldcontain" plugin - simple class additions to make form row separators
-*/
(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 ).fieldcontain();
-});
-
-})( jQuery );/*
-* plugin for creating CSS grids
-*/
+$.widget( "mobile.collapsibleset", $.mobile.widget, {
+ options: {
+ initSelector: ":jqmData(role='collapsible-set')"
+ },
+ _create: function() {
+ var $el = this.element.addClass( "ui-collapsible-set" ),
+ o = this.options;
-(function( $, undefined ) {
+ // 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 ( $el.jqmData( "inset" ) !== undefined ) {
+ o.inset = $el.jqmData( "inset" );
+ }
+ o.inset = o.inset !== undefined ? o.inset : true;
+
+ // 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" );
+ if ( collapsible.jqmData( "collapsible-last" ) && !!o.inset ) {
+ collapsible.find( ".ui-collapsible-heading" ).first()
+ .find( "a" ).first()
+ .toggleClass( "ui-corner-bottom", isCollapse )
+ .find( ".ui-btn-inner" )
+ .toggleClass( "ui-corner-bottom", isCollapse );
+ collapsible.find( ".ui-collapsible-content" ).toggleClass( "ui-corner-bottom", !isCollapse );
+ }
+ })
+ .bind( "expand", function( event ) {
+ var closestCollapsible = $( event.target )
+ .closest( ".ui-collapsible" );
+ if ( closestCollapsible.parent().is( ":jqmData(role='collapsible-set')" ) ) {
+ closestCollapsible
+ .siblings( ".ui-collapsible" )
+ .trigger( "collapse" );
+ }
+ });
+ }
+ },
-$.fn.grid = function( options ) {
- return this.each(function() {
+ _init: function() {
+ var $el = this.element,
+ collapsiblesInSet = $el.children( ":jqmData(role='collapsible')" ),
+ expanded = collapsiblesInSet.filter( ":jqmData(collapsed='false')" );
+ this.refresh();
- 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;
+ // Because the corners are handled by the collapsible itself and the default state is collapsed
+ // That was causing https://github.com/jquery/jquery-mobile/issues/4116
+ expanded.trigger( "expand" );
+ },
- if ( !grid ) {
- if ( $kids.length <= 5 ) {
- for ( var letter in gridCols ) {
- if ( gridCols[ letter ] === $kids.length ) {
- grid = letter;
- }
- }
- } else {
- grid = "a";
- }
- }
- iterator = gridCols[grid];
+ 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
+ if ( !!o.inset ) {
+ collapsiblesInSet.each(function() {
+ $( this ).jqmRemoveData( "collapsible-last" )
+ .find( ".ui-collapsible-heading" )
+ .find( "a" ).first()
+ .removeClass( "ui-corner-top ui-corner-bottom" )
+ .find( ".ui-btn-inner" )
+ .removeClass( "ui-corner-top ui-corner-bottom" );
+ });
- $this.addClass( "ui-grid-" + grid );
+ collapsiblesInSet.first()
+ .find( "a" )
+ .first()
+ .addClass( "ui-corner-top" )
+ .find( ".ui-btn-inner" )
+ .addClass( "ui-corner-top" );
+
+ collapsiblesInSet.last()
+ .jqmData( "collapsible-last", true )
+ .find( "a" )
+ .first()
+ .addClass( "ui-corner-bottom" )
+ .find( ".ui-btn-inner" )
+ .addClass( "ui-corner-bottom" );
+ }
+ }
+});
- $kids.filter( ":nth-child(" + iterator + "n+1)" ).addClass( "ui-block-a" );
+//auto self-init widgets
+$( document ).bind( "pagecreate create", function( e ) {
+ $.mobile.collapsibleset.prototype.enhanceWithin( e.target );
+});
- 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 );/*
-* "navbar" plugin
-*/
+})( jQuery );
(function( $, undefined ) {
@@ -4074,44 +5436,46 @@ $.widget( "mobile.navbar", $.mobile.widget, {
initSelector: ":jqmData(role='navbar')"
},
- _create: function(){
+ _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" )
+ $navbar.addClass( "ui-navbar ui-mini" )
+ .attr( "role", "navigation" )
.find( "ul" )
- .grid({ grid: this.options.grid });
-
- if ( !iconpos ) {
- $navbar.addClass( "ui-navbar-noicons" );
- }
+ .jqmEnhanceable()
+ .grid({ grid: this.options.grid });
$navbtns.buttonMarkup({
corners: false,
shadow: false,
+ inline: true,
iconpos: iconpos
});
$navbar.delegate( "a", "vclick", function( event ) {
- $navbtns.not( ".ui-state-persist" ).removeClass( $.mobile.activeBtnClass );
- $( this ).addClass( $.mobile.activeBtnClass );
+ 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.options.initSelector, e.target ).navbar();
+$( document ).bind( "pagecreate create", function( e ) {
+ $.mobile.navbar.prototype.enhanceWithin( e.target );
});
})( jQuery );
-/*
-* "listview" plugin
-*/
(function( $, undefined ) {
@@ -4121,6 +5485,7 @@ $( document ).bind( "pagecreate create", function( e ){
var listCountPerPage = {};
$.widget( "mobile.listview", $.mobile.widget, {
+
options: {
theme: null,
countTheme: "c",
@@ -4133,11 +5498,14 @@ $.widget( "mobile.listview", $.mobile.widget, {
},
_create: function() {
- var t = this;
+ var t = this,
+ listviewClasses = "";
+
+ listviewClasses += t.options.inset ? " ui-listview-inset ui-corner-all ui-shadow " : "";
// create listview markup
t.element.addClass(function( i, orig ) {
- return orig + " ui-listview " + ( t.options.inset ? " ui-listview-inset ui-corner-all ui-shadow " : "" );
+ return orig + " ui-listview " + listviewClasses;
});
t.refresh( true );
@@ -4164,11 +5532,14 @@ $.widget( "mobile.listview", $.mobile.widget, {
$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" );
+ $li = this.element.children( "li" );
+ // At create time and when autodividers calls refresh the li are not visible yet so we need to rely on .ui-screen-hidden
+ $visibleli = create || $li.filter( ":visible" ).length === 0 ? $li.not( ".ui-screen-hidden" ) : $li.filter( ":visible" );
+ // ui-li-last is used for setting border-bottom on the last li
+ $li.filter( ".ui-li-last" ).removeClass( "ui-li-last" );
+
+ if ( this.options.inset ) {
this._removeCorners( $li );
// Select the first visible li element
@@ -4176,27 +5547,29 @@ $.widget( "mobile.listview", $.mobile.widget, {
.addClass( "ui-corner-top" );
$topli.add( $topli.find( ".ui-btn-inner" )
- .not( ".ui-li-link-alt span:first-child" ) )
- .addClass( "ui-corner-top" )
- .end()
+ .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")
+ .not( ".ui-li-icon" )
.addClass( "ui-corner-tl" );
// Select the last visible li element
$bottomli = $visibleli.last()
- .addClass( "ui-corner-bottom" );
+ .addClass( "ui-corner-bottom ui-li-last" );
$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")
+ .not( ".ui-li-icon" )
.addClass( "ui-corner-bl" );
+ } else {
+ $visibleli.last().addClass( "ui-li-last" );
}
if ( !create ) {
this.element.trigger( "updatelayout" );
@@ -4214,8 +5587,7 @@ $.widget( "mobile.listview", $.mobile.widget, {
// 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 )
- {
+ _findFirstElementByTagName: function( ele, nextProp, lcName, ucName ) {
var dict = {};
dict[ lcName ] = dict[ ucName ] = true;
while ( ele ) {
@@ -4226,8 +5598,7 @@ $.widget( "mobile.listview", $.mobile.widget, {
}
return null;
},
- _getChildrenByTagName: function( ele, lcName, ucName )
- {
+ _getChildrenByTagName: function( ele, lcName, ucName ) {
var results = [],
dict = {};
dict[ lcName ] = dict[ ucName ] = true;
@@ -4241,8 +5612,7 @@ $.widget( "mobile.listview", $.mobile.widget, {
return $( results );
},
- _addThumbClasses: function( containers )
- {
+ _addThumbClasses: function( containers ) {
var i, img, len = containers.length;
for ( i = 0; i < len; i++ ) {
img = $( this._findFirstElementByTagName( containers[ i ].firstChild, "nextSibling", "img", "IMG" ) );
@@ -4264,15 +5634,31 @@ $.widget( "mobile.listview", $.mobile.widget, {
listsplittheme = $list.jqmData( "splittheme" ),
listspliticon = $list.jqmData( "spliticon" ),
li = this._getChildrenByTagName( $list[ 0 ], "li", "LI" ),
- counter = $.support.cssPseudoElement || !$.nodeName( $list[ 0 ], "ol" ) ? 0 : 1,
+ ol = !!$.nodeName( $list[ 0 ], "ol" ),
+ jsCount = !$.support.cssPseudoElement,
+ start = $list.attr( "start" ),
itemClassDict = {},
item, itemClass, itemTheme,
- a, last, splittheme, countParent, icon, imgParents, img;
+ a, last, splittheme, counter, startCount, newStartCount, countParent, icon, imgParents, img, linkIcon;
- if ( counter ) {
+ if ( ol && jsCount ) {
$list.find( ".ui-li-dec" ).remove();
}
-
+
+ if ( ol ) {
+ // Check if a start attribute has been set while taking a value of 0 into account
+ if ( start || start === 0 ) {
+ if ( !jsCount ) {
+ startCount = parseFloat( start ) - 1;
+ $list.css( "counter-reset", "listnumbering " + startCount );
+ } else {
+ counter = parseFloat( start );
+ }
+ } else if ( jsCount ) {
+ counter = 1;
+ }
+ }
+
if ( !o.theme ) {
o.theme = $.mobile.getInheritedTheme( this.element, "c" );
}
@@ -4283,11 +5669,12 @@ $.widget( "mobile.listview", $.mobile.widget, {
// If we're creating the element, we update it regardless
if ( create || !item.hasClass( "ui-li" ) ) {
- itemTheme = item.jqmData("theme") || o.theme;
+ itemTheme = item.jqmData( "theme" ) || o.theme;
a = this._getChildrenByTagName( item[ 0 ], "a", "A" );
+ var isDivider = ( item.jqmData( "role" ) === "list-divider" );
- if ( a.length ) {
- icon = item.jqmData("icon");
+ if ( a.length && !isDivider ) {
+ icon = item.jqmData( "icon" );
item.buttonMarkup({
wrapperEls: "div",
@@ -4298,19 +5685,20 @@ $.widget( "mobile.listview", $.mobile.widget, {
theme: itemTheme
});
- if ( ( icon != false ) && ( a.length == 1 ) ) {
+ if ( ( icon !== false ) && ( a.length === 1 ) ) {
item.addClass( "ui-li-has-arrow" );
}
- a.first().addClass( "ui-link-inherit" );
+ 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)
+ last.appendTo( item )
.attr( "title", last.getEncodedText() )
.addClass( "ui-li-link-alt" )
.empty()
@@ -4319,7 +5707,7 @@ $.widget( "mobile.listview", $.mobile.widget, {
corners: false,
theme: itemTheme,
icon: false,
- iconpos: false
+ iconpos: "notext"
})
.find( ".ui-btn-inner" )
.append(
@@ -4328,30 +5716,40 @@ $.widget( "mobile.listview", $.mobile.widget, {
corners: true,
theme: splittheme,
iconpos: "notext",
- icon: listspliticon || last.jqmData( "icon" ) || o.splitIcon
+ // link icon overrides list item icon overrides ul element overrides options
+ icon: linkIcon || icon || listspliticon || o.splitIcon
})
);
}
- } else if ( item.jqmData( "role" ) === "list-divider" ) {
+ } else if ( isDivider ) {
- itemClass += " ui-li-divider ui-btn ui-bar-" + dividertheme;
+ itemClass += " ui-li-divider ui-bar-" + dividertheme;
item.attr( "role", "heading" );
- //reset counter when a divider heading is encountered
- if ( counter ) {
- counter = 1;
+ if ( ol ) {
+ //reset counter when a divider heading is encountered
+ if ( start || start === 0 ) {
+ if ( !jsCount ) {
+ newStartCount = parseFloat( start ) - 1;
+ item.css( "counter-reset", "listnumbering " + newStartCount );
+ } else {
+ counter = parseFloat( start );
+ }
+ } else if ( jsCount ) {
+ counter = 1;
+ }
}
-
+
} else {
- itemClass += " ui-li-static ui-body-" + itemTheme;
+ itemClass += " ui-li-static ui-btn-up-" + itemTheme;
}
}
- if ( counter && itemClass.indexOf( "ui-li-divider" ) < 0 ) {
- countParent = item.is( ".ui-li-static:first" ) ? item : item.find( ".ui-link-inherit" );
+ if ( ol && jsCount && itemClass.indexOf( "ui-li-divider" ) < 0 ) {
+ countParent = itemClass.indexOf( "ui-li-static" ) > 0 ? item : item.find( ".ui-link-inherit" );
countParent.addClass( "ui-li-jsnumbering" )
- .prepend( "
" + (counter++) + ". " );
+ .prepend( "
" + ( counter++ ) + ". " );
}
// Instead of setting item class directly on the list item and its
@@ -4383,12 +5781,12 @@ $.widget( "mobile.listview", $.mobile.widget, {
.end()
.find( ".ui-li-aside" ).each(function() {
- var $this = $(this);
+ var $this = $( this );
$this.prependTo( $this.parent() ); //shift aside to front for css float
})
.end()
- .find( ".ui-li-count" ).each( function() {
+ .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" );
@@ -4408,6 +5806,9 @@ $.widget( "mobile.listview", $.mobile.widget, {
this._addThumbClasses( $list.find( ".ui-link-inherit" ) );
this._refreshCorners( create );
+
+ // autodividers binds to this to redraw dividers after the listview refresh
+ this._trigger( "afterrefresh" );
},
//create a string for ID/subpage url creation
@@ -4438,8 +5839,8 @@ $.widget( "mobile.listview", $.mobile.widget, {
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) + "" ),
+ nodeElsFull = $( list.prevAll().toArray().reverse() ),
+ nodeEls = nodeElsFull.length ? nodeElsFull : $( "
" + $.trim(parent.contents()[ 0 ].nodeValue) + "" ),
title = nodeEls.first().getEncodedText(),//url limits to first 30 chars of text
id = ( parentUrl || "" ) + "&" + $.mobile.subPageUrlKey + "=" + listId,
theme = list.jqmData( "theme" ) || o.theme,
@@ -4450,16 +5851,16 @@ $.widget( "mobile.listview", $.mobile.widget, {
hasSubPages = true;
newPage = list.detach()
- .wrap( "
" )
+ .wrap( "
" )
.parent()
.before( "
" )
- .after( persistentFooterID ? $( "