2013-04-21 17:55:07 +02:00
/ *
2015-03-09 15:45:35 +01:00
Copyright 2008 - 2015 Clipperz Srl
2013-04-21 17:55:07 +02:00
This file is part of Clipperz , the online password manager .
For further information about its features and functionalities please
refer to http : //www.clipperz.com.
* Clipperz is free software : you can redistribute it and / or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
* Clipperz is distributed in the hope that it will be useful , but
WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE .
See the GNU Affero General Public License for more details .
* You should have received a copy of the GNU Affero General Public
License along with Clipperz . If not , see http : //www.gnu.org/licenses/.
* /
/ *
* jQuery Mobile Git Build : SHA1 : 56 a6976b89feddf34e163c3fcc7196ae0a44c1a0 < > Date : Mon Feb 4 08 : 28 : 28 2013 - 0800
* http : //jquerymobile.com
*
* Copyright 2010 , 2013 jQuery Foundation , Inc . and other contributors
* Released under the MIT license .
* http : //jquery.org/license
*
* /
( function ( root , doc , factory ) {
if ( typeof define === "function" && define . amd ) {
// AMD. Register as an anonymous module.
define ( [ "jquery" ] , function ( $ ) {
factory ( $ , root , doc ) ;
return $ . mobile ;
} ) ;
} else {
// Browser globals
factory ( root . jQuery , root , doc ) ;
}
} ( this , document , function ( jQuery , window , document , undefined ) {
( function ( $ ) {
$ . mobile = { } ;
} ( jQuery ) ) ;
( function ( $ , window , undefined ) {
var nsNormalizeDict = { } ;
// jQuery.mobile configurable options
$ . mobile = $ . extend ( $ . mobile , {
// Version of the jQuery Mobile Framework
version : "1.3.0-rc.1" ,
// 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" ,
// Class used for "focus" form element state, from CSS framework
focusClass : "ui-focus" ,
// Automatically handle clicks and form submissions through Ajax, when same-domain
ajaxEnabled : true ,
// Automatically load and show pages based on location.hash
hashListeningEnabled : true ,
// disable to prevent jquery from bothering with links
linkBindingEnabled : true ,
// Set default page transition - 'none' for no transitions
defaultPageTransition : "fade" ,
// Set maximum window width for transitions to apply - 'false' for no limit
maxTransitionWidth : false ,
// Minimum scroll distance that will be remembered when returning to a page
minScrollBack : 250 ,
// DEPRECATED: the following property is no longer in use, but defined until 2.0 to prevent conflicts
touchOverflowEnabled : false ,
// Set default dialog transition - 'none' for no transitions
defaultDialogTransition : "pop" ,
// Error response message - appears when an Ajax page request fails
pageLoadErrorMessage : "Error Loading Page" ,
// For error messages, which theme does the box uses?
pageLoadErrorMessageTheme : "e" ,
// replace calls to window.history.back with phonegaps navigation helper
// where it is provided on the window object
phonegapNavigationEnabled : false ,
//automatically initialize the DOM when it's ready
autoInitializePage : true ,
pushStateEnabled : true ,
// allows users to opt in to ignoring content by marking a parent element as
// data-ignored
ignoreContentEnabled : false ,
// turn of binding to the native orientationchange due to android orientation behavior
orientationChangeEnabled : true ,
buttonMarkup : {
hoverDelay : 200
} ,
// define the window and the document objects
window : $ ( window ) ,
document : $ ( document ) ,
// 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
} ,
// Place to store various widget extensions
behaviors : { } ,
// Scroll page vertically: scroll to 0 to hide iOS address bar, or pass a Y value
silentScroll : function ( ypos ) {
if ( $ . type ( ypos ) !== "number" ) {
ypos = $ . mobile . defaultHomeScroll ;
}
// prevent scrollstart and scrollstop events
$ . event . special . scrollstart . enabled = false ;
setTimeout ( function ( ) {
window . scrollTo ( 0 , ypos ) ;
$ . mobile . document . trigger ( "silentscroll" , { x : 0 , y : ypos } ) ;
} , 20 ) ;
setTimeout ( function ( ) {
$ . event . special . scrollstart . enabled = true ;
} , 150 ) ;
} ,
// Expose our cache for testing purposes.
nsNormalizeDict : nsNormalizeDict ,
// Take a data attribute property, prepend the namespace
// and then camel case the attribute string. Add the result
// to our nsNormalizeDict so we don't have to do this again.
nsNormalize : function ( prop ) {
if ( ! prop ) {
return ;
}
return nsNormalizeDict [ prop ] || ( nsNormalizeDict [ prop ] = $ . camelCase ( $ . mobile . ns + prop ) ) ;
} ,
// Find the closest parent with a theme class on it. Note that
// we are not using $.fn.closest() on purpose here because this
// method gets called quite a bit and we need it to be as fast
// as possible.
getInheritedTheme : function ( el , defaultTheme ) {
var e = el [ 0 ] ,
ltr = "" ,
re = /ui-(bar|body|overlay)-([a-z])\b/ ,
c , m ;
while ( e ) {
c = e . className || "" ;
if ( c && ( m = re . exec ( c ) ) && ( ltr = m [ 2 ] ) ) {
// We found a parent with a theme class
// on it so bail from this loop.
break ;
}
e = e . parentNode ;
}
// Return the theme letter we found, if none, return the
// specified default.
return ltr || defaultTheme || "a" ;
} ,
// TODO the following $ and $.fn extensions can/probably should be moved into jquery.mobile.core.helpers
//
// Find the closest javascript page element to gather settings data jsperf test
// http://jsperf.com/single-complex-selector-vs-many-complex-selectors/edit
// possibly naive, but it shows that the parsing overhead for *just* the page selector vs
// the page and dialog selector is negligable. This could probably be speed up by
// doing a similar parent node traversal to the one found in the inherited theme code above
closestPageData : function ( $target ) {
return $target
. closest ( ':jqmData(role="page"), :jqmData(role="dialog")' )
. data ( "mobile-page" ) ;
} ,
enhanceable : function ( $set ) {
return this . haveParents ( $set , "enhance" ) ;
} ,
hijackable : function ( $set ) {
return this . haveParents ( $set , "ajax" ) ;
} ,
haveParents : function ( $set , attr ) {
if ( ! $ . mobile . ignoreContentEnabled ) {
return $set ;
}
var count = $set . length ,
$newSet = $ ( ) ,
e , $element , excluded ;
for ( var i = 0 ; i < count ; i ++ ) {
$element = $set . eq ( i ) ;
excluded = false ;
e = $set [ i ] ;
while ( e ) {
var c = e . getAttribute ? e . getAttribute ( "data-" + $ . mobile . ns + attr ) : "" ;
if ( c === "false" ) {
excluded = true ;
break ;
}
e = e . parentNode ;
}
if ( ! excluded ) {
$newSet = $newSet . add ( $element ) ;
}
}
return $newSet ;
} ,
getScreenHeight : function ( ) {
// Native innerHeight returns more accurate value for this across platforms,
// jQuery version is here as a normalized fallback for platforms like Symbian
return window . innerHeight || $ . mobile . window . height ( ) ;
}
} , $ . mobile ) ;
// Mobile version of data and removeData and hasData methods
// ensures all data is set and retrieved using jQuery Mobile's data namespace
$ . fn . jqmData = function ( prop , value ) {
var result ;
if ( typeof prop !== "undefined" ) {
if ( prop ) {
prop = $ . mobile . nsNormalize ( prop ) ;
}
// undefined is permitted as an explicit input for the second param
// in this case it returns the value and does not set it to undefined
if ( arguments . length < 2 || value === undefined ) {
result = this . data ( prop ) ;
} else {
result = this . data ( prop , value ) ;
}
}
return result ;
} ;
$ . jqmData = function ( elem , prop , value ) {
var result ;
if ( typeof prop !== "undefined" ) {
result = $ . data ( elem , prop ? $ . mobile . nsNormalize ( prop ) : prop , value ) ;
}
return result ;
} ;
$ . fn . jqmRemoveData = function ( prop ) {
return this . removeData ( $ . mobile . nsNormalize ( prop ) ) ;
} ;
$ . jqmRemoveData = function ( elem , prop ) {
return $ . removeData ( elem , $ . mobile . nsNormalize ( prop ) ) ;
} ;
$ . fn . removeWithDependents = function ( ) {
$ . removeWithDependents ( this ) ;
} ;
$ . removeWithDependents = function ( elem ) {
var $elem = $ ( elem ) ;
( $elem . jqmData ( 'dependents' ) || $ ( ) ) . remove ( ) ;
$elem . remove ( ) ;
} ;
$ . fn . addDependents = function ( newDependents ) {
$ . addDependents ( $ ( this ) , newDependents ) ;
} ;
$ . addDependents = function ( elem , newDependents ) {
var dependents = $ ( elem ) . jqmData ( 'dependents' ) || $ ( ) ;
$ ( elem ) . jqmData ( 'dependents' , $ . merge ( dependents , newDependents ) ) ;
} ;
// 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 $ ( "<div/>" ) . text ( $ ( this ) . text ( ) ) . html ( ) ;
} ;
// fluent helper function for the mobile namespaced equivalent
$ . fn . jqmEnhanceable = function ( ) {
return $ . mobile . enhanceable ( this ) ;
} ;
$ . fn . jqmHijackable = function ( ) {
return $ . mobile . hijackable ( this ) ;
} ;
// Monkey-patching Sizzle to filter the :jqmData selector
var oldFind = $ . find ,
jqmDataRE = /:jqmData\(([^)]*)\)/g ;
$ . find = function ( selector , context , ret , extra ) {
selector = selector . replace ( jqmDataRE , "[data-" + ( $ . mobile . ns || "" ) + "$1]" ) ;
return oldFind . call ( this , selector , context , ret , extra ) ;
} ;
$ . extend ( $ . find , oldFind ) ;
$ . find . matches = function ( expr , set ) {
return $ . find ( expr , null , null , set ) ;
} ;
$ . find . matchesSelector = function ( node , expr ) {
return $ . find ( expr , null , null , [ node ] ) . length > 0 ;
} ;
} ) ( jQuery , this ) ;
/ * !
* jQuery UI Widget v1 . 10.0 pre - 2012 - 11 - 13 ( ff055a0c353c3c8ce6e5bfa07ad7cb03e8885bc5 )
* http : //jqueryui.com
*
* Copyright 2010 , 2013 jQuery Foundation and other contributors
* Released under the MIT license .
* http : //jquery.org/license
*
* http : //api.jqueryui.com/jQuery.widget/
* /
( function ( $ , undefined ) {
var uuid = 0 ,
slice = Array . prototype . slice ,
_cleanData = $ . cleanData ;
$ . cleanData = function ( elems ) {
for ( var i = 0 , elem ; ( elem = elems [ i ] ) != null ; i ++ ) {
try {
$ ( elem ) . triggerHandler ( "remove" ) ;
// http://bugs.jquery.com/ticket/8235
} catch ( e ) { }
}
_cleanData ( elems ) ;
} ;
$ . widget = function ( name , base , prototype ) {
var fullName , existingConstructor , constructor , basePrototype ,
namespace = name . split ( "." ) [ 0 ] ;
name = name . split ( "." ) [ 1 ] ;
fullName = namespace + "-" + name ;
if ( ! prototype ) {
prototype = base ;
base = $ . Widget ;
}
// create selector for plugin
$ . expr [ ":" ] [ fullName . toLowerCase ( ) ] = function ( elem ) {
return ! ! $ . data ( elem , fullName ) ;
} ;
$ [ namespace ] = $ [ namespace ] || { } ;
existingConstructor = $ [ namespace ] [ name ] ;
constructor = $ [ namespace ] [ name ] = function ( options , element ) {
// allow instantiation without "new" keyword
if ( ! this . _createWidget ) {
return new constructor ( options , element ) ;
}
// allow instantiation without initializing for simple inheritance
// must use "new" keyword (the code above always passes args)
if ( arguments . length ) {
this . _createWidget ( options , element ) ;
}
} ;
// extend with the existing constructor to carry over any static properties
$ . extend ( constructor , existingConstructor , {
version : prototype . version ,
// copy the object used to create the prototype in case we need to
// redefine the widget later
_proto : $ . extend ( { } , prototype ) ,
// track widgets that inherit from this widget in case this widget is
// redefined after a widget inherits from it
_childConstructors : [ ]
} ) ;
basePrototype = new base ( ) ;
// we need to make the options hash a property directly on the new instance
// otherwise we'll modify the options hash on the prototype that we're
// inheriting from
basePrototype . options = $ . widget . extend ( { } , basePrototype . options ) ;
$ . each ( prototype , function ( prop , value ) {
if ( $ . isFunction ( value ) ) {
prototype [ prop ] = ( function ( ) {
var _super = function ( ) {
return base . prototype [ prop ] . apply ( this , arguments ) ;
} ,
_superApply = function ( args ) {
return base . prototype [ prop ] . apply ( this , args ) ;
} ;
return function ( ) {
var _ _super = this . _super ,
_ _superApply = this . _superApply ,
returnValue ;
this . _super = _super ;
this . _superApply = _superApply ;
returnValue = value . apply ( this , arguments ) ;
this . _super = _ _super ;
this . _superApply = _ _superApply ;
return returnValue ;
} ;
} ) ( ) ;
}
} ) ;
constructor . prototype = $ . widget . extend ( basePrototype , {
// TODO: remove support for widgetEventPrefix
// always use the name + a colon as the prefix, e.g., draggable:start
// don't prefix for widgets that aren't DOM-based
widgetEventPrefix : existingConstructor ? basePrototype . widgetEventPrefix : name
} , prototype , {
constructor : constructor ,
namespace : namespace ,
widgetName : name ,
widgetFullName : fullName
} ) ;
// If this widget is being redefined then we need to find all widgets that
// are inheriting from it and redefine all of them so that they inherit from
// the new version of this widget. We're essentially trying to replace one
// level in the prototype chain.
if ( existingConstructor ) {
$ . each ( existingConstructor . _childConstructors , function ( i , child ) {
var childPrototype = child . prototype ;
// redefine the child widget using the same prototype that was
// originally used, but inherit from the new version of the base
$ . widget ( childPrototype . namespace + "." + childPrototype . widgetName , constructor , child . _proto ) ;
} ) ;
// remove the list of existing child constructors from the old constructor
// so the old child constructors can be garbage collected
delete existingConstructor . _childConstructors ;
} else {
base . _childConstructors . push ( constructor ) ;
}
$ . widget . bridge ( name , constructor ) ;
} ;
$ . widget . extend = function ( target ) {
var input = slice . call ( arguments , 1 ) ,
inputIndex = 0 ,
inputLength = input . length ,
key ,
value ;
for ( ; inputIndex < inputLength ; inputIndex ++ ) {
for ( key in input [ inputIndex ] ) {
value = input [ inputIndex ] [ key ] ;
if ( input [ inputIndex ] . hasOwnProperty ( key ) && value !== undefined ) {
// Clone objects
if ( $ . isPlainObject ( value ) ) {
target [ key ] = $ . isPlainObject ( target [ key ] ) ?
$ . widget . extend ( { } , target [ key ] , value ) :
// Don't extend strings, arrays, etc. with objects
$ . widget . extend ( { } , value ) ;
// Copy everything else by reference
} else {
target [ key ] = value ;
}
}
}
}
return target ;
} ;
$ . widget . bridge = function ( name , object ) {
var fullName = object . prototype . widgetFullName || name ;
$ . fn [ name ] = function ( options ) {
var isMethodCall = typeof options === "string" ,
args = slice . call ( arguments , 1 ) ,
returnValue = this ;
// allow multiple hashes to be passed on init
options = ! isMethodCall && args . length ?
$ . widget . extend . apply ( null , [ options ] . concat ( args ) ) :
options ;
if ( isMethodCall ) {
this . each ( function ( ) {
var methodValue ,
instance = $ . data ( this , fullName ) ;
if ( ! instance ) {
return $ . error ( "cannot call methods on " + name + " prior to initialization; " +
"attempted to call method '" + options + "'" ) ;
}
if ( ! $ . isFunction ( instance [ options ] ) || options . charAt ( 0 ) === "_" ) {
return $ . error ( "no such method '" + options + "' for " + name + " widget instance" ) ;
}
methodValue = instance [ options ] . apply ( instance , args ) ;
if ( methodValue !== instance && methodValue !== undefined ) {
returnValue = methodValue && methodValue . jquery ?
returnValue . pushStack ( methodValue . get ( ) ) :
methodValue ;
return false ;
}
} ) ;
} else {
this . each ( function ( ) {
var instance = $ . data ( this , fullName ) ;
if ( instance ) {
instance . option ( options || { } ) . _init ( ) ;
} else {
$ . data ( this , fullName , new object ( options , this ) ) ;
}
} ) ;
}
return returnValue ;
} ;
} ;
$ . Widget = function ( /* options, element */ ) { } ;
$ . Widget . _childConstructors = [ ] ;
$ . Widget . prototype = {
widgetName : "widget" ,
widgetEventPrefix : "" ,
defaultElement : "<div>" ,
options : {
disabled : false ,
// callbacks
create : null
} ,
_createWidget : function ( options , element ) {
element = $ ( element || this . defaultElement || this ) [ 0 ] ;
this . element = $ ( element ) ;
this . uuid = uuid ++ ;
this . eventNamespace = "." + this . widgetName + this . uuid ;
this . options = $ . widget . extend ( { } ,
this . options ,
this . _getCreateOptions ( ) ,
options ) ;
this . bindings = $ ( ) ;
this . hoverable = $ ( ) ;
this . focusable = $ ( ) ;
if ( element !== this ) {
$ . data ( element , this . widgetFullName , this ) ;
this . _on ( true , this . element , {
remove : function ( event ) {
if ( event . target === element ) {
this . destroy ( ) ;
}
}
} ) ;
this . document = $ ( element . style ?
// element within the document
element . ownerDocument :
// element is window or document
element . document || element ) ;
this . window = $ ( this . document [ 0 ] . defaultView || this . document [ 0 ] . parentWindow ) ;
}
this . _create ( ) ;
this . _trigger ( "create" , null , this . _getCreateEventData ( ) ) ;
this . _init ( ) ;
} ,
_getCreateOptions : $ . noop ,
_getCreateEventData : $ . noop ,
_create : $ . noop ,
_init : $ . noop ,
destroy : function ( ) {
this . _destroy ( ) ;
// we can probably remove the unbind calls in 2.0
// all event bindings should go through this._on()
this . element
. unbind ( this . eventNamespace )
// 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 . eventNamespace )
. removeAttr ( "aria-disabled" )
. removeClass (
this . widgetFullName + "-disabled " +
"ui-state-disabled" ) ;
// clean up events and states
this . bindings . unbind ( this . eventNamespace ) ;
this . hoverable . removeClass ( "ui-state-hover" ) ;
this . focusable . removeClass ( "ui-state-focus" ) ;
} ,
_destroy : $ . noop ,
widget : function ( ) {
return this . element ;
} ,
option : function ( key , value ) {
var options = key ,
parts ,
curOption ,
i ;
if ( arguments . length === 0 ) {
// don't return a reference to the internal hash
return $ . widget . extend ( { } , this . options ) ;
}
if ( typeof key === "string" ) {
// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
options = { } ;
parts = key . split ( "." ) ;
key = parts . shift ( ) ;
if ( parts . length ) {
curOption = options [ key ] = $ . widget . extend ( { } , this . options [ key ] ) ;
for ( i = 0 ; i < parts . length - 1 ; i ++ ) {
curOption [ parts [ i ] ] = curOption [ parts [ i ] ] || { } ;
curOption = curOption [ parts [ i ] ] ;
}
key = parts . pop ( ) ;
if ( value === undefined ) {
return curOption [ key ] === undefined ? null : curOption [ key ] ;
}
curOption [ key ] = value ;
} else {
if ( value === undefined ) {
return this . options [ key ] === undefined ? null : this . options [ key ] ;
}
options [ key ] = value ;
}
}
this . _setOptions ( options ) ;
return this ;
} ,
_setOptions : function ( options ) {
var key ;
for ( key in options ) {
this . _setOption ( key , options [ key ] ) ;
}
return this ;
} ,
_setOption : function ( key , value ) {
this . options [ key ] = value ;
if ( key === "disabled" ) {
this . widget ( )
. toggleClass ( this . widgetFullName + "-disabled ui-state-disabled" , ! ! value )
. attr ( "aria-disabled" , value ) ;
this . hoverable . removeClass ( "ui-state-hover" ) ;
this . focusable . removeClass ( "ui-state-focus" ) ;
}
return this ;
} ,
enable : function ( ) {
return this . _setOption ( "disabled" , false ) ;
} ,
disable : function ( ) {
return this . _setOption ( "disabled" , true ) ;
} ,
_on : function ( suppressDisabledCheck , element , handlers ) {
var delegateElement ,
instance = this ;
// no suppressDisabledCheck flag, shuffle arguments
if ( typeof suppressDisabledCheck !== "boolean" ) {
handlers = element ;
element = suppressDisabledCheck ;
suppressDisabledCheck = false ;
}
// no element argument, shuffle and use this.element
if ( ! handlers ) {
handlers = element ;
element = this . element ;
delegateElement = this . widget ( ) ;
} else {
// accept selectors, DOM elements
element = delegateElement = $ ( element ) ;
this . bindings = this . bindings . add ( element ) ;
}
$ . each ( handlers , function ( event , handler ) {
function handlerProxy ( ) {
// allow widgets to customize the disabled handling
// - disabled as an array instead of boolean
// - disabled class as method for disabling individual parts
if ( ! suppressDisabledCheck &&
( instance . options . disabled === true ||
$ ( this ) . hasClass ( "ui-state-disabled" ) ) ) {
return ;
}
return ( typeof handler === "string" ? instance [ handler ] : handler )
. apply ( instance , arguments ) ;
}
// copy the guid so direct unbinding works
if ( typeof handler !== "string" ) {
handlerProxy . guid = handler . guid =
handler . guid || handlerProxy . guid || $ . guid ++ ;
}
var match = event . match ( /^(\w+)\s*(.*)$/ ) ,
eventName = match [ 1 ] + instance . eventNamespace ,
selector = match [ 2 ] ;
if ( selector ) {
delegateElement . delegate ( selector , eventName , handlerProxy ) ;
} else {
element . bind ( eventName , handlerProxy ) ;
}
} ) ;
} ,
_off : function ( element , eventName ) {
eventName = ( eventName || "" ) . split ( " " ) . join ( this . eventNamespace + " " ) + this . eventNamespace ;
element . unbind ( eventName ) . undelegate ( eventName ) ;
} ,
_delay : function ( handler , delay ) {
function handlerProxy ( ) {
return ( typeof handler === "string" ? instance [ handler ] : handler )
. apply ( instance , arguments ) ;
}
var instance = this ;
return setTimeout ( handlerProxy , delay || 0 ) ;
} ,
_hoverable : function ( element ) {
this . hoverable = this . hoverable . add ( element ) ;
this . _on ( element , {
mouseenter : function ( event ) {
$ ( event . currentTarget ) . addClass ( "ui-state-hover" ) ;
} ,
mouseleave : function ( event ) {
$ ( event . currentTarget ) . removeClass ( "ui-state-hover" ) ;
}
} ) ;
} ,
_focusable : function ( element ) {
this . focusable = this . focusable . add ( element ) ;
this . _on ( element , {
focusin : function ( event ) {
$ ( event . currentTarget ) . addClass ( "ui-state-focus" ) ;
} ,
focusout : function ( event ) {
$ ( event . currentTarget ) . removeClass ( "ui-state-focus" ) ;
}
} ) ;
} ,
_trigger : function ( type , event , data ) {
var prop , orig ,
callback = this . options [ type ] ;
data = data || { } ;
event = $ . Event ( event ) ;
event . type = ( type === this . widgetEventPrefix ?
type :
this . widgetEventPrefix + type ) . toLowerCase ( ) ;
// the original event may come from any element
// so we need to reset the target on the new event
event . target = this . element [ 0 ] ;
// copy original event properties over to the new event
orig = event . originalEvent ;
if ( orig ) {
for ( prop in orig ) {
if ( ! ( prop in event ) ) {
event [ prop ] = orig [ prop ] ;
}
}
}
this . element . trigger ( event , data ) ;
return ! ( $ . isFunction ( callback ) &&
callback . apply ( this . element [ 0 ] , [ event ] . concat ( data ) ) === false ||
event . isDefaultPrevented ( ) ) ;
}
} ;
$ . each ( { show : "fadeIn" , hide : "fadeOut" } , function ( method , defaultEffect ) {
$ . Widget . prototype [ "_" + method ] = function ( element , options , callback ) {
if ( typeof options === "string" ) {
options = { effect : options } ;
}
var hasOptions ,
effectName = ! options ?
method :
options === true || typeof options === "number" ?
defaultEffect :
options . effect || defaultEffect ;
options = options || { } ;
if ( typeof options === "number" ) {
options = { duration : options } ;
}
hasOptions = ! $ . isEmptyObject ( options ) ;
options . complete = callback ;
if ( options . delay ) {
element . delay ( options . delay ) ;
}
if ( hasOptions && $ . effects && $ . effects . effect [ effectName ] ) {
element [ method ] ( options ) ;
} else if ( effectName !== method && element [ effectName ] ) {
element [ effectName ] ( options . duration , options . easing , callback ) ;
} else {
element . queue ( function ( next ) {
$ ( this ) [ method ] ( ) ;
if ( callback ) {
callback . call ( element [ 0 ] ) ;
}
next ( ) ;
} ) ;
}
} ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ . widget ( "mobile.widget" , {
// decorate the parent _createWidget to trigger `widgetinit` for users
// who wish to do post post `widgetcreate` alterations/additions
//
// TODO create a pull request for jquery ui to trigger this event
// in the original _createWidget
_createWidget : function ( ) {
$ . Widget . prototype . _createWidget . apply ( this , arguments ) ;
this . _trigger ( 'init' ) ;
} ,
_getCreateOptions : function ( ) {
var elem = this . element ,
options = { } ;
$ . each ( this . options , function ( option ) {
var value = elem . jqmData ( option . replace ( /[A-Z]/g , function ( c ) {
return "-" + c . toLowerCase ( ) ;
} )
) ;
if ( value !== undefined ) {
options [ option ] = value ;
}
} ) ;
return options ;
} ,
enhanceWithin : function ( target , useKeepNative ) {
this . enhance ( $ ( this . options . initSelector , $ ( target ) ) , useKeepNative ) ;
} ,
enhance : function ( targets , useKeepNative ) {
var page , keepNative , $widgetElements = $ ( targets ) , self = this ;
// 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 ) ;
( function ( $ , window ) {
// DEPRECATED
// NOTE global mobile object settings
$ . extend ( $ . mobile , {
// DEPRECATED Should the text be visble in the loading message?
loadingMessageTextVisible : undefined ,
// DEPRECATED When the text is visible, what theme does the loading box use?
loadingMessageTheme : undefined ,
// DEPRECATED default message setting
loadingMessage : undefined ,
// 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 ) ;
} ,
// DEPRECATED
hidePageLoadingMsg : function ( ) {
$ . mobile . loading ( 'hide' ) ;
} ,
loading : function ( ) {
this . loaderWidget . loader . apply ( this . loaderWidget , arguments ) ;
}
} ) ;
// TODO move loader class down into the widget settings
var loaderClass = "ui-loader" , $html = $ ( "html" ) , $window = $ . mobile . window ;
$ . widget ( "mobile.loader" , {
// NOTE if the global config settings are defined they will override these
// options
options : {
// the theme for the loading message
theme : "a" ,
// whether the text in the loading message is shown
textVisible : false ,
// custom html for the inner content of the loading message
html : "" ,
// the text to be displayed when the popup is shown
text : "loading"
} ,
defaultHtml : "<div class='" + loaderClass + "'>" +
"<span class='ui-icon ui-icon-loading'></span>" +
"<h1></h1>" +
"</div>" ,
// For non-fixed supportin browsers. Position at y center (if scrollTop supported), above the activeBtn (if defined), or just 100px from top
fakeFixLoader : function ( ) {
var activeBtn = $ ( "." + $ . mobile . activeBtnClass ) . first ( ) ;
this . element
. css ( {
top : $ . support . scrollTop && $window . scrollTop ( ) + $window . height ( ) / 2 ||
activeBtn . length && activeBtn . offset ( ) . top || 100
} ) ;
} ,
// check position of loader to see if it appears to be "fixed" to center
// if not, use abs positioning
checkLoaderPosition : function ( ) {
var offset = this . element . offset ( ) ,
scrollTop = $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" , $ . proxy ( this . fakeFixLoader , this ) ) ;
}
} ,
resetHtml : function ( ) {
this . element . html ( $ ( this . defaultHtml ) . html ( ) ) ;
} ,
// Turn on/off page loading message. Theme doubles as an object argument
// with the following shape: { theme: '', text: '', html: '', textVisible: '' }
// NOTE that the $.mobile.loading* settings and params past the first are deprecated
// TODO sweet jesus we need to break some of this out
show : function ( theme , msgText , textonly ) {
var textVisible , message , $header , loadSettings ;
this . resetHtml ( ) ;
// use the prototype options so that people can set them globally at
// mobile init. Consistency, it's what's for dinner
if ( $ . type ( theme ) === "object" ) {
loadSettings = $ . extend ( { } , this . options , theme ) ;
// prefer object property from the param then the old theme setting
theme = loadSettings . theme || $ . mobile . loadingMessageTheme ;
} else {
loadSettings = this . options ;
// 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 ;
}
// 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" ) ;
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 ;
}
// add the proper css given the options (theme, text, etc)
// Force text visibility if the second argument was supplied, or
// if the text was explicitly set in the object args
this . element . attr ( "class" , loaderClass +
" ui-corner-all ui-body-" + theme +
" ui-loader-" + ( textVisible || msgText || theme . text ? "verbose" : "default" ) +
( loadSettings . textonly || textonly ? " ui-loader-textonly" : "" ) ) ;
// TODO verify that jquery.fn.html is ok to use in both cases here
// this might be overly defensive in preventing unknowing xss
// if the html attribute is defined on the loading settings, use that
// otherwise use the fallbacks from above
if ( loadSettings . html ) {
this . element . html ( loadSettings . html ) ;
} else {
this . element . find ( "h1" ) . text ( message ) ;
}
// attach the loader to the DOM
this . element . appendTo ( $ . mobile . pageContainer ) ;
// check that the loader is visible
this . checkLoaderPosition ( ) ;
// on scroll check the loader position
$window . bind ( "scroll" , $ . proxy ( this . checkLoaderPosition , this ) ) ;
}
} ,
hide : function ( ) {
$html . removeClass ( "ui-loading" ) ;
if ( $ . mobile . loadingMessage ) {
this . element . removeClass ( "ui-loader-fakefix" ) ;
}
$ . mobile . window . unbind ( "scroll" , this . fakeFixLoader ) ;
$ . mobile . window . unbind ( "scroll" , this . checkLoaderPosition ) ;
}
} ) ;
$window . bind ( 'pagecontainercreate' , function ( ) {
$ . mobile . loaderWidget = $ . mobile . loaderWidget || $ ( $ . mobile . loader . prototype . defaultHtml ) . loader ( ) ;
} ) ;
} ) ( jQuery , this ) ;
( function ( $ , undefined ) {
/*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */
window . matchMedia = window . matchMedia || ( function ( doc , undefined ) {
var bool ,
docElem = doc . documentElement ,
refNode = docElem . firstElementChild || docElem . firstChild ,
// fakeBody required for <FF4 when executed in <head>
fakeBody = doc . createElement ( "body" ) ,
div = doc . createElement ( "div" ) ;
div . id = "mq-test-1" ;
div . style . cssText = "position:absolute;top:-100em" ;
fakeBody . style . background = "none" ;
fakeBody . appendChild ( div ) ;
return function ( q ) {
div . innerHTML = "­<style media=\"" + q + "\"> #mq-test-1 { width: 42px; }</style>" ;
docElem . insertBefore ( fakeBody , refNode ) ;
bool = div . offsetWidth === 42 ;
docElem . removeChild ( fakeBody ) ;
return {
matches : bool ,
media : q
} ;
} ;
} ( document ) ) ;
// $.mobile.media uses matchMedia to return a boolean.
$ . mobile . media = function ( q ) {
return window . matchMedia ( q ) . matches ;
} ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
var support = {
touch : "ontouchend" in document
} ;
$ . mobile . support = $ . mobile . support || { } ;
$ . extend ( $ . support , support ) ;
$ . extend ( $ . mobile . support , support ) ;
} ( jQuery ) ) ;
( function ( $ , undefined ) {
$ . extend ( $ . support , {
orientation : "orientation" in window && "onorientationchange" in window
} ) ;
} ( jQuery ) ) ;
( function ( $ , undefined ) {
// thx Modernizr
function propExists ( prop ) {
var uc _prop = prop . charAt ( 0 ) . toUpperCase ( ) + prop . substr ( 1 ) ,
props = ( prop + " " + vendors . join ( uc _prop + " " ) + uc _prop ) . split ( " " ) ;
for ( var v in props ) {
if ( fbCSS [ props [ v ] ] !== undefined ) {
return true ;
}
}
}
var fakeBody = $ ( "<body>" ) . prependTo ( "html" ) ,
fbCSS = fakeBody [ 0 ] . style ,
vendors = [ "Webkit" , "Moz" , "O" ] ,
webos = "palmGetResource" in window , //only used to rule out scrollTop
opera = window . opera ,
operamini = window . operamini && ( { } ) . toString . call ( window . operamini ) === "[object OperaMini]" ,
bb = window . blackberry && ! propExists ( "-webkit-transform" ) ; //only used to rule out box shadow, as it's filled opaque on BB 5 and lower
function validStyle ( prop , value , check _vend ) {
var div = document . createElement ( 'div' ) ,
uc = function ( txt ) {
return txt . charAt ( 0 ) . toUpperCase ( ) + txt . substr ( 1 ) ;
} ,
vend _pref = function ( vend ) {
if ( vend === "" ) {
return "" ;
} else {
return "-" + vend . charAt ( 0 ) . toLowerCase ( ) + vend . substr ( 1 ) + "-" ;
}
} ,
check _style = function ( vend ) {
var vend _prop = vend _pref ( vend ) + prop + ": " + value + ";" ,
uc _vend = uc ( vend ) ,
propStyle = uc _vend + ( uc _vend === "" ? prop : uc ( prop ) ) ;
div . setAttribute ( "style" , vend _prop ) ;
if ( ! ! div . style [ propStyle ] ) {
ret = true ;
}
} ,
check _vends = check _vend ? check _vend : vendors ,
ret ;
for ( var i = 0 ; i < check _vends . length ; i ++ ) {
check _style ( check _vends [ i ] ) ;
}
return ! ! ret ;
}
function transform3dTest ( ) {
var mqProp = "transform-3d" ,
// Because the `translate3d` test below throws false positives in Android:
ret = $ . mobile . media ( "(-" + vendors . join ( "-" + mqProp + "),(-" ) + "-" + mqProp + "),(" + mqProp + ")" ) ;
if ( ret ) {
return ! ! ret ;
}
var el = document . createElement ( "div" ) ,
transforms = {
// We’ re omitting Opera for the time being; MS uses unprefixed.
'MozTransform' : '-moz-transform' ,
'transform' : 'transform'
} ;
fakeBody . append ( el ) ;
for ( var t in transforms ) {
if ( el . style [ t ] !== undefined ) {
el . style [ t ] = 'translate3d( 100px, 1px, 1px )' ;
ret = window . getComputedStyle ( el ) . getPropertyValue ( transforms [ t ] ) ;
}
}
return ( ! ! ret && ret !== "none" ) ;
}
// Test for dynamic-updating base tag support ( allows us to avoid href,src attr rewriting )
function baseTagTest ( ) {
var fauxBase = location . protocol + "//" + location . host + location . pathname + "ui-dir/" ,
base = $ ( "head base" ) ,
fauxEle = null ,
href = "" ,
link , rebase ;
if ( ! base . length ) {
base = fauxEle = $ ( "<base>" , { "href" : fauxBase } ) . appendTo ( "head" ) ;
} else {
href = base . attr ( "href" ) ;
}
link = $ ( "<a href='testurl' />" ) . prependTo ( fakeBody ) ;
rebase = link [ 0 ] . href ;
base [ 0 ] . href = href || location . pathname ;
if ( fauxEle ) {
fauxEle . remove ( ) ;
}
return rebase . indexOf ( fauxBase ) === 0 ;
}
// Thanks Modernizr
function cssPointerEventsTest ( ) {
var element = document . createElement ( 'x' ) ,
documentElement = document . documentElement ,
getComputedStyle = window . getComputedStyle ,
supports ;
if ( ! ( 'pointerEvents' in element . style ) ) {
return false ;
}
element . style . pointerEvents = 'auto' ;
element . style . pointerEvents = 'x' ;
documentElement . appendChild ( element ) ;
supports = getComputedStyle &&
getComputedStyle ( element , '' ) . pointerEvents === 'auto' ;
documentElement . removeChild ( element ) ;
return ! ! supports ;
}
function boundingRect ( ) {
var div = document . createElement ( "div" ) ;
return typeof div . getBoundingClientRect !== "undefined" ;
}
// non-UA-based IE version check by James Padolsey, modified by jdalton - from http://gist.github.com/527683
// allows for inclusion of IE 6+, including Windows Mobile 7
$ . extend ( $ . mobile , { browser : { } } ) ;
$ . mobile . browser . oldIE = ( function ( ) {
var v = 3 ,
div = document . createElement ( "div" ) ,
a = div . all || [ ] ;
do {
div . innerHTML = "<!--[if gt IE " + ( ++ v ) + "]><br><![endif]-->" ;
} while ( a [ 0 ] ) ;
return v > 4 ? v : ! v ;
} ) ( ) ;
function fixedPosition ( ) {
var w = window ,
ua = navigator . userAgent ,
platform = navigator . platform ,
// Rendering engine is Webkit, and capture major version
wkmatch = ua . match ( /AppleWebKit\/([0-9]+)/ ) ,
wkversion = ! ! wkmatch && wkmatch [ 1 ] ,
ffmatch = ua . match ( /Fennec\/([0-9]+)/ ) ,
ffversion = ! ! ffmatch && ffmatch [ 1 ] ,
operammobilematch = ua . match ( /Opera Mobi\/([0-9]+)/ ) ,
omversion = ! ! operammobilematch && operammobilematch [ 1 ] ;
if (
// iOS 4.3 and older : Platform is iPhone/Pad/Touch and Webkit version is less than 534 (ios5)
( ( platform . indexOf ( "iPhone" ) > - 1 || platform . indexOf ( "iPad" ) > - 1 || platform . indexOf ( "iPod" ) > - 1 ) && wkversion && wkversion < 534 ) ||
// Opera Mini
( w . operamini && ( { } ) . toString . call ( w . operamini ) === "[object OperaMini]" ) ||
( operammobilematch && omversion < 7458 ) ||
//Android lte 2.1: Platform is Android and Webkit version is less than 533 (Android 2.2)
( ua . indexOf ( "Android" ) > - 1 && wkversion && wkversion < 533 ) ||
// Firefox Mobile before 6.0 -
( ffversion && ffversion < 6 ) ||
// WebOS less than 3
( "palmGetResource" in window && wkversion && wkversion < 534 ) ||
// MeeGo
( ua . indexOf ( "MeeGo" ) > - 1 && ua . indexOf ( "NokiaBrowser/8.5.0" ) > - 1 ) ) {
return false ;
}
return true ;
}
$ . extend ( $ . support , {
cssTransitions : "WebKitTransitionEvent" in window ||
validStyle ( 'transition' , 'height 100ms linear' , [ "Webkit" , "Moz" , "" ] ) &&
! $ . mobile . browser . oldIE && ! opera ,
// Note, Chrome for iOS has an extremely quirky implementation of popstate.
// We've chosen to take the shortest path to a bug fix here for issue #5426
// See the following link for information about the regex chosen
// https://developers.google.com/chrome/mobile/docs/user-agent#chrome_for_ios_user-agent
pushState : "pushState" in history &&
"replaceState" in history &&
( window . navigator . userAgent . search ( /CriOS/ ) === - 1 ) ,
mediaquery : $ . mobile . media ( "only all" ) ,
cssPseudoElement : ! ! propExists ( "content" ) ,
touchOverflow : ! ! propExists ( "overflowScrolling" ) ,
cssTransform3d : transform3dTest ( ) ,
boxShadow : ! ! propExists ( "boxShadow" ) && ! bb ,
fixedPosition : fixedPosition ( ) ,
scrollTop : ( "pageXOffset" in window ||
"scrollTop" in document . documentElement ||
"scrollTop" in fakeBody [ 0 ] ) && ! webos && ! operamini ,
dynamicBaseTag : baseTagTest ( ) ,
cssPointerEvents : cssPointerEventsTest ( ) ,
boundingRect : boundingRect ( )
} ) ;
fakeBody . remove ( ) ;
// $.mobile.ajaxBlacklist is used to override ajaxEnabled on platforms that have known conflicts with hash history updates (BB5, Symbian)
// or that generally work better browsing in regular http for full page refreshes (Opera Mini)
// Note: This detection below is used as a last resort.
// We recommend only using these detection methods when all other more reliable/forward-looking approaches are not possible
var nokiaLTE7 _3 = ( function ( ) {
var ua = window . navigator . userAgent ;
//The following is an attempt to match Nokia browsers that are running Symbian/s60, with webkit, version 7.3 or older
return ua . indexOf ( "Nokia" ) > - 1 &&
( ua . indexOf ( "Symbian/3" ) > - 1 || ua . indexOf ( "Series60/5" ) > - 1 ) &&
ua . indexOf ( "AppleWebKit" ) > - 1 &&
ua . match ( /(BrowserNG|NokiaBrowser)\/7\.[0-3]/ ) ;
} ) ( ) ;
// Support conditions that must be met in order to proceed
// default enhanced qualifications are media query support OR IE 7+
$ . mobile . gradeA = function ( ) {
return ( $ . support . mediaquery || $ . mobile . browser . oldIE && $ . mobile . browser . oldIE >= 7 ) && ( $ . support . boundingRect || $ . fn . jquery . match ( /1\.[0-7+]\.[0-9+]?/ ) !== null ) ;
} ;
$ . mobile . ajaxBlacklist =
// BlackBerry browsers, pre-webkit
window . blackberry && ! window . WebKitPoint ||
// Opera Mini
operamini ||
// Symbian webkits pre 7.3
nokiaLTE7 _3 ;
// Lastly, this workaround is the only way we've found so far to get pre 7.3 Symbian webkit devices
// to render the stylesheets when they're referenced before this script, as we'd recommend doing.
// This simply reappends the CSS in place, which for some reason makes it apply
if ( nokiaLTE7 _3 ) {
$ ( function ( ) {
$ ( "head link[rel='stylesheet']" ) . attr ( "rel" , "alternate stylesheet" ) . attr ( "rel" , "stylesheet" ) ;
} ) ;
}
// For ruling out shadows via css
if ( ! $ . support . boxShadow ) {
$ ( "html" ) . addClass ( "ui-mobile-nosupport-boxshadow" ) ;
}
} ) ( jQuery ) ;
( function ( $ , undefined ) {
var $win = $ . mobile . window , self , history ;
$ . event . special . navigate = self = {
bound : false ,
pushStateEnabled : true ,
originalEventName : undefined ,
// If pushstate support is present and push state support is defined to
// be true on the mobile namespace.
isPushStateEnabled : function ( ) {
return $ . support . pushState &&
$ . mobile . pushStateEnabled === true &&
this . isHashChangeEnabled ( ) ;
} ,
// !! assumes mobile namespace is present
isHashChangeEnabled : function ( ) {
return $ . mobile . hashListeningEnabled === true ;
} ,
// TODO a lot of duplication between popstate and hashchange
popstate : function ( event ) {
var newEvent = new $ . Event ( "navigate" ) ,
beforeNavigate = new $ . Event ( "beforenavigate" ) ,
state = event . originalEvent . state || { } ,
href = location . href ;
$win . trigger ( beforeNavigate ) ;
if ( beforeNavigate . isDefaultPrevented ( ) ) {
return ;
}
if ( event . historyState ) {
$ . extend ( state , event . historyState ) ;
}
// Make sure the original event is tracked for the end
// user to inspect incase they want to do something special
newEvent . originalEvent = event ;
// NOTE we let the current stack unwind because any assignment to
// location.hash will stop the world and run this event handler. By
// doing this we create a similar behavior to hashchange on hash
// assignment
setTimeout ( function ( ) {
$win . trigger ( newEvent , {
state : state
} ) ;
} , 0 ) ;
} ,
hashchange : function ( event , data ) {
var newEvent = new $ . Event ( "navigate" ) ,
beforeNavigate = new $ . Event ( "beforenavigate" ) ;
$win . trigger ( beforeNavigate ) ;
if ( beforeNavigate . isDefaultPrevented ( ) ) {
return ;
}
// Make sure the original event is tracked for the end
// user to inspect incase they want to do something special
newEvent . originalEvent = event ;
// Trigger the hashchange with state provided by the user
// that altered the hash
$win . trigger ( newEvent , {
// Users that want to fully normalize the two events
// will need to do history management down the stack and
// add the state to the event before this binding is fired
// TODO consider allowing for the explicit addition of callbacks
// to be fired before this value is set to avoid event timing issues
state : event . hashchangeState || { }
} ) ;
} ,
// TODO We really only want to set this up once
// but I'm not clear if there's a beter way to achieve
// this with the jQuery special event structure
setup : function ( data , namespaces ) {
if ( self . bound ) {
return ;
}
self . bound = true ;
if ( self . isPushStateEnabled ( ) ) {
self . originalEventName = "popstate" ;
$win . bind ( "popstate.navigate" , self . popstate ) ;
} else if ( self . isHashChangeEnabled ( ) ) {
self . originalEventName = "hashchange" ;
$win . bind ( "hashchange.navigate" , self . hashchange ) ;
}
}
} ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
var path , documentBase , $base , dialogHashKey = "&ui-state=dialog" ;
$ . mobile . path = path = {
uiStateKey : "&ui-state" ,
// This scary looking regular expression parses an absolute URL or its relative
// variants (protocol, site, document, query, and hash), into the various
// components (protocol, host, path, query, fragment, etc that make up the
// URL as well as some other commonly used sub-parts. When used with RegExp.exec()
// or String.match, it parses the URL into a results array that looks like this:
//
// [0]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread#msg-content
// [1]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread
// [2]: http://jblas:password@mycompany.com:8080/mail/inbox
// [3]: http://jblas:password@mycompany.com:8080
// [4]: http:
// [5]: //
// [6]: jblas:password@mycompany.com:8080
// [7]: jblas:password
// [8]: jblas
// [9]: password
// [10]: mycompany.com:8080
// [11]: mycompany.com
// [12]: 8080
// [13]: /mail/inbox
// [14]: /mail/
// [15]: inbox
// [16]: ?msg=1234&type=unread
// [17]: #msg-content
//
urlParseRE : /^\s*(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/ ,
// Abstraction to address xss (Issue #4787) by removing the authority in
// browsers that auto decode it. All references to location.href should be
// replaced with a call to this method so that it can be dealt with properly here
getLocation : function ( url ) {
var uri = url ? this . parseUrl ( url ) : location ,
hash = this . parseUrl ( url || location . href ) . hash ;
// mimic the browser with an empty string when the hash is empty
hash = hash === "#" ? "" : hash ;
// Make sure to parse the url or the location object for the hash because using location.hash
// is autodecoded in firefox, the rest of the url should be from the object (location unless
// we're testing) to avoid the inclusion of the authority
return uri . protocol + "//" + uri . host + uri . pathname + uri . search + hash ;
} ,
parseLocation : function ( ) {
return this . parseUrl ( this . getLocation ( ) ) ;
} ,
//Parse a URL into a structure that allows easy access to
//all of the URL components by name.
parseUrl : function ( url ) {
// If we're passed an object, we'll assume that it is
// a parsed url object and just return it back to the caller.
if ( $ . type ( url ) === "object" ) {
return url ;
}
var matches = path . urlParseRE . exec ( url || "" ) || [ ] ;
// Create an object that allows the caller to access the sub-matches
// by name. Note that IE returns an empty string instead of undefined,
// like all other browsers do, so we normalize everything so its consistent
// no matter what browser we're running on.
return {
href : matches [ 0 ] || "" ,
hrefNoHash : matches [ 1 ] || "" ,
hrefNoSearch : matches [ 2 ] || "" ,
domain : matches [ 3 ] || "" ,
protocol : matches [ 4 ] || "" ,
doubleSlash : matches [ 5 ] || "" ,
authority : matches [ 6 ] || "" ,
username : matches [ 8 ] || "" ,
password : matches [ 9 ] || "" ,
host : matches [ 10 ] || "" ,
hostname : matches [ 11 ] || "" ,
port : matches [ 12 ] || "" ,
pathname : matches [ 13 ] || "" ,
directory : matches [ 14 ] || "" ,
filename : matches [ 15 ] || "" ,
search : matches [ 16 ] || "" ,
hash : matches [ 17 ] || ""
} ;
} ,
//Turn relPath into an asbolute path. absPath is
//an optional absolute path which describes what
//relPath is relative to.
makePathAbsolute : function ( relPath , absPath ) {
if ( relPath && relPath . charAt ( 0 ) === "/" ) {
return relPath ;
}
relPath = relPath || "" ;
absPath = absPath ? absPath . replace ( /^\/|(\/[^\/]*|[^\/]+)$/g , "" ) : "" ;
var absStack = absPath ? absPath . split ( "/" ) : [ ] ,
relStack = relPath . split ( "/" ) ;
for ( var i = 0 ; i < relStack . length ; i ++ ) {
var d = relStack [ i ] ;
switch ( d ) {
case "." :
break ;
case ".." :
if ( absStack . length ) {
absStack . pop ( ) ;
}
break ;
default :
absStack . push ( d ) ;
break ;
}
}
return "/" + absStack . join ( "/" ) ;
} ,
//Returns true if both urls have the same domain.
isSameDomain : function ( absUrl1 , absUrl2 ) {
return path . parseUrl ( absUrl1 ) . domain === path . parseUrl ( absUrl2 ) . domain ;
} ,
//Returns true for any relative variant.
isRelativeUrl : function ( url ) {
// All relative Url variants have one thing in common, no protocol.
return path . parseUrl ( url ) . protocol === "" ;
} ,
//Returns true for an absolute url.
isAbsoluteUrl : function ( url ) {
return path . parseUrl ( url ) . protocol !== "" ;
} ,
//Turn the specified realtive URL into an absolute one. This function
//can handle all relative variants (protocol, site, document, query, fragment).
makeUrlAbsolute : function ( relUrl , absUrl ) {
if ( ! path . isRelativeUrl ( relUrl ) ) {
return relUrl ;
}
if ( absUrl === undefined ) {
absUrl = this . documentBase ;
}
var relObj = path . parseUrl ( relUrl ) ,
absObj = path . parseUrl ( absUrl ) ,
protocol = relObj . protocol || absObj . protocol ,
doubleSlash = relObj . protocol ? relObj . doubleSlash : ( relObj . doubleSlash || absObj . doubleSlash ) ,
authority = relObj . authority || absObj . authority ,
hasPath = relObj . pathname !== "" ,
pathname = path . makePathAbsolute ( relObj . pathname || absObj . filename , absObj . pathname ) ,
search = relObj . search || ( ! hasPath && absObj . search ) || "" ,
hash = relObj . hash ;
return protocol + doubleSlash + authority + pathname + search + hash ;
} ,
//Add search (aka query) params to the specified url.
addSearchParams : function ( url , params ) {
var u = path . parseUrl ( url ) ,
p = ( typeof params === "object" ) ? $ . param ( params ) : params ,
s = u . search || "?" ;
return u . hrefNoSearch + s + ( s . charAt ( s . length - 1 ) !== "?" ? "&" : "" ) + p + ( u . hash || "" ) ;
} ,
convertUrlToDataUrl : function ( absUrl ) {
var u = path . parseUrl ( absUrl ) ;
if ( path . isEmbeddedPage ( u ) ) {
// For embedded pages, remove the dialog hash key as in getFilePath(),
// and remove otherwise the Data Url won't match the id of the embedded Page.
return u . hash
. split ( dialogHashKey ) [ 0 ]
. replace ( /^#/ , "" )
. replace ( /\?.*$/ , "" ) ;
} else if ( path . isSameDomain ( u , this . documentBase ) ) {
return u . hrefNoHash . replace ( this . documentBase . domain , "" ) . split ( dialogHashKey ) [ 0 ] ;
}
return window . decodeURIComponent ( absUrl ) ;
} ,
//get path from current hash, or from a file path
get : function ( newPath ) {
if ( newPath === undefined ) {
newPath = path . parseLocation ( ) . hash ;
}
return path . stripHash ( newPath ) . replace ( /[^\/]*\.[^\/*]+$/ , '' ) ;
} ,
//set location hash to path
set : function ( path ) {
location . hash = path ;
} ,
//test if a given url (string) is a path
//NOTE might be exceptionally naive
isPath : function ( url ) {
return ( /\// ) . test ( url ) ;
} ,
//return a url path with the window's location protocol/hostname/pathname removed
clean : function ( url ) {
return url . replace ( this . documentBase . domain , "" ) ;
} ,
//just return the url without an initial #
stripHash : function ( url ) {
return url . replace ( /^#/ , "" ) ;
} ,
stripQueryParams : function ( url ) {
return url . replace ( /\?.*$/ , "" ) ;
} ,
//remove the preceding hash, any query params, and dialog notations
cleanHash : function ( hash ) {
return path . stripHash ( hash . replace ( /\?.*$/ , "" ) . replace ( dialogHashKey , "" ) ) ;
} ,
isHashValid : function ( hash ) {
return ( /^#[^#]+$/ ) . test ( hash ) ;
} ,
//check whether a url is referencing the same domain, or an external domain or different protocol
//could be mailto, etc
isExternal : function ( url ) {
var u = path . parseUrl ( url ) ;
return u . protocol && u . domain !== this . documentUrl . domain ? true : false ;
} ,
hasProtocol : function ( url ) {
return ( /^(:?\w+:)/ ) . test ( url ) ;
} ,
isEmbeddedPage : function ( url ) {
var u = path . parseUrl ( url ) ;
//if the path is absolute, then we need to compare the url against
//both the this.documentUrl and the documentBase. The main reason for this
//is that links embedded within external documents will refer to the
//application document, whereas links embedded within the application
//document will be resolved against the document base.
if ( u . protocol !== "" ) {
return ( ! this . isPath ( u . hash ) && u . hash && ( u . hrefNoHash === this . documentUrl . hrefNoHash || ( this . documentBaseDiffers && u . hrefNoHash === this . documentBase . hrefNoHash ) ) ) ;
}
return ( /^#/ ) . test ( u . href ) ;
} ,
squash : function ( url , resolutionUrl ) {
var state , href , cleanedUrl , search , stateIndex ,
isPath = this . isPath ( url ) ,
uri = this . parseUrl ( url ) ,
preservedHash = uri . hash ,
uiState = "" ;
// produce a url against which we can resole the provided path
resolutionUrl = resolutionUrl || ( path . isPath ( url ) ? path . getLocation ( ) : path . getDocumentUrl ( ) ) ;
// If the url is anything but a simple string, remove any preceding hash
// eg #foo/bar -> foo/bar
// #foo -> #foo
cleanedUrl = isPath ? path . stripHash ( url ) : url ;
// If the url is a full url with a hash check if the parsed hash is a path
// if it is, strip the #, and use it otherwise continue without change
cleanedUrl = path . isPath ( uri . hash ) ? path . stripHash ( uri . hash ) : cleanedUrl ;
// Split the UI State keys off the href
stateIndex = cleanedUrl . indexOf ( this . uiStateKey ) ;
// store the ui state keys for use
if ( stateIndex > - 1 ) {
uiState = cleanedUrl . slice ( stateIndex ) ;
cleanedUrl = cleanedUrl . slice ( 0 , stateIndex ) ;
}
// make the cleanedUrl absolute relative to the resolution url
href = path . makeUrlAbsolute ( cleanedUrl , resolutionUrl ) ;
// grab the search from the resolved url since parsing from
// the passed url may not yield the correct result
search = this . parseUrl ( href ) . search ;
// TODO all this crap is terrible, clean it up
if ( isPath ) {
// reject the hash if it's a path or it's just a dialog key
if ( path . isPath ( preservedHash ) || preservedHash . replace ( "#" , "" ) . indexOf ( this . uiStateKey ) === 0 ) {
preservedHash = "" ;
}
// Append the UI State keys where it exists and it's been removed
// from the url
if ( uiState && preservedHash . indexOf ( this . uiStateKey ) === - 1 ) {
preservedHash += uiState ;
}
// make sure that pound is on the front of the hash
if ( preservedHash . indexOf ( "#" ) === - 1 && preservedHash !== "" ) {
preservedHash = "#" + preservedHash ;
}
// reconstruct each of the pieces with the new search string and hash
href = path . parseUrl ( href ) ;
href = href . protocol + "//" + href . host + href . pathname + search + preservedHash ;
} else {
href += href . indexOf ( "#" ) > - 1 ? uiState : "#" + uiState ;
}
return href ;
} ,
isPreservableHash : function ( hash ) {
return hash . replace ( "#" , "" ) . indexOf ( this . uiStateKey ) === 0 ;
}
} ;
path . documentUrl = path . parseLocation ( ) ;
$base = $ ( "head" ) . find ( "base" ) ;
path . documentBase = $base . length ?
path . parseUrl ( path . makeUrlAbsolute ( $base . attr ( "href" ) , path . documentUrl . href ) ) :
path . documentUrl ;
path . documentBaseDiffers = ( path . documentUrl . hrefNoHash !== path . documentBase . hrefNoHash ) ;
//return the original document url
path . getDocumentUrl = function ( asParsedObject ) {
return asParsedObject ? $ . extend ( { } , path . documentUrl ) : path . documentUrl . href ;
} ;
//return the original document base url
path . getDocumentBase = function ( asParsedObject ) {
return asParsedObject ? $ . extend ( { } , path . documentBase ) : path . documentBase . href ;
} ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
var path = $ . mobile . path ;
$ . mobile . History = function ( stack , index ) {
this . stack = stack || [ ] ;
this . activeIndex = index || 0 ;
} ;
$ . extend ( $ . mobile . History . prototype , {
getActive : function ( ) {
return this . stack [ this . activeIndex ] ;
} ,
getLast : function ( ) {
return this . stack [ this . previousIndex ] ;
} ,
getNext : function ( ) {
return this . stack [ this . activeIndex + 1 ] ;
} ,
getPrev : function ( ) {
return this . stack [ this . activeIndex - 1 ] ;
} ,
// addNew is used whenever a new page is added
add : function ( url , data ) {
data = data || { } ;
//if there's forward history, wipe it
if ( this . getNext ( ) ) {
this . clearForward ( ) ;
}
// if the hash is included in the data make sure the shape
// is consistent for comparison
if ( data . hash && data . hash . indexOf ( "#" ) === - 1 ) {
data . hash = "#" + data . hash ;
}
data . url = url ;
this . stack . push ( data ) ;
this . activeIndex = this . stack . length - 1 ;
} ,
//wipe urls ahead of active index
clearForward : function ( ) {
this . stack = this . stack . slice ( 0 , this . activeIndex + 1 ) ;
} ,
find : function ( url , stack , earlyReturn ) {
stack = stack || this . stack ;
var entry , i , length = stack . length , index ;
for ( i = 0 ; i < length ; i ++ ) {
entry = stack [ i ] ;
if ( decodeURIComponent ( url ) === decodeURIComponent ( entry . url ) ||
decodeURIComponent ( url ) === decodeURIComponent ( entry . hash ) ) {
index = i ;
if ( earlyReturn ) {
return index ;
}
}
}
return index ;
} ,
closest : function ( url ) {
var closest , a = this . activeIndex ;
// First, take the slice of the history stack before the current index and search
// for a url match. If one is found, we'll avoid avoid looking through forward history
// NOTE the preference for backward history movement is driven by the fact that
// most mobile browsers only have a dedicated back button, and users rarely use
// the forward button in desktop browser anyhow
closest = this . find ( url , this . stack . slice ( 0 , a ) ) ;
// If nothing was found in backward history check forward. The `true`
// value passed as the third parameter causes the find method to break
// on the first match in the forward history slice. The starting index
// of the slice must then be added to the result to get the element index
// in the original history stack :( :(
//
// TODO this is hyper confusing and should be cleaned up (ugh so bad)
if ( closest === undefined ) {
closest = this . find ( url , this . stack . slice ( a ) , true ) ;
closest = closest === undefined ? closest : closest + a ;
}
return closest ;
} ,
direct : function ( opts ) {
var newActiveIndex = this . closest ( opts . url ) , a = this . activeIndex ;
// save new page index, null check to prevent falsey 0 result
// record the previous index for reference
if ( newActiveIndex !== undefined ) {
this . activeIndex = newActiveIndex ;
this . previousIndex = a ;
}
// invoke callbacks where appropriate
//
// TODO this is also convoluted and confusing
if ( newActiveIndex < a ) {
( opts . present || opts . back || $ . noop ) ( this . getActive ( ) , 'back' ) ;
} else if ( newActiveIndex > a ) {
( opts . present || opts . forward || $ . noop ) ( this . getActive ( ) , 'forward' ) ;
} else if ( newActiveIndex === undefined && opts . missing ) {
opts . missing ( this . getActive ( ) ) ;
}
}
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
var path = $ . mobile . path ;
$ . mobile . Navigator = function ( history ) {
this . history = history ;
this . ignoreInitialHashChange = true ;
// This ensures that browsers which don't fire the initial popstate
// like opera don't have further hash assignment popstates blocked
setTimeout ( $ . proxy ( function ( ) {
this . ignoreInitialHashChange = false ;
} , this ) , 200 ) ;
$ . mobile . window . bind ( {
"popstate.history" : $ . proxy ( this . popstate , this ) ,
"hashchange.history" : $ . proxy ( this . hashchange , this )
} ) ;
} ;
$ . extend ( $ . mobile . Navigator . prototype , {
squash : function ( url , data ) {
var state , href , hash = path . isPath ( url ) ? path . stripHash ( url ) : url ;
href = path . squash ( url ) ;
// make sure to provide this information when it isn't explicitly set in the
// data object that was passed to the squash method
state = $ . extend ( {
hash : hash ,
url : href
} , data ) ;
// replace the current url with the new href and store the state
// Note that in some cases we might be replacing an url with the
// same url. We do this anyways because we need to make sure that
// all of our history entries have a state object associated with
// them. This allows us to work around the case where $.mobile.back()
// is called to transition from an external page to an embedded page.
// In that particular case, a hashchange event is *NOT* generated by the browser.
// Ensuring each history entry has a state object means that onPopState()
// will always trigger our hashchange callback even when a hashchange event
// is not fired.
window . history . replaceState ( state , state . title || document . title , href ) ;
return state ;
} ,
hash : function ( url , href ) {
var parsed , loc , hash ;
// Grab the hash for recording. If the passed url is a path
// we used the parsed version of the squashed url to reconstruct,
// otherwise we assume it's a hash and store it directly
parsed = path . parseUrl ( url ) ;
loc = path . parseLocation ( ) ;
if ( loc . pathname + loc . search === parsed . pathname + parsed . search ) {
// If the pathname and search of the passed url is identical to the current loc
// then we must use the hash. Otherwise there will be no event
// eg, url = "/foo/bar?baz#bang", location.href = "http://example.com/foo/bar?baz"
hash = parsed . hash ? parsed . hash : parsed . pathname + parsed . search ;
} else if ( path . isPath ( url ) ) {
var resolved = path . parseUrl ( href ) ;
// If the passed url is a path, make it domain relative and remove any trailing hash
hash = resolved . pathname + resolved . search + ( path . isPreservableHash ( resolved . hash ) ? resolved . hash . replace ( "#" , "" ) : "" ) ;
} else {
hash = url ;
}
return hash ;
} ,
// TODO reconsider name
go : function ( url , data , noEvents ) {
var state , href , hash , popstateEvent ,
isPopStateEvent = $ . event . special . navigate . isPushStateEnabled ( ) ;
// Get the url as it would look squashed on to the current resolution url
href = path . squash ( url ) ;
// sort out what the hash sould be from the url
hash = this . hash ( url , href ) ;
// Here we prevent the next hash change or popstate event from doing any
// history management. In the case of hashchange we don't swallow it
// if there will be no hashchange fired (since that won't reset the value)
// and will swallow the following hashchange
if ( noEvents && hash !== path . stripHash ( path . parseLocation ( ) . hash ) ) {
this . preventNextHashChange = noEvents ;
}
// IMPORTANT in the case where popstate is supported the event will be triggered
// directly, stopping further execution - ie, interupting the flow of this
// method call to fire bindings at this expression. Below the navigate method
// there is a binding to catch this event and stop its propagation.
//
// We then trigger a new popstate event on the window with a null state
// so that the navigate events can conclude their work properly
//
// if the url is a path we want to preserve the query params that are available on
// the current url.
this . preventHashAssignPopState = true ;
window . location . hash = hash ;
// If popstate is enabled and the browser triggers `popstate` events when the hash
// is set (this often happens immediately in browsers like Chrome), then the
// this flag will be set to false already. If it's a browser that does not trigger
// a `popstate` on hash assignement or `replaceState` then we need avoid the branch
// that swallows the event created by the popstate generated by the hash assignment
// At the time of this writing this happens with Opera 12 and some version of IE
this . preventHashAssignPopState = false ;
state = $ . extend ( {
url : href ,
hash : hash ,
title : document . title
} , data ) ;
if ( isPopStateEvent ) {
popstateEvent = new $ . Event ( "popstate" ) ;
popstateEvent . originalEvent = {
type : "popstate" ,
state : null
} ;
this . squash ( url , state ) ;
// Trigger a new faux popstate event to replace the one that we
// caught that was triggered by the hash setting above.
if ( ! noEvents ) {
this . ignorePopState = true ;
$ . mobile . window . trigger ( popstateEvent ) ;
}
}
// record the history entry so that the information can be included
// in hashchange event driven navigate events in a similar fashion to
// the state that's provided by popstate
this . history . add ( state . url , state ) ;
} ,
// This binding is intended to catch the popstate events that are fired
// when execution of the `$.navigate` method stops at window.location.hash = url;
// and completely prevent them from propagating. The popstate event will then be
// retriggered after execution resumes
//
// TODO grab the original event here and use it for the synthetic event in the
// second half of the navigate execution that will follow this binding
popstate : function ( event ) {
var active , hash , state , closestIndex ;
// Partly to support our test suite which manually alters the support
// value to test hashchange. Partly to prevent all around weirdness
if ( ! $ . event . special . navigate . isPushStateEnabled ( ) ) {
return ;
}
// If this is the popstate triggered by the actual alteration of the hash
// prevent it completely. History is tracked manually
if ( this . preventHashAssignPopState ) {
this . preventHashAssignPopState = false ;
event . stopImmediatePropagation ( ) ;
return ;
}
// if this is the popstate triggered after the `replaceState` call in the go
// method, then simply ignore it. The history entry has already been captured
if ( this . ignorePopState ) {
this . ignorePopState = false ;
return ;
}
// If there is no state, and the history stack length is one were
// probably getting the page load popstate fired by browsers like chrome
// avoid it and set the one time flag to false
if ( ! event . originalEvent . state &&
this . history . stack . length === 1 &&
this . ignoreInitialHashChange ) {
this . ignoreInitialHashChange = false ;
return ;
}
// account for direct manipulation of the hash. That is, we will receive a popstate
// when the hash is changed by assignment, and it won't have a state associated. We
// then need to squash the hash. See below for handling of hash assignment that
// matches an existing history entry
// TODO it might be better to only add to the history stack
// when the hash is adjacent to the active history entry
hash = path . parseLocation ( ) . hash ;
if ( ! event . originalEvent . state && hash ) {
// squash the hash that's been assigned on the URL with replaceState
// also grab the resulting state object for storage
state = this . squash ( hash ) ;
// record the new hash as an additional history entry
// to match the browser's treatment of hash assignment
this . history . add ( state . url , state ) ;
// pass the newly created state information
// along with the event
event . historyState = state ;
// do not alter history, we've added a new history entry
// so we know where we are
return ;
}
// If all else fails this is a popstate that comes from the back or forward buttons
// make sure to set the state of our history stack properly, and record the directionality
this . history . direct ( {
url : ( event . originalEvent . state || { } ) . url || hash ,
// When the url is either forward or backward in history include the entry
// as data on the event object for merging as data in the navigate event
present : function ( historyEntry , direction ) {
// make sure to create a new object to pass down as the navigate event data
event . historyState = $ . extend ( { } , historyEntry ) ;
event . historyState . direction = direction ;
}
} ) ;
} ,
// NOTE must bind before `navigate` special event hashchange binding otherwise the
// navigation data won't be attached to the hashchange event in time for those
// bindings to attach it to the `navigate` special event
// TODO add a check here that `hashchange.navigate` is bound already otherwise it's
// broken (exception?)
hashchange : function ( event ) {
var history , hash ;
// If hashchange listening is explicitly disabled or pushstate is supported
// avoid making use of the hashchange handler.
if ( ! $ . event . special . navigate . isHashChangeEnabled ( ) ||
$ . event . special . navigate . isPushStateEnabled ( ) ) {
return ;
}
// On occasion explicitly want to prevent the next hash from propogating because we only
// with to alter the url to represent the new state do so here
if ( this . preventNextHashChange ) {
this . preventNextHashChange = false ;
event . stopImmediatePropagation ( ) ;
return ;
}
history = this . history ;
hash = path . parseLocation ( ) . hash ;
// If this is a hashchange caused by the back or forward button
// make sure to set the state of our history stack properly
this . history . direct ( {
url : hash ,
// When the url is either forward or backward in history include the entry
// as data on the event object for merging as data in the navigate event
present : function ( historyEntry , direction ) {
// make sure to create a new object to pass down as the navigate event data
event . hashchangeState = $ . extend ( { } , historyEntry ) ;
event . hashchangeState . direction = direction ;
} ,
// When we don't find a hash in our history clearly we're aiming to go there
// record the entry as new for future traversal
//
// NOTE it's not entirely clear that this is the right thing to do given that we
// can't know the users intention. It might be better to explicitly _not_
// support location.hash assignment in preference to $.navigate calls
// TODO first arg to add should be the href, but it causes issues in identifying
// embeded pages
missing : function ( ) {
history . add ( hash , {
hash : hash ,
title : document . title
} ) ;
}
} ) ;
}
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
// TODO consider queueing navigation activity until previous activities have completed
// so that end users don't have to think about it. Punting for now
// TODO !! move the event bindings into callbacks on the navigate event
$ . mobile . navigate = function ( url , data , noEvents ) {
$ . mobile . navigate . navigator . go ( url , data , noEvents ) ;
} ;
// expose the history on the navigate method in anticipation of full integration with
// existing navigation functionalty that is tightly coupled to the history information
$ . mobile . navigate . history = new $ . mobile . History ( ) ;
// instantiate an instance of the navigator for use within the $.navigate method
$ . mobile . navigate . navigator = new $ . mobile . Navigator ( $ . mobile . navigate . history ) ;
var loc = $ . mobile . path . parseLocation ( ) ;
$ . mobile . navigate . history . add ( loc . href , { hash : loc . hash } ) ;
} ) ( jQuery ) ;
// This plugin is an experiment for abstracting away the touch and mouse
// events so that developers don't have to worry about which method of input
// the device their document is loaded on supports.
//
// The idea here is to allow the developer to register listeners for the
// basic mouse events, such as mousedown, mousemove, mouseup, and click,
// and the plugin will take care of registering the correct listeners
// behind the scenes to invoke the listener at the fastest possible time
// for that device, while still retaining the order of event firing in
// the traditional mouse environment, should multiple handlers be registered
// on the same element for different events.
//
// The current version exposes the following virtual events to jQuery bind methods:
// "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel"
( function ( $ , window , document , undefined ) {
var dataPropertyName = "virtualMouseBindings" ,
touchTargetPropertyName = "virtualTouchID" ,
virtualEventNames = "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel" . split ( " " ) ,
touchEventProps = "clientX clientY pageX pageY screenX screenY" . split ( " " ) ,
mouseHookProps = $ . event . mouseHooks ? $ . event . mouseHooks . props : [ ] ,
mouseEventProps = $ . event . props . concat ( mouseHookProps ) ,
activeDocHandlers = { } ,
resetTimerID = 0 ,
startX = 0 ,
startY = 0 ,
didScroll = false ,
clickBlockList = [ ] ,
blockMouseTriggers = false ,
blockTouchTriggers = false ,
eventCaptureSupported = "addEventListener" in document ,
$document = $ ( document ) ,
nextTouchID = 1 ,
lastTouchID = 0 , threshold ;
$ . vmouse = {
moveDistanceThreshold : 10 ,
clickDistanceThreshold : 10 ,
resetTimerDuration : 1500
} ;
function getNativeEvent ( event ) {
while ( event && typeof event . originalEvent !== "undefined" ) {
event = event . originalEvent ;
}
return event ;
}
function createVirtualEvent ( event , eventType ) {
var t = event . type ,
oe , props , ne , prop , ct , touch , i , j , len ;
event = $ . Event ( event ) ;
event . type = eventType ;
oe = event . originalEvent ;
props = $ . event . props ;
// addresses separation of $.event.props in to $.event.mouseHook.props and Issue 3280
// https://github.com/jquery/jquery-mobile/issues/3280
if ( t . search ( /^(mouse|click)/ ) > - 1 ) {
props = mouseEventProps ;
}
// copy original event properties over to the new event
// this would happen if we could call $.event.fix instead of $.Event
// but we don't have a way to force an event to be fixed multiple times
if ( oe ) {
for ( i = props . length , prop ; i ; ) {
prop = props [ -- i ] ;
event [ prop ] = oe [ prop ] ;
}
}
// make sure that if the mouse and click virtual events are generated
// without a .which one is defined
if ( t . search ( /mouse(down|up)|click/ ) > - 1 && ! event . which ) {
event . which = 1 ;
}
if ( t . search ( /^touch/ ) !== - 1 ) {
ne = getNativeEvent ( oe ) ;
t = ne . touches ;
ct = ne . changedTouches ;
touch = ( t && t . length ) ? t [ 0 ] : ( ( ct && ct . length ) ? ct [ 0 ] : undefined ) ;
if ( touch ) {
for ( j = 0 , len = touchEventProps . length ; j < len ; j ++ ) {
prop = touchEventProps [ j ] ;
event [ prop ] = touch [ prop ] ;
}
}
}
return event ;
}
function getVirtualBindingFlags ( element ) {
var flags = { } ,
b , k ;
while ( element ) {
b = $ . data ( element , dataPropertyName ) ;
for ( k in b ) {
if ( b [ k ] ) {
flags [ k ] = flags . hasVirtualBinding = true ;
}
}
element = element . parentNode ;
}
return flags ;
}
function getClosestElementWithVirtualBinding ( element , eventType ) {
var b ;
while ( element ) {
b = $ . data ( element , dataPropertyName ) ;
if ( b && ( ! eventType || b [ eventType ] ) ) {
return element ;
}
element = element . parentNode ;
}
return null ;
}
function enableTouchBindings ( ) {
blockTouchTriggers = false ;
}
function disableTouchBindings ( ) {
blockTouchTriggers = true ;
}
function enableMouseBindings ( ) {
lastTouchID = 0 ;
clickBlockList . length = 0 ;
blockMouseTriggers = false ;
// When mouse bindings are enabled, our
// touch bindings are disabled.
disableTouchBindings ( ) ;
}
function disableMouseBindings ( ) {
// When mouse bindings are disabled, our
// touch bindings are enabled.
enableTouchBindings ( ) ;
}
function startResetTimer ( ) {
clearResetTimer ( ) ;
resetTimerID = setTimeout ( function ( ) {
resetTimerID = 0 ;
enableMouseBindings ( ) ;
} , $ . vmouse . resetTimerDuration ) ;
}
function clearResetTimer ( ) {
if ( resetTimerID ) {
clearTimeout ( resetTimerID ) ;
resetTimerID = 0 ;
}
}
function triggerVirtualEvent ( eventType , event , flags ) {
var ve ;
if ( ( flags && flags [ eventType ] ) ||
( ! flags && getClosestElementWithVirtualBinding ( event . target , eventType ) ) ) {
ve = createVirtualEvent ( event , eventType ) ;
$ ( event . target ) . trigger ( ve ) ;
}
return ve ;
}
function mouseEventCallback ( event ) {
var touchID = $ . data ( event . target , touchTargetPropertyName ) ;
if ( ! blockMouseTriggers && ( ! lastTouchID || lastTouchID !== touchID ) ) {
var ve = triggerVirtualEvent ( "v" + event . type , event ) ;
if ( ve ) {
if ( ve . isDefaultPrevented ( ) ) {
event . preventDefault ( ) ;
}
if ( ve . isPropagationStopped ( ) ) {
event . stopPropagation ( ) ;
}
if ( ve . isImmediatePropagationStopped ( ) ) {
event . stopImmediatePropagation ( ) ;
}
}
}
}
function handleTouchStart ( event ) {
var touches = getNativeEvent ( event ) . touches ,
target , flags ;
if ( touches && touches . length === 1 ) {
target = event . target ;
flags = getVirtualBindingFlags ( target ) ;
if ( flags . hasVirtualBinding ) {
lastTouchID = nextTouchID ++ ;
$ . data ( target , touchTargetPropertyName , lastTouchID ) ;
clearResetTimer ( ) ;
disableMouseBindings ( ) ;
didScroll = false ;
var t = getNativeEvent ( event ) . touches [ 0 ] ;
startX = t . pageX ;
startY = t . pageY ;
triggerVirtualEvent ( "vmouseover" , event , flags ) ;
triggerVirtualEvent ( "vmousedown" , event , flags ) ;
}
}
}
function handleScroll ( event ) {
if ( blockTouchTriggers ) {
return ;
}
if ( ! didScroll ) {
triggerVirtualEvent ( "vmousecancel" , event , getVirtualBindingFlags ( event . target ) ) ;
}
didScroll = true ;
startResetTimer ( ) ;
}
function handleTouchMove ( event ) {
if ( blockTouchTriggers ) {
return ;
}
var t = getNativeEvent ( event ) . touches [ 0 ] ,
didCancel = didScroll ,
moveThreshold = $ . vmouse . moveDistanceThreshold ,
flags = getVirtualBindingFlags ( event . target ) ;
didScroll = didScroll ||
( Math . abs ( t . pageX - startX ) > moveThreshold ||
Math . abs ( t . pageY - startY ) > moveThreshold ) ;
if ( didScroll && ! didCancel ) {
triggerVirtualEvent ( "vmousecancel" , event , flags ) ;
}
triggerVirtualEvent ( "vmousemove" , event , flags ) ;
startResetTimer ( ) ;
}
function handleTouchEnd ( event ) {
if ( blockTouchTriggers ) {
return ;
}
disableTouchBindings ( ) ;
var flags = getVirtualBindingFlags ( event . target ) ,
t ;
triggerVirtualEvent ( "vmouseup" , event , flags ) ;
if ( ! didScroll ) {
var ve = triggerVirtualEvent ( "vclick" , event , flags ) ;
if ( ve && ve . isDefaultPrevented ( ) ) {
// The target of the mouse events that follow the touchend
// event don't necessarily match the target used during the
// touch. This means we need to rely on coordinates for blocking
// any click that is generated.
t = getNativeEvent ( event ) . changedTouches [ 0 ] ;
clickBlockList . push ( {
touchID : lastTouchID ,
x : t . clientX ,
y : t . clientY
} ) ;
// Prevent any mouse events that follow from triggering
// virtual event notifications.
blockMouseTriggers = true ;
}
}
triggerVirtualEvent ( "vmouseout" , event , flags ) ;
didScroll = false ;
startResetTimer ( ) ;
}
function hasVirtualBindings ( ele ) {
var bindings = $ . data ( ele , dataPropertyName ) ,
k ;
if ( bindings ) {
for ( k in bindings ) {
if ( bindings [ k ] ) {
return true ;
}
}
}
return false ;
}
function dummyMouseHandler ( ) { }
function getSpecialEventObject ( eventType ) {
var realType = eventType . substr ( 1 ) ;
return {
setup : function ( data , namespace ) {
// If this is the first virtual mouse binding for this element,
// add a bindings object to its data.
if ( ! hasVirtualBindings ( this ) ) {
$ . data ( this , dataPropertyName , { } ) ;
}
// If setup is called, we know it is the first binding for this
// eventType, so initialize the count for the eventType to zero.
var bindings = $ . data ( this , dataPropertyName ) ;
bindings [ eventType ] = true ;
// If this is the first virtual mouse event for this type,
// register a global handler on the document.
activeDocHandlers [ eventType ] = ( activeDocHandlers [ eventType ] || 0 ) + 1 ;
if ( activeDocHandlers [ eventType ] === 1 ) {
$document . bind ( realType , mouseEventCallback ) ;
}
// Some browsers, like Opera Mini, won't dispatch mouse/click events
// for elements unless they actually have handlers registered on them.
// To get around this, we register dummy handlers on the elements.
$ ( this ) . bind ( realType , dummyMouseHandler ) ;
// For now, if event capture is not supported, we rely on mouse handlers.
if ( eventCaptureSupported ) {
// If this is the first virtual mouse binding for the document,
// register our touchstart handler on the document.
activeDocHandlers [ "touchstart" ] = ( activeDocHandlers [ "touchstart" ] || 0 ) + 1 ;
if ( activeDocHandlers [ "touchstart" ] === 1 ) {
$document . bind ( "touchstart" , handleTouchStart )
. bind ( "touchend" , handleTouchEnd )
// On touch platforms, touching the screen and then dragging your finger
// causes the window content to scroll after some distance threshold is
// exceeded. On these platforms, a scroll prevents a click event from being
// dispatched, and on some platforms, even the touchend is suppressed. To
// mimic the suppression of the click event, we need to watch for a scroll
// event. Unfortunately, some platforms like iOS don't dispatch scroll
// events until *AFTER* the user lifts their finger (touchend). This means
// we need to watch both scroll and touchmove events to figure out whether
// or not a scroll happenens before the touchend event is fired.
. bind ( "touchmove" , handleTouchMove )
. bind ( "scroll" , handleScroll ) ;
}
}
} ,
teardown : function ( data , namespace ) {
// If this is the last virtual binding for this eventType,
// remove its global handler from the document.
-- activeDocHandlers [ eventType ] ;
if ( ! activeDocHandlers [ eventType ] ) {
$document . unbind ( realType , mouseEventCallback ) ;
}
if ( eventCaptureSupported ) {
// If this is the last virtual mouse binding in existence,
// remove our document touchstart listener.
-- activeDocHandlers [ "touchstart" ] ;
if ( ! activeDocHandlers [ "touchstart" ] ) {
$document . unbind ( "touchstart" , handleTouchStart )
. unbind ( "touchmove" , handleTouchMove )
. unbind ( "touchend" , handleTouchEnd )
. unbind ( "scroll" , handleScroll ) ;
}
}
var $this = $ ( this ) ,
bindings = $ . data ( this , dataPropertyName ) ;
// teardown may be called when an element was
// removed from the DOM. If this is the case,
// jQuery core may have already stripped the element
// of any data bindings so we need to check it before
// using it.
if ( bindings ) {
bindings [ eventType ] = false ;
}
// Unregister the dummy event handler.
$this . unbind ( realType , dummyMouseHandler ) ;
// If this is the last virtual mouse binding on the
// element, remove the binding data from the element.
if ( ! hasVirtualBindings ( this ) ) {
$this . removeData ( dataPropertyName ) ;
}
}
} ;
}
// Expose our custom events to the jQuery bind/unbind mechanism.
for ( var i = 0 ; i < virtualEventNames . length ; i ++ ) {
$ . event . special [ virtualEventNames [ i ] ] = getSpecialEventObject ( virtualEventNames [ i ] ) ;
}
// Add a capture click handler to block clicks.
// Note that we require event capture support for this so if the device
// doesn't support it, we punt for now and rely solely on mouse events.
if ( eventCaptureSupported ) {
document . addEventListener ( "click" , function ( e ) {
var cnt = clickBlockList . length ,
target = e . target ,
x , y , ele , i , o , touchID ;
if ( cnt ) {
x = e . clientX ;
y = e . clientY ;
threshold = $ . vmouse . clickDistanceThreshold ;
// The idea here is to run through the clickBlockList to see if
// the current click event is in the proximity of one of our
// vclick events that had preventDefault() called on it. If we find
// one, then we block the click.
//
// Why do we have to rely on proximity?
//
// Because the target of the touch event that triggered the vclick
// can be different from the target of the click event synthesized
// by the browser. The target of a mouse/click event that is syntehsized
// from a touch event seems to be implementation specific. For example,
// some browsers will fire mouse/click events for a link that is near
// a touch event, even though the target of the touchstart/touchend event
// says the user touched outside the link. Also, it seems that with most
// browsers, the target of the mouse/click event is not calculated until the
// time it is dispatched, so if you replace an element that you touched
// with another element, the target of the mouse/click will be the new
// element underneath that point.
//
// Aside from proximity, we also check to see if the target and any
// of its ancestors were the ones that blocked a click. This is necessary
// because of the strange mouse/click target calculation done in the
// Android 2.1 browser, where if you click on an element, and there is a
// mouse/click handler on one of its ancestors, the target will be the
// innermost child of the touched element, even if that child is no where
// near the point of touch.
ele = target ;
while ( ele ) {
for ( i = 0 ; i < cnt ; i ++ ) {
o = clickBlockList [ i ] ;
touchID = 0 ;
if ( ( ele === target && Math . abs ( o . x - x ) < threshold && Math . abs ( o . y - y ) < threshold ) ||
$ . data ( ele , touchTargetPropertyName ) === o . touchID ) {
// XXX: We may want to consider removing matches from the block list
// instead of waiting for the reset timer to fire.
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
return ;
}
}
ele = ele . parentNode ;
}
}
} , true ) ;
}
} ) ( jQuery , window , document ) ;
( function ( $ , window , undefined ) {
var $document = $ ( document ) ;
// add new event shortcuts
$ . each ( ( "touchstart touchmove touchend " +
"tap taphold " +
"swipe swipeleft swiperight " +
"scrollstart scrollstop" ) . split ( " " ) , function ( i , name ) {
$ . fn [ name ] = function ( fn ) {
return fn ? this . bind ( name , fn ) : this . trigger ( name ) ;
} ;
// jQuery < 1.8
if ( $ . attrFn ) {
$ . attrFn [ name ] = true ;
}
} ) ;
var supportTouch = $ . mobile . support . touch ,
scrollEvent = "touchmove scroll" ,
touchStartEvent = supportTouch ? "touchstart" : "mousedown" ,
touchStopEvent = supportTouch ? "touchend" : "mouseup" ,
touchMoveEvent = supportTouch ? "touchmove" : "mousemove" ;
function triggerCustomEvent ( obj , eventType , event ) {
var originalType = event . type ;
event . type = eventType ;
$ . event . dispatch . call ( obj , event ) ;
event . type = originalType ;
}
// also handles scrollstop
$ . event . special . scrollstart = {
enabled : true ,
setup : function ( ) {
var thisObject = this ,
$this = $ ( thisObject ) ,
scrolling ,
timer ;
function trigger ( event , state ) {
scrolling = state ;
triggerCustomEvent ( thisObject , scrolling ? "scrollstart" : "scrollstop" , event ) ;
}
// iPhone triggers scroll after a small delay; use touchmove instead
$this . bind ( scrollEvent , function ( event ) {
if ( ! $ . event . special . scrollstart . enabled ) {
return ;
}
if ( ! scrolling ) {
trigger ( event , true ) ;
}
clearTimeout ( timer ) ;
timer = setTimeout ( function ( ) {
trigger ( event , false ) ;
} , 50 ) ;
} ) ;
}
} ;
// also handles taphold
$ . event . special . tap = {
tapholdThreshold : 750 ,
setup : function ( ) {
var thisObject = this ,
$this = $ ( thisObject ) ;
$this . bind ( "vmousedown" , function ( event ) {
if ( event . which && event . which !== 1 ) {
return false ;
}
var origTarget = event . target ,
origEvent = event . originalEvent ,
timer ;
function clearTapTimer ( ) {
clearTimeout ( timer ) ;
}
function clearTapHandlers ( ) {
clearTapTimer ( ) ;
$this . unbind ( "vclick" , clickHandler )
. unbind ( "vmouseup" , clearTapTimer ) ;
$document . unbind ( "vmousecancel" , clearTapHandlers ) ;
}
function clickHandler ( event ) {
clearTapHandlers ( ) ;
// ONLY trigger a 'tap' event if the start target is
// the same as the stop target.
if ( origTarget === event . target ) {
triggerCustomEvent ( thisObject , "tap" , event ) ;
}
}
$this . bind ( "vmouseup" , clearTapTimer )
. bind ( "vclick" , clickHandler ) ;
$document . bind ( "vmousecancel" , clearTapHandlers ) ;
timer = setTimeout ( function ( ) {
triggerCustomEvent ( thisObject , "taphold" , $ . Event ( "taphold" , { target : origTarget } ) ) ;
} , $ . event . special . tap . tapholdThreshold ) ;
} ) ;
}
} ;
// also handles swipeleft, swiperight
$ . event . special . swipe = {
scrollSupressionThreshold : 30 , // More than this horizontal displacement, and we will suppress scrolling.
durationThreshold : 1000 , // More time than this, and it isn't a swipe.
horizontalDistanceThreshold : 30 , // Swipe horizontal displacement must be more than this.
verticalDistanceThreshold : 75 , // Swipe vertical displacement must be less than this.
start : function ( event ) {
var data = event . originalEvent . touches ?
event . originalEvent . touches [ 0 ] : event ;
return {
time : ( new Date ( ) ) . getTime ( ) ,
coords : [ data . pageX , data . pageY ] ,
origin : $ ( event . target )
} ;
} ,
stop : function ( event ) {
var data = event . originalEvent . touches ?
event . originalEvent . touches [ 0 ] : event ;
return {
time : ( new Date ( ) ) . getTime ( ) ,
coords : [ data . pageX , data . pageY ]
} ;
} ,
handleSwipe : function ( start , stop ) {
if ( stop . time - start . time < $ . event . special . swipe . durationThreshold &&
Math . abs ( start . coords [ 0 ] - stop . coords [ 0 ] ) > $ . event . special . swipe . horizontalDistanceThreshold &&
Math . abs ( start . coords [ 1 ] - stop . coords [ 1 ] ) < $ . event . special . swipe . verticalDistanceThreshold ) {
start . origin . trigger ( "swipe" )
. trigger ( start . coords [ 0 ] > stop . coords [ 0 ] ? "swipeleft" : "swiperight" ) ;
}
} ,
setup : function ( ) {
var thisObject = this ,
$this = $ ( thisObject ) ;
$this . bind ( touchStartEvent , function ( event ) {
var start = $ . event . special . swipe . start ( event ) ,
stop ;
function moveHandler ( event ) {
if ( ! start ) {
return ;
}
stop = $ . event . special . swipe . stop ( event ) ;
// prevent scrolling
if ( Math . abs ( start . coords [ 0 ] - stop . coords [ 0 ] ) > $ . event . special . swipe . scrollSupressionThreshold ) {
event . preventDefault ( ) ;
}
}
$this . bind ( touchMoveEvent , moveHandler )
. one ( touchStopEvent , function ( ) {
$this . unbind ( touchMoveEvent , moveHandler ) ;
if ( start && stop ) {
$ . event . special . swipe . handleSwipe ( start , stop ) ;
}
start = stop = undefined ;
} ) ;
} ) ;
}
} ;
$ . each ( {
scrollstop : "scrollstart" ,
taphold : "tap" ,
swipeleft : "swipe" ,
swiperight : "swipe"
} , function ( event , sourceEvent ) {
$ . event . special [ event ] = {
setup : function ( ) {
$ ( this ) . bind ( sourceEvent , $ . noop ) ;
}
} ;
} ) ;
} ) ( jQuery , this ) ;
// throttled resize event
( function ( $ ) {
$ . event . special . throttledresize = {
setup : function ( ) {
$ ( this ) . bind ( "resize" , handler ) ;
} ,
teardown : function ( ) {
$ ( this ) . unbind ( "resize" , handler ) ;
}
} ;
var throttle = 250 ,
handler = function ( ) {
curr = ( new Date ( ) ) . getTime ( ) ;
diff = curr - lastCall ;
if ( diff >= throttle ) {
lastCall = curr ;
$ ( this ) . trigger ( "throttledresize" ) ;
} else {
if ( heldCall ) {
clearTimeout ( heldCall ) ;
}
// Promise a held call will still execute
heldCall = setTimeout ( handler , throttle - diff ) ;
}
} ,
lastCall = 0 ,
heldCall ,
curr ,
diff ;
} ) ( jQuery ) ;
( function ( $ , window ) {
var win = $ ( window ) ,
event _name = "orientationchange" ,
special _event ,
get _orientation ,
last _orientation ,
initial _orientation _is _landscape ,
initial _orientation _is _default ,
portrait _map = { "0" : true , "180" : true } ;
// It seems that some device/browser vendors use window.orientation values 0 and 180 to
// denote the "default" orientation. For iOS devices, and most other smart-phones tested,
// the default orientation is always "portrait", but in some Android and RIM based tablets,
// the default orientation is "landscape". The following code attempts to use the window
// dimensions to figure out what the current orientation is, and then makes adjustments
// to the to the portrait_map if necessary, so that we can properly decode the
// window.orientation value whenever get_orientation() is called.
//
// Note that we used to use a media query to figure out what the orientation the browser
// thinks it is in:
//
// initial_orientation_is_landscape = $.mobile.media("all and (orientation: landscape)");
//
// but there was an iPhone/iPod Touch bug beginning with iOS 4.2, up through iOS 5.1,
// where the browser *ALWAYS* applied the landscape media query. This bug does not
// happen on iPad.
if ( $ . support . orientation ) {
// Check the window width and height to figure out what the current orientation
// of the device is at this moment. Note that we've initialized the portrait map
// values to 0 and 180, *AND* we purposely check for landscape so that if we guess
// wrong, , we default to the assumption that portrait is the default orientation.
// We use a threshold check below because on some platforms like iOS, the iPhone
// form-factor can report a larger width than height if the user turns on the
// developer console. The actual threshold value is somewhat arbitrary, we just
// need to make sure it is large enough to exclude the developer console case.
var ww = window . innerWidth || win . width ( ) ,
wh = window . innerHeight || win . height ( ) ,
landscape _threshold = 50 ;
initial _orientation _is _landscape = ww > wh && ( ww - wh ) > landscape _threshold ;
// Now check to see if the current window.orientation is 0 or 180.
initial _orientation _is _default = portrait _map [ window . orientation ] ;
// If the initial orientation is landscape, but window.orientation reports 0 or 180, *OR*
// if the initial orientation is portrait, but window.orientation reports 90 or -90, we
// need to flip our portrait_map values because landscape is the default orientation for
// this device/browser.
if ( ( initial _orientation _is _landscape && initial _orientation _is _default ) || ( ! initial _orientation _is _landscape && ! initial _orientation _is _default ) ) {
portrait _map = { "-90" : true , "90" : true } ;
}
}
$ . event . special . orientationchange = $ . extend ( { } , $ . event . special . orientationchange , {
setup : function ( ) {
// If the event is supported natively, return false so that jQuery
// will bind to the event using DOM methods.
if ( $ . support . orientation && ! $ . event . special . orientationchange . disabled ) {
return false ;
}
// Get the current orientation to avoid initial double-triggering.
last _orientation = get _orientation ( ) ;
// Because the orientationchange event doesn't exist, simulate the
// event by testing window dimensions on resize.
win . bind ( "throttledresize" , handler ) ;
} ,
teardown : function ( ) {
// If the event is not supported natively, return false so that
// jQuery will unbind the event using DOM methods.
if ( $ . support . orientation && ! $ . event . special . orientationchange . disabled ) {
return false ;
}
// Because the orientationchange event doesn't exist, unbind the
// resize event handler.
win . unbind ( "throttledresize" , handler ) ;
} ,
add : function ( handleObj ) {
// Save a reference to the bound event handler.
var old _handler = handleObj . handler ;
handleObj . handler = function ( event ) {
// Modify event object, adding the .orientation property.
event . orientation = get _orientation ( ) ;
// Call the originally-bound event handler and return its result.
return old _handler . apply ( this , arguments ) ;
} ;
}
} ) ;
// If the event is not supported natively, this handler will be bound to
// the window resize event to simulate the orientationchange event.
function handler ( ) {
// Get the current orientation.
var orientation = get _orientation ( ) ;
if ( orientation !== last _orientation ) {
// The orientation has changed, so trigger the orientationchange event.
last _orientation = orientation ;
win . trigger ( event _name ) ;
}
}
// Get the current page orientation. This method is exposed publicly, should it
// be needed, as jQuery.event.special.orientationchange.orientation()
$ . event . special . orientationchange . orientation = get _orientation = function ( ) {
var isPortrait = true , elem = document . documentElement ;
// prefer window orientation to the calculation based on screensize as
// the actual screen resize takes place before or after the orientation change event
// has been fired depending on implementation (eg android 2.3 is before, iphone after).
// More testing is required to determine if a more reliable method of determining the new screensize
// is possible when orientationchange is fired. (eg, use media queries + element + opacity)
if ( $ . support . orientation ) {
// if the window orientation registers as 0 or 180 degrees report
// portrait, otherwise landscape
isPortrait = portrait _map [ window . orientation ] ;
} else {
isPortrait = elem && elem . clientWidth / elem . clientHeight < 1.1 ;
}
return isPortrait ? "portrait" : "landscape" ;
} ;
$ . fn [ event _name ] = function ( fn ) {
return fn ? this . bind ( event _name , fn ) : this . trigger ( event _name ) ;
} ;
// jQuery < 1.8
if ( $ . attrFn ) {
$ . attrFn [ event _name ] = true ;
}
} ( jQuery , this ) ) ;
( function ( $ , undefined ) {
$ . widget ( "mobile.page" , $ . mobile . widget , {
options : {
theme : "c" ,
domCache : false ,
keepNativeDefault : ":jqmData(role='none'), :jqmData(role='nojs')"
} ,
_create : function ( ) {
// if false is returned by the callbacks do not create the page
if ( this . _trigger ( "beforecreate" ) === false ) {
return false ;
}
this . element
. attr ( "tabindex" , "0" )
. addClass ( "ui-page ui-body-" + this . options . theme ) ;
this . _on ( this . element , {
pagebeforehide : "removeContainerBackground" ,
pagebeforeshow : "_handlePageBeforeShow"
} ) ;
} ,
_handlePageBeforeShow : function ( e ) {
this . setContainerBackground ( ) ;
} ,
removeContainerBackground : function ( ) {
$ . mobile . pageContainer . removeClass ( "ui-overlay-" + $ . mobile . getInheritedTheme ( this . element . parent ( ) ) ) ;
} ,
// set the page container background to the page theme
setContainerBackground : function ( theme ) {
if ( this . options . theme ) {
$ . mobile . pageContainer . addClass ( "ui-overlay-" + ( theme || this . options . theme ) ) ;
}
} ,
keepNativeSelector : function ( ) {
var options = this . options ,
keepNativeDefined = options . keepNative && $ . trim ( options . keepNative ) ;
if ( keepNativeDefined && options . keepNative !== options . keepNativeDefault ) {
return [ options . keepNative , options . keepNativeDefault ] . join ( ", " ) ;
}
return options . keepNativeDefault ;
}
} ) ;
} ) ( jQuery ) ;
// Script: jQuery hashchange event
//
// *Version: 1.3, Last updated: 7/21/2010*
//
// Project Home - http://benalman.com/projects/jquery-hashchange-plugin/
// GitHub - http://github.com/cowboy/jquery-hashchange/
// Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js
// (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (0.8kb gzipped)
//
// About: License
//
// Copyright (c) 2010 "Cowboy" Ben Alman,
// Dual licensed under the MIT and GPL licenses.
// http://benalman.com/about/license/
//
// About: Examples
//
// These working examples, complete with fully commented code, illustrate a few
// ways in which this plugin can be used.
//
// hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/
// document.domain - http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/
//
// About: Support and Testing
//
// Information about what version or versions of jQuery this plugin has been
// tested with, what browsers it has been tested in, and where the unit tests
// reside (so you can test it yourself).
//
// jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2
// Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5,
// Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5.
// Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/
//
// About: Known issues
//
// While this jQuery hashchange event implementation is quite stable and
// robust, there are a few unfortunate browser bugs surrounding expected
// hashchange event-based behaviors, independent of any JavaScript
// window.onhashchange abstraction. See the following examples for more
// information:
//
// Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/
// Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/
// WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/
// Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/
//
// Also note that should a browser natively support the window.onhashchange
// event, but not report that it does, the fallback polling loop will be used.
//
// About: Release History
//
// 1.3 - (7/21/2010) Reorganized IE6/7 Iframe code to make it more
// "removable" for mobile-only development. Added IE6/7 document.title
// support. Attempted to make Iframe as hidden as possible by using
// techniques from http://www.paciellogroup.com/blog/?p=604. Added
// support for the "shortcut" format $(window).hashchange( fn ) and
// $(window).hashchange() like jQuery provides for built-in events.
// Renamed jQuery.hashchangeDelay to <jQuery.fn.hashchange.delay> and
// lowered its default value to 50. Added <jQuery.fn.hashchange.domain>
// and <jQuery.fn.hashchange.src> properties plus document-domain.html
// file to address access denied issues when setting document.domain in
// IE6/7.
// 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin
// from a page on another domain would cause an error in Safari 4. Also,
// IE6/7 Iframe is now inserted after the body (this actually works),
// which prevents the page from scrolling when the event is first bound.
// Event can also now be bound before DOM ready, but it won't be usable
// before then in IE6/7.
// 1.1 - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug
// where browser version is incorrectly reported as 8.0, despite
// inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag.
// 1.0 - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special
// window.onhashchange functionality into a separate plugin for users
// who want just the basic event & back button support, without all the
// extra awesomeness that BBQ provides. This plugin will be included as
// part of jQuery BBQ, but also be available separately.
( function ( $ , window , undefined ) {
// Reused string.
var str _hashchange = 'hashchange' ,
// Method / object references.
doc = document ,
fake _onhashchange ,
special = $ . event . special ,
// Does the browser support window.onhashchange? Note that IE8 running in
// IE7 compatibility mode reports true for 'onhashchange' in window, even
// though the event isn't supported, so also test document.documentMode.
doc _mode = doc . documentMode ,
supports _onhashchange = 'on' + str _hashchange in window && ( doc _mode === undefined || doc _mode > 7 ) ;
// Get location.hash (or what you'd expect location.hash to be) sans any
// leading #. Thanks for making this necessary, Firefox!
function get _fragment ( url ) {
url = url || location . href ;
return '#' + url . replace ( /^[^#]*#?(.*)$/ , '$1' ) ;
} ;
// Method: jQuery.fn.hashchange
//
// Bind a handler to the window.onhashchange event or trigger all bound
// window.onhashchange event handlers. This behavior is consistent with
// jQuery's built-in event handlers.
//
// Usage:
//
// > jQuery(window).hashchange( [ handler ] );
//
// Arguments:
//
// handler - (Function) Optional handler to be bound to the hashchange
// event. This is a "shortcut" for the more verbose form:
// jQuery(window).bind( 'hashchange', handler ). If handler is omitted,
// all bound window.onhashchange event handlers will be triggered. This
// is a shortcut for the more verbose
// jQuery(window).trigger( 'hashchange' ). These forms are described in
// the <hashchange event> section.
//
// Returns:
//
// (jQuery) The initial jQuery collection of elements.
// Allow the "shortcut" format $(elem).hashchange( fn ) for binding and
// $(elem).hashchange() for triggering, like jQuery does for built-in events.
$ . fn [ str _hashchange ] = function ( fn ) {
return fn ? this . bind ( str _hashchange , fn ) : this . trigger ( str _hashchange ) ;
} ;
// Property: jQuery.fn.hashchange.delay
//
// The numeric interval (in milliseconds) at which the <hashchange event>
// polling loop executes. Defaults to 50.
// Property: jQuery.fn.hashchange.domain
//
// If you're setting document.domain in your JavaScript, and you want hash
// history to work in IE6/7, not only must this property be set, but you must
// also set document.domain BEFORE jQuery is loaded into the page. This
// property is only applicable if you are supporting IE6/7 (or IE8 operating
// in "IE7 compatibility" mode).
//
// In addition, the <jQuery.fn.hashchange.src> property must be set to the
// path of the included "document-domain.html" file, which can be renamed or
// modified if necessary (note that the document.domain specified must be the
// same in both your main JavaScript as well as in this file).
//
// Usage:
//
// jQuery.fn.hashchange.domain = document.domain;
// Property: jQuery.fn.hashchange.src
//
// If, for some reason, you need to specify an Iframe src file (for example,
// when setting document.domain as in <jQuery.fn.hashchange.domain>), you can
// do so using this property. Note that when using this property, history
// won't be recorded in IE6/7 until the Iframe src file loads. This property
// is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7
// compatibility" mode).
//
// Usage:
//
// jQuery.fn.hashchange.src = 'path/to/file.html';
$ . fn [ str _hashchange ] . delay = 50 ;
/ *
$ . fn [ str _hashchange ] . domain = null ;
$ . fn [ str _hashchange ] . src = null ;
* /
// Event: hashchange event
//
// Fired when location.hash changes. In browsers that support it, the native
// HTML5 window.onhashchange event is used, otherwise a polling loop is
// initialized, running every <jQuery.fn.hashchange.delay> milliseconds to
// see if the hash has changed. In IE6/7 (and IE8 operating in "IE7
// compatibility" mode), a hidden Iframe is created to allow the back button
// and hash-based history to work.
//
// Usage as described in <jQuery.fn.hashchange>:
//
// > // Bind an event handler.
// > jQuery(window).hashchange( function(e) {
// > var hash = location.hash;
// > ...
// > });
// >
// > // Manually trigger the event handler.
// > jQuery(window).hashchange();
//
// A more verbose usage that allows for event namespacing:
//
// > // Bind an event handler.
// > jQuery(window).bind( 'hashchange', function(e) {
// > var hash = location.hash;
// > ...
// > });
// >
// > // Manually trigger the event handler.
// > jQuery(window).trigger( 'hashchange' );
//
// Additional Notes:
//
// * The polling loop and Iframe are not created until at least one handler
// is actually bound to the 'hashchange' event.
// * If you need the bound handler(s) to execute immediately, in cases where
// a location.hash exists on page load, via bookmark or page refresh for
// example, use jQuery(window).hashchange() or the more verbose
// jQuery(window).trigger( 'hashchange' ).
// * The event can be bound before DOM ready, but since it won't be usable
// before then in IE6/7 (due to the necessary Iframe), recommended usage is
// to bind it inside a DOM ready handler.
// Override existing $.event.special.hashchange methods (allowing this plugin
// to be defined after jQuery BBQ in BBQ's source code).
special [ str _hashchange ] = $ . extend ( special [ str _hashchange ] , {
// Called only when the first 'hashchange' event is bound to window.
setup : function ( ) {
// If window.onhashchange is supported natively, there's nothing to do..
if ( supports _onhashchange ) { return false ; }
// Otherwise, we need to create our own. And we don't want to call this
// until the user binds to the event, just in case they never do, since it
// will create a polling loop and possibly even a hidden Iframe.
$ ( fake _onhashchange . start ) ;
} ,
// Called only when the last 'hashchange' event is unbound from window.
teardown : function ( ) {
// If window.onhashchange is supported natively, there's nothing to do..
if ( supports _onhashchange ) { return false ; }
// Otherwise, we need to stop ours (if possible).
$ ( fake _onhashchange . stop ) ;
}
} ) ;
// fake_onhashchange does all the work of triggering the window.onhashchange
// event for browsers that don't natively support it, including creating a
// polling loop to watch for hash changes and in IE 6/7 creating a hidden
// Iframe to enable back and forward.
fake _onhashchange = ( function ( ) {
var self = { } ,
timeout _id ,
// Remember the initial hash so it doesn't get triggered immediately.
last _hash = get _fragment ( ) ,
fn _retval = function ( val ) { return val ; } ,
history _set = fn _retval ,
history _get = fn _retval ;
// Start the polling loop.
self . start = function ( ) {
timeout _id || poll ( ) ;
} ;
// Stop the polling loop.
self . stop = function ( ) {
timeout _id && clearTimeout ( timeout _id ) ;
timeout _id = undefined ;
} ;
// This polling loop checks every $.fn.hashchange.delay milliseconds to see
// if location.hash has changed, and triggers the 'hashchange' event on
// window when necessary.
function poll ( ) {
var hash = get _fragment ( ) ,
history _hash = history _get ( last _hash ) ;
if ( hash !== last _hash ) {
history _set ( last _hash = hash , history _hash ) ;
$ ( window ) . trigger ( str _hashchange ) ;
} else if ( history _hash !== last _hash ) {
location . href = location . href . replace ( /#.*/ , '' ) + history _hash ;
}
timeout _id = setTimeout ( poll , $ . fn [ str _hashchange ] . delay ) ;
} ;
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
window . attachEvent && ! window . addEventListener && ! supports _onhashchange && ( function ( ) {
// Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8
// when running in "IE7 compatibility" mode.
var iframe ,
iframe _src ;
// When the event is bound and polling starts in IE 6/7, create a hidden
// Iframe for history handling.
self . start = function ( ) {
if ( ! iframe ) {
iframe _src = $ . fn [ str _hashchange ] . src ;
iframe _src = iframe _src && iframe _src + get _fragment ( ) ;
// Create hidden Iframe. Attempt to make Iframe as hidden as possible
// by using techniques from http://www.paciellogroup.com/blog/?p=604.
iframe = $ ( '<iframe tabindex="-1" title="empty"/>' ) . hide ( )
// When Iframe has completely loaded, initialize the history and
// start polling.
. one ( 'load' , function ( ) {
iframe _src || history _set ( get _fragment ( ) ) ;
poll ( ) ;
} )
// Load Iframe src if specified, otherwise nothing.
. attr ( 'src' , iframe _src || 'javascript:0' )
// Append Iframe after the end of the body to prevent unnecessary
// initial page scrolling (yes, this works).
. insertAfter ( 'body' ) [ 0 ] . contentWindow ;
// Whenever `document.title` changes, update the Iframe's title to
// prettify the back/next history menu entries. Since IE sometimes
// errors with "Unspecified error" the very first time this is set
// (yes, very useful) wrap this with a try/catch block.
doc . onpropertychange = function ( ) {
try {
if ( event . propertyName === 'title' ) {
iframe . document . title = doc . title ;
}
} catch ( e ) { }
} ;
}
} ;
// Override the "stop" method since an IE6/7 Iframe was created. Even
// if there are no longer any bound event handlers, the polling loop
// is still necessary for back/next to work at all!
self . stop = fn _retval ;
// Get history by looking at the hidden Iframe's location.hash.
history _get = function ( ) {
return get _fragment ( iframe . location . href ) ;
} ;
// Set a new history item by opening and then closing the Iframe
// document, *then* setting its location.hash. If document.domain has
// been set, update that as well.
history _set = function ( hash , history _hash ) {
var iframe _doc = iframe . document ,
domain = $ . fn [ str _hashchange ] . domain ;
if ( hash !== history _hash ) {
// Update Iframe with any initial `document.title` that might be set.
iframe _doc . title = doc . title ;
// Opening the Iframe's document after it has been closed is what
// actually adds a history entry.
iframe _doc . open ( ) ;
// Set document.domain for the Iframe document as well, if necessary.
domain && iframe _doc . write ( '<script>document.domain="' + domain + '"</script>' ) ;
iframe _doc . close ( ) ;
// Update the Iframe's hash, for great justice.
iframe . location . hash = hash ;
}
} ;
} ) ( ) ;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// ^^^^^^^^^^^^^^^^^^^ REMOVE IF NOT SUPPORTING IE6/7/8 ^^^^^^^^^^^^^^^^^^^
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
return self ;
} ) ( ) ;
} ) ( jQuery , this ) ;
( function ( $ , window , undefined ) {
var createHandler = function ( sequential ) {
// Default to sequential
if ( sequential === undefined ) {
sequential = true ;
}
return function ( name , reverse , $to , $from ) {
var deferred = new $ . Deferred ( ) ,
reverseClass = reverse ? " reverse" : "" ,
active = $ . mobile . urlHistory . getActive ( ) ,
toScroll = active . lastScroll || $ . mobile . defaultHomeScroll ,
screenHeight = $ . mobile . getScreenHeight ( ) ,
maxTransitionOverride = $ . mobile . maxTransitionWidth !== false && $ . mobile . window . width ( ) > $ . mobile . maxTransitionWidth ,
none = ! $ . support . cssTransitions || maxTransitionOverride || ! name || name === "none" || Math . max ( $ . mobile . window . scrollTop ( ) , toScroll ) > $ . mobile . getMaxScrollForTransition ( ) ,
toPreClass = " ui-page-pre-in" ,
toggleViewportClass = function ( ) {
$ . mobile . pageContainer . toggleClass ( "ui-mobile-viewport-transitioning viewport-" + name ) ;
} ,
scrollPage = function ( ) {
// By using scrollTo instead of silentScroll, we can keep things better in order
// Just to be precautios, disable scrollstart listening like silentScroll would
$ . event . special . scrollstart . enabled = false ;
window . scrollTo ( 0 , toScroll ) ;
// reenable scrollstart listening like silentScroll would
setTimeout ( function ( ) {
$ . event . special . scrollstart . enabled = true ;
} , 150 ) ;
} ,
cleanFrom = function ( ) {
$from
. removeClass ( $ . mobile . activePageClass + " out in reverse " + name )
. height ( "" ) ;
} ,
startOut = function ( ) {
// if it's not sequential, call the doneOut transition to start the TO page animating in simultaneously
if ( ! sequential ) {
doneOut ( ) ;
}
else {
$from . animationComplete ( doneOut ) ;
}
// Set the from page's height and start it transitioning out
// Note: setting an explicit height helps eliminate tiling in the transitions
$from
. height ( screenHeight + $ . mobile . window . scrollTop ( ) )
. addClass ( name + " out" + reverseClass ) ;
} ,
doneOut = function ( ) {
if ( $from && sequential ) {
cleanFrom ( ) ;
}
startIn ( ) ;
} ,
startIn = function ( ) {
// Prevent flickering in phonegap container: see comments at #4024 regarding iOS
$to . css ( "z-index" , - 10 ) ;
$to . addClass ( $ . mobile . activePageClass + toPreClass ) ;
// Send focus to page as it is now display: block
$ . mobile . focusPage ( $to ) ;
// Set to page height
$to . height ( screenHeight + toScroll ) ;
scrollPage ( ) ;
// Restores visibility of the new page: added together with $to.css( "z-index", -10 );
$to . css ( "z-index" , "" ) ;
if ( ! none ) {
$to . animationComplete ( doneIn ) ;
}
$to
. removeClass ( toPreClass )
. addClass ( name + " in" + reverseClass ) ;
if ( none ) {
doneIn ( ) ;
}
} ,
doneIn = function ( ) {
if ( ! sequential ) {
if ( $from ) {
cleanFrom ( ) ;
}
}
$to
. removeClass ( "out in reverse " + name )
. height ( "" ) ;
toggleViewportClass ( ) ;
// In some browsers (iOS5), 3D transitions block the ability to scroll to the desired location during transition
// This ensures we jump to that spot after the fact, if we aren't there already.
if ( $ . mobile . window . scrollTop ( ) !== toScroll ) {
scrollPage ( ) ;
}
deferred . resolve ( name , reverse , $to , $from , true ) ;
} ;
toggleViewportClass ( ) ;
if ( $from && ! none ) {
startOut ( ) ;
}
else {
doneOut ( ) ;
}
return deferred . promise ( ) ;
} ;
} ;
// generate the handlers from the above
var sequentialHandler = createHandler ( ) ,
simultaneousHandler = createHandler ( false ) ,
defaultGetMaxScrollForTransition = function ( ) {
return $ . mobile . getScreenHeight ( ) * 3 ;
} ;
// Make our transition handler the public default.
$ . mobile . defaultTransitionHandler = sequentialHandler ;
//transition handler dictionary for 3rd party transitions
$ . mobile . transitionHandlers = {
"default" : $ . mobile . defaultTransitionHandler ,
"sequential" : sequentialHandler ,
"simultaneous" : simultaneousHandler
} ;
$ . mobile . transitionFallbacks = { } ;
// If transition is defined, check if css 3D transforms are supported, and if not, if a fallback is specified
$ . mobile . _maybeDegradeTransition = function ( transition ) {
if ( transition && ! $ . support . cssTransform3d && $ . mobile . transitionFallbacks [ transition ] ) {
transition = $ . mobile . transitionFallbacks [ transition ] ;
}
return transition ;
} ;
// Set the getMaxScrollForTransition to default if no implementation was set by user
$ . mobile . getMaxScrollForTransition = $ . mobile . getMaxScrollForTransition || defaultGetMaxScrollForTransition ;
} ) ( jQuery , this ) ;
( function ( $ , undefined ) {
//define vars for interal use
var $window = $ . mobile . window ,
$html = $ ( 'html' ) ,
$head = $ ( 'head' ) ,
// NOTE: path extensions dependent on core attributes. Moved here to remove deps from
// $.mobile.path definition
path = $ . extend ( $ . mobile . path , {
//return the substring of a filepath before the sub-page key, for making a server request
getFilePath : function ( path ) {
var splitkey = '&' + $ . mobile . subPageUrlKey ;
return path && path . split ( splitkey ) [ 0 ] . split ( dialogHashKey ) [ 0 ] ;
} ,
//check if the specified url refers to the first page in the main application document.
isFirstPageUrl : function ( url ) {
// We only deal with absolute paths.
var u = path . parseUrl ( path . makeUrlAbsolute ( url , this . documentBase ) ) ,
// Does the url have the same path as the document?
samePath = u . hrefNoHash === this . documentUrl . hrefNoHash || ( this . documentBaseDiffers && u . hrefNoHash === this . documentBase . hrefNoHash ) ,
// Get the first page element.
fp = $ . mobile . firstPage ,
// Get the id of the first page element if it has one.
fpId = fp && fp [ 0 ] ? fp [ 0 ] . id : undefined ;
// The url refers to the first page if the path matches the document and
// it either has no hash value, or the hash is exactly equal to the id of the
// first page element.
return samePath && ( ! u . hash || u . hash === "#" || ( fpId && u . hash . replace ( /^#/ , "" ) === fpId ) ) ;
} ,
// Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR
// requests if the document doing the request was loaded via the file:// protocol.
// This is usually to allow the application to "phone home" and fetch app specific
// data. We normally let the browser handle external/cross-domain urls, but if the
// allowCrossDomainPages option is true, we will allow cross-domain http/https
// requests to go through our page loading logic.
isPermittedCrossDomainRequest : function ( docUrl , reqUrl ) {
return $ . mobile . allowCrossDomainPages &&
docUrl . protocol === "file:" &&
reqUrl . search ( /^https?:/ ) !== - 1 ;
}
} ) ,
//will be defined when a link is clicked and given an active class
$activeClickedLink = null ,
// resolved on domready
domreadyDeferred = $ . Deferred ( ) ,
//urlHistory is purely here to make guesses at whether the back or forward button was clicked
//and provide an appropriate transition
urlHistory = $ . mobile . navigate . history ,
//define first selector to receive focus when a page is shown
focusable = "[tabindex],a,button:visible,select:visible,input" ,
//queue to hold simultanious page transitions
pageTransitionQueue = [ ] ,
//indicates whether or not page is in process of transitioning
isPageTransitioning = false ,
//nonsense hash change key for dialogs, so they create a history entry
dialogHashKey = "&ui-state=dialog" ,
//existing base tag?
$base = $head . children ( "base" ) ,
//tuck away the original document URL minus any fragment.
documentUrl = path . documentUrl ,
//if the document has an embedded base tag, documentBase is set to its
//initial value. If a base tag does not exist, then we default to the documentUrl.
documentBase = path . documentBase ,
//cache the comparison once.
documentBaseDiffers = path . documentBaseDiffers ,
getScreenHeight = $ . mobile . getScreenHeight ;
//base element management, defined depending on dynamic base tag support
var base = $ . support . dynamicBaseTag ? {
//define base element, for use in routing asset urls that are referenced in Ajax-requested markup
element : ( $base . length ? $base : $ ( "<base>" , { href : documentBase . hrefNoHash } ) . prependTo ( $head ) ) ,
//set the generated BASE element's href attribute to a new page's base path
set : function ( href ) {
href = path . parseUrl ( href ) . hrefNoHash ;
base . element . attr ( "href" , path . makeUrlAbsolute ( href , documentBase ) ) ;
} ,
//set the generated BASE element's href attribute to a new page's base path
reset : function ( ) {
base . element . attr ( "href" , documentBase . hrefNoSearch ) ;
}
} : undefined ;
//return the original document url
$ . mobile . getDocumentUrl = path . getDocumentUrl ;
//return the original document base url
$ . mobile . getDocumentBase = path . getDocumentBase ;
/* internal utility functions */
// NOTE Issue #4950 Android phonegap doesn't navigate back properly
// when a full page refresh has taken place. It appears that hashchange
// and replacestate history alterations work fine but we need to support
// both forms of history traversal in our code that uses backward history
// movement
$ . mobile . back = function ( ) {
var nav = window . navigator ;
// if the setting is on and the navigator object is
// available use the phonegap navigation capability
if ( this . phonegapNavigationEnabled &&
nav &&
nav . app &&
nav . app . backHistory ) {
nav . app . backHistory ( ) ;
} else {
window . history . back ( ) ;
}
} ;
//direct focus to the page title, or otherwise first focusable element
$ . mobile . focusPage = function ( page ) {
var autofocus = page . find ( "[autofocus]" ) ,
pageTitle = page . find ( ".ui-title:eq(0)" ) ;
if ( autofocus . length ) {
autofocus . focus ( ) ;
return ;
}
if ( pageTitle . length ) {
pageTitle . focus ( ) ;
} else {
page . focus ( ) ;
}
} ;
//remove active classes after page transition or error
function removeActiveLinkClass ( forceRemoval ) {
if ( ! ! $activeClickedLink && ( ! $activeClickedLink . closest ( "." + $ . mobile . activePageClass ) . length || forceRemoval ) ) {
$activeClickedLink . removeClass ( $ . mobile . activeBtnClass ) ;
}
$activeClickedLink = null ;
}
function releasePageTransitionLock ( ) {
isPageTransitioning = false ;
if ( pageTransitionQueue . length > 0 ) {
$ . mobile . changePage . apply ( null , pageTransitionQueue . pop ( ) ) ;
}
}
// Save the last scroll distance per page, before it is hidden
var setLastScrollEnabled = true ,
setLastScroll , delayedSetLastScroll ;
setLastScroll = function ( ) {
// this barrier prevents setting the scroll value based on the browser
// scrolling the window based on a hashchange
if ( ! setLastScrollEnabled ) {
return ;
}
var active = $ . mobile . urlHistory . getActive ( ) ;
if ( active ) {
var lastScroll = $window . scrollTop ( ) ;
// Set active page's lastScroll prop.
// If the location we're scrolling to is less than minScrollBack, let it go.
active . lastScroll = lastScroll < $ . mobile . minScrollBack ? $ . mobile . defaultHomeScroll : lastScroll ;
}
} ;
// bind to scrollstop to gather scroll position. The delay allows for the hashchange
// event to fire and disable scroll recording in the case where the browser scrolls
// to the hash targets location (sometimes the top of the page). once pagechange fires
// getLastScroll is again permitted to operate
delayedSetLastScroll = function ( ) {
setTimeout ( setLastScroll , 100 ) ;
} ;
// disable an scroll setting when a hashchange has been fired, this only works
// because the recording of the scroll position is delayed for 100ms after
// the browser might have changed the position because of the hashchange
$window . bind ( $ . support . pushState ? "popstate" : "hashchange" , function ( ) {
setLastScrollEnabled = false ;
} ) ;
// handle initial hashchange from chrome :(
$window . one ( $ . support . pushState ? "popstate" : "hashchange" , function ( ) {
setLastScrollEnabled = true ;
} ) ;
// wait until the mobile page container has been determined to bind to pagechange
$window . one ( "pagecontainercreate" , function ( ) {
// once the page has changed, re-enable the scroll recording
$ . mobile . pageContainer . bind ( "pagechange" , function ( ) {
setLastScrollEnabled = true ;
// remove any binding that previously existed on the get scroll
// which may or may not be different than the scroll element determined for
// this page previously
$window . unbind ( "scrollstop" , delayedSetLastScroll ) ;
// determine and bind to the current scoll element which may be the window
// or in the case of touch overflow the element with touch overflow
$window . bind ( "scrollstop" , delayedSetLastScroll ) ;
} ) ;
} ) ;
// bind to scrollstop for the first page as "pagechange" won't be fired in that case
$window . bind ( "scrollstop" , delayedSetLastScroll ) ;
// No-op implementation of transition degradation
$ . mobile . _maybeDegradeTransition = $ . mobile . _maybeDegradeTransition || function ( transition ) {
return transition ;
} ;
//function for transitioning between two existing pages
function transitionPages ( toPage , fromPage , transition , reverse ) {
if ( fromPage ) {
//trigger before show/hide events
fromPage . data ( "mobile-page" ) . _trigger ( "beforehide" , null , { nextPage : toPage } ) ;
}
toPage . data ( "mobile-page" ) . _trigger ( "beforeshow" , null , { prevPage : fromPage || $ ( "" ) } ) ;
//clear page loader
$ . mobile . hidePageLoadingMsg ( ) ;
transition = $ . mobile . _maybeDegradeTransition ( transition ) ;
//find the transition handler for the specified transition. If there
//isn't one in our transitionHandlers dictionary, use the default one.
//call the handler immediately to kick-off the transition.
var th = $ . mobile . transitionHandlers [ transition || "default" ] || $ . mobile . defaultTransitionHandler ,
promise = th ( transition , reverse , toPage , fromPage ) ;
promise . done ( function ( ) {
//trigger show/hide events
if ( fromPage ) {
fromPage . data ( "mobile-page" ) . _trigger ( "hide" , null , { nextPage : toPage } ) ;
}
//trigger pageshow, define prevPage as either fromPage or empty jQuery obj
toPage . data ( "mobile-page" ) . _trigger ( "show" , null , { prevPage : fromPage || $ ( "" ) } ) ;
} ) ;
return promise ;
}
//simply set the active page's minimum height to screen height, depending on orientation
$ . mobile . resetActivePageHeight = function resetActivePageHeight ( height ) {
var aPage = $ ( "." + $ . mobile . activePageClass ) ,
aPagePadT = parseFloat ( aPage . css ( "padding-top" ) ) ,
aPagePadB = parseFloat ( aPage . css ( "padding-bottom" ) ) ,
aPageBorderT = parseFloat ( aPage . css ( "border-top-width" ) ) ,
aPageBorderB = parseFloat ( aPage . css ( "border-bottom-width" ) ) ;
height = ( typeof height === "number" ) ? height : getScreenHeight ( ) ;
aPage . css ( "min-height" , height - aPagePadT - aPagePadB - aPageBorderT - aPageBorderB ) ;
} ;
//shared page enhancements
function enhancePage ( $page , role ) {
// If a role was specified, make sure the data-role attribute
// on the page element is in sync.
if ( role ) {
$page . attr ( "data-" + $ . mobile . ns + "role" , role ) ;
}
//run page plugin
$page . page ( ) ;
}
// determine the current base url
function findBaseWithDefault ( ) {
var closestBase = ( $ . mobile . activePage && getClosestBaseUrl ( $ . mobile . activePage ) ) ;
return closestBase || documentBase . hrefNoHash ;
}
/* exposed $.mobile methods */
//animation complete callback
$ . fn . animationComplete = function ( callback ) {
if ( $ . support . cssTransitions ) {
return $ ( this ) . one ( 'webkitAnimationEnd animationend' , callback ) ;
}
else {
// defer execution for consistency between webkit/non webkit
setTimeout ( callback , 0 ) ;
return $ ( this ) ;
}
} ;
//expose path object on $.mobile
$ . mobile . path = path ;
//expose base object on $.mobile
$ . mobile . base = base ;
//history stack
$ . mobile . urlHistory = urlHistory ;
$ . mobile . dialogHashKey = dialogHashKey ;
//enable cross-domain page support
$ . mobile . allowCrossDomainPages = false ;
$ . mobile . _bindPageRemove = function ( ) {
var page = $ ( this ) ;
// when dom caching is not enabled or the page is embedded bind to remove the page on hide
if ( ! page . data ( "mobile-page" ) . options . domCache &&
page . is ( ":jqmData(external-page='true')" ) ) {
page . bind ( 'pagehide.remove' , function ( e ) {
var $this = $ ( this ) ,
prEvent = new $ . Event ( "pageremove" ) ;
$this . trigger ( prEvent ) ;
if ( ! prEvent . isDefaultPrevented ( ) ) {
$this . removeWithDependents ( ) ;
}
} ) ;
}
} ;
// Load a page into the DOM.
$ . mobile . loadPage = function ( url , options ) {
// This function uses deferred notifications to let callers
// know when the page is done loading, or if an error has occurred.
var deferred = $ . Deferred ( ) ,
// The default loadPage options with overrides specified by
// the caller.
settings = $ . extend ( { } , $ . mobile . loadPage . defaults , options ) ,
// The DOM element for the page after it has been loaded.
page = null ,
// If the reloadPage option is true, and the page is already
// in the DOM, dupCachedPage will be set to the page element
// so that it can be removed after the new version of the
// page is loaded off the network.
dupCachedPage = null ,
// The absolute version of the URL passed into the function. This
// version of the URL may contain dialog/subpage params in it.
absUrl = path . makeUrlAbsolute ( url , findBaseWithDefault ( ) ) ;
// If the caller provided data, and we're using "get" request,
// append the data to the URL.
if ( settings . data && settings . type === "get" ) {
absUrl = path . addSearchParams ( absUrl , settings . data ) ;
settings . data = undefined ;
}
// If the caller is using a "post" request, reloadPage must be true
if ( settings . data && settings . type === "post" ) {
settings . reloadPage = true ;
}
// The absolute version of the URL minus any dialog/subpage params.
// In otherwords the real URL of the page to be loaded.
var fileUrl = path . getFilePath ( absUrl ) ,
// The version of the Url actually stored in the data-url attribute of
// the page. For embedded pages, it is just the id of the page. For pages
// within the same domain as the document base, it is the site relative
// path. For cross-domain pages (Phone Gap only) the entire absolute Url
// used to load the page.
dataUrl = path . convertUrlToDataUrl ( absUrl ) ;
// Make sure we have a pageContainer to work with.
settings . pageContainer = settings . pageContainer || $ . mobile . pageContainer ;
// Check to see if the page already exists in the DOM.
// NOTE do _not_ use the :jqmData psuedo selector because parenthesis
// are a valid url char and it breaks on the first occurence
page = settings . pageContainer . children ( "[data-" + $ . mobile . ns + "url='" + dataUrl + "']" ) ;
// If we failed to find the page, check to see if the url is a
// reference to an embedded page. If so, it may have been dynamically
// injected by a developer, in which case it would be lacking a data-url
// attribute and in need of enhancement.
if ( page . length === 0 && dataUrl && ! path . isPath ( dataUrl ) ) {
page = settings . pageContainer . children ( "#" + dataUrl )
. attr ( "data-" + $ . mobile . ns + "url" , dataUrl )
. jqmData ( "url" , dataUrl ) ;
}
// If we failed to find a page in the DOM, check the URL to see if it
// refers to the first page in the application. If it isn't a reference
// to the first page and refers to non-existent embedded page, error out.
if ( page . length === 0 ) {
if ( $ . mobile . firstPage && path . isFirstPageUrl ( fileUrl ) ) {
// Check to make sure our cached-first-page is actually
// in the DOM. Some user deployed apps are pruning the first
// page from the DOM for various reasons, we check for this
// case here because we don't want a first-page with an id
// falling through to the non-existent embedded page error
// case. If the first-page is not in the DOM, then we let
// things fall through to the ajax loading code below so
// that it gets reloaded.
if ( $ . mobile . firstPage . parent ( ) . length ) {
page = $ ( $ . mobile . firstPage ) ;
}
} else if ( path . isEmbeddedPage ( fileUrl ) ) {
deferred . reject ( absUrl , options ) ;
return deferred . promise ( ) ;
}
}
// If the page we are interested in is already in the DOM,
// and the caller did not indicate that we should force a
// reload of the file, we are done. Otherwise, track the
// existing page as a duplicated.
if ( page . length ) {
if ( ! settings . reloadPage ) {
enhancePage ( page , settings . role ) ;
deferred . resolve ( absUrl , options , page ) ;
return deferred . promise ( ) ;
}
dupCachedPage = page ;
}
var mpc = settings . pageContainer ,
pblEvent = new $ . Event ( "pagebeforeload" ) ,
triggerData = { url : url , absUrl : absUrl , dataUrl : dataUrl , deferred : deferred , options : settings } ;
// Let listeners know we're about to load a page.
mpc . trigger ( pblEvent , triggerData ) ;
// If the default behavior is prevented, stop here!
if ( pblEvent . isDefaultPrevented ( ) ) {
return deferred . promise ( ) ;
}
if ( settings . showLoadMsg ) {
// This configurable timeout allows cached pages a brief delay to load without showing a message
var loadMsgDelay = setTimeout ( function ( ) {
$ . mobile . showPageLoadingMsg ( ) ;
} , settings . loadMsgDelay ) ,
// Shared logic for clearing timeout and removing message.
hideMsg = function ( ) {
// Stop message show timer
clearTimeout ( loadMsgDelay ) ;
// Hide loading message
$ . mobile . hidePageLoadingMsg ( ) ;
} ;
}
// Reset base to the default document base.
if ( base ) {
base . reset ( ) ;
}
if ( ! ( $ . mobile . allowCrossDomainPages || path . isSameDomain ( documentUrl , absUrl ) ) ) {
deferred . reject ( absUrl , options ) ;
} else {
// Load the new page.
$ . ajax ( {
url : fileUrl ,
type : settings . type ,
data : settings . data ,
dataType : "html" ,
success : function ( html , textStatus , xhr ) {
//pre-parse html to check for a data-url,
//use it as the new fileUrl, base path, etc
var all = $ ( "<div></div>" ) ,
//page title regexp
newPageTitle = html . match ( /<title[^>]*>([^<]*)/ ) && RegExp . $1 ,
// TODO handle dialogs again
pageElemRegex = new RegExp ( "(<[^>]+\\bdata-" + $ . mobile . ns + "role=[\"']?page[\"']?[^>]*>)" ) ,
dataUrlRegex = new RegExp ( "\\bdata-" + $ . mobile . ns + "url=[\"']?([^\"'>]*)[\"']?" ) ;
// data-url must be provided for the base tag so resource requests can be directed to the
// correct url. loading into a temprorary element makes these requests immediately
if ( pageElemRegex . test ( html ) &&
RegExp . $1 &&
dataUrlRegex . test ( RegExp . $1 ) &&
RegExp . $1 ) {
url = fileUrl = path . getFilePath ( $ ( "<div>" + RegExp . $1 + "</div>" ) . text ( ) ) ;
}
if ( base ) {
base . set ( fileUrl ) ;
}
//workaround to allow scripts to execute when included in page divs
all . get ( 0 ) . innerHTML = html ;
page = all . find ( ":jqmData(role='page'), :jqmData(role='dialog')" ) . first ( ) ;
//if page elem couldn't be found, create one and insert the body element's contents
if ( ! page . length ) {
page = $ ( "<div data-" + $ . mobile . ns + "role='page'>" + ( html . split ( /<\/?body[^>]*>/gmi ) [ 1 ] || "" ) + "</div>" ) ;
}
if ( newPageTitle && ! page . jqmData ( "title" ) ) {
if ( ~ newPageTitle . indexOf ( "&" ) ) {
newPageTitle = $ ( "<div>" + newPageTitle + "</div>" ) . text ( ) ;
}
page . jqmData ( "title" , newPageTitle ) ;
}
//rewrite src and href attrs to use a base url
if ( ! $ . support . dynamicBaseTag ) {
var newPath = path . get ( fileUrl ) ;
page . find ( "[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]" ) . each ( function ( ) {
var thisAttr = $ ( this ) . is ( '[href]' ) ? 'href' :
$ ( this ) . is ( '[src]' ) ? 'src' : 'action' ,
thisUrl = $ ( this ) . attr ( thisAttr ) ;
// XXX_jblas: We need to fix this so that it removes the document
// base URL, and then prepends with the new page URL.
//if full path exists and is same, chop it - helps IE out
thisUrl = thisUrl . replace ( location . protocol + '//' + location . host + location . pathname , '' ) ;
if ( ! /^(\w+:|#|\/)/ . test ( thisUrl ) ) {
$ ( this ) . attr ( thisAttr , newPath + thisUrl ) ;
}
} ) ;
}
//append to page and enhance
// TODO taging a page with external to make sure that embedded pages aren't removed
// by the various page handling code is bad. Having page handling code in many
// places is bad. Solutions post 1.0
page
. attr ( "data-" + $ . mobile . ns + "url" , path . convertUrlToDataUrl ( fileUrl ) )
. attr ( "data-" + $ . mobile . ns + "external-page" , true )
. appendTo ( settings . pageContainer ) ;
// wait for page creation to leverage options defined on widget
page . one ( 'pagecreate' , $ . mobile . _bindPageRemove ) ;
enhancePage ( page , settings . role ) ;
// Enhancing the page may result in new dialogs/sub pages being inserted
// into the DOM. If the original absUrl refers to a sub-page, that is the
// real page we are interested in.
if ( absUrl . indexOf ( "&" + $ . mobile . subPageUrlKey ) > - 1 ) {
page = settings . pageContainer . children ( "[data-" + $ . mobile . ns + "url='" + dataUrl + "']" ) ;
}
// Remove loading message.
if ( settings . showLoadMsg ) {
hideMsg ( ) ;
}
// Add the page reference and xhr to our triggerData.
triggerData . xhr = xhr ;
triggerData . textStatus = textStatus ;
triggerData . page = page ;
// Let listeners know the page loaded successfully.
settings . pageContainer . trigger ( "pageload" , triggerData ) ;
deferred . resolve ( absUrl , options , page , dupCachedPage ) ;
} ,
error : function ( xhr , textStatus , errorThrown ) {
//set base back to current path
if ( base ) {
base . set ( path . get ( ) ) ;
}
// Add error info to our triggerData.
triggerData . xhr = xhr ;
triggerData . textStatus = textStatus ;
triggerData . errorThrown = errorThrown ;
var plfEvent = new $ . Event ( "pageloadfailed" ) ;
// Let listeners know the page load failed.
settings . pageContainer . trigger ( plfEvent , triggerData ) ;
// If the default behavior is prevented, stop here!
// Note that it is the responsibility of the listener/handler
// that called preventDefault(), to resolve/reject the
// deferred object within the triggerData.
if ( plfEvent . isDefaultPrevented ( ) ) {
return ;
}
// Remove loading message.
if ( settings . showLoadMsg ) {
// Remove loading message.
hideMsg ( ) ;
// show error message
$ . mobile . showPageLoadingMsg ( $ . mobile . pageLoadErrorMessageTheme , $ . mobile . pageLoadErrorMessage , true ) ;
// hide after delay
setTimeout ( $ . mobile . hidePageLoadingMsg , 1500 ) ;
}
deferred . reject ( absUrl , options ) ;
}
} ) ;
}
return deferred . promise ( ) ;
} ;
$ . mobile . loadPage . defaults = {
type : "get" ,
data : undefined ,
reloadPage : false ,
role : undefined , // By default we rely on the role defined by the @data-role attribute.
showLoadMsg : false ,
pageContainer : undefined ,
loadMsgDelay : 50 // This delay allows loads that pull from browser cache to occur without showing the loading message.
} ;
// Show a specific page in the page container.
$ . mobile . changePage = function ( toPage , options ) {
// If we are in the midst of a transition, queue the current request.
// We'll call changePage() once we're done with the current transition to
// service the request.
if ( isPageTransitioning ) {
pageTransitionQueue . unshift ( arguments ) ;
return ;
}
var settings = $ . extend ( { } , $ . mobile . changePage . defaults , options ) , isToPageString ;
// Make sure we have a pageContainer to work with.
settings . pageContainer = settings . pageContainer || $ . mobile . pageContainer ;
// Make sure we have a fromPage.
settings . fromPage = settings . fromPage || $ . mobile . activePage ;
isToPageString = ( typeof toPage === "string" ) ;
var mpc = settings . pageContainer ,
pbcEvent = new $ . Event ( "pagebeforechange" ) ,
triggerData = { toPage : toPage , options : settings } ;
// NOTE: preserve the original target as the dataUrl value will be simplified
// eg, removing ui-state, and removing query params from the hash
// this is so that users who want to use query params have access to them
// in the event bindings for the page life cycle See issue #5085
if ( isToPageString ) {
// if the toPage is a string simply convert it
triggerData . absUrl = path . makeUrlAbsolute ( toPage , findBaseWithDefault ( ) ) ;
} else {
// if the toPage is a jQuery object grab the absolute url stored
// in the loadPage callback where it exists
triggerData . absUrl = toPage . data ( 'absUrl' ) ;
}
// Let listeners know we're about to change the current page.
mpc . trigger ( pbcEvent , triggerData ) ;
// If the default behavior is prevented, stop here!
if ( pbcEvent . isDefaultPrevented ( ) ) {
return ;
}
// We allow "pagebeforechange" observers to modify the toPage in the trigger
// data to allow for redirects. Make sure our toPage is updated.
//
// We also need to re-evaluate whether it is a string, because an object can
// also be replaced by a string
toPage = triggerData . toPage ;
isToPageString = ( typeof toPage === "string" ) ;
// Set the isPageTransitioning flag to prevent any requests from
// entering this method while we are in the midst of loading a page
// or transitioning.
isPageTransitioning = true ;
// If the caller passed us a url, call loadPage()
// to make sure it is loaded into the DOM. We'll listen
// to the promise object it returns so we know when
// it is done loading or if an error ocurred.
if ( isToPageString ) {
// preserve the original target as the dataUrl value will be simplified
// eg, removing ui-state, and removing query params from the hash
// this is so that users who want to use query params have access to them
// in the event bindings for the page life cycle See issue #5085
settings . target = toPage ;
$ . mobile . loadPage ( toPage , settings )
. done ( function ( url , options , newPage , dupCachedPage ) {
isPageTransitioning = false ;
options . duplicateCachedPage = dupCachedPage ;
// store the original absolute url so that it can be provided
// to events in the triggerData of the subsequent changePage call
newPage . data ( 'absUrl' , triggerData . absUrl ) ;
$ . mobile . changePage ( newPage , options ) ;
} )
. fail ( function ( url , options ) {
isPageTransitioning = false ;
//clear out the active button state
removeActiveLinkClass ( true ) ;
//release transition lock so navigation is free again
releasePageTransitionLock ( ) ;
settings . pageContainer . trigger ( "pagechangefailed" , triggerData ) ;
} ) ;
return ;
}
// If we are going to the first-page of the application, we need to make
// sure settings.dataUrl is set to the application document url. This allows
// us to avoid generating a document url with an id hash in the case where the
// first-page of the document has an id attribute specified.
if ( toPage [ 0 ] === $ . mobile . firstPage [ 0 ] && ! settings . dataUrl ) {
settings . dataUrl = documentUrl . hrefNoHash ;
}
// The caller passed us a real page DOM element. Update our
// internal state and then trigger a transition to the page.
var fromPage = settings . fromPage ,
url = ( settings . dataUrl && path . convertUrlToDataUrl ( settings . dataUrl ) ) || toPage . jqmData ( "url" ) ,
// The pageUrl var is usually the same as url, except when url is obscured as a dialog url. pageUrl always contains the file path
pageUrl = url ,
fileUrl = path . getFilePath ( url ) ,
active = urlHistory . getActive ( ) ,
activeIsInitialPage = urlHistory . activeIndex === 0 ,
historyDir = 0 ,
pageTitle = document . title ,
isDialog = settings . role === "dialog" || toPage . jqmData ( "role" ) === "dialog" ;
// By default, we prevent changePage requests when the fromPage and toPage
// are the same element, but folks that generate content manually/dynamically
// and reuse pages want to be able to transition to the same page. To allow
// this, they will need to change the default value of allowSamePageTransition
// to true, *OR*, pass it in as an option when they manually call changePage().
// It should be noted that our default transition animations assume that the
// formPage and toPage are different elements, so they may behave unexpectedly.
// It is up to the developer that turns on the allowSamePageTransitiona option
// to either turn off transition animations, or make sure that an appropriate
// animation transition is used.
if ( fromPage && fromPage [ 0 ] === toPage [ 0 ] && ! settings . allowSamePageTransition ) {
isPageTransitioning = false ;
mpc . trigger ( "pagechange" , triggerData ) ;
// Even if there is no page change to be done, we should keep the urlHistory in sync with the hash changes
if ( settings . fromHashChange ) {
urlHistory . direct ( { url : url } ) ;
}
return ;
}
// We need to make sure the page we are given has already been enhanced.
enhancePage ( toPage , settings . role ) ;
// If the changePage request was sent from a hashChange event, check to see if the
// page is already within the urlHistory stack. If so, we'll assume the user hit
// the forward/back button and will try to match the transition accordingly.
if ( settings . fromHashChange ) {
historyDir = options . direction === "back" ? - 1 : 1 ;
}
// Kill the keyboard.
// XXX_jblas: We need to stop crawling the entire document to kill focus. Instead,
// we should be tracking focus with a delegate() handler so we already have
// the element in hand at this point.
// Wrap this in a try/catch block since IE9 throw "Unspecified error" if document.activeElement
// is undefined when we are in an IFrame.
try {
if ( document . activeElement && document . activeElement . nodeName . toLowerCase ( ) !== 'body' ) {
$ ( document . activeElement ) . blur ( ) ;
} else {
$ ( "input:focus, textarea:focus, select:focus" ) . blur ( ) ;
}
} catch ( e ) { }
// Record whether we are at a place in history where a dialog used to be - if so, do not add a new history entry and do not change the hash either
var alreadyThere = false ;
// If we're displaying the page as a dialog, we don't want the url
// for the dialog content to be used in the hash. Instead, we want
// to append the dialogHashKey to the url of the current page.
if ( isDialog && active ) {
// on the initial page load active.url is undefined and in that case should
// be an empty string. Moving the undefined -> empty string back into
// urlHistory.addNew seemed imprudent given undefined better represents
// the url state
// If we are at a place in history that once belonged to a dialog, reuse
// this state without adding to urlHistory and without modifying the hash.
// However, if a dialog is already displayed at this point, and we're
// about to display another dialog, then we must add another hash and
// history entry on top so that one may navigate back to the original dialog
if ( active . url &&
active . url . indexOf ( dialogHashKey ) > - 1 &&
$ . mobile . activePage &&
! $ . mobile . activePage . is ( ".ui-dialog" ) &&
urlHistory . activeIndex > 0 ) {
settings . changeHash = false ;
alreadyThere = true ;
}
// Normally, we tack on a dialog hash key, but if this is the location of a stale dialog,
// we reuse the URL from the entry
url = ( active . url || "" ) ;
// account for absolute urls instead of just relative urls use as hashes
if ( ! alreadyThere && url . indexOf ( "#" ) > - 1 ) {
url += dialogHashKey ;
} else {
url += "#" + dialogHashKey ;
}
// tack on another dialogHashKey if this is the same as the initial hash
// this makes sure that a history entry is created for this dialog
if ( urlHistory . activeIndex === 0 && url === urlHistory . initialDst ) {
url += dialogHashKey ;
}
}
// if title element wasn't found, try the page div data attr too
// If this is a deep-link or a reload ( active === undefined ) then just use pageTitle
var newPageTitle = ( ! active ) ? pageTitle : toPage . jqmData ( "title" ) || toPage . children ( ":jqmData(role='header')" ) . find ( ".ui-title" ) . getEncodedText ( ) ;
if ( ! ! newPageTitle && pageTitle === document . title ) {
pageTitle = newPageTitle ;
}
if ( ! toPage . jqmData ( "title" ) ) {
toPage . jqmData ( "title" , pageTitle ) ;
}
// Make sure we have a transition defined.
settings . transition = settings . transition ||
( ( historyDir && ! activeIsInitialPage ) ? active . transition : undefined ) ||
( isDialog ? $ . mobile . defaultDialogTransition : $ . mobile . defaultPageTransition ) ;
//add page to history stack if it's not back or forward
if ( ! historyDir && alreadyThere ) {
urlHistory . getActive ( ) . pageUrl = pageUrl ;
}
// Set the location hash.
if ( url && ! settings . fromHashChange ) {
var params ;
// rebuilding the hash here since we loose it earlier on
// TODO preserve the originally passed in path
if ( ! path . isPath ( url ) && url . indexOf ( "#" ) < 0 ) {
url = "#" + url ;
}
// TODO the property names here are just silly
params = {
transition : settings . transition ,
title : pageTitle ,
pageUrl : pageUrl ,
role : settings . role
} ;
if ( settings . changeHash !== false && $ . mobile . hashListeningEnabled ) {
$ . mobile . navigate ( url , params , true ) ;
} else if ( toPage [ 0 ] !== $ . mobile . firstPage [ 0 ] ) {
$ . mobile . navigate . history . add ( url , params ) ;
}
}
//set page title
document . title = pageTitle ;
//set "toPage" as activePage
$ . mobile . activePage = toPage ;
// If we're navigating back in the URL history, set reverse accordingly.
settings . reverse = settings . reverse || historyDir < 0 ;
transitionPages ( toPage , fromPage , settings . transition , settings . reverse )
. done ( function ( name , reverse , $to , $from , alreadyFocused ) {
removeActiveLinkClass ( ) ;
//if there's a duplicateCachedPage, remove it from the DOM now that it's hidden
if ( settings . duplicateCachedPage ) {
settings . duplicateCachedPage . remove ( ) ;
}
// Send focus to the newly shown page. Moved from promise .done binding in transitionPages
// itself to avoid ie bug that reports offsetWidth as > 0 (core check for visibility)
// despite visibility: hidden addresses issue #2965
// https://github.com/jquery/jquery-mobile/issues/2965
if ( ! alreadyFocused ) {
$ . mobile . focusPage ( toPage ) ;
}
releasePageTransitionLock ( ) ;
mpc . trigger ( "pagechange" , triggerData ) ;
} ) ;
} ;
$ . mobile . changePage . defaults = {
transition : undefined ,
reverse : false ,
changeHash : true ,
fromHashChange : false ,
role : undefined , // By default we rely on the role defined by the @data-role attribute.
duplicateCachedPage : undefined ,
pageContainer : undefined ,
showLoadMsg : true , //loading message shows by default when pages are being fetched during changePage
dataUrl : undefined ,
fromPage : undefined ,
allowSamePageTransition : false
} ;
/* Event Bindings - hashchange, submit, and click */
function findClosestLink ( ele )
{
while ( ele ) {
// Look for the closest element with a nodeName of "a".
// Note that we are checking if we have a valid nodeName
// before attempting to access it. This is because the
// node we get called with could have originated from within
// an embedded SVG document where some symbol instance elements
// don't have nodeName defined on them, or strings are of type
// SVGAnimatedString.
if ( ( typeof ele . nodeName === "string" ) && ele . nodeName . toLowerCase ( ) === "a" ) {
break ;
}
ele = ele . parentNode ;
}
return ele ;
}
// The base URL for any given element depends on the page it resides in.
function getClosestBaseUrl ( ele )
{
// Find the closest page and extract out its url.
var url = $ ( ele ) . closest ( ".ui-page" ) . jqmData ( "url" ) ,
base = documentBase . hrefNoHash ;
if ( ! url || ! path . isPath ( url ) ) {
url = base ;
}
return path . makeUrlAbsolute ( url , base ) ;
}
function maybeCreateHiddenInput ( $el ) {
var type = $el . attr ( "type" ) ,
name = $el . attr ( "name" ) ;
if ( type !== "button" && type !== "reset" && name ) {
// Add hidden input so the value of the clicked button will be recorded
// in the submitted form data, but remove the hidden input from the form
// after the value has been recorded so as to avoid multiple copies of it
// preceding the original button.
$ . mobile . document . one ( "submit" ,
$ . proxy ( function ( ) { this . remove ( ) ; } ,
$ ( "<input>" , {
type : "hidden" ,
name : $el . attr ( "name" ) ,
value : $el . attr ( "value" )
} ) . insertBefore ( $el ) ) ) ;
}
}
//The following event bindings should be bound after mobileinit has been triggered
//the following deferred is resolved in the init file
$ . mobile . navreadyDeferred = $ . Deferred ( ) ;
$ . mobile . _registerInternalEvents = function ( ) {
var getAjaxFormData = function ( $form , calculateOnly ) {
var type , target , url , ret = true ;
if ( ! $ . mobile . ajaxEnabled ||
// test that the form is, itself, ajax false
$form . is ( ":jqmData(ajax='false')" ) ||
// test that $.mobile.ignoreContentEnabled is set and
// the form or one of it's parents is ajax=false
! $form . jqmHijackable ( ) . length ) {
return false ;
}
target = $form . attr ( "target" ) ;
url = $form . attr ( "action" ) ;
// If no action is specified, browsers default to using the
// URL of the document containing the form. Since we dynamically
// pull in pages from external documents, the form should submit
// to the URL for the source document of the page containing
// the form.
if ( ! url ) {
// Get the @data-url for the page containing the form.
url = getClosestBaseUrl ( $form ) ;
if ( url === documentBase . hrefNoHash ) {
// The url we got back matches the document base,
// which means the page must be an internal/embedded page,
// so default to using the actual document url as a browser
// would.
url = documentUrl . hrefNoSearch ;
}
}
url = path . makeUrlAbsolute ( url , getClosestBaseUrl ( $form ) ) ;
if ( ( path . isExternal ( url ) && ! path . isPermittedCrossDomainRequest ( documentUrl , url ) ) || target ) {
return false ;
}
if ( ! calculateOnly ) {
type = $form . attr ( "method" ) ;
ret = {
url : url ,
options : {
type : type && type . length && type . toLowerCase ( ) || "get" ,
data : $form . serialize ( ) ,
transition : $form . jqmData ( "transition" ) ,
reverse : $form . jqmData ( "direction" ) === "reverse" ,
reloadPage : true
}
} ;
}
return ret ;
} ;
//bind to form submit events, handle with Ajax
$ . mobile . document . delegate ( "form" , "submit" , function ( event ) {
var formData = getAjaxFormData ( $ ( this ) ) ;
if ( formData ) {
$ . mobile . changePage ( formData . url , formData . options ) ;
event . preventDefault ( ) ;
}
} ) ;
//add active state on vclick
$ . mobile . document . bind ( "vclick" , function ( event ) {
var $btn , btnEls , target = event . target , needClosest = false ;
// if this isn't a left click we don't care. Its important to note
// that when the virtual event is generated it will create the which attr
if ( event . which > 1 || ! $ . mobile . linkBindingEnabled ) {
return ;
}
// If this is a vclick on a button, we must make sure its value will be
// recorded in the resulting form data by adding a hidden input that
// carries the button's value
maybeCreateHiddenInput ( $ ( target ) ) ;
// Try to find a target element to which the active class will be applied
if ( $ . data ( target , "mobile-button" ) ) {
// If the form will not be submitted via AJAX, do not add active class
if ( ! getAjaxFormData ( $ ( target ) . closest ( "form" ) , true ) ) {
return ;
}
// We will apply the active state to this button widget - the parent
// of the input that was clicked will have the associated data
if ( target . parentNode ) {
target = target . parentNode ;
}
} else {
target = findClosestLink ( target ) ;
if ( ! ( target && path . parseUrl ( target . getAttribute ( "href" ) || "#" ) . hash !== "#" ) ) {
return ;
}
// TODO teach $.mobile.hijackable to operate on raw dom elements so the
// link wrapping can be avoided
if ( ! $ ( target ) . jqmHijackable ( ) . length ) {
return ;
}
}
// Avoid calling .closest by using the data set during .buttonMarkup()
// List items have the button data in the parent of the element clicked
if ( ! ! ~ target . className . indexOf ( "ui-link-inherit" ) ) {
if ( target . parentNode ) {
btnEls = $ . data ( target . parentNode , "buttonElements" ) ;
}
// Otherwise, look for the data on the target itself
} else {
btnEls = $ . data ( target , "buttonElements" ) ;
}
// If found, grab the button's outer element
if ( btnEls ) {
target = btnEls . outer ;
} else {
needClosest = true ;
}
$btn = $ ( target ) ;
// If the outer element wasn't found by the our heuristics, use .closest()
if ( needClosest ) {
$btn = $btn . closest ( ".ui-btn" ) ;
}
if ( $btn . length > 0 && ! $btn . hasClass ( "ui-disabled" ) ) {
removeActiveLinkClass ( true ) ;
$activeClickedLink = $btn ;
$activeClickedLink . addClass ( $ . mobile . activeBtnClass ) ;
}
} ) ;
// click routing - direct to HTTP or Ajax, accordingly
$ . mobile . document . bind ( "click" , function ( event ) {
if ( ! $ . mobile . linkBindingEnabled || event . isDefaultPrevented ( ) ) {
return ;
}
var link = findClosestLink ( event . target ) , $link = $ ( link ) , httpCleanup ;
// If there is no link associated with the click or its not a left
// click we want to ignore the click
// TODO teach $.mobile.hijackable to operate on raw dom elements so the link wrapping
// can be avoided
if ( ! link || event . which > 1 || ! $link . jqmHijackable ( ) . length ) {
return ;
}
//remove active link class if external (then it won't be there if you come back)
httpCleanup = function ( ) {
window . setTimeout ( function ( ) { removeActiveLinkClass ( true ) ; } , 200 ) ;
} ;
//if there's a data-rel=back attr, go back in history
if ( $link . is ( ":jqmData(rel='back')" ) ) {
$ . mobile . back ( ) ;
return false ;
}
var baseUrl = getClosestBaseUrl ( $link ) ,
//get href, if defined, otherwise default to empty hash
href = path . makeUrlAbsolute ( $link . attr ( "href" ) || "#" , baseUrl ) ;
//if ajax is disabled, exit early
if ( ! $ . mobile . ajaxEnabled && ! path . isEmbeddedPage ( href ) ) {
httpCleanup ( ) ;
//use default click handling
return ;
}
// XXX_jblas: Ideally links to application pages should be specified as
// an url to the application document with a hash that is either
// the site relative path or id to the page. But some of the
// internal code that dynamically generates sub-pages for nested
// lists and select dialogs, just write a hash in the link they
// create. This means the actual URL path is based on whatever
// the current value of the base tag is at the time this code
// is called. For now we are just assuming that any url with a
// hash in it is an application page reference.
if ( href . search ( "#" ) !== - 1 ) {
href = href . replace ( /[^#]*#/ , "" ) ;
if ( ! href ) {
//link was an empty hash meant purely
//for interaction, so we ignore it.
event . preventDefault ( ) ;
return ;
} else if ( path . isPath ( href ) ) {
//we have apath so make it the href we want to load.
href = path . makeUrlAbsolute ( href , baseUrl ) ;
} else {
//we have a simple id so use the documentUrl as its base.
href = path . makeUrlAbsolute ( "#" + href , documentUrl . hrefNoHash ) ;
}
}
// Should we handle this link, or let the browser deal with it?
var useDefaultUrlHandling = $link . is ( "[rel='external']" ) || $link . is ( ":jqmData(ajax='false')" ) || $link . is ( "[target]" ) ,
// Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR
// requests if the document doing the request was loaded via the file:// protocol.
// This is usually to allow the application to "phone home" and fetch app specific
// data. We normally let the browser handle external/cross-domain urls, but if the
// allowCrossDomainPages option is true, we will allow cross-domain http/https
// requests to go through our page loading logic.
//check for protocol or rel and its not an embedded page
//TODO overlap in logic from isExternal, rel=external check should be
// moved into more comprehensive isExternalLink
isExternal = useDefaultUrlHandling || ( path . isExternal ( href ) && ! path . isPermittedCrossDomainRequest ( documentUrl , href ) ) ;
if ( isExternal ) {
httpCleanup ( ) ;
//use default click handling
return ;
}
//use ajax
var transition = $link . jqmData ( "transition" ) ,
reverse = $link . jqmData ( "direction" ) === "reverse" ||
// deprecated - remove by 1.0
$link . jqmData ( "back" ) ,
//this may need to be more specific as we use data-rel more
role = $link . attr ( "data-" + $ . mobile . ns + "rel" ) || undefined ;
$ . mobile . changePage ( href , { transition : transition , reverse : reverse , role : role , link : $link } ) ;
event . preventDefault ( ) ;
} ) ;
//prefetch pages when anchors with data-prefetch are encountered
$ . mobile . document . delegate ( ".ui-page" , "pageshow.prefetch" , function ( ) {
var urls = [ ] ;
$ ( this ) . find ( "a:jqmData(prefetch)" ) . each ( function ( ) {
var $link = $ ( this ) ,
url = $link . attr ( "href" ) ;
if ( url && $ . inArray ( url , urls ) === - 1 ) {
urls . push ( url ) ;
$ . mobile . loadPage ( url , { role : $link . attr ( "data-" + $ . mobile . ns + "rel" ) } ) ;
}
} ) ;
} ) ;
$ . mobile . _handleHashChange = function ( url , data ) {
//find first page via hash
var to = path . stripHash ( url ) ,
//transition is false if it's the first page, undefined otherwise (and may be overridden by default)
transition = $ . mobile . urlHistory . stack . length === 0 ? "none" : undefined ,
// default options for the changPage calls made after examining the current state
// of the page and the hash, NOTE that the transition is derived from the previous
// history entry
changePageOptions = {
changeHash : false ,
fromHashChange : true ,
reverse : data . direction === "back"
} ;
$ . extend ( changePageOptions , data , {
transition : ( urlHistory . getLast ( ) || { } ) . transition || transition
} ) ;
// special case for dialogs
if ( urlHistory . activeIndex > 0 && to . indexOf ( dialogHashKey ) > - 1 && urlHistory . initialDst !== to ) {
// If current active page is not a dialog skip the dialog and continue
// in the same direction
if ( $ . mobile . activePage && ! $ . mobile . activePage . is ( ".ui-dialog" ) ) {
//determine if we're heading forward or backward and continue accordingly past
//the current dialog
if ( data . direction === "back" ) {
$ . mobile . back ( ) ;
} else {
window . history . forward ( ) ;
}
// prevent changePage call
return ;
} else {
// if the current active page is a dialog and we're navigating
// to a dialog use the dialog objected saved in the stack
to = data . pageUrl ;
var active = $ . mobile . urlHistory . getActive ( ) ;
// make sure to set the role, transition and reversal
// as most of this is lost by the domCache cleaning
$ . extend ( changePageOptions , {
role : active . role ,
transition : active . transition ,
reverse : data . direction === "back"
} ) ;
}
}
//if to is defined, load it
if ( to ) {
// At this point, 'to' can be one of 3 things, a cached page element from
// a history stack entry, an id, or site-relative/absolute URL. If 'to' is
// an id, we need to resolve it against the documentBase, not the location.href,
// since the hashchange could've been the result of a forward/backward navigation
// that crosses from an external page/dialog to an internal page/dialog.
to = ! path . isPath ( to ) ? ( path . makeUrlAbsolute ( '#' + to , documentBase ) ) : to ;
// If we're about to go to an initial URL that contains a reference to a non-existent
// internal page, go to the first page instead. We know that the initial hash refers to a
// non-existent page, because the initial hash did not end up in the initial urlHistory entry
if ( to === path . makeUrlAbsolute ( '#' + urlHistory . initialDst , documentBase ) &&
urlHistory . stack . length && urlHistory . stack [ 0 ] . url !== urlHistory . initialDst . replace ( dialogHashKey , "" ) ) {
to = $ . mobile . firstPage ;
}
$ . mobile . changePage ( to , changePageOptions ) ;
} else {
//there's no hash, go to the first page in the dom
$ . mobile . changePage ( $ . mobile . firstPage , changePageOptions ) ;
}
} ;
// TODO roll the logic here into the handleHashChange method
$window . bind ( "navigate" , function ( e , data ) {
var url = $ . event . special . navigate . originalEventName . indexOf ( "hashchange" ) > - 1 ? data . state . hash : data . state . url ;
if ( ! url ) {
url = $ . mobile . path . parseLocation ( ) . hash ;
}
if ( ! url || url === "#" || url . indexOf ( "#" + $ . mobile . path . uiStateKey ) === 0 ) {
url = location . href ;
}
$ . mobile . _handleHashChange ( url , data . state ) ;
} ) ;
//set page min-heights to be device specific
$ . mobile . document . bind ( "pageshow" , $ . mobile . resetActivePageHeight ) ;
$ . mobile . window . bind ( "throttledresize" , $ . mobile . resetActivePageHeight ) ;
} ; //navreadyDeferred done callback
$ ( function ( ) { domreadyDeferred . resolve ( ) ; } ) ;
$ . when ( domreadyDeferred , $ . mobile . navreadyDeferred ) . done ( function ( ) { $ . mobile . _registerInternalEvents ( ) ; } ) ;
} ) ( jQuery ) ;
/ *
* fallback transition for flip in non - 3 D 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 - 3 D 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 - 3 D supporting browsers ( which tend to handle complex transitions poorly in general
* /
( function ( $ , window , undefined ) {
$ . mobile . transitionFallbacks . pop = "fade" ;
} ) ( jQuery , this ) ;
/ *
* fallback transition for slide in non - 3 D supporting browsers ( which tend to handle complex transitions poorly in general
* /
( function ( $ , window , undefined ) {
// Use the simultaneous transitions handler for slide transitions
$ . mobile . transitionHandlers . slide = $ . mobile . transitionHandlers . simultaneous ;
// Set the slide transitions's fallback to "fade"
$ . mobile . transitionFallbacks . slide = "fade" ;
} ) ( jQuery , this ) ;
/ *
* fallback transition for slidedown in non - 3 D supporting browsers ( which tend to handle complex transitions poorly in general
* /
( function ( $ , window , undefined ) {
$ . mobile . transitionFallbacks . slidedown = "fade" ;
} ) ( jQuery , this ) ;
/ *
* fallback transition for slidefade in non - 3 D supporting browsers ( which tend to handle complex transitions poorly in general
* /
( function ( $ , window , undefined ) {
// Set the slide transitions's fallback to "fade"
$ . mobile . transitionFallbacks . slidefade = "fade" ;
} ) ( jQuery , this ) ;
/ *
* fallback transition for slideup in non - 3 D supporting browsers ( which tend to handle complex transitions poorly in general
* /
( function ( $ , window , undefined ) {
$ . mobile . transitionFallbacks . slideup = "fade" ;
} ) ( jQuery , this ) ;
/ *
* fallback transition for turn in non - 3 D supporting browsers ( which tend to handle complex transitions poorly in general
* /
( function ( $ , window , undefined ) {
$ . mobile . transitionFallbacks . turn = "fade" ;
} ) ( jQuery , this ) ;
( function ( $ , undefined ) {
$ . mobile . page . prototype . options . degradeInputs = {
color : false ,
date : false ,
datetime : false ,
"datetime-local" : false ,
email : false ,
month : false ,
number : false ,
range : "number" ,
search : "text" ,
tel : false ,
time : false ,
url : false ,
week : false
} ;
//auto self-init widgets
$ . mobile . document . bind ( "pagecreate create" , function ( e ) {
var page = $ . mobile . closestPageData ( $ ( e . target ) ) , options ;
if ( ! page ) {
return ;
}
options = page . options ;
// degrade inputs to avoid poorly implemented native functionality
$ ( e . target ) . find ( "input" ) . not ( page . keepNativeSelector ( ) ) . each ( function ( ) {
var $this = $ ( this ) ,
type = this . getAttribute ( "type" ) ,
optType = options . degradeInputs [ type ] || "text" ;
if ( options . degradeInputs [ type ] ) {
var html = $ ( "<div>" ) . html ( $this . clone ( ) ) . html ( ) ,
// In IE browsers, the type sometimes doesn't exist in the cloned markup, so we replace the closing tag instead
hasType = html . indexOf ( " type=" ) > - 1 ,
findstr = hasType ? /\s+type=["']?\w+['"]?/ : /\/?>/ ,
repstr = " type=\"" + optType + "\" data-" + $ . mobile . ns + "type=\"" + type + "\"" + ( hasType ? "" : ">" ) ;
$this . replaceWith ( html . replace ( findstr , repstr ) ) ;
}
} ) ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , window , undefined ) {
$ . widget ( "mobile.dialog" , $ . mobile . widget , {
options : {
closeBtn : "left" ,
closeBtnText : "Close" ,
overlayTheme : "a" ,
corners : true ,
initSelector : ":jqmData(role='dialog')"
} ,
// Override the theme set by the page plugin on pageshow
_handlePageBeforeShow : function ( ) {
this . _isCloseable = true ;
if ( this . options . overlayTheme ) {
this . element
. page ( "removeContainerBackground" )
. page ( "setContainerBackground" , this . options . overlayTheme ) ;
}
} ,
_create : function ( ) {
var self = this ,
$el = this . element ,
cornerClass = ! ! this . options . corners ? " ui-corner-all" : "" ,
dialogWrap = $ ( "<div/>" , {
"role" : "dialog" ,
"class" : "ui-dialog-contain ui-overlay-shadow" + cornerClass
} ) ;
$el . addClass ( "ui-dialog ui-overlay-" + this . options . overlayTheme ) ;
// Class the markup for dialog styling
// Set aria role
$el . wrapInner ( dialogWrap ) ;
/ * b i n d e v e n t s
- clicks and submits should use the closing transition that the dialog opened with
unless a data - transition is specified on the link / form
- if the click was on the close button , or the link has a data - rel = "back" it ' ll go back in history naturally
* /
$el . bind ( "vclick submit" , function ( event ) {
var $target = $ ( event . target ) . closest ( event . type === "vclick" ? "a" : "form" ) ,
active ;
if ( $target . length && ! $target . jqmData ( "transition" ) ) {
active = $ . mobile . urlHistory . getActive ( ) || { } ;
$target . attr ( "data-" + $ . mobile . ns + "transition" , ( active . transition || $ . mobile . defaultDialogTransition ) )
. attr ( "data-" + $ . mobile . ns + "direction" , "reverse" ) ;
}
} ) ;
this . _on ( $el , {
pagebeforeshow : "_handlePageBeforeShow"
} ) ;
$ . extend ( this , {
_createComplete : false
} ) ;
this . _setCloseBtn ( this . options . closeBtn ) ;
} ,
_setCloseBtn : function ( value ) {
var self = this , btn , location ;
if ( this . _headerCloseButton ) {
this . _headerCloseButton . remove ( ) ;
this . _headerCloseButton = null ;
}
if ( value !== "none" ) {
// Sanitize value
location = ( value === "left" ? "left" : "right" ) ;
btn = $ ( "<a href='#' class='ui-btn-" + location + "' data-" + $ . mobile . ns + "icon='delete' data-" + $ . mobile . ns + "iconpos='notext'>" + this . options . closeBtnText + "</a>" ) ;
this . element . children ( ) . find ( ":jqmData(role='header')" ) . first ( ) . prepend ( btn ) ;
if ( this . _createComplete && $ . fn . buttonMarkup ) {
btn . buttonMarkup ( ) ;
}
this . _createComplete = true ;
// this must be an anonymous function so that select menu dialogs can replace
// the close method. This is a change from previously just defining data-rel=back
// on the button and letting nav handle it
//
// Use click rather than vclick in order to prevent the possibility of unintentionally
// reopening the dialog if the dialog opening item was directly under the close button.
btn . bind ( "click" , function ( ) {
self . close ( ) ;
} ) ;
this . _headerCloseButton = btn ;
}
} ,
_setOption : function ( key , value ) {
if ( key === "closeBtn" ) {
this . _setCloseBtn ( value ) ;
this . _super ( key , value ) ;
this . element . attr ( "data-" + ( $ . mobile . ns || "" ) + "close-btn" , value ) ;
}
} ,
// Close method goes back in history
close : function ( ) {
var idx , dst , hist = $ . mobile . navigate . history ;
if ( this . _isCloseable ) {
this . _isCloseable = false ;
// If the hash listening is enabled and there is at least one preceding history
// entry it's ok to go back. Initial pages with the dialog hash state are an example
// where the stack check is necessary
if ( $ . mobile . hashListeningEnabled && hist . activeIndex > 0 ) {
$ . mobile . back ( ) ;
} else {
idx = Math . max ( 0 , hist . activeIndex - 1 ) ;
dst = hist . stack [ idx ] . pageUrl || hist . stack [ idx ] . url ;
hist . previousIndex = hist . activeIndex ;
hist . activeIndex = idx ;
if ( ! $ . mobile . path . isPath ( dst ) ) {
dst = $ . mobile . path . makeUrlAbsolute ( "#" + dst ) ;
}
$ . mobile . changePage ( dst , { direction : "back" , changeHash : false , fromHashChange : true } ) ;
}
}
}
} ) ;
//auto self-init widgets
$ . mobile . document . delegate ( $ . mobile . dialog . prototype . options . initSelector , "pagecreate" , function ( ) {
$ . mobile . dialog . prototype . enhance ( this ) ;
} ) ;
} ) ( jQuery , this ) ;
( function ( $ , undefined ) {
$ . mobile . page . prototype . options . backBtnText = "Back" ;
$ . mobile . page . prototype . options . addBackBtn = false ;
$ . mobile . page . prototype . options . backBtnTheme = null ;
$ . mobile . page . prototype . options . headerTheme = "a" ;
$ . mobile . page . prototype . options . footerTheme = "a" ;
$ . mobile . page . prototype . options . contentTheme = null ;
// NOTE bind used to force this binding to run before the buttonMarkup binding
// which expects .ui-footer top be applied in its gigantic selector
// TODO remove the buttonMarkup giant selector and move it to the various modules
// on which it depends
$ . mobile . document . bind ( "pagecreate" , function ( e ) {
var $page = $ ( e . target ) ,
o = $page . data ( "mobile-page" ) . options ,
pageRole = $page . jqmData ( "role" ) ,
pageTheme = o . theme ;
$ ( ":jqmData(role='header'), :jqmData(role='footer'), :jqmData(role='content')" , $page )
. jqmEnhanceable ( )
. each ( function ( ) {
var $this = $ ( this ) ,
role = $this . jqmData ( "role" ) ,
theme = $this . jqmData ( "theme" ) ,
contentTheme = theme || o . contentTheme || ( pageRole === "dialog" && pageTheme ) ,
$headeranchors ,
leftbtn ,
rightbtn ,
backBtn ;
$this . addClass ( "ui-" + role ) ;
//apply theming and markup modifications to page,header,content,footer
if ( role === "header" || role === "footer" ) {
var thisTheme = theme || ( role === "header" ? o . headerTheme : o . footerTheme ) || pageTheme ;
$this
//add theme class
. addClass ( "ui-bar-" + thisTheme )
// Add ARIA role
. attr ( "role" , role === "header" ? "banner" : "contentinfo" ) ;
if ( role === "header" ) {
// Right,left buttons
$headeranchors = $this . children ( "a, button" ) ;
leftbtn = $headeranchors . hasClass ( "ui-btn-left" ) ;
rightbtn = $headeranchors . hasClass ( "ui-btn-right" ) ;
leftbtn = leftbtn || $headeranchors . eq ( 0 ) . not ( ".ui-btn-right" ) . addClass ( "ui-btn-left" ) . length ;
rightbtn = rightbtn || $headeranchors . eq ( 1 ) . addClass ( "ui-btn-right" ) . length ;
}
// Auto-add back btn on pages beyond first view
if ( o . addBackBtn &&
role === "header" &&
$ ( ".ui-page" ) . length > 1 &&
$page . jqmData ( "url" ) !== $ . mobile . path . stripHash ( location . hash ) &&
! leftbtn ) {
backBtn = $ ( "<a href='javascript:void(0);' class='ui-btn-left' data-" + $ . mobile . ns + "rel='back' data-" + $ . mobile . ns + "icon='arrow-l'>" + o . backBtnText + "</a>" )
// 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 ) {
$ . mobile . behaviors . addFirstLastClasses = {
_getVisibles : function ( $els , create ) {
var visibles ;
if ( create ) {
visibles = $els . not ( ".ui-screen-hidden" ) ;
} else {
visibles = $els . filter ( ":visible" ) ;
if ( visibles . length === 0 ) {
visibles = $els . not ( ".ui-screen-hidden" ) ;
}
}
return visibles ;
} ,
_addFirstLastClasses : function ( $els , $visibles , create ) {
$els . removeClass ( "ui-first-child ui-last-child" ) ;
$visibles . eq ( 0 ) . addClass ( "ui-first-child" ) . end ( ) . last ( ) . addClass ( "ui-last-child" ) ;
if ( ! create ) {
this . element . trigger ( "updatelayout" ) ;
}
}
} ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
// 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 ) {
$ . mobile . behaviors . formReset = {
_handleFormReset : function ( ) {
this . _on ( this . element . closest ( "form" ) , {
reset : function ( ) {
this . _delay ( "_reset" ) ;
}
} ) ;
}
} ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
// This function calls getAttribute, which should be safe for data-* attributes
var getAttrFixed = function ( e , key ) {
var value = e . getAttribute ( key ) ;
return value === "true" ? true :
value === "false" ? false :
value === null ? undefined : value ;
} ;
$ . fn . buttonMarkup = function ( options ) {
var $workingSet = this ,
nsKey = "data-" + $ . mobile . ns ,
key ;
// Enforce options to be of type string
options = ( options && ( $ . type ( options ) === "object" ) ) ? options : { } ;
for ( var i = 0 ; i < $workingSet . length ; i ++ ) {
var el = $workingSet . eq ( i ) ,
e = el [ 0 ] ,
o = $ . extend ( { } , $ . fn . buttonMarkup . defaults , {
icon : options . icon !== undefined ? options . icon : getAttrFixed ( e , nsKey + "icon" ) ,
iconpos : options . iconpos !== undefined ? options . iconpos : getAttrFixed ( e , nsKey + "iconpos" ) ,
theme : options . theme !== undefined ? options . theme : getAttrFixed ( e , nsKey + "theme" ) || $ . mobile . getInheritedTheme ( el , "c" ) ,
inline : options . inline !== undefined ? options . inline : getAttrFixed ( e , nsKey + "inline" ) ,
shadow : options . shadow !== undefined ? options . shadow : getAttrFixed ( e , nsKey + "shadow" ) ,
corners : options . corners !== undefined ? options . corners : getAttrFixed ( e , nsKey + "corners" ) ,
iconshadow : options . iconshadow !== undefined ? options . iconshadow : getAttrFixed ( e , nsKey + "iconshadow" ) ,
mini : options . mini !== undefined ? options . mini : getAttrFixed ( e , nsKey + "mini" )
} , options ) ,
// Classes Defined
innerClass = "ui-btn-inner" ,
textClass = "ui-btn-text" ,
buttonClass , iconClass ,
hover = false ,
state = "up" ,
// Button inner markup
buttonInner ,
buttonText ,
buttonIcon ,
buttonElements ;
for ( key in o ) {
e . setAttribute ( nsKey + key , o [ key ] ) ;
}
if ( getAttrFixed ( e , nsKey + "rel" ) === "popup" && el . attr ( "href" ) ) {
e . setAttribute ( "aria-haspopup" , true ) ;
e . setAttribute ( "aria-owns" , el . attr ( "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 ;
hover = buttonElements . hover ;
state = buttonElements . state ;
}
else {
buttonInner = document . createElement ( o . wrapperEls ) ;
buttonText = document . createElement ( o . wrapperEls ) ;
}
buttonIcon = o . icon ? document . createElement ( "span" ) : null ;
if ( attachEvents && ! buttonElements ) {
attachEvents ( ) ;
}
// if not, try to find closest theme container
if ( ! o . theme ) {
o . theme = $ . mobile . getInheritedTheme ( el , "c" ) ;
}
buttonClass = "ui-btn " ;
buttonClass += ( hover ? "ui-btn-hover-" + o . theme : "" ) ;
buttonClass += ( state ? " ui-btn-" + state + "-" + o . theme : "" ) ;
buttonClass += o . shadow ? " ui-shadow" : "" ;
buttonClass += o . corners ? " ui-btn-corner-all" : "" ;
if ( o . mini !== undefined ) {
// Used to control styling in headers/footers, where buttons default to `mini` style.
buttonClass += o . mini === true ? " ui-mini" : " ui-fullsize" ;
}
if ( o . inline !== undefined ) {
// Used to control styling in headers/footers, where buttons default to `inline` style.
buttonClass += o . inline === true ? " ui-btn-inline" : " ui-btn-block" ;
}
if ( o . icon ) {
o . icon = "ui-icon-" + o . icon ;
o . iconpos = o . iconpos || "left" ;
iconClass = "ui-icon " + o . icon ;
if ( o . iconshadow ) {
iconClass += " ui-icon-shadow" ;
}
}
if ( o . iconpos ) {
buttonClass += " ui-btn-icon-" + o . iconpos ;
if ( o . iconpos === "notext" && ! el . attr ( "title" ) ) {
el . attr ( "title" , el . getEncodedText ( ) ) ;
}
}
if ( 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 = {
hover : hover ,
state : state ,
bcls : buttonClass ,
outer : e ,
inner : buttonInner ,
text : buttonText ,
icon : buttonIcon
} ;
$ . data ( e , 'buttonElements' , buttonElements ) ;
$ . data ( buttonInner , 'buttonElements' , buttonElements ) ;
$ . data ( buttonText , 'buttonElements' , buttonElements ) ;
if ( buttonIcon ) {
$ . data ( buttonIcon , 'buttonElements' , buttonElements ) ;
}
}
return this ;
} ;
$ . fn . buttonMarkup . defaults = {
corners : true ,
shadow : true ,
iconshadow : true ,
wrapperEls : "span"
} ;
function closestEnabledButton ( element ) {
var cname ;
while ( element ) {
// Note that we check for typeof className below because the element we
// handed could be in an SVG DOM where className on SVG elements is defined to
// be of a different type (SVGAnimatedString). We only operate on HTML DOM
// elements, so we look for plain "string".
cname = ( typeof element . className === 'string' ) && ( element . className + ' ' ) ;
if ( cname && cname . indexOf ( "ui-btn " ) > - 1 && cname . indexOf ( "ui-disabled " ) < 0 ) {
break ;
}
element = element . parentNode ;
}
return element ;
}
function updateButtonClass ( $btn , classToRemove , classToAdd , hover , state ) {
var buttonElements = $ . data ( $btn [ 0 ] , "buttonElements" ) ;
$btn . removeClass ( classToRemove ) . addClass ( classToAdd ) ;
if ( buttonElements ) {
buttonElements . bcls = $ ( document . createElement ( "div" ) )
. addClass ( buttonElements . bcls + " " + classToAdd )
. removeClass ( classToRemove )
. attr ( "class" ) ;
if ( hover !== undefined ) {
buttonElements . hover = hover ;
}
buttonElements . state = state ;
}
}
var attachEvents = function ( ) {
var hoverDelay = $ . mobile . buttonMarkup . hoverDelay , hov , foc ;
$ . mobile . document . bind ( {
"vmousedown vmousecancel vmouseup vmouseover vmouseout focus blur scrollstart" : function ( event ) {
var theme ,
$btn = $ ( closestEnabledButton ( event . target ) ) ,
isTouchEvent = event . originalEvent && /^touch/ . test ( event . originalEvent . type ) ,
evt = event . type ;
if ( $btn . length ) {
theme = $btn . attr ( "data-" + $ . mobile . ns + "theme" ) ;
if ( evt === "vmousedown" ) {
if ( isTouchEvent ) {
// Use a short delay to determine if the user is scrolling before highlighting
hov = setTimeout ( function ( ) {
updateButtonClass ( $btn , "ui-btn-up-" + theme , "ui-btn-down-" + theme , undefined , "down" ) ;
} , hoverDelay ) ;
} else {
updateButtonClass ( $btn , "ui-btn-up-" + theme , "ui-btn-down-" + theme , undefined , "down" ) ;
}
} else if ( evt === "vmousecancel" || evt === "vmouseup" ) {
updateButtonClass ( $btn , "ui-btn-down-" + theme , "ui-btn-up-" + theme , undefined , "up" ) ;
} else if ( evt === "vmouseover" || evt === "focus" ) {
if ( isTouchEvent ) {
// Use a short delay to determine if the user is scrolling before highlighting
foc = setTimeout ( function ( ) {
updateButtonClass ( $btn , "ui-btn-up-" + theme , "ui-btn-hover-" + theme , true , "" ) ;
} , hoverDelay ) ;
} else {
updateButtonClass ( $btn , "ui-btn-up-" + theme , "ui-btn-hover-" + theme , true , "" ) ;
}
} else if ( evt === "vmouseout" || evt === "blur" || evt === "scrollstart" ) {
updateButtonClass ( $btn , "ui-btn-hover-" + theme + " ui-btn-down-" + theme , "ui-btn-up-" + theme , false , "up" ) ;
if ( hov ) {
clearTimeout ( hov ) ;
}
if ( foc ) {
clearTimeout ( foc ) ;
}
}
}
} ,
"focusin focus" : function ( event ) {
$ ( closestEnabledButton ( event . target ) ) . addClass ( $ . mobile . focusClass ) ;
} ,
"focusout blur" : function ( event ) {
$ ( closestEnabledButton ( event . target ) ) . removeClass ( $ . mobile . focusClass ) ;
}
} ) ;
attachEvents = null ;
} ;
//links in bars, or those with data-role become buttons
//auto self-init widgets
$ . mobile . document . bind ( "pagecreate create" , function ( e ) {
$ ( ":jqmData(role='button'), .ui-bar > a, .ui-header > a, .ui-footer > a, .ui-bar > :jqmData(role='controlgroup') > a" , e . target )
. jqmEnhanceable ( )
. not ( "button, input, .ui-btn, :jqmData(role='none'), :jqmData(role='nojs')" )
. buttonMarkup ( ) ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ . widget ( "mobile.collapsible" , $ . mobile . widget , {
options : {
expandCueText : " click to expand contents" ,
collapseCueText : " click to collapse contents" ,
collapsed : true ,
heading : "h1,h2,h3,h4,h5,h6,legend" ,
collapsedIcon : "plus" ,
expandedIcon : "minus" ,
iconpos : "left" ,
theme : null ,
contentTheme : null ,
inset : true ,
corners : true ,
mini : false ,
initSelector : ":jqmData(role='collapsible')"
} ,
_create : function ( ) {
var $el = this . element ,
o = this . options ,
collapsible = $el . addClass ( "ui-collapsible" ) ,
collapsibleHeading = $el . children ( o . heading ) . first ( ) ,
collapsibleContent = collapsible . wrapInner ( "<div class='ui-collapsible-content'></div>" ) . children ( ".ui-collapsible-content" ) ,
collapsibleSet = $el . closest ( ":jqmData(role='collapsible-set')" ) . addClass ( "ui-collapsible-set" ) ,
collapsibleClasses = "" ;
// Replace collapsibleHeading if it's a legend
if ( collapsibleHeading . is ( "legend" ) ) {
collapsibleHeading = $ ( "<div role='heading'>" + collapsibleHeading . html ( ) + "</div>" ) . insertBefore ( collapsibleHeading ) ;
collapsibleHeading . next ( ) . remove ( ) ;
}
// If we are in a collapsible set
if ( collapsibleSet . length ) {
// Inherit the theme from collapsible-set
if ( ! o . theme ) {
o . theme = collapsibleSet . jqmData ( "theme" ) || $ . mobile . getInheritedTheme ( collapsibleSet , "c" ) ;
}
// Inherit the content-theme from collapsible-set
if ( ! o . contentTheme ) {
o . contentTheme = collapsibleSet . jqmData ( "content-theme" ) ;
}
// Get the preference for collapsed icon in the set, but override with data- attribute on the individual collapsible
o . collapsedIcon = $el . jqmData ( "collapsed-icon" ) || collapsibleSet . jqmData ( "collapsed-icon" ) || o . collapsedIcon ;
// Get the preference for expanded icon in the set, but override with data- attribute on the individual collapsible
o . expandedIcon = $el . jqmData ( "expanded-icon" ) || collapsibleSet . jqmData ( "expanded-icon" ) || o . expandedIcon ;
// Gets the preference icon position in the set, but override with data- attribute on the individual collapsible
o . iconpos = $el . jqmData ( "iconpos" ) || collapsibleSet . jqmData ( "iconpos" ) || o . iconpos ;
// Inherit the preference for inset from collapsible-set or set the default value to ensure equalty within a set
if ( collapsibleSet . jqmData ( "inset" ) !== undefined ) {
o . inset = collapsibleSet . jqmData ( "inset" ) ;
} else {
o . inset = true ;
}
// Set corners for individual collapsibles to false when in a collapsible-set
o . corners = false ;
// Gets the preference for mini in the set
if ( ! o . mini ) {
o . mini = collapsibleSet . jqmData ( "mini" ) ;
}
} else {
// get inherited theme if not a set and no theme has been set
if ( ! o . theme ) {
o . theme = $ . mobile . getInheritedTheme ( $el , "c" ) ;
}
}
if ( ! ! o . inset ) {
collapsibleClasses += " ui-collapsible-inset" ;
if ( ! ! o . corners ) {
collapsibleClasses += " ui-corner-all" ;
}
}
if ( o . contentTheme ) {
collapsibleClasses += " ui-collapsible-themed-content" ;
collapsibleContent . addClass ( "ui-body-" + o . contentTheme ) ;
}
if ( collapsibleClasses !== "" ) {
collapsible . addClass ( collapsibleClasses ) ;
}
collapsibleHeading
//drop heading in before content
. insertBefore ( collapsibleContent )
//modify markup & attributes
. addClass ( "ui-collapsible-heading" )
. append ( "<span class='ui-collapsible-heading-status'></span>" )
. wrapInner ( "<a href='#' class='ui-collapsible-heading-toggle'></a>" )
. find ( "a" )
. first ( )
. buttonMarkup ( {
shadow : false ,
corners : false ,
iconpos : o . iconpos ,
icon : o . collapsedIcon ,
mini : o . mini ,
theme : o . theme
} ) ;
//events
collapsible
. bind ( "expand collapse" , function ( event ) {
if ( ! event . isDefaultPrevented ( ) ) {
var $this = $ ( this ) ,
isCollapse = ( event . type === "collapse" ) ;
event . preventDefault ( ) ;
collapsibleHeading
. toggleClass ( "ui-collapsible-heading-collapsed" , isCollapse )
. find ( ".ui-collapsible-heading-status" )
. text ( isCollapse ? o . expandCueText : o . collapseCueText )
. end ( )
. find ( ".ui-icon" )
. toggleClass ( "ui-icon-" + o . expandedIcon , ! isCollapse )
// logic or cause same icon for expanded/collapsed state would remove the ui-icon-class
. toggleClass ( "ui-icon-" + o . collapsedIcon , ( isCollapse || o . expandedIcon === o . collapsedIcon ) )
. end ( )
. find ( "a" ) . first ( ) . removeClass ( $ . mobile . activeBtnClass ) ;
$this . toggleClass ( "ui-collapsible-collapsed" , isCollapse ) ;
collapsibleContent . toggleClass ( "ui-collapsible-content-collapsed" , isCollapse ) . attr ( "aria-hidden" , isCollapse ) ;
collapsibleContent . trigger ( "updatelayout" ) ;
}
} )
. trigger ( o . collapsed ? "collapse" : "expand" ) ;
collapsibleHeading
. bind ( "tap" , function ( event ) {
collapsibleHeading . find ( "a" ) . first ( ) . addClass ( $ . mobile . activeBtnClass ) ;
} )
. bind ( "click" , function ( event ) {
var type = collapsibleHeading . is ( ".ui-collapsible-heading-collapsed" ) ? "expand" : "collapse" ;
collapsible . trigger ( type ) ;
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
} ) ;
}
} ) ;
//auto self-init widgets
$ . mobile . document . bind ( "pagecreate create" , function ( e ) {
$ . mobile . collapsible . prototype . enhanceWithin ( e . target ) ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ . widget ( "mobile.collapsibleset" , $ . mobile . widget , {
options : {
initSelector : ":jqmData(role='collapsible-set')"
} ,
_create : function ( ) {
var $el = this . element . addClass ( "ui-collapsible-set" ) ,
o = this . options ;
// Inherit the theme from collapsible-set
if ( ! o . theme ) {
o . theme = $ . mobile . getInheritedTheme ( $el , "c" ) ;
}
// Inherit the content-theme from collapsible-set
if ( ! o . contentTheme ) {
o . contentTheme = $el . jqmData ( "content-theme" ) ;
}
// Inherit the corner styling from collapsible-set
if ( ! o . corners ) {
o . corners = $el . jqmData ( "corners" ) ;
}
if ( $el . jqmData ( "inset" ) !== undefined ) {
o . inset = $el . jqmData ( "inset" ) ;
}
o . inset = o . inset !== undefined ? o . inset : true ;
o . corners = o . corners !== undefined ? o . corners : true ;
if ( ! ! o . corners && ! ! o . inset ) {
$el . addClass ( "ui-corner-all" ) ;
}
// Initialize the collapsible set if it's not already initialized
if ( ! $el . jqmData ( "collapsiblebound" ) ) {
$el
. jqmData ( "collapsiblebound" , true )
. bind ( "expand" , function ( event ) {
var closestCollapsible = $ ( event . target )
. closest ( ".ui-collapsible" ) ;
if ( closestCollapsible . parent ( ) . is ( ":jqmData(role='collapsible-set')" ) ) {
closestCollapsible
. siblings ( ".ui-collapsible" )
. trigger ( "collapse" ) ;
}
} ) ;
}
} ,
_init : function ( ) {
var $el = this . element ,
collapsiblesInSet = $el . children ( ":jqmData(role='collapsible')" ) ,
expanded = collapsiblesInSet . filter ( ":jqmData(collapsed='false')" ) ;
this . _refresh ( "true" ) ;
// Because the corners are handled by the collapsible itself and the default state is collapsed
// That was causing https://github.com/jquery/jquery-mobile/issues/4116
expanded . trigger ( "expand" ) ;
} ,
_refresh : function ( create ) {
var collapsiblesInSet = this . element . children ( ":jqmData(role='collapsible')" ) ;
$ . mobile . collapsible . prototype . enhance ( collapsiblesInSet . not ( ".ui-collapsible" ) ) ;
this . _addFirstLastClasses ( collapsiblesInSet , this . _getVisibles ( collapsiblesInSet , create ) , create ) ;
} ,
refresh : function ( ) {
this . _refresh ( false ) ;
}
} ) ;
$ . widget ( "mobile.collapsibleset" , $ . mobile . collapsibleset , $ . mobile . behaviors . addFirstLastClasses ) ;
//auto self-init widgets
$ . mobile . document . bind ( "pagecreate create" , function ( e ) {
$ . mobile . collapsibleset . prototype . enhanceWithin ( e . target ) ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ . widget ( "mobile.navbar" , $ . mobile . widget , {
options : {
iconpos : "top" ,
grid : null ,
initSelector : ":jqmData(role='navbar')"
} ,
_create : function ( ) {
var $navbar = this . element ,
$navbtns = $navbar . find ( "a" ) ,
iconpos = $navbtns . filter ( ":jqmData(icon)" ) . length ?
this . options . iconpos : undefined ;
$navbar . addClass ( "ui-navbar ui-mini" )
. attr ( "role" , "navigation" )
. find ( "ul" )
. jqmEnhanceable ( )
. grid ( { grid : this . options . grid } ) ;
$navbtns . buttonMarkup ( {
corners : false ,
shadow : false ,
inline : true ,
iconpos : iconpos
} ) ;
$navbar . delegate ( "a" , "vclick" , function ( event ) {
if ( ! $ ( event . target ) . hasClass ( "ui-disabled" ) ) {
$navbtns . removeClass ( $ . mobile . activeBtnClass ) ;
$ ( this ) . addClass ( $ . mobile . activeBtnClass ) ;
// The code below is a workaround to fix #1181. We have to see why removeActiveLinkClass() doesn't take care of it.
var activeNavbtn = $ ( this ) ;
$ ( document ) . one ( "pagechange" , function ( event ) {
activeNavbtn . removeClass ( $ . mobile . activeBtnClass ) ;
} ) ;
}
} ) ;
// Buttons in the navbar with ui-state-persist class should regain their active state before page show
$navbar . closest ( ".ui-page" ) . bind ( "pagebeforeshow" , function ( ) {
$navbtns . filter ( ".ui-state-persist" ) . addClass ( $ . mobile . activeBtnClass ) ;
} ) ;
}
} ) ;
//auto self-init widgets
$ . mobile . document . bind ( "pagecreate create" , function ( e ) {
$ . mobile . navbar . prototype . enhanceWithin ( e . target ) ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
//Keeps track of the number of lists per page UID
//This allows support for multiple nested list in the same page
//https://github.com/jquery/jquery-mobile/issues/1617
var listCountPerPage = { } ;
$ . widget ( "mobile.listview" , $ . mobile . widget , {
options : {
theme : null ,
countTheme : "c" ,
headerTheme : "b" ,
dividerTheme : "b" ,
icon : "arrow-r" ,
splitIcon : "arrow-r" ,
splitTheme : "b" ,
corners : true ,
shadow : true ,
inset : false ,
initSelector : ":jqmData(role='listview')"
} ,
_create : function ( ) {
var t = this ,
listviewClasses = "" ;
listviewClasses += t . options . inset ? " ui-listview-inset" : "" ;
if ( ! ! t . options . inset ) {
listviewClasses += t . options . corners ? " ui-corner-all" : "" ;
listviewClasses += t . options . shadow ? " ui-shadow" : "" ;
}
// create listview markup
t . element . addClass ( function ( i , orig ) {
return orig + " ui-listview" + listviewClasses ;
} ) ;
t . refresh ( true ) ;
} ,
// This is a generic utility method for finding the first
// node with a given nodeName. It uses basic DOM traversal
// to be fast and is meant to be a substitute for simple
// $.fn.closest() and $.fn.children() calls on a single
// element. Note that callers must pass both the lowerCase
// and upperCase version of the nodeName they are looking for.
// The main reason for this is that this function will be
// called many times and we want to avoid having to lowercase
// the nodeName from the element every time to ensure we have
// a match. Note that this function lives here for now, but may
// be moved into $.mobile if other components need a similar method.
_findFirstElementByTagName : function ( ele , nextProp , lcName , ucName ) {
var dict = { } ;
dict [ lcName ] = dict [ ucName ] = true ;
while ( ele ) {
if ( dict [ ele . nodeName ] ) {
return ele ;
}
ele = ele [ nextProp ] ;
}
return null ;
} ,
_getChildrenByTagName : function ( ele , lcName , ucName ) {
var results = [ ] ,
dict = { } ;
dict [ lcName ] = dict [ ucName ] = true ;
ele = ele . firstChild ;
while ( ele ) {
if ( dict [ ele . nodeName ] ) {
results . push ( ele ) ;
}
ele = ele . nextSibling ;
}
return $ ( results ) ;
} ,
_addThumbClasses : function ( containers ) {
var i , img , len = containers . length ;
for ( i = 0 ; i < len ; i ++ ) {
img = $ ( this . _findFirstElementByTagName ( containers [ i ] . firstChild , "nextSibling" , "img" , "IMG" ) ) ;
if ( img . length ) {
img . addClass ( "ui-li-thumb" ) ;
$ ( this . _findFirstElementByTagName ( img [ 0 ] . parentNode , "parentNode" , "li" , "LI" ) ) . addClass ( img . is ( ".ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" ) ;
}
}
} ,
refresh : function ( create ) {
this . parentPage = this . element . closest ( ".ui-page" ) ;
this . _createSubPages ( ) ;
var o = this . options ,
$list = this . element ,
self = this ,
dividertheme = $list . jqmData ( "dividertheme" ) || o . dividerTheme ,
listsplittheme = $list . jqmData ( "splittheme" ) ,
listspliticon = $list . jqmData ( "spliticon" ) ,
listicon = $list . jqmData ( "icon" ) ,
li = this . _getChildrenByTagName ( $list [ 0 ] , "li" , "LI" ) ,
ol = ! ! $ . nodeName ( $list [ 0 ] , "ol" ) ,
jsCount = ! $ . support . cssPseudoElement ,
start = $list . attr ( "start" ) ,
itemClassDict = { } ,
item , itemClass , itemTheme ,
a , last , splittheme , counter , startCount , newStartCount , countParent , icon , imgParents , img , linkIcon ;
if ( ol && jsCount ) {
$list . find ( ".ui-li-dec" ) . remove ( ) ;
}
if ( ol ) {
// Check if a start attribute has been set while taking a value of 0 into account
if ( start || start === 0 ) {
if ( ! jsCount ) {
startCount = parseInt ( start , 10 ) - 1 ;
$list . css ( "counter-reset" , "listnumbering " + startCount ) ;
} else {
counter = parseInt ( start , 10 ) ;
}
} else if ( jsCount ) {
counter = 1 ;
}
}
if ( ! o . theme ) {
o . theme = $ . mobile . getInheritedTheme ( this . element , "c" ) ;
}
for ( var pos = 0 , numli = li . length ; pos < numli ; pos ++ ) {
item = li . eq ( pos ) ;
itemClass = "ui-li" ;
// If we're creating the element, we update it regardless
if ( create || ! item . hasClass ( "ui-li" ) ) {
itemTheme = item . jqmData ( "theme" ) || o . theme ;
a = this . _getChildrenByTagName ( item [ 0 ] , "a" , "A" ) ;
var isDivider = ( item . jqmData ( "role" ) === "list-divider" ) ;
if ( a . length && ! isDivider ) {
icon = item . jqmData ( "icon" ) ;
item . buttonMarkup ( {
wrapperEls : "div" ,
shadow : false ,
corners : false ,
iconpos : "right" ,
icon : a . length > 1 || icon === false ? false : icon || listicon || o . icon ,
theme : itemTheme
} ) ;
if ( ( icon !== false ) && ( a . length === 1 ) ) {
item . addClass ( "ui-li-has-arrow" ) ;
}
a . first ( ) . removeClass ( "ui-link" ) . addClass ( "ui-link-inherit" ) ;
if ( a . length > 1 ) {
itemClass += " ui-li-has-alt" ;
last = a . last ( ) ;
splittheme = listsplittheme || last . jqmData ( "theme" ) || o . splitTheme ;
linkIcon = last . jqmData ( "icon" ) ;
last . appendTo ( item )
. attr ( "title" , $ . trim ( last . getEncodedText ( ) ) )
. addClass ( "ui-li-link-alt" )
. empty ( )
. buttonMarkup ( {
shadow : false ,
corners : false ,
theme : itemTheme ,
icon : false ,
iconpos : "notext"
} )
. find ( ".ui-btn-inner" )
. append (
$ ( document . createElement ( "span" ) ) . buttonMarkup ( {
shadow : true ,
corners : true ,
theme : splittheme ,
iconpos : "notext" ,
// link icon overrides list item icon overrides ul element overrides options
icon : linkIcon || icon || listspliticon || o . splitIcon
} )
) ;
}
} else if ( isDivider ) {
itemClass += " ui-li-divider ui-bar-" + ( item . jqmData ( "theme" ) || dividertheme ) ;
item . attr ( "role" , "heading" ) ;
if ( ol ) {
//reset counter when a divider heading is encountered
if ( start || start === 0 ) {
if ( ! jsCount ) {
newStartCount = parseInt ( start , 10 ) - 1 ;
item . css ( "counter-reset" , "listnumbering " + newStartCount ) ;
} else {
counter = parseInt ( start , 10 ) ;
}
} else if ( jsCount ) {
counter = 1 ;
}
}
} else {
itemClass += " ui-li-static ui-btn-up-" + itemTheme ;
}
}
if ( ol && jsCount && itemClass . indexOf ( "ui-li-divider" ) < 0 ) {
countParent = itemClass . indexOf ( "ui-li-static" ) > 0 ? item : item . find ( ".ui-link-inherit" ) ;
countParent . addClass ( "ui-li-jsnumbering" )
. prepend ( "<span class='ui-li-dec'>" + ( counter ++ ) + ". </span>" ) ;
}
// Instead of setting item class directly on the list item and its
// btn-inner at this point in time, push the item into a dictionary
// that tells us what class to set on it so we can do this after this
// processing loop is finished.
if ( ! itemClassDict [ itemClass ] ) {
itemClassDict [ itemClass ] = [ ] ;
}
itemClassDict [ itemClass ] . push ( item [ 0 ] ) ;
}
// Set the appropriate listview item classes on each list item
// and their btn-inner elements. The main reason we didn't do this
// in the for-loop above is because we can eliminate per-item function overhead
// by calling addClass() and children() once or twice afterwards. This
// can give us a significant boost on platforms like WP7.5.
for ( itemClass in itemClassDict ) {
$ ( itemClassDict [ itemClass ] ) . addClass ( itemClass ) . children ( ".ui-btn-inner" ) . addClass ( itemClass ) ;
}
$list . find ( "h1, h2, h3, h4, h5, h6" ) . addClass ( "ui-li-heading" )
. end ( )
. find ( "p, dl" ) . addClass ( "ui-li-desc" )
. end ( )
. find ( ".ui-li-aside" ) . each ( function ( ) {
var $this = $ ( this ) ;
$this . prependTo ( $this . parent ( ) ) ; //shift aside to front for css float
} )
. end ( )
. find ( ".ui-li-count" ) . each ( function ( ) {
$ ( this ) . closest ( "li" ) . addClass ( "ui-li-has-count" ) ;
} ) . addClass ( "ui-btn-up-" + ( $list . jqmData ( "counttheme" ) || this . options . countTheme ) + " ui-btn-corner-all" ) ;
// The idea here is to look at the first image in the list item
// itself, and any .ui-link-inherit element it may contain, so we
// can place the appropriate classes on the image and list item.
// Note that we used to use something like:
//
// li.find(">img:eq(0), .ui-link-inherit>img:eq(0)").each( ... );
//
// But executing a find() like that on Windows Phone 7.5 took a
// really long time. Walking things manually with the code below
// allows the 400 listview item page to load in about 3 seconds as
// opposed to 30 seconds.
this . _addThumbClasses ( li ) ;
this . _addThumbClasses ( $list . find ( ".ui-link-inherit" ) ) ;
this . _addFirstLastClasses ( li , this . _getVisibles ( li , create ) , create ) ;
// autodividers binds to this to redraw dividers after the listview refresh
this . _trigger ( "afterrefresh" ) ;
} ,
//create a string for ID/subpage url creation
_idStringEscape : function ( str ) {
return str . replace ( /[^a-zA-Z0-9]/g , '-' ) ;
} ,
_createSubPages : function ( ) {
var parentList = this . element ,
parentPage = parentList . closest ( ".ui-page" ) ,
parentUrl = parentPage . jqmData ( "url" ) ,
parentId = parentUrl || parentPage [ 0 ] [ $ . expando ] ,
parentListId = parentList . attr ( "id" ) ,
o = this . options ,
dns = "data-" + $ . mobile . ns ,
self = this ,
persistentFooterID = parentPage . find ( ":jqmData(role='footer')" ) . jqmData ( "id" ) ,
hasSubPages ;
if ( typeof listCountPerPage [ parentId ] === "undefined" ) {
listCountPerPage [ parentId ] = - 1 ;
}
parentListId = parentListId || ++ listCountPerPage [ parentId ] ;
$ ( parentList . find ( "li>ul, li>ol" ) . toArray ( ) . reverse ( ) ) . each ( function ( i ) {
var self = this ,
list = $ ( this ) ,
listId = list . attr ( "id" ) || parentListId + "-" + i ,
parent = list . parent ( ) ,
nodeElsFull = $ ( list . prevAll ( ) . toArray ( ) . reverse ( ) ) ,
nodeEls = nodeElsFull . length ? nodeElsFull : $ ( "<span>" + $ . trim ( parent . contents ( ) [ 0 ] . nodeValue ) + "</span>" ) ,
title = nodeEls . first ( ) . getEncodedText ( ) , //url limits to first 30 chars of text
id = ( parentUrl || "" ) + "&" + $ . mobile . subPageUrlKey + "=" + listId ,
theme = list . jqmData ( "theme" ) || o . theme ,
countTheme = list . jqmData ( "counttheme" ) || parentList . jqmData ( "counttheme" ) || o . countTheme ,
newPage , anchor ;
//define hasSubPages for use in later removal
hasSubPages = true ;
newPage = list . detach ( )
. wrap ( "<div " + dns + "role='page' " + dns + "url='" + id + "' " + dns + "theme='" + theme + "' " + dns + "count-theme='" + countTheme + "'><div " + dns + "role='content'></div></div>" )
. parent ( )
. before ( "<div " + dns + "role='header' " + dns + "theme='" + o . headerTheme + "'><div class='ui-title'>" + title + "</div></div>" )
. after ( persistentFooterID ? $ ( "<div " + dns + "role='footer' " + dns + "id='" + persistentFooterID + "'>" ) : "" )
. parent ( )
. appendTo ( $ . mobile . pageContainer ) ;
newPage . page ( ) ;
anchor = parent . find ( 'a:first' ) ;
if ( ! anchor . length ) {
anchor = $ ( "<a/>" ) . html ( nodeEls || title ) . prependTo ( parent . empty ( ) ) ;
}
anchor . attr ( "href" , "#" + id ) ;
} ) . listview ( ) ;
// on pagehide, remove any nested pages along with the parent page, as long as they aren't active
// and aren't embedded
if ( hasSubPages &&
parentPage . is ( ":jqmData(external-page='true')" ) &&
parentPage . data ( "mobile-page" ) . options . domCache === false ) {
var newRemove = function ( e , ui ) {
var nextPage = ui . nextPage , npURL ,
prEvent = new $ . Event ( "pageremove" ) ;
if ( ui . nextPage ) {
npURL = nextPage . jqmData ( "url" ) ;
if ( npURL . indexOf ( parentUrl + "&" + $ . mobile . subPageUrlKey ) !== 0 ) {
self . childPages ( ) . remove ( ) ;
parentPage . trigger ( prEvent ) ;
if ( ! prEvent . isDefaultPrevented ( ) ) {
parentPage . removeWithDependents ( ) ;
}
}
}
} ;
// unbind the original page remove and replace with our specialized version
parentPage
. unbind ( "pagehide.remove" )
. bind ( "pagehide.remove" , newRemove ) ;
}
} ,
// TODO sort out a better way to track sub pages of the listview this is brittle
childPages : function ( ) {
var parentUrl = this . parentPage . jqmData ( "url" ) ;
return $ ( ":jqmData(url^='" + parentUrl + "&" + $ . mobile . subPageUrlKey + "')" ) ;
}
} ) ;
$ . widget ( "mobile.listview" , $ . mobile . listview , $ . mobile . behaviors . addFirstLastClasses ) ;
//auto self-init widgets
$ . mobile . document . bind ( "pagecreate create" , function ( e ) {
$ . mobile . listview . prototype . enhanceWithin ( e . target ) ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ . mobile . listview . prototype . options . autodividers = false ;
$ . mobile . listview . prototype . options . autodividersSelector = function ( elt ) {
// look for the text in the given element
var text = $ . trim ( elt . text ( ) ) || null ;
if ( ! text ) {
return null ;
}
// create the text for the divider (first uppercased letter)
text = text . slice ( 0 , 1 ) . toUpperCase ( ) ;
return text ;
} ;
$ . mobile . document . delegate ( "ul,ol" , "listviewcreate" , function ( ) {
var list = $ ( this ) ,
listview = list . data ( "mobile-listview" ) ;
if ( ! listview || ! listview . options . autodividers ) {
return ;
}
var replaceDividers = function ( ) {
list . find ( "li:jqmData(role='list-divider')" ) . remove ( ) ;
var lis = list . find ( 'li' ) ,
lastDividerText = null , li , dividerText ;
for ( var i = 0 ; i < lis . length ; i ++ ) {
li = lis [ i ] ;
dividerText = listview . options . autodividersSelector ( $ ( li ) ) ;
if ( dividerText && lastDividerText !== dividerText ) {
var divider = document . createElement ( 'li' ) ;
divider . appendChild ( document . createTextNode ( dividerText ) ) ;
divider . setAttribute ( 'data-' + $ . mobile . ns + 'role' , 'list-divider' ) ;
li . parentNode . insertBefore ( divider , li ) ;
}
lastDividerText = dividerText ;
}
} ;
var afterListviewRefresh = function ( ) {
list . unbind ( 'listviewafterrefresh' , afterListviewRefresh ) ;
replaceDividers ( ) ;
listview . refresh ( ) ;
list . bind ( 'listviewafterrefresh' , afterListviewRefresh ) ;
} ;
afterListviewRefresh ( ) ;
} ) ;
} ) ( jQuery ) ;
/ *
* "checkboxradio" plugin
* /
( function ( $ , undefined ) {
$ . widget ( "mobile.checkboxradio" , $ . mobile . widget , {
options : {
theme : null ,
mini : false ,
initSelector : "input[type='checkbox'],input[type='radio']"
} ,
_create : function ( ) {
var self = this ,
input = this . element ,
o = this . options ,
inheritAttr = function ( input , dataAttr ) {
return input . jqmData ( dataAttr ) || input . closest ( "form, fieldset" ) . jqmData ( dataAttr ) ;
} ,
// NOTE: Windows Phone could not find the label through a selector
// filter works though.
parentLabel = $ ( input ) . closest ( "label" ) ,
label = parentLabel . length ? parentLabel : $ ( input ) . closest ( "form, fieldset, :jqmData(role='page'), :jqmData(role='dialog')" ) . find ( "label" ) . filter ( "[for='" + input [ 0 ] . id + "']" ) . first ( ) ,
inputtype = input [ 0 ] . type ,
mini = inheritAttr ( input , "mini" ) || o . mini ,
checkedState = inputtype + "-on" ,
uncheckedState = inputtype + "-off" ,
iconpos = inheritAttr ( input , "iconpos" ) ,
checkedClass = "ui-" + checkedState ,
uncheckedClass = "ui-" + uncheckedState ;
if ( inputtype !== "checkbox" && inputtype !== "radio" ) {
return ;
}
// Expose for other methods
$ . extend ( this , {
label : label ,
inputtype : inputtype ,
checkedClass : checkedClass ,
uncheckedClass : uncheckedClass ,
checkedicon : checkedState ,
uncheckedicon : uncheckedState
} ) ;
// If there's no selected theme check the data attr
if ( ! o . theme ) {
o . theme = $ . mobile . getInheritedTheme ( this . element , "c" ) ;
}
label . buttonMarkup ( {
theme : o . theme ,
icon : uncheckedState ,
shadow : false ,
mini : mini ,
iconpos : iconpos
} ) ;
// Wrap the input + label in a div
var wrapper = document . createElement ( 'div' ) ;
wrapper . className = 'ui-' + inputtype ;
input . add ( label ) . wrapAll ( wrapper ) ;
label . bind ( {
vmouseover : function ( event ) {
if ( $ ( this ) . parent ( ) . is ( ".ui-disabled" ) ) {
event . stopPropagation ( ) ;
}
} ,
vclick : function ( event ) {
if ( input . is ( ":disabled" ) ) {
event . preventDefault ( ) ;
return ;
}
self . _cacheVals ( ) ;
input . prop ( "checked" , inputtype === "radio" && true || ! input . prop ( "checked" ) ) ;
// trigger click handler's bound directly to the input as a substitute for
// how label clicks behave normally in the browsers
// TODO: it would be nice to let the browser's handle the clicks and pass them
// through to the associate input. we can swallow that click at the parent
// wrapper element level
input . triggerHandler ( 'click' ) ;
// Input set for common radio buttons will contain all the radio
// buttons, but will not for checkboxes. clearing the checked status
// of other radios ensures the active button state is applied properly
self . _getInputSet ( ) . not ( input ) . prop ( "checked" , false ) ;
self . _updateAll ( ) ;
return false ;
}
} ) ;
input
. bind ( {
vmousedown : function ( ) {
self . _cacheVals ( ) ;
} ,
vclick : function ( ) {
var $this = $ ( this ) ;
// Adds checked attribute to checked input when keyboard is used
if ( $this . is ( ":checked" ) ) {
$this . prop ( "checked" , true ) ;
self . _getInputSet ( ) . not ( $this ) . prop ( "checked" , false ) ;
} else {
$this . prop ( "checked" , false ) ;
}
self . _updateAll ( ) ;
} ,
focus : function ( ) {
label . addClass ( $ . mobile . focusClass ) ;
} ,
blur : function ( ) {
label . removeClass ( $ . mobile . focusClass ) ;
}
} ) ;
if ( this . _handleFormReset ) {
this . _handleFormReset ( ) ;
}
this . refresh ( ) ;
} ,
_cacheVals : function ( ) {
this . _getInputSet ( ) . each ( function ( ) {
$ ( this ) . jqmData ( "cacheVal" , this . checked ) ;
} ) ;
} ,
//returns either a set of radios with the same name attribute, or a single checkbox
_getInputSet : function ( ) {
if ( this . inputtype === "checkbox" ) {
return this . element ;
}
return this . element . closest ( "form, :jqmData(role='page'), :jqmData(role='dialog')" )
. find ( "input[name='" + this . element [ 0 ] . name + "'][type='" + this . inputtype + "']" ) ;
} ,
_updateAll : function ( ) {
var self = this ;
this . _getInputSet ( ) . each ( function ( ) {
var $this = $ ( this ) ;
if ( this . checked || self . inputtype === "checkbox" ) {
$this . trigger ( "change" ) ;
}
} )
. checkboxradio ( "refresh" ) ;
} ,
_reset : function ( ) {
this . refresh ( ) ;
} ,
refresh : function ( ) {
var input = this . element [ 0 ] ,
active = " " + $ . mobile . activeBtnClass ,
checkedClass = this . checkedClass + ( this . element . parents ( ".ui-controlgroup-horizontal" ) . length ? active : "" ) ,
label = this . label ;
if ( input . checked ) {
label . removeClass ( this . uncheckedClass + active ) . addClass ( checkedClass ) . buttonMarkup ( { icon : this . checkedicon } ) ;
} else {
label . removeClass ( checkedClass ) . addClass ( this . uncheckedClass ) . buttonMarkup ( { icon : this . uncheckedicon } ) ;
}
if ( input . disabled ) {
this . disable ( ) ;
} else {
this . enable ( ) ;
}
} ,
disable : function ( ) {
this . element . prop ( "disabled" , true ) . parent ( ) . addClass ( "ui-disabled" ) ;
} ,
enable : function ( ) {
this . element . prop ( "disabled" , false ) . parent ( ) . removeClass ( "ui-disabled" ) ;
}
} ) ;
$ . widget ( "mobile.checkboxradio" , $ . mobile . checkboxradio , $ . mobile . behaviors . formReset ) ;
//auto self-init widgets
$ . mobile . document . bind ( "pagecreate create" , function ( e ) {
$ . mobile . checkboxradio . prototype . enhanceWithin ( e . target , true ) ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ . widget ( "mobile.button" , $ . mobile . widget , {
options : {
theme : null ,
icon : null ,
iconpos : null ,
corners : true ,
shadow : true ,
iconshadow : true ,
inline : null ,
mini : null ,
initSelector : "button, [type='button'], [type='submit'], [type='reset']"
} ,
_create : function ( ) {
var $el = this . element ,
$button ,
// create a copy of this.options we can pass to buttonMarkup
o = ( function ( tdo ) {
var key , ret = { } ;
for ( key in tdo ) {
if ( tdo [ key ] !== null && key !== "initSelector" ) {
ret [ key ] = tdo [ key ] ;
}
}
return ret ;
} ) ( this . options ) ,
classes = "" ,
$buttonPlaceholder ;
// if this is a link, check if it's been enhanced and, if not, use the right function
if ( $el [ 0 ] . tagName === "A" ) {
if ( ! $el . hasClass ( "ui-btn" ) ) {
$el . buttonMarkup ( ) ;
}
return ;
}
// get the inherited theme
// TODO centralize for all widgets
if ( ! this . options . theme ) {
this . options . theme = $ . mobile . getInheritedTheme ( this . element , "c" ) ;
}
// TODO: Post 1.1--once we have time to test thoroughly--any classes manually applied to the original element should be carried over to the enhanced element, with an `-enhanced` suffix. See https://github.com/jquery/jquery-mobile/issues/3577
/ * i f ( $ e l [ 0 ] . c l a s s N a m e . l e n g t h ) {
classes = $el [ 0 ] . className ;
} * /
if ( ! ! ~ $el [ 0 ] . className . indexOf ( "ui-btn-left" ) ) {
classes = "ui-btn-left" ;
}
if ( ! ! ~ $el [ 0 ] . className . indexOf ( "ui-btn-right" ) ) {
classes = "ui-btn-right" ;
}
if ( $el . attr ( "type" ) === "submit" || $el . attr ( "type" ) === "reset" ) {
classes ? classes += " ui-submit" : classes = "ui-submit" ;
}
$ ( "label[for='" + $el . attr ( "id" ) + "']" ) . addClass ( "ui-submit" ) ;
// Add ARIA role
this . button = $ ( "<div></div>" )
[ $el . html ( ) ? "html" : "text" ] ( $el . html ( ) || $el . val ( ) )
. insertBefore ( $el )
. buttonMarkup ( o )
. addClass ( classes )
. append ( $el . addClass ( "ui-btn-hidden" ) ) ;
$button = this . button ;
$el . bind ( {
focus : function ( ) {
$button . addClass ( $ . mobile . focusClass ) ;
} ,
blur : function ( ) {
$button . removeClass ( $ . mobile . focusClass ) ;
}
} ) ;
this . refresh ( ) ;
} ,
_setOption : function ( key , value ) {
var op = { } ;
op [ key ] = value ;
if ( key !== "initSelector" ) {
this . button . buttonMarkup ( op ) ;
// Record the option change in the options and in the DOM data-* attributes
this . element . attr ( "data-" + ( $ . mobile . ns || "" ) + ( key . replace ( /([A-Z])/ , "-$1" ) . toLowerCase ( ) ) , value ) ;
}
this . _super ( "_setOption" , key , value ) ;
} ,
enable : function ( ) {
this . element . attr ( "disabled" , false ) ;
this . button . removeClass ( "ui-disabled" ) . attr ( "aria-disabled" , false ) ;
return this . _setOption ( "disabled" , false ) ;
} ,
disable : function ( ) {
this . element . attr ( "disabled" , true ) ;
this . button . addClass ( "ui-disabled" ) . attr ( "aria-disabled" , true ) ;
return this . _setOption ( "disabled" , true ) ;
} ,
refresh : function ( ) {
var $el = this . element ;
if ( $el . prop ( "disabled" ) ) {
this . disable ( ) ;
} else {
this . enable ( ) ;
}
// Grab the button's text element from its implementation-independent data item
$ ( this . button . data ( 'buttonElements' ) . text ) [ $el . html ( ) ? "html" : "text" ] ( $el . html ( ) || $el . val ( ) ) ;
}
} ) ;
//auto self-init widgets
$ . mobile . document . bind ( "pagecreate create" , function ( e ) {
$ . mobile . button . prototype . enhanceWithin ( e . target , true ) ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ . widget ( "mobile.controlgroup" , $ . mobile . widget , {
options : {
shadow : false ,
corners : true ,
excludeInvisible : true ,
type : "vertical" ,
mini : false ,
initSelector : ":jqmData(role='controlgroup')"
} ,
_create : function ( ) {
var $el = this . element ,
ui = {
inner : $ ( "<div class='ui-controlgroup-controls'></div>" ) ,
legend : $ ( "<div role='heading' class='ui-controlgroup-label'></div>" )
} ,
grouplegend = $el . children ( "legend" ) ,
self = this ;
// Apply the proto
$el . wrapInner ( ui . inner ) ;
if ( grouplegend . length ) {
ui . legend . append ( grouplegend ) . insertBefore ( $el . children ( 0 ) ) ;
}
$el . addClass ( "ui-corner-all ui-controlgroup" ) ;
$ . extend ( this , {
_initialRefresh : true
} ) ;
$ . each ( this . options , function ( key , value ) {
// Cause initial options to be applied by their handler by temporarily setting the option to undefined
// - the handler then sets it to the initial value
self . options [ key ] = undefined ;
self . _setOption ( key , value , true ) ;
} ) ;
} ,
_init : function ( ) {
this . refresh ( ) ;
} ,
_setOption : function ( key , value ) {
var setter = "_set" + key . charAt ( 0 ) . toUpperCase ( ) + key . slice ( 1 ) ;
if ( this [ setter ] !== undefined ) {
this [ setter ] ( value ) ;
}
this . _super ( key , value ) ;
this . element . attr ( "data-" + ( $ . mobile . ns || "" ) + ( key . replace ( /([A-Z])/ , "-$1" ) . toLowerCase ( ) ) , value ) ;
} ,
_setType : function ( value ) {
this . element
. removeClass ( "ui-controlgroup-horizontal ui-controlgroup-vertical" )
. addClass ( "ui-controlgroup-" + value ) ;
this . refresh ( ) ;
} ,
_setCorners : function ( value ) {
this . element . toggleClass ( "ui-corner-all" , value ) ;
} ,
_setShadow : function ( value ) {
this . element . toggleClass ( "ui-shadow" , value ) ;
} ,
_setMini : function ( value ) {
this . element . toggleClass ( "ui-mini" , value ) ;
} ,
container : function ( ) {
return this . element . children ( ".ui-controlgroup-controls" ) ;
} ,
refresh : function ( ) {
var els = this . element . find ( ".ui-btn" ) . not ( ".ui-slider-handle" ) ,
create = this . _initialRefresh ;
if ( $ . mobile . checkboxradio ) {
this . element . find ( ":mobile-checkboxradio" ) . checkboxradio ( "refresh" ) ;
}
this . _addFirstLastClasses ( els , this . options . excludeInvisible ? this . _getVisibles ( els , create ) : els , create ) ;
this . _initialRefresh = false ;
}
} ) ;
$ . widget ( "mobile.controlgroup" , $ . mobile . controlgroup , $ . mobile . behaviors . addFirstLastClasses ) ;
// TODO: Implement a mechanism to allow widgets to become enhanced in the
// correct order when their correct enhancement depends on other widgets in
// the page being correctly enhanced already.
//
// For now, we wait until dom-ready to attach the controlgroup's enhancement
// hook, because by that time, all the other widgets' enhancement hooks should
// already be in place, ensuring that all widgets that need to be grouped will
// already have been enhanced by the time the controlgroup is created.
$ ( function ( ) {
$ . mobile . document . bind ( "pagecreate create" , function ( e ) {
$ . mobile . controlgroup . prototype . enhanceWithin ( e . target , true ) ;
} ) ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ ( document ) . bind ( "pagecreate create" , function ( e ) {
//links within content areas, tests included with page
$ ( e . target )
. find ( "a" )
. jqmEnhanceable ( )
. not ( ".ui-btn, .ui-link-inherit, :jqmData(role='none'), :jqmData(role='nojs')" )
. addClass ( "ui-link" ) ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
function fitSegmentInsideSegment ( winSize , segSize , offset , desired ) {
var ret = desired ;
if ( winSize < segSize ) {
// Center segment if it's bigger than the window
ret = offset + ( winSize - segSize ) / 2 ;
} else {
// Otherwise center it at the desired coordinate while keeping it completely inside the window
ret = Math . min ( Math . max ( offset , desired - segSize / 2 ) , offset + winSize - segSize ) ;
}
return ret ;
}
function windowCoords ( ) {
var $win = $ . mobile . window ;
return {
x : $win . scrollLeft ( ) ,
y : $win . scrollTop ( ) ,
cx : ( window . innerWidth || $win . width ( ) ) ,
cy : ( window . innerHeight || $win . height ( ) )
} ;
}
$ . widget ( "mobile.popup" , $ . mobile . widget , {
options : {
theme : null ,
overlayTheme : null ,
shadow : true ,
corners : true ,
transition : "none" ,
positionTo : "origin" ,
tolerance : null ,
initSelector : ":jqmData(role='popup')" ,
closeLinkSelector : "a:jqmData(rel='back')" ,
closeLinkEvents : "click.popup" ,
navigateEvents : "navigate.popup" ,
closeEvents : "navigate.popup pagebeforechange.popup" ,
dismissible : true ,
// NOTE Windows Phone 7 has a scroll position caching issue that
// requires us to disable popup history management by default
// https://github.com/jquery/jquery-mobile/issues/4784
//
// NOTE this option is modified in _create!
history : ! $ . mobile . browser . oldIE
} ,
_eatEventAndClose : function ( e ) {
e . preventDefault ( ) ;
e . stopImmediatePropagation ( ) ;
if ( this . options . dismissible ) {
this . close ( ) ;
}
return false ;
} ,
// Make sure the screen size is increased beyond the page height if the popup's causes the document to increase in height
_resizeScreen : function ( ) {
var popupHeight = this . _ui . container . outerHeight ( true ) ;
this . _ui . screen . removeAttr ( "style" ) ;
if ( popupHeight > this . _ui . screen . height ( ) ) {
this . _ui . screen . height ( popupHeight ) ;
}
} ,
_handleWindowKeyUp : function ( e ) {
if ( this . _isOpen && e . keyCode === $ . mobile . keyCode . ESCAPE ) {
return this . _eatEventAndClose ( e ) ;
}
} ,
_expectResizeEvent : function ( ) {
var winCoords = windowCoords ( ) ;
if ( this . _resizeData ) {
if ( winCoords . x === this . _resizeData . winCoords . x &&
winCoords . y === this . _resizeData . winCoords . y &&
winCoords . cx === this . _resizeData . winCoords . cx &&
winCoords . cy === this . _resizeData . winCoords . cy ) {
// timeout not refreshed
return false ;
} else {
// clear existing timeout - it will be refreshed below
clearTimeout ( this . _resizeData . timeoutId ) ;
}
}
this . _resizeData = {
timeoutId : setTimeout ( $ . proxy ( this , "_resizeTimeout" ) , 200 ) ,
winCoords : winCoords
} ;
return true ;
} ,
_resizeTimeout : function ( ) {
if ( this . _isOpen ) {
if ( ! this . _expectResizeEvent ( ) ) {
if ( this . _ui . container . hasClass ( "ui-popup-hidden" ) ) {
// effectively rapid-open the popup while leaving the screen intact
this . _ui . container . removeClass ( "ui-popup-hidden" ) ;
this . reposition ( { positionTo : "window" } ) ;
this . _ignoreResizeEvents ( ) ;
}
this . _resizeScreen ( ) ;
this . _resizeData = null ;
this . _orientationchangeInProgress = false ;
}
} else {
this . _resizeData = null ;
this . _orientationchangeInProgress = false ;
}
} ,
_ignoreResizeEvents : function ( ) {
var self = this ;
if ( this . _ignoreResizeTo ) {
clearTimeout ( this . _ignoreResizeTo ) ;
}
this . _ignoreResizeTo = setTimeout ( function ( ) { self . _ignoreResizeTo = 0 ; } , 1000 ) ;
} ,
_handleWindowResize : function ( e ) {
if ( this . _isOpen && this . _ignoreResizeTo === 0 ) {
if ( ( this . _expectResizeEvent ( ) || this . _orientationchangeInProgress ) &&
! this . _ui . container . hasClass ( "ui-popup-hidden" ) ) {
// effectively rapid-close the popup while leaving the screen intact
this . _ui . container
. addClass ( "ui-popup-hidden" )
. removeAttr ( "style" ) ;
}
}
} ,
_handleWindowOrientationchange : function ( e ) {
if ( ! this . _orientationchangeInProgress && this . _isOpen && this . _ignoreResizeTo === 0 ) {
this . _expectResizeEvent ( ) ;
this . _orientationchangeInProgress = true ;
}
} ,
// When the popup is open, attempting to focus on an element that is not a
// child of the popup will redirect focus to the popup
_handleDocumentFocusIn : function ( e ) {
var tgt = e . target , $tgt , ui = this . _ui ;
if ( ! this . _isOpen ) {
return ;
}
if ( tgt !== ui . container [ 0 ] ) {
$tgt = $ ( e . target ) ;
if ( 0 === $tgt . parents ( ) . filter ( ui . container [ 0 ] ) . length ) {
$ ( document . activeElement ) . one ( "focus" , function ( e ) {
$tgt . blur ( ) ;
} ) ;
ui . focusElement . focus ( ) ;
e . preventDefault ( ) ;
e . stopImmediatePropagation ( ) ;
return false ;
} else if ( ui . focusElement [ 0 ] === ui . container [ 0 ] ) {
ui . focusElement = $tgt ;
}
} else if ( ui . focusElement && ui . focusElement [ 0 ] !== ui . container [ 0 ] ) {
ui . container . blur ( ) ;
ui . focusElement . focus ( ) ;
}
this . _ignoreResizeEvents ( ) ;
} ,
_create : function ( ) {
var ui = {
screen : $ ( "<div class='ui-screen-hidden ui-popup-screen'></div>" ) ,
placeholder : $ ( "<div style='display: none;'><!-- placeholder --></div>" ) ,
container : $ ( "<div class='ui-popup-container ui-popup-hidden'></div>" )
} ,
thisPage = this . element . closest ( ".ui-page" ) ,
myId = this . element . attr ( "id" ) ,
self = this ;
// We need to adjust the history option to be false if there's no AJAX nav.
// We can't do it in the option declarations because those are run before
// it is determined whether there shall be AJAX nav.
this . options . history = this . options . history && $ . mobile . ajaxEnabled && $ . mobile . hashListeningEnabled ;
if ( thisPage . length === 0 ) {
thisPage = $ ( "body" ) ;
}
// define the container for navigation event bindings
// TODO this would be nice at the the mobile widget level
this . options . container = this . options . container || $ . mobile . pageContainer ;
// Apply the proto
thisPage . append ( ui . screen ) ;
ui . container . insertAfter ( ui . screen ) ;
// Leave a placeholder where the element used to be
ui . placeholder . insertAfter ( this . element ) ;
if ( myId ) {
ui . screen . attr ( "id" , myId + "-screen" ) ;
ui . container . attr ( "id" , myId + "-popup" ) ;
ui . placeholder . html ( "<!-- placeholder for " + myId + " -->" ) ;
}
ui . container . append ( this . element ) ;
ui . focusElement = ui . container ;
// Add class to popup element
this . element . addClass ( "ui-popup" ) ;
// Define instance variables
$ . extend ( this , {
_scrollTop : 0 ,
_page : thisPage ,
_ui : ui ,
_fallbackTransition : "" ,
_currentTransition : false ,
_prereqs : null ,
_isOpen : false ,
_tolerance : null ,
_resizeData : null ,
_ignoreResizeTo : 0 ,
_orientationchangeInProgress : false
} ) ;
$ . each ( this . options , function ( key , value ) {
// Cause initial options to be applied by their handler by temporarily setting the option to undefined
// - the handler then sets it to the initial value
self . options [ key ] = undefined ;
self . _setOption ( key , value , true ) ;
} ) ;
ui . screen . bind ( "vclick" , $ . proxy ( this , "_eatEventAndClose" ) ) ;
this . _on ( $ . mobile . window , {
orientationchange : $ . proxy ( this , "_handleWindowOrientationchange" ) ,
resize : $ . proxy ( this , "_handleWindowResize" ) ,
keyup : $ . proxy ( this , "_handleWindowKeyUp" )
} ) ;
this . _on ( $ . mobile . document , {
focusin : $ . proxy ( this , "_handleDocumentFocusIn" )
} ) ;
} ,
_applyTheme : function ( dst , theme , prefix ) {
var classes = ( dst . attr ( "class" ) || "" ) . split ( " " ) ,
alreadyAdded = true ,
currentTheme = null ,
matches ,
themeStr = String ( theme ) ;
while ( classes . length > 0 ) {
currentTheme = classes . pop ( ) ;
matches = ( new RegExp ( "^ui-" + prefix + "-([a-z])$" ) ) . exec ( currentTheme ) ;
if ( matches && matches . length > 1 ) {
currentTheme = matches [ 1 ] ;
break ;
} else {
currentTheme = null ;
}
}
if ( theme !== currentTheme ) {
dst . removeClass ( "ui-" + prefix + "-" + currentTheme ) ;
if ( ! ( theme === null || theme === "none" ) ) {
dst . addClass ( "ui-" + prefix + "-" + themeStr ) ;
}
}
} ,
_setTheme : function ( value ) {
this . _applyTheme ( this . element , value , "body" ) ;
} ,
_setOverlayTheme : function ( value ) {
this . _applyTheme ( this . _ui . screen , value , "overlay" ) ;
if ( this . _isOpen ) {
this . _ui . screen . addClass ( "in" ) ;
}
} ,
_setShadow : function ( value ) {
this . element . toggleClass ( "ui-overlay-shadow" , value ) ;
} ,
_setCorners : function ( value ) {
this . element . toggleClass ( "ui-corner-all" , value ) ;
} ,
_applyTransition : function ( value ) {
this . _ui . container . removeClass ( this . _fallbackTransition ) ;
if ( value && value !== "none" ) {
this . _fallbackTransition = $ . mobile . _maybeDegradeTransition ( value ) ;
if ( this . _fallbackTransition === "none" ) {
this . _fallbackTransition = "" ;
}
this . _ui . container . addClass ( this . _fallbackTransition ) ;
}
} ,
_setTransition : function ( value ) {
if ( ! this . _currentTransition ) {
this . _applyTransition ( value ) ;
}
} ,
_setTolerance : function ( value ) {
var tol = { t : 30 , r : 15 , b : 30 , l : 15 } ;
if ( value !== undefined ) {
var ar = String ( value ) . split ( "," ) ;
$ . each ( ar , function ( idx , val ) { ar [ idx ] = parseInt ( val , 10 ) ; } ) ;
switch ( ar . length ) {
// All values are to be the same
case 1 :
if ( ! isNaN ( ar [ 0 ] ) ) {
tol . t = tol . r = tol . b = tol . l = ar [ 0 ] ;
}
break ;
// The first value denotes top/bottom tolerance, and the second value denotes left/right tolerance
case 2 :
if ( ! isNaN ( ar [ 0 ] ) ) {
tol . t = tol . b = ar [ 0 ] ;
}
if ( ! isNaN ( ar [ 1 ] ) ) {
tol . l = tol . r = ar [ 1 ] ;
}
break ;
// The array contains values in the order top, right, bottom, left
case 4 :
if ( ! isNaN ( ar [ 0 ] ) ) {
tol . t = ar [ 0 ] ;
}
if ( ! isNaN ( ar [ 1 ] ) ) {
tol . r = ar [ 1 ] ;
}
if ( ! isNaN ( ar [ 2 ] ) ) {
tol . b = ar [ 2 ] ;
}
if ( ! isNaN ( ar [ 3 ] ) ) {
tol . l = ar [ 3 ] ;
}
break ;
default :
break ;
}
}
this . _tolerance = tol ;
} ,
_setOption : function ( key , value ) {
var exclusions , setter = "_set" + key . charAt ( 0 ) . toUpperCase ( ) + key . slice ( 1 ) ;
if ( this [ setter ] !== undefined ) {
this [ setter ] ( value ) ;
}
// TODO REMOVE FOR 1.2.1 by moving them out to a default options object
exclusions = [
"initSelector" ,
"closeLinkSelector" ,
"closeLinkEvents" ,
"navigateEvents" ,
"closeEvents" ,
"history" ,
"container"
] ;
$ . mobile . widget . prototype . _setOption . apply ( this , arguments ) ;
if ( $ . inArray ( key , exclusions ) === - 1 ) {
// Record the option change in the options and in the DOM data-* attributes
this . element . attr ( "data-" + ( $ . mobile . ns || "" ) + ( key . replace ( /([A-Z])/ , "-$1" ) . toLowerCase ( ) ) , value ) ;
}
} ,
// Try and center the overlay over the given coordinates
_placementCoords : function ( desired ) {
// rectangle within which the popup must fit
var
winCoords = windowCoords ( ) ,
rc = {
x : this . _tolerance . l ,
y : winCoords . y + this . _tolerance . t ,
cx : winCoords . cx - this . _tolerance . l - this . _tolerance . r ,
cy : winCoords . cy - this . _tolerance . t - this . _tolerance . b
} ,
menuSize , ret ;
// Clamp the width of the menu before grabbing its size
this . _ui . container . css ( "max-width" , rc . cx ) ;
menuSize = {
cx : this . _ui . container . outerWidth ( true ) ,
cy : this . _ui . container . outerHeight ( true )
} ;
// Center the menu over the desired coordinates, while not going outside
// the window tolerances. This will center wrt. the window if the popup is too large.
ret = {
x : fitSegmentInsideSegment ( rc . cx , menuSize . cx , rc . x , desired . x ) ,
y : fitSegmentInsideSegment ( rc . cy , menuSize . cy , rc . y , desired . y )
} ;
// Make sure the top of the menu is visible
ret . y = Math . max ( 0 , ret . y ) ;
// If the height of the menu is smaller than the height of the document
// align the bottom with the bottom of the document
// fix for $.mobile.document.height() bug in core 1.7.2.
var docEl = document . documentElement , docBody = document . body ,
docHeight = Math . max ( docEl . clientHeight , docBody . scrollHeight , docBody . offsetHeight , docEl . scrollHeight , docEl . offsetHeight ) ;
ret . y -= Math . min ( ret . y , Math . max ( 0 , ret . y + menuSize . cy - docHeight ) ) ;
return { left : ret . x , top : ret . y } ;
} ,
_createPrereqs : function ( screenPrereq , containerPrereq , whenDone ) {
var self = this , prereqs ;
// It is important to maintain both the local variable prereqs and self._prereqs. The local variable remains in
// the closure of the functions which call the callbacks passed in. The comparison between the local variable and
// self._prereqs is necessary, because once a function has been passed to .animationComplete() it will be called
// next time an animation completes, even if that's not the animation whose end the function was supposed to catch
// (for example, if an abort happens during the opening animation, the .animationComplete handler is not called for
// that animation anymore, but the handler remains attached, so it is called the next time the popup is opened
// - making it stale. Comparing the local variable prereqs to the widget-level variable self._prereqs ensures that
// callbacks triggered by a stale .animationComplete will be ignored.
prereqs = {
screen : $ . Deferred ( ) ,
container : $ . Deferred ( )
} ;
prereqs . screen . then ( function ( ) {
if ( prereqs === self . _prereqs ) {
screenPrereq ( ) ;
}
} ) ;
prereqs . container . then ( function ( ) {
if ( prereqs === self . _prereqs ) {
containerPrereq ( ) ;
}
} ) ;
$ . when ( prereqs . screen , prereqs . container ) . done ( function ( ) {
if ( prereqs === self . _prereqs ) {
self . _prereqs = null ;
whenDone ( ) ;
}
} ) ;
self . _prereqs = prereqs ;
} ,
_animate : function ( args ) {
// NOTE before removing the default animation of the screen
// this had an animate callback that would resolve the deferred
// now the deferred is resolved immediately
// TODO remove the dependency on the screen deferred
this . _ui . screen
. removeClass ( args . classToRemove )
. addClass ( args . screenClassToAdd ) ;
args . prereqs . screen . resolve ( ) ;
if ( args . transition && args . transition !== "none" ) {
if ( args . applyTransition ) {
this . _applyTransition ( args . transition ) ;
}
if ( this . _fallbackTransition ) {
this . _ui . container
. animationComplete ( $ . proxy ( args . prereqs . container , "resolve" ) )
. addClass ( args . containerClassToAdd )
. removeClass ( args . classToRemove ) ;
return ;
}
}
this . _ui . container . removeClass ( args . classToRemove ) ;
args . prereqs . container . resolve ( ) ;
} ,
// The desired coordinates passed in will be returned untouched if no reference element can be identified via
// desiredPosition.positionTo. Nevertheless, this function ensures that its return value always contains valid
// x and y coordinates by specifying the center middle of the window if the coordinates are absent.
// options: { x: coordinate, y: coordinate, positionTo: string: "origin", "window", or jQuery selector
_desiredCoords : function ( o ) {
var dst = null , offset , winCoords = windowCoords ( ) , x = o . x , y = o . y , pTo = o . positionTo ;
// Establish which element will serve as the reference
if ( pTo && pTo !== "origin" ) {
if ( pTo === "window" ) {
x = winCoords . cx / 2 + winCoords . x ;
y = winCoords . cy / 2 + winCoords . y ;
} else {
try {
dst = $ ( pTo ) ;
} catch ( e ) {
dst = null ;
}
if ( dst ) {
dst . filter ( ":visible" ) ;
if ( dst . length === 0 ) {
dst = null ;
}
}
}
}
// If an element was found, center over it
if ( dst ) {
offset = dst . offset ( ) ;
x = offset . left + dst . outerWidth ( ) / 2 ;
y = offset . top + dst . outerHeight ( ) / 2 ;
}
// Make sure x and y are valid numbers - center over the window
if ( $ . type ( x ) !== "number" || isNaN ( x ) ) {
x = winCoords . cx / 2 + winCoords . x ;
}
if ( $ . type ( y ) !== "number" || isNaN ( y ) ) {
y = winCoords . cy / 2 + winCoords . y ;
}
return { x : x , y : y } ;
} ,
_reposition : function ( o ) {
// We only care about position-related parameters for repositioning
o = { x : o . x , y : o . y , positionTo : o . positionTo } ;
this . _trigger ( "beforeposition" , o ) ;
this . _ui . container . offset ( this . _placementCoords ( this . _desiredCoords ( o ) ) ) ;
} ,
reposition : function ( o ) {
if ( this . _isOpen ) {
this . _reposition ( o ) ;
}
} ,
_openPrereqsComplete : function ( ) {
this . _ui . container . addClass ( "ui-popup-active" ) ;
this . _isOpen = true ;
this . _resizeScreen ( ) ;
this . _ui . container . attr ( "tabindex" , "0" ) . focus ( ) ;
this . _ignoreResizeEvents ( ) ;
this . _trigger ( "afteropen" ) ;
} ,
_open : function ( options ) {
var o = $ . extend ( { } , this . options , options ) ,
// TODO move blacklist to private method
androidBlacklist = ( function ( ) {
var w = window ,
ua = navigator . userAgent ,
// Rendering engine is Webkit, and capture major version
wkmatch = ua . match ( /AppleWebKit\/([0-9\.]+)/ ) ,
wkversion = ! ! wkmatch && wkmatch [ 1 ] ,
androidmatch = ua . match ( /Android (\d+(?:\.\d+))/ ) ,
andversion = ! ! androidmatch && androidmatch [ 1 ] ,
chromematch = ua . indexOf ( "Chrome" ) > - 1 ;
// Platform is Android, WebKit version is greater than 534.13 ( Android 3.2.1 ) and not Chrome.
if ( androidmatch !== null && andversion === "4.0" && wkversion && wkversion > 534.13 && ! chromematch ) {
return true ;
}
return false ;
} ( ) ) ;
// Count down to triggering "popupafteropen" - we have two prerequisites:
// 1. The popup window animation completes (container())
// 2. The screen opacity animation completes (screen())
this . _createPrereqs (
$ . noop ,
$ . noop ,
$ . proxy ( this , "_openPrereqsComplete" ) ) ;
this . _currentTransition = o . transition ;
this . _applyTransition ( o . transition ) ;
if ( ! this . options . theme ) {
this . _setTheme ( this . _page . jqmData ( "theme" ) || $ . mobile . getInheritedTheme ( this . _page , "c" ) ) ;
}
this . _ui . screen . removeClass ( "ui-screen-hidden" ) ;
this . _ui . container . removeClass ( "ui-popup-hidden" ) ;
// Give applications a chance to modify the contents of the container before it appears
this . _reposition ( o ) ;
if ( this . options . overlayTheme && androidBlacklist ) {
/ * T O D O :
The native browser on Android 4.0 . X ( "Ice Cream Sandwich" ) suffers from an issue where the popup overlay appears to be z - indexed
above the popup itself when certain other styles exist on the same page -- namely , any element set to ` position: fixed ` and certain
types of input . These issues are reminiscent of previously uncovered bugs in older versions of Android ' s native browser :
https : //github.com/scottjehl/Device-Bugs/issues/3
This fix closes the following bugs ( I use "closes" with reluctance , and stress that this issue should be revisited as soon as possible ) :
https : //github.com/jquery/jquery-mobile/issues/4816
https : //github.com/jquery/jquery-mobile/issues/4844
https : //github.com/jquery/jquery-mobile/issues/4874
* /
// TODO sort out why this._page isn't working
this . element . closest ( ".ui-page" ) . addClass ( "ui-popup-open" ) ;
}
this . _animate ( {
additionalCondition : true ,
transition : o . transition ,
classToRemove : "" ,
screenClassToAdd : "in" ,
containerClassToAdd : "in" ,
applyTransition : false ,
prereqs : this . _prereqs
} ) ;
} ,
_closePrereqScreen : function ( ) {
this . _ui . screen
. removeClass ( "out" )
. addClass ( "ui-screen-hidden" ) ;
} ,
_closePrereqContainer : function ( ) {
this . _ui . container
. removeClass ( "reverse out" )
. addClass ( "ui-popup-hidden" )
. removeAttr ( "style" ) ;
} ,
_closePrereqsDone : function ( ) {
var opts = this . options ;
this . _ui . container . removeAttr ( "tabindex" ) ;
// remove the global mutex for popups
$ . mobile . popup . active = undefined ;
// alert users that the popup is closed
this . _trigger ( "afterclose" ) ;
} ,
_close : function ( immediate ) {
this . _ui . container . removeClass ( "ui-popup-active" ) ;
this . _page . removeClass ( "ui-popup-open" ) ;
this . _isOpen = false ;
// Count down to triggering "popupafterclose" - we have two prerequisites:
// 1. The popup window reverse animation completes (container())
// 2. The screen opacity animation completes (screen())
this . _createPrereqs (
$ . proxy ( this , "_closePrereqScreen" ) ,
$ . proxy ( this , "_closePrereqContainer" ) ,
$ . proxy ( this , "_closePrereqsDone" ) ) ;
this . _animate ( {
additionalCondition : this . _ui . screen . hasClass ( "in" ) ,
transition : ( immediate ? "none" : ( this . _currentTransition ) ) ,
classToRemove : "in" ,
screenClassToAdd : "out" ,
containerClassToAdd : "reverse out" ,
applyTransition : true ,
prereqs : this . _prereqs
} ) ;
} ,
_unenhance : function ( ) {
// Put the element back to where the placeholder was and remove the "ui-popup" class
this . _setTheme ( "none" ) ;
this . element
// Cannot directly insertAfter() - we need to detach() first, because
// insertAfter() will do nothing if the payload div was not attached
// to the DOM at the time the widget was created, and so the payload
// will remain inside the container even after we call insertAfter().
// If that happens and we remove the container a few lines below, we
// will cause an infinite recursion - #5244
. detach ( )
. insertAfter ( this . _ui . placeholder )
. removeClass ( "ui-popup ui-overlay-shadow ui-corner-all" ) ;
this . _ui . screen . remove ( ) ;
this . _ui . container . remove ( ) ;
this . _ui . placeholder . remove ( ) ;
} ,
_destroy : function ( ) {
if ( $ . mobile . popup . active === this ) {
this . element . one ( "popupafterclose" , $ . proxy ( this , "_unenhance" ) ) ;
this . close ( ) ;
} else {
this . _unenhance ( ) ;
}
} ,
_closePopup : function ( e , data ) {
var parsedDst , toUrl , o = this . options , immediate = false ;
// restore location on screen
window . scrollTo ( 0 , this . _scrollTop ) ;
if ( e && e . type === "pagebeforechange" && data ) {
// Determine whether we need to rapid-close the popup, or whether we can
// take the time to run the closing transition
if ( typeof data . toPage === "string" ) {
parsedDst = data . toPage ;
} else {
parsedDst = data . toPage . jqmData ( "url" ) ;
}
parsedDst = $ . mobile . path . parseUrl ( parsedDst ) ;
toUrl = parsedDst . pathname + parsedDst . search + parsedDst . hash ;
if ( this . _myUrl !== $ . mobile . path . makeUrlAbsolute ( toUrl ) ) {
// Going to a different page - close immediately
immediate = true ;
} else {
e . preventDefault ( ) ;
}
}
// remove nav bindings
o . container . unbind ( o . closeEvents ) ;
// unbind click handlers added when history is disabled
this . element . undelegate ( o . closeLinkSelector , o . closeLinkEvents ) ;
this . _close ( immediate ) ;
} ,
// any navigation event after a popup is opened should close the popup
// NOTE the pagebeforechange is bound to catch navigation events that don't
// alter the url (eg, dialogs from popups)
_bindContainerClose : function ( ) {
this . options . container
. one ( this . options . closeEvents , $ . proxy ( this , "_closePopup" ) ) ;
} ,
// TODO no clear deliniation of what should be here and
// what should be in _open. Seems to be "visual" vs "history" for now
open : function ( options ) {
var self = this , opts = this . options , url , hashkey , activePage , currentIsDialog , hasHash , urlHistory ;
// make sure open is idempotent
if ( $ . mobile . popup . active ) {
return ;
}
// set the global popup mutex
$ . mobile . popup . active = this ;
this . _scrollTop = $ . mobile . window . scrollTop ( ) ;
// if history alteration is disabled close on navigate events
// and leave the url as is
if ( ! ( opts . history ) ) {
self . _open ( options ) ;
self . _bindContainerClose ( ) ;
// When histoy is disabled we have to grab the data-rel
// back link clicks so we can close the popup instead of
// relying on history to do it for us
self . element
. delegate ( opts . closeLinkSelector , opts . closeLinkEvents , function ( e ) {
self . close ( ) ;
e . preventDefault ( ) ;
} ) ;
return ;
}
// cache some values for min/readability
urlHistory = $ . mobile . urlHistory ;
hashkey = $ . mobile . dialogHashKey ;
activePage = $ . mobile . activePage ;
currentIsDialog = activePage . is ( ".ui-dialog" ) ;
this . _myUrl = url = urlHistory . getActive ( ) . url ;
hasHash = ( url . indexOf ( hashkey ) > - 1 ) && ! currentIsDialog && ( urlHistory . activeIndex > 0 ) ;
if ( hasHash ) {
self . _open ( options ) ;
self . _bindContainerClose ( ) ;
return ;
}
// if the current url has no dialog hash key proceed as normal
// otherwise, if the page is a dialog simply tack on the hash key
if ( url . indexOf ( hashkey ) === - 1 && ! currentIsDialog ) {
url = url + ( url . indexOf ( "#" ) > - 1 ? hashkey : "#" + hashkey ) ;
} else {
url = $ . mobile . path . parseLocation ( ) . hash + hashkey ;
}
// Tack on an extra hashkey if this is the first page and we've just reconstructed the initial hash
if ( urlHistory . activeIndex === 0 && url === urlHistory . initialDst ) {
url += hashkey ;
}
// swallow the the initial navigation event, and bind for the next
$ ( window ) . one ( "beforenavigate" , function ( e ) {
e . preventDefault ( ) ;
self . _open ( options ) ;
self . _bindContainerClose ( ) ;
} ) ;
this . urlAltered = true ;
$ . mobile . navigate ( url , { role : "dialog" } ) ;
} ,
close : function ( ) {
// make sure close is idempotent
if ( $ . mobile . popup . active !== this ) {
return ;
}
this . _scrollTop = $ . mobile . window . scrollTop ( ) ;
if ( this . options . history && this . urlAltered ) {
$ . mobile . back ( ) ;
this . urlAltered = false ;
} else {
// simulate the nav bindings having fired
this . _closePopup ( ) ;
}
}
} ) ;
// TODO this can be moved inside the widget
$ . mobile . popup . handleLink = function ( $link ) {
var closestPage = $link . closest ( ":jqmData(role='page')" ) ,
scope = ( ( closestPage . length === 0 ) ? $ ( "body" ) : closestPage ) ,
// NOTE make sure to get only the hash, ie7 (wp7) return the absolute href
// in this case ruining the element selection
popup = $ ( $ . mobile . path . parseUrl ( $link . attr ( "href" ) ) . hash , scope [ 0 ] ) ,
offset ;
if ( popup . data ( "mobile-popup" ) ) {
offset = $link . offset ( ) ;
popup . popup ( "open" , {
x : offset . left + $link . outerWidth ( ) / 2 ,
y : offset . top + $link . outerHeight ( ) / 2 ,
transition : $link . jqmData ( "transition" ) ,
positionTo : $link . jqmData ( "position-to" )
} ) ;
}
//remove after delay
setTimeout ( function ( ) {
// Check if we are in a listview
var $parent = $link . parent ( ) . parent ( ) ;
if ( $parent . hasClass ( "ui-li" ) ) {
$link = $parent . parent ( ) ;
}
$link . removeClass ( $ . mobile . activeBtnClass ) ;
} , 300 ) ;
} ;
// TODO move inside _create
$ . mobile . document . bind ( "pagebeforechange" , function ( e , data ) {
if ( data . options . role === "popup" ) {
$ . mobile . popup . handleLink ( data . options . link ) ;
e . preventDefault ( ) ;
}
} ) ;
$ . mobile . document . bind ( "pagecreate create" , function ( e ) {
$ . mobile . popup . prototype . enhanceWithin ( e . target , true ) ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ . widget ( "mobile.panel" , $ . mobile . widget , {
options : {
classes : {
panel : "ui-panel" ,
panelOpen : "ui-panel-open" ,
panelClosed : "ui-panel-closed" ,
panelFixed : "ui-panel-fixed" ,
panelInner : "ui-panel-inner" ,
modal : "ui-panel-dismiss" ,
modalOpen : "ui-panel-dismiss-open" ,
pagePanel : "ui-page-panel" ,
pagePanelOpen : "ui-page-panel-open" ,
contentWrap : "ui-panel-content-wrap" ,
contentWrapOpen : "ui-panel-content-wrap-open" ,
contentWrapClosed : "ui-panel-content-wrap-closed" ,
contentFixedToolbar : "ui-panel-content-fixed-toolbar" ,
contentFixedToolbarOpen : "ui-panel-content-fixed-toolbar-open" ,
contentFixedToolbarClosed : "ui-panel-content-fixed-toolbar-closed" ,
animate : "ui-panel-animate"
} ,
animate : true ,
theme : "c" ,
position : "left" ,
dismissible : true ,
display : "reveal" , //accepts reveal, push, overlay
initSelector : ":jqmData(role='panel')" ,
swipeClose : true ,
positionFixed : false
} ,
_panelID : null ,
_closeLink : null ,
_page : null ,
_modal : null ,
_pannelInner : null ,
_wrapper : null ,
_fixedToolbar : null ,
_create : function ( ) {
var self = this ,
$el = self . element ,
page = $el . closest ( ":jqmData(role='page')" ) ,
_getPageTheme = function ( ) {
var $theme = $ . data ( page [ 0 ] , "mobilePage" ) . options . theme ,
$pageThemeClass = "ui-body-" + $theme ;
return $pageThemeClass ;
} ,
_getPanelInner = function ( ) {
var $pannelInner = $el . find ( "." + self . options . classes . panelInner ) ;
if ( $pannelInner . length === 0 ) {
$pannelInner = $el . children ( ) . wrapAll ( '<div class="' + self . options . classes . panelInner + '" />' ) . parent ( ) ;
}
return $pannelInner ;
} ,
_getWrapper = function ( ) {
var $wrapper = page . find ( "." + self . options . classes . contentWrap ) ;
if ( $wrapper . length === 0 ) {
$wrapper = page . children ( ".ui-header:not(:jqmData(position='fixed')), .ui-content:not(:jqmData(role='popup')), .ui-footer:not(:jqmData(position='fixed'))" ) . wrapAll ( '<div class="' + self . options . classes . contentWrap + ' ' + _getPageTheme ( ) + '" />' ) . parent ( ) ;
if ( $ . support . cssTransform3d && ! ! self . options . animate ) {
$wrapper . addClass ( self . options . classes . animate ) ;
}
}
return $wrapper ;
} ,
_getFixedToolbar = function ( ) {
var $fixedToolbar = page . find ( "." + self . options . classes . contentFixedToolbar ) ;
if ( $fixedToolbar . length === 0 ) {
$fixedToolbar = page . find ( ".ui-header:jqmData(position='fixed'), .ui-footer:jqmData(position='fixed')" ) . addClass ( self . options . classes . contentFixedToolbar ) ;
if ( $ . support . cssTransform3d && ! ! self . options . animate ) {
$fixedToolbar . addClass ( self . options . classes . animate ) ;
}
}
return $fixedToolbar ;
} ;
// expose some private props to other methods
$ . extend ( this , {
_panelID : $el . attr ( "id" ) ,
_closeLink : $el . find ( ":jqmData(rel='close')" ) ,
_page : $el . closest ( ":jqmData(role='page')" ) ,
_pageTheme : _getPageTheme ( ) ,
_pannelInner : _getPanelInner ( ) ,
_wrapper : _getWrapper ( ) ,
_fixedToolbar : _getFixedToolbar ( )
} ) ;
self . _addPanelClasses ( ) ;
self . _wrapper . addClass ( this . options . classes . contentWrapClosed ) ;
self . _fixedToolbar . addClass ( this . options . classes . contentFixedToolbarClosed ) ;
// add class to page so we can set "overflow-x: hidden;" for it to fix Android zoom issue
self . _page . addClass ( self . options . classes . pagePanel ) ;
// if animating, add the class to do so
if ( $ . support . cssTransform3d && ! ! self . options . animate ) {
this . element . addClass ( self . options . classes . animate ) ;
}
self . _bindUpdateLayout ( ) ;
self . _bindCloseEvents ( ) ;
self . _bindLinkListeners ( ) ;
self . _bindPageEvents ( ) ;
if ( ! ! self . options . dismissible ) {
self . _createModal ( ) ;
}
self . _bindSwipeEvents ( ) ;
} ,
_createModal : function ( options ) {
var self = this ;
self . _modal = $ ( "<div class='" + self . options . classes . modal + "' data-panelid='" + self . _panelID + "'></div>" )
. on ( "mousedown" , function ( ) {
self . close ( ) ;
} )
. appendTo ( this . _page ) ;
} ,
_getPosDisplayClasses : function ( prefix ) {
return prefix + "-position-" + this . options . position + " " + prefix + "-display-" + this . options . display ;
} ,
_getPanelClasses : function ( ) {
var panelClasses = this . options . classes . panel +
" " + this . _getPosDisplayClasses ( this . options . classes . panel ) +
" " + this . options . classes . panelClosed ;
if ( this . options . theme ) {
panelClasses += " ui-body-" + this . options . theme ;
}
if ( ! ! this . options . positionFixed ) {
panelClasses += " " + this . options . classes . panelFixed ;
}
return panelClasses ;
} ,
_addPanelClasses : function ( ) {
this . element . addClass ( this . _getPanelClasses ( ) ) ;
} ,
_bindCloseEvents : function ( ) {
var self = this ;
self . _closeLink . on ( "click.panel" , function ( e ) {
e . preventDefault ( ) ;
self . close ( ) ;
return false ;
} ) ;
self . element . on ( "click.panel" , "a:jqmData(ajax='false')" , function ( e ) {
self . close ( ) ;
} ) ;
} ,
_positionPanel : function ( ) {
var self = this ,
pannelInnerHeight = self . _pannelInner . outerHeight ( ) ,
expand = pannelInnerHeight > $ . mobile . getScreenHeight ( ) ;
if ( expand || ! self . options . positionFixed ) {
if ( expand ) {
self . _unfixPanel ( ) ;
$ . mobile . resetActivePageHeight ( pannelInnerHeight ) ;
}
self . _scrollIntoView ( pannelInnerHeight ) ;
} else {
self . _fixPanel ( ) ;
}
} ,
_scrollIntoView : function ( pannelInnerHeight ) {
if ( pannelInnerHeight < $ ( window ) . scrollTop ( ) ) {
window . scrollTo ( 0 , 0 ) ;
}
} ,
_bindFixListener : function ( ) {
this . _on ( $ ( window ) , { "throttledresize" : "_positionPanel" } ) ;
} ,
_unbindFixListener : function ( ) {
this . _off ( $ ( window ) , "throttledresize" ) ;
} ,
_unfixPanel : function ( ) {
if ( ! ! this . options . positionFixed && $ . support . fixedPosition ) {
this . element . removeClass ( this . options . classes . panelFixed ) ;
}
} ,
_fixPanel : function ( ) {
if ( ! ! this . options . positionFixed && $ . support . fixedPosition ) {
this . element . addClass ( this . options . classes . panelFixed ) ;
}
} ,
_bindUpdateLayout : function ( ) {
var self = this ;
self . element . on ( "updatelayout" , function ( e ) {
if ( self . _open ) {
self . _positionPanel ( ) ;
}
} ) ;
} ,
_bindLinkListeners : function ( ) {
var self = this ;
self . _page . on ( "click.panel" , "a" , function ( e ) {
if ( this . href . split ( "#" ) [ 1 ] === self . _panelID && self . _panelID !== undefined ) {
e . preventDefault ( ) ;
var $link = $ ( this ) ;
if ( $link . is ( ":jqmData(role='button')" ) ) {
$link . addClass ( $ . mobile . activeBtnClass ) ;
self . element . one ( "panelopen panelclose" , function ( ) {
$link . removeClass ( $ . mobile . activeBtnClass ) ;
} ) ;
}
self . toggle ( ) ;
return false ;
}
} ) ;
} ,
_bindSwipeEvents : function ( ) {
var self = this ,
area = self . _modal ? self . element . add ( self . _modal ) : self . element ;
// on swipe, close the panel
if ( ! ! self . options . swipeClose ) {
if ( self . options . position === "left" ) {
area . on ( "swipeleft.panel" , function ( e ) {
self . close ( ) ;
} ) ;
} else {
area . on ( "swiperight.panel" , function ( e ) {
self . close ( ) ;
} ) ;
}
}
} ,
_bindPageEvents : function ( ) {
var self = this ;
self . _page
// Close the panel if another panel on the page opens
. on ( "panelbeforeopen" , function ( e ) {
if ( self . _open && e . target !== self . element [ 0 ] ) {
self . close ( ) ;
}
} )
// clean up open panels after page hide
. on ( "pagehide" , function ( e ) {
if ( self . _open ) {
self . close ( true ) ;
}
} )
// on escape, close? might need to have a target check too...
. on ( "keyup.panel" , function ( e ) {
if ( e . keyCode === 27 && self . _open ) {
self . close ( ) ;
}
} ) ;
} ,
// state storage of open or closed
_open : false ,
_contentWrapOpenClasses : null ,
_fixedToolbarOpenClasses : null ,
_modalOpenClasses : null ,
open : function ( immediate ) {
if ( ! this . _open ) {
var self = this ,
o = self . options ,
_openPanel = function ( ) {
self . _page . off ( "panelclose" ) ;
self . _page . jqmData ( "panel" , "open" ) ;
if ( ! immediate && $ . support . cssTransform3d && ! ! o . animate ) {
self . element . add ( self . _wrapper ) . on ( self . _transitionEndEvents , complete ) ;
} else {
setTimeout ( complete , 0 ) ;
}
if ( self . options . theme && self . options . display !== "overlay" ) {
self . _page
. removeClass ( self . _pageTheme )
. addClass ( "ui-body-" + self . options . theme ) ;
}
self . element . removeClass ( o . classes . panelClosed ) . addClass ( o . classes . panelOpen ) ;
self . _contentWrapOpenClasses = self . _getPosDisplayClasses ( o . classes . contentWrap ) ;
self . _wrapper
. removeClass ( o . classes . contentWrapClosed )
. addClass ( self . _contentWrapOpenClasses + " " + o . classes . contentWrapOpen ) ;
self . _fixedToolbarOpenClasses = self . _getPosDisplayClasses ( o . classes . contentFixedToolbar ) ;
self . _fixedToolbar
. removeClass ( o . classes . contentFixedToolbarClosed )
. addClass ( self . _fixedToolbarOpenClasses + " " + o . classes . contentFixedToolbarOpen ) ;
self . _modalOpenClasses = self . _getPosDisplayClasses ( o . classes . modal ) + " " + o . classes . modalOpen ;
if ( self . _modal ) {
self . _modal . addClass ( self . _modalOpenClasses ) ;
}
} ,
complete = function ( ) {
self . element . add ( self . _wrapper ) . off ( self . _transitionEndEvents , complete ) ;
self . _page . addClass ( o . classes . pagePanelOpen ) ;
self . _positionPanel ( ) ;
self . _bindFixListener ( ) ;
self . _trigger ( "open" ) ;
} ;
if ( this . element . closest ( ".ui-page-active" ) . length < 0 ) {
immediate = true ;
}
self . _trigger ( "beforeopen" ) ;
if ( self . _page . jqmData ( 'panel' ) === "open" ) {
self . _page . on ( "panelclose" , function ( ) {
_openPanel ( ) ;
} ) ;
} else {
_openPanel ( ) ;
}
self . _open = true ;
}
} ,
close : function ( immediate ) {
if ( this . _open ) {
var o = this . options ,
self = this ,
_closePanel = function ( ) {
if ( ! immediate && $ . support . cssTransform3d && ! ! o . animate ) {
self . element . add ( self . _wrapper ) . on ( self . _transitionEndEvents , complete ) ;
} else {
setTimeout ( complete , 0 ) ;
}
self . _page . removeClass ( o . classes . pagePanelOpen ) ;
self . element . removeClass ( o . classes . panelOpen ) ;
self . _wrapper . removeClass ( o . classes . contentWrapOpen ) ;
self . _fixedToolbar . removeClass ( o . classes . contentFixedToolbarOpen ) ;
if ( self . _modal ) {
self . _modal . removeClass ( self . _modalOpenClasses ) ;
}
} ,
complete = function ( ) {
if ( self . options . theme && self . options . display !== "overlay" ) {
self . _page . removeClass ( "ui-body-" + self . options . theme ) . addClass ( self . _pageTheme ) ;
}
self . element . add ( self . _wrapper ) . off ( self . _transitionEndEvents , complete ) ;
self . element . addClass ( o . classes . panelClosed ) ;
self . _wrapper
. removeClass ( self . _contentWrapOpenClasses )
. addClass ( o . classes . contentWrapClosed ) ;
self . _fixedToolbar
. removeClass ( self . _fixedToolbarOpenClasses )
. addClass ( o . classes . contentFixedToolbarClosed ) ;
self . _fixPanel ( ) ;
self . _unbindFixListener ( ) ;
$ . mobile . resetActivePageHeight ( ) ;
self . _page . jqmRemoveData ( "panel" ) ;
self . _trigger ( "close" ) ;
} ;
if ( this . element . closest ( ".ui-page-active" ) . length < 0 ) {
immediate = true ;
}
self . _trigger ( "beforeclose" ) ;
_closePanel ( ) ;
self . _open = false ;
}
} ,
toggle : function ( options ) {
this [ this . _open ? "close" : "open" ] ( ) ;
} ,
_transitionEndEvents : "webkitTransitionEnd oTransitionEnd otransitionend transitionend msTransitionEnd" ,
_destroy : function ( ) {
var classes = this . options . classes ,
theme = this . options . theme ,
hasOtherSiblingPanels = this . element . siblings ( "." + classes . panel ) . length ;
// create
if ( ! hasOtherSiblingPanels ) {
this . _wrapper . children ( ) . unwrap ( ) ;
this . _page . find ( "a" ) . unbind ( "panelopen panelclose" ) ;
this . _page . removeClass ( classes . pagePanel ) ;
if ( this . _open ) {
this . _page . jqmRemoveData ( "panel" ) ;
this . _page . removeClass ( classes . pagePanelOpen ) ;
if ( theme ) {
this . _page . removeClass ( "ui-body-" + theme ) . addClass ( this . _pageTheme ) ;
}
$ . mobile . resetActivePageHeight ( ) ;
}
} else if ( this . _open ) {
this . _wrapper . removeClass ( classes . contentWrapOpen ) ;
this . _fixedToolbar . removeClass ( classes . contentFixedToolbarOpen ) ;
this . _page . jqmRemoveData ( "panel" ) ;
this . _page . removeClass ( classes . pagePanelOpen ) ;
if ( theme ) {
this . _page . removeClass ( "ui-body-" + theme ) . addClass ( this . _pageTheme ) ;
}
}
this . _pannelInner . children ( ) . unwrap ( ) ;
this . element . removeClass ( [ this . _getPanelClasses ( ) , classes . panelAnimate ] . join ( " " ) )
. off ( "swipeleft.panel swiperight.panel" )
. off ( "panelbeforeopen" )
. off ( "panelhide" )
. off ( "keyup.panel" )
. off ( "updatelayout" ) ;
this . _closeLink . off ( "click.panel" ) ;
if ( this . _modal ) {
this . _modal . remove ( ) ;
}
// open and close
this . element . off ( this . _transitionEndEvents )
. removeClass ( [ classes . panelUnfixed , classes . panelClosed , classes . panelOpen ] . join ( " " ) ) ;
}
} ) ;
//auto self-init widgets
$ ( document ) . bind ( "pagecreate create" , function ( e ) {
$ . mobile . panel . prototype . enhanceWithin ( e . target ) ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ . widget ( "mobile.table" , $ . mobile . widget , {
options : {
classes : {
table : "ui-table"
} ,
initSelector : ":jqmData(role='table')"
} ,
_create : function ( ) {
var self = this ,
trs = this . element . find ( "thead tr" ) ;
this . element . addClass ( this . options . classes . table ) ;
// Expose headers and allHeaders properties on the widget
// headers references the THs within the first TR in the table
self . headers = this . element . find ( "tr:eq(0)" ) . children ( ) ;
// allHeaders references headers, plus all THs in the thead, which may include several rows, or not
self . allHeaders = self . headers . add ( trs . children ( ) ) ;
trs . each ( function ( ) {
var coltally = 0 ;
$ ( this ) . children ( ) . each ( function ( i ) {
var span = parseInt ( $ ( this ) . attr ( "colspan" ) , 10 ) ,
sel = ":nth-child(" + ( coltally + 1 ) + ")" ;
$ ( this )
. jqmData ( "colstart" , coltally + 1 ) ;
if ( span ) {
for ( var j = 0 ; j < span - 1 ; j ++ ) {
coltally ++ ;
sel += ", :nth-child(" + ( coltally + 1 ) + ")" ;
}
}
// Store "cells" data on header as a reference to all cells in the same column as this TH
$ ( this )
. jqmData ( "cells" , self . element . find ( "tr" ) . not ( trs . eq ( 0 ) ) . not ( this ) . children ( sel ) ) ;
coltally ++ ;
} ) ;
} ) ;
}
} ) ;
//auto self-init widgets
$ . mobile . document . bind ( "pagecreate create" , function ( e ) {
$ . mobile . table . prototype . enhanceWithin ( e . target ) ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ . mobile . table . prototype . options . mode = "columntoggle" ;
$ . mobile . table . prototype . options . columnBtnTheme = null ;
$ . mobile . table . prototype . options . columnPopupTheme = null ;
$ . mobile . table . prototype . options . columnBtnText = "Columns..." ;
$ . mobile . table . prototype . options . classes = $ . extend (
$ . mobile . table . prototype . options . classes ,
{
popup : "ui-table-columntoggle-popup" ,
columnBtn : "ui-table-columntoggle-btn" ,
priorityPrefix : "ui-table-priority-" ,
columnToggleTable : "ui-table-columntoggle"
}
) ;
$ . mobile . document . delegate ( ":jqmData(role='table')" , "tablecreate" , function ( ) {
var $table = $ ( this ) ,
self = $table . data ( "mobile-table" ) ,
o = self . options ,
ns = $ . mobile . ns ;
if ( o . mode !== "columntoggle" ) {
return ;
}
self . element . addClass ( o . classes . columnToggleTable ) ;
var id = ( $table . attr ( "id" ) || o . classes . popup ) + "-popup" , //TODO BETTER FALLBACK ID HERE
$menuButton = $ ( "<a href='#" + id + "' class='" + o . classes . columnBtn + "' data-" + ns + "rel='popup' data-" + ns + "mini='true'>" + o . columnBtnText + "</a>" ) ,
$popup = $ ( "<div data-" + ns + "role='popup' data-" + ns + "role='fieldcontain' class='" + o . classes . popup + "' id='" + id + "'></div>" ) ,
$menu = $ ( "<fieldset data-" + ns + "role='controlgroup'></fieldset>" ) ;
// create the hide/show toggles
self . headers . not ( "td" ) . each ( function ( ) {
var priority = $ ( this ) . jqmData ( "priority" ) ,
$cells = $ ( this ) . add ( $ ( this ) . jqmData ( "cells" ) ) ;
if ( priority ) {
$cells . addClass ( o . classes . priorityPrefix + priority ) ;
$ ( "<label><input type='checkbox' checked />" + $ ( this ) . text ( ) + "</label>" )
. appendTo ( $menu )
. children ( 0 )
. jqmData ( "cells" , $cells )
. checkboxradio ( {
theme : o . columnPopupTheme
} ) ;
}
} ) ;
$menu . appendTo ( $popup ) ;
// bind change event listeners to inputs - TODO: move to a private method?
$menu . on ( "change" , "input" , function ( e ) {
if ( this . checked ) {
$ ( this ) . jqmData ( "cells" ) . removeClass ( "ui-table-cell-hidden" ) . addClass ( "ui-table-cell-visible" ) ;
}
else {
$ ( this ) . jqmData ( "cells" ) . removeClass ( "ui-table-cell-visible" ) . addClass ( "ui-table-cell-hidden" ) ;
}
} ) ;
$menuButton
. insertBefore ( $table )
. buttonMarkup ( {
theme : o . columnBtnTheme
} ) ;
$popup
. insertBefore ( $table )
. popup ( ) ;
// refresh method
self . refresh = function ( ) {
$menu . find ( "input" ) . each ( function ( ) {
this . checked = $ ( this ) . jqmData ( "cells" ) . eq ( 0 ) . css ( "display" ) === "table-cell" ;
$ ( this ) . checkboxradio ( "refresh" ) ;
} ) ;
} ;
$ . mobile . window . on ( "throttledresize" , self . refresh ) ;
self . refresh ( ) ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ . mobile . table . prototype . options . mode = "reflow" ;
$ . mobile . table . prototype . options . classes = $ . extend (
$ . mobile . table . prototype . options . classes ,
{
reflowTable : "ui-table-reflow" ,
cellLabels : "ui-table-cell-label"
}
) ;
$ . mobile . document . delegate ( ":jqmData(role='table')" , "tablecreate" , function ( ) {
var $table = $ ( this ) ,
self = $table . data ( "mobile-table" ) ,
o = self . options ;
// If it's not reflow mode, return here.
if ( o . mode !== "reflow" ) {
return ;
}
self . element . addClass ( o . classes . reflowTable ) ;
// get headers in reverse order so that top-level headers are appended last
var reverseHeaders = $ ( self . allHeaders . get ( ) . reverse ( ) ) ;
// create the hide/show toggles
reverseHeaders . each ( function ( i ) {
var $cells = $ ( this ) . jqmData ( "cells" ) ,
colstart = $ ( this ) . jqmData ( "colstart" ) ,
hierarchyClass = $cells . not ( this ) . filter ( "thead th" ) . length && " ui-table-cell-label-top" ,
text = $ ( this ) . text ( ) ;
if ( text !== "" ) {
if ( hierarchyClass ) {
var iteration = parseInt ( $ ( this ) . attr ( "colspan" ) , 10 ) ,
filter = "" ;
if ( iteration ) {
filter = "td:nth-child(" + iteration + "n + " + ( colstart ) + ")" ;
}
$cells . filter ( filter ) . prepend ( "<b class='" + o . classes . cellLabels + hierarchyClass + "'>" + text + "</b>" ) ;
}
else {
$cells . prepend ( "<b class='" + o . classes . cellLabels + "'>" + text + "</b>" ) ;
}
}
} ) ;
} ) ;
} ) ( jQuery ) ;
( function ( $ ) {
var meta = $ ( "meta[name=viewport]" ) ,
initialContent = meta . attr ( "content" ) ,
disabledZoom = initialContent + ",maximum-scale=1, user-scalable=no" ,
enabledZoom = initialContent + ",maximum-scale=10, user-scalable=yes" ,
disabledInitially = /(user-scalable[\s]*=[\s]*no)|(maximum-scale[\s]*=[\s]*1)[$,\s]/ . test ( initialContent ) ;
$ . mobile . zoom = $ . extend ( { } , {
enabled : ! disabledInitially ,
locked : false ,
disable : function ( lock ) {
if ( ! disabledInitially && ! $ . mobile . zoom . locked ) {
meta . attr ( "content" , disabledZoom ) ;
$ . mobile . zoom . enabled = false ;
$ . mobile . zoom . locked = lock || false ;
}
} ,
enable : function ( unlock ) {
if ( ! disabledInitially && ( ! $ . mobile . zoom . locked || unlock === true ) ) {
meta . attr ( "content" , enabledZoom ) ;
$ . mobile . zoom . enabled = true ;
$ . mobile . zoom . locked = false ;
}
} ,
restore : function ( ) {
if ( ! disabledInitially ) {
meta . attr ( "content" , initialContent ) ;
$ . mobile . zoom . enabled = true ;
}
}
} ) ;
} ( jQuery ) ) ;
( function ( $ , undefined ) {
$ . widget ( "mobile.textinput" , $ . mobile . widget , {
options : {
theme : null ,
mini : false ,
// This option defaults to true on iOS devices.
preventFocusZoom : /iPhone|iPad|iPod/ . test ( navigator . platform ) && navigator . userAgent . indexOf ( "AppleWebKit" ) > - 1 ,
initSelector : "input[type='text'], input[type='search'], :jqmData(type='search'), input[type='number'], :jqmData(type='number'), input[type='password'], input[type='email'], input[type='url'], input[type='tel'], textarea, input[type='time'], input[type='date'], input[type='month'], input[type='week'], input[type='datetime'], input[type='datetime-local'], input[type='color'], input:not([type]), input[type='file']" ,
clearBtn : false ,
clearSearchButtonText : null , //deprecating for 1.3...
clearBtnText : "clear text" ,
disabled : false
} ,
_create : function ( ) {
var self = this ,
input = this . element ,
o = this . options ,
theme = o . theme || $ . mobile . getInheritedTheme ( this . element , "c" ) ,
themeclass = " ui-body-" + theme ,
miniclass = o . mini ? " ui-mini" : "" ,
isSearch = input . is ( "[type='search'], :jqmData(type='search')" ) ,
focusedEl ,
clearbtn ,
clearBtnText = o . clearSearchButtonText || o . clearBtnText ,
clearBtnBlacklist = input . is ( "textarea, :jqmData(type='range')" ) ,
inputNeedsClearBtn = ! ! o . clearBtn && ! clearBtnBlacklist ,
inputNeedsWrap = input . is ( "input" ) && ! input . is ( ":jqmData(type='range')" ) ;
function toggleClear ( ) {
setTimeout ( function ( ) {
clearbtn . toggleClass ( "ui-input-clear-hidden" , ! input . val ( ) ) ;
} , 0 ) ;
}
$ ( "label[for='" + input . attr ( "id" ) + "']" ) . addClass ( "ui-input-text" ) ;
focusedEl = input . addClass ( "ui-input-text ui-body-" + theme ) ;
// XXX: Temporary workaround for issue 785 (Apple bug 8910589).
// Turn off autocorrect and autocomplete on non-iOS 5 devices
// since the popup they use can't be dismissed by the user. Note
// that we test for the presence of the feature by looking for
// the autocorrect property on the input element. We currently
// have no test for iOS 5 or newer so we're temporarily using
// the touchOverflow support flag for jQM 1.0. Yes, I feel dirty. - jblas
if ( typeof input [ 0 ] . autocorrect !== "undefined" && ! $ . support . touchOverflow ) {
// Set the attribute instead of the property just in case there
// is code that attempts to make modifications via HTML.
input [ 0 ] . setAttribute ( "autocorrect" , "off" ) ;
input [ 0 ] . setAttribute ( "autocomplete" , "off" ) ;
}
//"search" and "text" input widgets
if ( isSearch ) {
focusedEl = input . wrap ( "<div class='ui-input-search ui-shadow-inset ui-btn-corner-all ui-btn-shadow ui-icon-searchfield" + themeclass + miniclass + "'></div>" ) . parent ( ) ;
} else if ( inputNeedsWrap ) {
focusedEl = input . wrap ( "<div class='ui-input-text ui-shadow-inset ui-corner-all ui-btn-shadow" + themeclass + miniclass + "'></div>" ) . parent ( ) ;
}
if ( inputNeedsClearBtn || isSearch ) {
clearbtn = $ ( "<a href='#' class='ui-input-clear' title='" + clearBtnText + "'>" + clearBtnText + "</a>" )
. bind ( "click" , function ( event ) {
input
. val ( "" )
. focus ( )
. trigger ( "change" ) ;
clearbtn . addClass ( "ui-input-clear-hidden" ) ;
event . preventDefault ( ) ;
} )
. appendTo ( focusedEl )
. buttonMarkup ( {
icon : "delete" ,
iconpos : "notext" ,
corners : true ,
shadow : true ,
mini : o . mini
} ) ;
if ( ! isSearch ) {
focusedEl . addClass ( "ui-input-has-clear" ) ;
}
toggleClear ( ) ;
input . bind ( "paste cut keyup input focus change blur" , toggleClear ) ;
}
else if ( ! inputNeedsWrap && ! isSearch ) {
input . addClass ( "ui-corner-all ui-shadow-inset" + themeclass + miniclass ) ;
}
input . focus ( function ( ) {
// In many situations, iOS will zoom into the input upon tap, this prevents that from happening
if ( o . preventFocusZoom ) {
$ . mobile . zoom . disable ( true ) ;
}
focusedEl . addClass ( $ . mobile . focusClass ) ;
} )
. blur ( function ( ) {
focusedEl . removeClass ( $ . mobile . focusClass ) ;
if ( o . preventFocusZoom ) {
$ . mobile . zoom . enable ( true ) ;
}
} )
// Autogrow
if ( input . is ( "textarea" ) ) {
var extraLineHeight = 15 ,
keyupTimeoutBuffer = 100 ,
keyupTimeout ;
this . _keyup = function ( ) {
var scrollHeight = input [ 0 ] . scrollHeight ,
clientHeight = input [ 0 ] . clientHeight ;
if ( clientHeight < scrollHeight ) {
input . height ( scrollHeight + extraLineHeight ) ;
}
} ;
input . on ( "keyup change input paste" , function ( ) {
clearTimeout ( keyupTimeout ) ;
keyupTimeout = setTimeout ( self . _keyup , keyupTimeoutBuffer ) ;
} ) ;
// binding to pagechange here ensures that for pages loaded via
// ajax the height is recalculated without user input
this . _on ( $ . mobile . document , { "pagechange" : "_keyup" } ) ;
// Issue 509: the browser is not providing scrollHeight properly until the styles load
if ( $ . trim ( input . val ( ) ) ) {
// bind to the window load to make sure the height is calculated based on BOTH
// the DOM and CSS
this . _on ( $ . mobile . window , { "load" : "_keyup" } ) ;
}
}
if ( input . attr ( "disabled" ) ) {
this . disable ( ) ;
}
} ,
disable : function ( ) {
var $el ,
isSearch = this . element . is ( "[type='search'], :jqmData(type='search')" ) ,
inputNeedsWrap = this . element . is ( "input" ) && ! this . element . is ( ":jqmData(type='range')" ) ,
parentNeedsDisabled = this . element . attr ( "disabled" , true ) && ( inputNeedsWrap || isSearch ) ;
if ( parentNeedsDisabled ) {
$el = this . element . parent ( ) ;
} else {
$el = this . element ;
}
$el . addClass ( "ui-disabled" ) ;
return this . _setOption ( "disabled" , true ) ;
} ,
enable : function ( ) {
var $el ,
isSearch = this . element . is ( "[type='search'], :jqmData(type='search')" ) ,
inputNeedsWrap = this . element . is ( "input" ) && ! this . element . is ( ":jqmData(type='range')" ) ,
parentNeedsEnabled = this . element . attr ( "disabled" , false ) && ( inputNeedsWrap || isSearch ) ;
if ( parentNeedsEnabled ) {
$el = this . element . parent ( ) ;
} else {
$el = this . element ;
}
$el . removeClass ( "ui-disabled" ) ;
return this . _setOption ( "disabled" , false ) ;
}
} ) ;
//auto self-init widgets
$ . mobile . document . bind ( "pagecreate create" , function ( e ) {
$ . mobile . textinput . prototype . enhanceWithin ( e . target , true ) ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ . mobile . listview . prototype . options . filter = false ;
$ . mobile . listview . prototype . options . filterPlaceholder = "Filter items..." ;
$ . mobile . listview . prototype . options . filterTheme = "c" ;
$ . mobile . listview . prototype . options . filterReveal = false ;
// TODO rename callback/deprecate and default to the item itself as the first argument
var defaultFilterCallback = function ( text , searchValue , item ) {
return text . toString ( ) . toLowerCase ( ) . indexOf ( searchValue ) === - 1 ;
} ;
$ . mobile . listview . prototype . options . filterCallback = defaultFilterCallback ;
$ . mobile . document . delegate ( "ul, ol" , "listviewcreate" , function ( ) {
var list = $ ( this ) ,
listview = list . data ( "mobile-listview" ) ;
if ( ! listview . options . filter ) {
return ;
}
if ( listview . options . filterReveal ) {
list . children ( ) . addClass ( "ui-screen-hidden" ) ;
}
var wrapper = $ ( "<form>" , {
"class" : "ui-listview-filter ui-bar-" + listview . options . filterTheme ,
"role" : "search"
} ) . submit ( function ( e ) {
e . preventDefault ( ) ;
search . blur ( ) ;
} ) ,
onKeyUp = function ( e ) {
var $this = $ ( this ) ,
val = this . value . toLowerCase ( ) ,
listItems = null ,
li = list . children ( ) ,
lastval = $this . jqmData ( "lastval" ) + "" ,
childItems = false ,
itemtext = "" ,
item ,
// Check if a custom filter callback applies
isCustomFilterCallback = listview . options . filterCallback !== defaultFilterCallback ;
if ( lastval && lastval === val ) {
// Execute the handler only once per value change
return ;
}
listview . _trigger ( "beforefilter" , "beforefilter" , { input : this } ) ;
// Change val as lastval for next execution
$this . jqmData ( "lastval" , val ) ;
if ( isCustomFilterCallback || val . length < lastval . length || val . indexOf ( lastval ) !== 0 ) {
// Custom filter callback applies or removed chars or pasted something totally different, check all items
listItems = list . children ( ) ;
} else {
// Only chars added, not removed, only use visible subset
listItems = list . children ( ":not(.ui-screen-hidden)" ) ;
if ( ! listItems . length && listview . options . filterReveal ) {
listItems = list . children ( ".ui-screen-hidden" ) ;
}
}
if ( val ) {
// This handles hiding regular rows without the text we search for
// and any list dividers without regular rows shown under it
for ( var i = listItems . length - 1 ; i >= 0 ; i -- ) {
item = $ ( listItems [ i ] ) ;
itemtext = item . jqmData ( "filtertext" ) || item . text ( ) ;
if ( item . is ( "li:jqmData(role=list-divider)" ) ) {
item . toggleClass ( "ui-filter-hidequeue" , ! childItems ) ;
// New bucket!
childItems = false ;
} else if ( listview . options . filterCallback ( itemtext , val , item ) ) {
//mark to be hidden
item . toggleClass ( "ui-filter-hidequeue" , true ) ;
} else {
// There's a shown item in the bucket
childItems = true ;
}
}
// Show items, not marked to be hidden
listItems
. filter ( ":not(.ui-filter-hidequeue)" )
. toggleClass ( "ui-screen-hidden" , false ) ;
// Hide items, marked to be hidden
listItems
. filter ( ".ui-filter-hidequeue" )
. toggleClass ( "ui-screen-hidden" , true )
. toggleClass ( "ui-filter-hidequeue" , false ) ;
} else {
//filtervalue is empty => show all
listItems . toggleClass ( "ui-screen-hidden" , ! ! listview . options . filterReveal ) ;
}
listview . _addFirstLastClasses ( li , listview . _getVisibles ( li , false ) , false ) ;
} ,
search = $ ( "<input>" , {
placeholder : listview . options . filterPlaceholder
} )
. attr ( "data-" + $ . mobile . ns + "type" , "search" )
. jqmData ( "lastval" , "" )
. bind ( "keyup change input" , onKeyUp )
. appendTo ( wrapper )
. textinput ( ) ;
if ( listview . options . inset ) {
wrapper . addClass ( "ui-listview-filter-inset" ) ;
}
wrapper . bind ( "submit" , function ( ) {
return false ;
} )
. insertBefore ( list ) ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ . widget ( "mobile.slider" , $ . mobile . widget , {
widgetEventPrefix : "slide" ,
options : {
theme : null ,
trackTheme : null ,
disabled : false ,
initSelector : "input[type='range'], :jqmData(type='range'), :jqmData(role='slider')" ,
mini : false ,
highlight : false
} ,
_create : function ( ) {
// TODO: Each of these should have comments explain what they're for
var self = this ,
control = this . element ,
parentTheme = $ . mobile . getInheritedTheme ( control , "c" ) ,
theme = this . options . theme || parentTheme ,
trackTheme = this . options . trackTheme || parentTheme ,
cType = control [ 0 ] . nodeName . toLowerCase ( ) ,
isSelect = this . isToggleSwitch = cType === "select" ,
isRangeslider = control . parent ( ) . is ( ":jqmData(role='rangeslider')" ) ,
selectClass = ( this . isToggleSwitch ) ? "ui-slider-switch" : "" ,
controlID = control . attr ( "id" ) ,
$label = $ ( "[for='" + controlID + "']" ) ,
labelID = $label . attr ( "id" ) || controlID + "-label" ,
label = $label . attr ( "id" , labelID ) ,
min = ! this . isToggleSwitch ? parseFloat ( control . attr ( "min" ) ) : 0 ,
max = ! this . isToggleSwitch ? parseFloat ( control . attr ( "max" ) ) : control . find ( "option" ) . length - 1 ,
step = window . parseFloat ( control . attr ( "step" ) || 1 ) ,
miniClass = ( this . options . mini || control . jqmData ( "mini" ) ) ? " ui-mini" : "" ,
domHandle = document . createElement ( "a" ) ,
handle = $ ( domHandle ) ,
domSlider = document . createElement ( "div" ) ,
slider = $ ( domSlider ) ,
valuebg = this . options . highlight && ! this . isToggleSwitch ? ( function ( ) {
var bg = document . createElement ( "div" ) ;
bg . className = "ui-slider-bg " + $ . mobile . activeBtnClass + " ui-btn-corner-all" ;
return $ ( bg ) . prependTo ( slider ) ;
} ) ( ) : false ,
options ;
domHandle . setAttribute ( "href" , "#" ) ;
domSlider . setAttribute ( "role" , "application" ) ;
domSlider . className = [ this . isToggleSwitch ? "ui-slider " : "ui-slider-track " , selectClass , " ui-btn-down-" , trackTheme , " ui-btn-corner-all" , miniClass ] . join ( "" ) ;
domHandle . className = "ui-slider-handle" ;
domSlider . appendChild ( domHandle ) ;
handle . buttonMarkup ( { corners : true , theme : theme , shadow : true } )
. attr ( {
"role" : "slider" ,
"aria-valuemin" : min ,
"aria-valuemax" : max ,
"aria-valuenow" : this . _value ( ) ,
"aria-valuetext" : this . _value ( ) ,
"title" : this . _value ( ) ,
"aria-labelledby" : labelID
} ) ;
$ . extend ( this , {
slider : slider ,
handle : handle ,
type : cType ,
step : step ,
max : max ,
min : min ,
valuebg : valuebg ,
isRangeslider : isRangeslider ,
dragging : false ,
beforeStart : null ,
userModified : false ,
mouseMoved : false
} ) ;
if ( this . isToggleSwitch ) {
var wrapper = document . createElement ( "div" ) ;
wrapper . className = "ui-slider-inneroffset" ;
for ( var j = 0 , length = domSlider . childNodes . length ; j < length ; j ++ ) {
wrapper . appendChild ( domSlider . childNodes [ j ] ) ;
}
domSlider . appendChild ( wrapper ) ;
// slider.wrapInner( "<div class='ui-slider-inneroffset'></div>" );
// make the handle move with a smooth transition
handle . addClass ( "ui-slider-handle-snapping" ) ;
options = control . find ( "option" ) ;
for ( var i = 0 , optionsCount = options . length ; i < optionsCount ; i ++ ) {
var side = ! i ? "b" : "a" ,
sliderTheme = ! i ? " ui-btn-down-" + trackTheme : ( " " + $ . mobile . activeBtnClass ) ,
sliderLabel = document . createElement ( "div" ) ,
sliderImg = document . createElement ( "span" ) ;
sliderImg . className = [ "ui-slider-label ui-slider-label-" , side , sliderTheme , " ui-btn-corner-all" ] . join ( "" ) ;
sliderImg . setAttribute ( "role" , "img" ) ;
sliderImg . appendChild ( document . createTextNode ( options [ i ] . innerHTML ) ) ;
$ ( sliderImg ) . prependTo ( slider ) ;
}
self . _labels = $ ( ".ui-slider-label" , slider ) ;
}
label . addClass ( "ui-slider" ) ;
// monitor the input for updated values
control . addClass ( this . isToggleSwitch ? "ui-slider-switch" : "ui-slider-input" ) ;
this . _on ( control , {
"change" : "_controlChange" ,
"keyup" : "_controlKeyup" ,
"blur" : "_controlBlur" ,
"vmouseup" : "_controlVMouseUp"
} ) ;
slider . bind ( "vmousedown" , $ . proxy ( this . _sliderVMouseDown , this ) )
. bind ( "vclick" , false ) ;
// We have to instantiate a new function object for the unbind to work properly
// since the method itself is defined in the prototype (causing it to unbind everything)
this . _on ( document , { "vmousemove" : "_preventDocumentDrag" } ) ;
this . _on ( slider . add ( document ) , { "vmouseup" : "_sliderVMouseUp" } ) ;
slider . insertAfter ( control ) ;
// wrap in a div for styling purposes
if ( ! this . isToggleSwitch && ! isRangeslider ) {
var wrapper = this . options . mini ? "<div class='ui-slider ui-mini'>" : "<div class='ui-slider'>" ;
control . add ( slider ) . wrapAll ( wrapper ) ;
}
// Only add focus class to toggle switch, sliders get it automatically from ui-btn
if ( this . isToggleSwitch ) {
this . handle . bind ( {
focus : function ( ) {
slider . addClass ( $ . mobile . focusClass ) ;
} ,
blur : function ( ) {
slider . removeClass ( $ . mobile . focusClass ) ;
}
} ) ;
}
// bind the handle event callbacks and set the context to the widget instance
this . _on ( this . handle , {
"vmousedown" : "_handleVMouseDown" ,
"keydown" : "_handleKeydown" ,
"keyup" : "_handleKeyup"
} ) ;
this . handle . bind ( "vclick" , false ) ;
if ( this . _handleFormReset ) {
this . _handleFormReset ( ) ;
}
this . refresh ( undefined , undefined , true ) ;
} ,
_controlChange : function ( event ) {
// if the user dragged the handle, the "change" event was triggered from inside refresh(); don't call refresh() again
if ( this . _trigger ( "controlchange" , event ) === false ) {
return false ;
}
if ( ! this . mouseMoved ) {
this . refresh ( this . _value ( ) , true ) ;
}
} ,
_controlKeyup : function ( event ) { // necessary?
this . refresh ( this . _value ( ) , true , true ) ;
} ,
_controlBlur : function ( event ) {
this . refresh ( this . _value ( ) , true ) ;
} ,
// it appears the clicking the up and down buttons in chrome on
// range/number inputs doesn't trigger a change until the field is
// blurred. Here we check thif the value has changed and refresh
_controlVMouseUp : function ( event ) {
this . _checkedRefresh ( ) ;
} ,
// NOTE force focus on handle
_handleVMouseDown : function ( event ) {
this . handle . focus ( ) ;
} ,
_handleKeydown : function ( event ) {
var index = this . _value ( ) ;
if ( this . options . disabled ) {
return ;
}
// In all cases prevent the default and mark the handle as active
switch ( event . keyCode ) {
case $ . mobile . keyCode . HOME :
case $ . mobile . keyCode . END :
case $ . mobile . keyCode . PAGE _UP :
case $ . mobile . keyCode . PAGE _DOWN :
case $ . mobile . keyCode . UP :
case $ . mobile . keyCode . RIGHT :
case $ . mobile . keyCode . DOWN :
case $ . mobile . keyCode . LEFT :
event . preventDefault ( ) ;
if ( ! this . _keySliding ) {
this . _keySliding = true ;
this . handle . addClass ( "ui-state-active" ) ;
}
break ;
}
// move the slider according to the keypress
switch ( event . keyCode ) {
case $ . mobile . keyCode . HOME :
this . refresh ( this . min ) ;
break ;
case $ . mobile . keyCode . END :
this . refresh ( this . max ) ;
break ;
case $ . mobile . keyCode . PAGE _UP :
case $ . mobile . keyCode . UP :
case $ . mobile . keyCode . RIGHT :
this . refresh ( index + this . step ) ;
break ;
case $ . mobile . keyCode . PAGE _DOWN :
case $ . mobile . keyCode . DOWN :
case $ . mobile . keyCode . LEFT :
this . refresh ( index - this . step ) ;
break ;
}
} , // remove active mark
_handleKeyup : function ( event ) {
if ( this . _keySliding ) {
this . _keySliding = false ;
this . handle . removeClass ( "ui-state-active" ) ;
}
} ,
_sliderVMouseDown : function ( event ) {
// NOTE: we don't do this in refresh because we still want to
// support programmatic alteration of disabled inputs
if ( this . options . disabled ) {
return false ;
}
if ( this . _trigger ( "beforestart" , event ) === false ) {
return false ;
}
this . dragging = true ;
this . userModified = false ;
this . mouseMoved = false ;
if ( this . isToggleSwitch ) {
this . beforeStart = this . element [ 0 ] . selectedIndex ;
}
this . refresh ( event ) ;
this . _trigger ( "start" ) ;
return false ;
} ,
_sliderVMouseUp : function ( ) {
if ( this . dragging ) {
this . dragging = false ;
if ( this . isToggleSwitch ) {
// make the handle move with a smooth transition
this . handle . addClass ( "ui-slider-handle-snapping" ) ;
if ( this . mouseMoved ) {
// this is a drag, change the value only if user dragged enough
if ( this . userModified ) {
this . refresh ( this . beforeStart === 0 ? 1 : 0 ) ;
} else {
this . refresh ( this . beforeStart ) ;
}
} else {
// this is just a click, change the value
this . refresh ( this . beforeStart === 0 ? 1 : 0 ) ;
}
}
this . mouseMoved = false ;
this . _trigger ( "stop" ) ;
return false ;
}
} ,
_preventDocumentDrag : function ( event ) {
// NOTE: we don't do this in refresh because we still want to
// support programmatic alteration of disabled inputs
if ( this . _trigger ( "drag" , event ) === false ) {
return false ;
}
if ( this . dragging && ! this . options . disabled ) {
// this.mouseMoved must be updated before refresh() because it will be used in the control "change" event
this . mouseMoved = true ;
if ( this . isToggleSwitch ) {
// make the handle move in sync with the mouse
this . handle . removeClass ( "ui-slider-handle-snapping" ) ;
}
this . refresh ( event ) ;
// only after refresh() you can calculate this.userModified
this . userModified = this . beforeStart !== this . element [ 0 ] . selectedIndex ;
return false ;
}
} ,
_checkedRefresh : function ( ) {
if ( this . value != this . _value ( ) ) {
this . refresh ( this . _value ( ) ) ;
}
} ,
_value : function ( ) {
return this . isToggleSwitch ? this . element [ 0 ] . selectedIndex : parseFloat ( this . element . val ( ) ) ;
} ,
_reset : function ( ) {
this . refresh ( undefined , false , true ) ;
} ,
refresh : function ( val , isfromControl , preventInputUpdate ) {
// NOTE: we don't return here because we want to support programmatic
// alteration of the input value, which should still update the slider
var self = this ,
parentTheme = $ . mobile . getInheritedTheme ( this . element , "c" ) ,
theme = this . options . theme || parentTheme ,
trackTheme = this . options . trackTheme || parentTheme ;
self . slider [ 0 ] . className = [ this . isToggleSwitch ? "ui-slider ui-slider-switch" : "ui-slider-track" , " ui-btn-down-" + trackTheme , ' ui-btn-corner-all' , ( this . options . mini ) ? " ui-mini" : "" ] . join ( "" ) ;
if ( this . options . disabled || this . element . attr ( "disabled" ) ) {
this . disable ( ) ;
}
// set the stored value for comparison later
this . value = this . _value ( ) ;
if ( this . options . highlight && ! this . isToggleSwitch && this . slider . find ( ".ui-slider-bg" ) . length === 0 ) {
this . valuebg = ( function ( ) {
var bg = document . createElement ( "div" ) ;
bg . className = "ui-slider-bg " + $ . mobile . activeBtnClass + " ui-btn-corner-all" ;
return $ ( bg ) . prependTo ( self . slider ) ;
} ) ( ) ;
}
this . handle . buttonMarkup ( { corners : true , theme : theme , shadow : true } ) ;
var pxStep , percent ,
control = this . element ,
isInput = ! this . isToggleSwitch ,
optionElements = isInput ? [ ] : control . find ( "option" ) ,
min = isInput ? parseFloat ( control . attr ( "min" ) ) : 0 ,
max = isInput ? parseFloat ( control . attr ( "max" ) ) : optionElements . length - 1 ,
step = ( isInput && parseFloat ( control . attr ( "step" ) ) > 0 ) ? parseFloat ( control . attr ( "step" ) ) : 1 ;
if ( typeof val === "object" ) {
var left , width , data = val ,
// a slight tolerance helped get to the ends of the slider
tol = 8 ;
left = this . slider . offset ( ) . left ;
width = this . slider . width ( ) ;
pxStep = width / ( ( max - min ) / step ) ;
if ( ! this . dragging ||
data . pageX < left - tol ||
data . pageX > left + width + tol ) {
return ;
}
if ( pxStep > 1 ) {
percent = ( ( data . pageX - left ) / width ) * 100 ;
} else {
percent = Math . round ( ( ( data . pageX - left ) / width ) * 100 ) ;
}
} else {
if ( val == null ) {
val = isInput ? parseFloat ( control . val ( ) || 0 ) : control [ 0 ] . selectedIndex ;
}
percent = ( parseFloat ( val ) - min ) / ( max - min ) * 100 ;
}
if ( isNaN ( percent ) ) {
return ;
}
var newval = ( percent / 100 ) * ( max - min ) + min ;
//from jQuery UI slider, the following source will round to the nearest step
var valModStep = ( newval - min ) % step ;
var alignValue = newval - valModStep ;
if ( Math . abs ( valModStep ) * 2 >= step ) {
alignValue += ( valModStep > 0 ) ? step : ( - step ) ;
}
var percentPerStep = 100 / ( ( max - min ) / step ) ;
// Since JavaScript has problems with large floats, round
// the final value to 5 digits after the decimal point (see jQueryUI: #4124)
newval = parseFloat ( alignValue . toFixed ( 5 ) ) ;
if ( typeof pxStep === "undefined" ) {
pxStep = width / ( ( max - min ) / step ) ;
}
if ( pxStep > 1 && isInput ) {
percent = ( newval - min ) * percentPerStep * ( 1 / step ) ;
}
if ( percent < 0 ) {
percent = 0 ;
}
if ( percent > 100 ) {
percent = 100 ;
}
if ( newval < min ) {
newval = min ;
}
if ( newval > max ) {
newval = max ;
}
this . handle . css ( "left" , percent + "%" ) ;
this . handle [ 0 ] . setAttribute ( "aria-valuenow" , isInput ? newval : optionElements . eq ( newval ) . attr ( "value" ) ) ;
this . handle [ 0 ] . setAttribute ( "aria-valuetext" , isInput ? newval : optionElements . eq ( newval ) . getEncodedText ( ) ) ;
this . handle [ 0 ] . setAttribute ( "title" , isInput ? newval : optionElements . eq ( newval ) . getEncodedText ( ) ) ;
if ( this . valuebg ) {
this . valuebg . css ( "width" , percent + "%" ) ;
}
// drag the label widths
if ( this . _labels ) {
var handlePercent = this . handle . width ( ) / this . slider . width ( ) * 100 ,
aPercent = percent && handlePercent + ( 100 - handlePercent ) * percent / 100 ,
bPercent = percent === 100 ? 0 : Math . min ( handlePercent + 100 - aPercent , 100 ) ;
this . _labels . each ( function ( ) {
var ab = $ ( this ) . is ( ".ui-slider-label-a" ) ;
$ ( this ) . width ( ( ab ? aPercent : bPercent ) + "%" ) ;
} ) ;
}
if ( ! preventInputUpdate ) {
var valueChanged = false ;
// update control"s value
if ( isInput ) {
valueChanged = control . val ( ) !== newval ;
control . val ( newval ) ;
} else {
valueChanged = control [ 0 ] . selectedIndex !== newval ;
control [ 0 ] . selectedIndex = newval ;
}
if ( this . _trigger ( "beforechange" , val ) === false ) {
return false ;
}
if ( ! isfromControl && valueChanged ) {
control . trigger ( "change" ) ;
}
}
} ,
enable : function ( ) {
this . element . attr ( "disabled" , false ) ;
this . slider . removeClass ( "ui-disabled" ) . attr ( "aria-disabled" , false ) ;
return this . _setOption ( "disabled" , false ) ;
} ,
disable : function ( ) {
this . element . attr ( "disabled" , true ) ;
this . slider . addClass ( "ui-disabled" ) . attr ( "aria-disabled" , true ) ;
return this . _setOption ( "disabled" , true ) ;
}
} ) ;
$ . widget ( "mobile.slider" , $ . mobile . slider , $ . mobile . behaviors . formReset ) ;
//auto self-init widgets
$ . mobile . document . bind ( "pagecreate create" , function ( e ) {
$ . mobile . slider . prototype . enhanceWithin ( e . target , true ) ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ . widget ( "mobile.rangeslider" , $ . mobile . widget , {
options : {
theme : null ,
trackTheme : null ,
disabled : false ,
initSelector : ":jqmData(role='rangeslider')" ,
mini : false ,
highlight : true
} ,
_create : function ( ) {
var secondLabel ,
$el = this . element ,
elClass = this . options . mini ? "ui-rangeslider ui-mini" : "ui-rangeslider" ,
_inputFirst = $el . find ( "input" ) . first ( ) ,
_inputLast = $el . find ( "input" ) . last ( ) ,
label = $el . find ( "label" ) . first ( ) ,
_sliderFirst = $ . data ( _inputFirst . get ( 0 ) , "mobileSlider" ) . slider ,
_sliderLast = $ . data ( _inputLast . get ( 0 ) , "mobileSlider" ) . slider ,
firstHandle = $ . data ( _inputFirst . get ( 0 ) , "mobileSlider" ) . handle ,
_sliders = $ ( "<div class=\"ui-rangeslider-sliders\" />" ) . appendTo ( $el ) ;
if ( $el . find ( "label" ) . length > 1 ) {
secondLabel = $el . find ( "label" ) . last ( ) . hide ( ) ;
}
_inputFirst . addClass ( "ui-rangeslider-first" ) ;
_inputLast . addClass ( "ui-rangeslider-last" ) ;
$el . addClass ( elClass ) ;
_sliderFirst . appendTo ( _sliders ) ;
_sliderLast . appendTo ( _sliders ) ;
label . prependTo ( $el ) ;
firstHandle . prependTo ( _sliderLast ) ;
$ . extend ( this , {
_inputFirst : _inputFirst ,
_inputLast : _inputLast ,
_sliderFirst : _sliderFirst ,
_sliderLast : _sliderLast ,
_targetVal : null ,
_sliderTarget : false ,
_sliders : _sliders ,
_proxy : false
} ) ;
this . refresh ( ) ;
this . _on ( this . element . find ( "input.ui-slider-input" ) , {
"slidebeforestart" : "_slidebeforestart" ,
"slidestop" : "_slidestop" ,
"slidedrag" : "_slidedrag" ,
"slidebeforechange" : "_change" ,
"blur" : "_change" ,
"keyup" : "_change"
} ) ;
this . _on ( firstHandle , {
"vmousedown" : "_dragFirstHandle"
} ) ;
} ,
_dragFirstHandle : function ( event ) {
//if the first handle is dragged send the event to the first slider
$ . data ( this . _inputFirst . get ( 0 ) , "mobileSlider" ) . dragging = true ;
$ . data ( this . _inputFirst . get ( 0 ) , "mobileSlider" ) . refresh ( event ) ;
return false ;
} ,
_slidedrag : function ( event ) {
var first = $ ( event . target ) . is ( this . _inputFirst ) ,
otherSlider = ( first ) ? this . _inputLast : this . _inputFirst ;
this . _sliderTarget = false ;
//if the drag was initaed on an extream and the other handle is focused send the events to
//the closest handle
if ( ( this . _proxy === "first" && first ) || ( this . _proxy === "last" && ! first ) ) {
$ . data ( otherSlider . get ( 0 ) , "mobileSlider" ) . dragging = true ;
$ . data ( otherSlider . get ( 0 ) , "mobileSlider" ) . refresh ( event ) ;
return false ;
}
} ,
_slidestop : function ( event ) {
var first = $ ( event . target ) . is ( this . _inputFirst ) ;
this . _proxy = false ;
//this stops dragging of the handle and brings the active track to the front
//this makes clicks on the track go the the last handle used
this . element . find ( "input" ) . trigger ( "vmouseup" ) ;
this . _sliderFirst . css ( "z-index" , first ? 1 : "" ) ;
} ,
_slidebeforestart : function ( event ) {
this . _sliderTarget = false ;
//if the track is the target remember this and the original value
if ( $ ( event . originalEvent . target ) . hasClass ( "ui-slider-track" ) ) {
this . _sliderTarget = true ;
this . _targetVal = $ ( event . target ) . val ( ) ;
}
} ,
_setOption : function ( options ) {
this . _superApply ( options ) ;
this . refresh ( ) ;
} ,
refresh : function ( ) {
var $el = this . element ,
o = this . options ;
$el . find ( "input" ) . slider ( {
theme : o . theme ,
trackTheme : o . trackTheme ,
disabled : o . disabled ,
mini : o . mini ,
highlight : o . highlight
} ) . slider ( "refresh" ) ;
this . _updateHighlight ( ) ;
} ,
_change : function ( event ) {
if ( event . type == "keyup" ) {
this . _updateHighlight ( ) ;
return false ;
}
var min = parseFloat ( this . _inputFirst . val ( ) , 10 ) ,
max = parseFloat ( this . _inputLast . val ( ) , 10 ) ,
first = $ ( event . target ) . hasClass ( "ui-rangeslider-first" ) ,
thisSlider = first ? this . _inputFirst : this . _inputLast ,
otherSlider = first ? this . _inputLast : this . _inputFirst ;
if ( min > max && ! this . _sliderTarget ) {
//this prevents min from being greater then max
thisSlider . val ( first ? max : min ) . slider ( "refresh" ) ;
this . _trigger ( "normalize" ) ;
} else if ( min > max ) {
//this makes it so clicks on the target on either extream go to the closest handle
thisSlider . val ( this . _targetVal ) . slider ( "refresh" ) ;
var self = this ;
//You must wait for the stack to unwind so first slider is updated before updating second
setTimeout ( function ( ) {
otherSlider . val ( first ? min : max ) . slider ( "refresh" ) ;
$ . data ( otherSlider . get ( 0 ) , "mobileSlider" ) . handle . focus ( ) ;
self . _sliderFirst . css ( "z-index" , first ? "" : 1 ) ;
self . _trigger ( "normalize" ) ;
} , 0 ) ;
this . _proxy = ( first ) ? "first" : "last" ;
}
//fixes issue where when both _sliders are at min they cannot be adjusted
if ( min === max ) {
$ . data ( thisSlider . get ( 0 ) , "mobileSlider" ) . handle . css ( "z-index" , 1 ) ;
$ . data ( otherSlider . get ( 0 ) , "mobileSlider" ) . handle . css ( "z-index" , 0 ) ;
} else {
$ . data ( otherSlider . get ( 0 ) , "mobileSlider" ) . handle . css ( "z-index" , "" ) ;
$ . data ( thisSlider . get ( 0 ) , "mobileSlider" ) . handle . css ( "z-index" , "" ) ;
}
this . _updateHighlight ( ) ;
if ( min >= max ) {
return false ;
}
} ,
_updateHighlight : function ( ) {
var min = parseInt ( $ . data ( this . _inputFirst . get ( 0 ) , "mobileSlider" ) . handle . get ( 0 ) . style . left , 10 ) ,
max = parseInt ( $ . data ( this . _inputLast . get ( 0 ) , "mobileSlider" ) . handle . get ( 0 ) . style . left , 10 ) ,
width = ( max - min ) ;
this . element . find ( ".ui-slider-bg" ) . css ( {
"margin-left" : min + "%" ,
"width" : width + "%"
} ) ;
} ,
_destroy : function ( ) {
this . element . removeClass ( "ui-rangeslider ui-mini" ) . find ( "label" ) . show ( ) ;
this . _inputFirst . after ( this . _sliderFirst ) ;
this . _inputLast . after ( this . _sliderLast ) ;
this . _sliders . remove ( ) ;
this . element . find ( "input" ) . removeClass ( "ui-rangeslider-first ui-rangeslider-last" ) . slider ( "destroy" ) ;
}
} ) ;
$ . widget ( "mobile.rangeslider" , $ . mobile . rangeslider , $ . mobile . behaviors . formReset ) ;
//auto self-init widgets
$ ( document ) . bind ( "pagecreate create" , function ( e ) {
$ . mobile . rangeslider . prototype . enhanceWithin ( e . target , true ) ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ . widget ( "mobile.selectmenu" , $ . mobile . widget , {
options : {
theme : null ,
disabled : false ,
icon : "arrow-d" ,
iconpos : "right" ,
inline : false ,
corners : true ,
shadow : true ,
iconshadow : true ,
overlayTheme : "a" ,
dividerTheme : "b" ,
hidePlaceholderMenuItems : true ,
closeText : "Close" ,
nativeMenu : true ,
// This option defaults to true on iOS devices.
preventFocusZoom : /iPhone|iPad|iPod/ . test ( navigator . platform ) && navigator . userAgent . indexOf ( "AppleWebKit" ) > - 1 ,
initSelector : "select:not( :jqmData(role='slider') )" ,
mini : false
} ,
_button : function ( ) {
return $ ( "<div/>" ) ;
} ,
_setDisabled : function ( value ) {
this . element . attr ( "disabled" , value ) ;
this . button . attr ( "aria-disabled" , value ) ;
return this . _setOption ( "disabled" , value ) ;
} ,
_focusButton : function ( ) {
var self = this ;
setTimeout ( function ( ) {
self . button . focus ( ) ;
} , 40 ) ;
} ,
_selectOptions : function ( ) {
return this . select . find ( "option" ) ;
} ,
// setup items that are generally necessary for select menu extension
_preExtension : function ( ) {
var classes = "" ;
// TODO: Post 1.1--once we have time to test thoroughly--any classes manually applied to the original element should be carried over to the enhanced element, with an `-enhanced` suffix. See https://github.com/jquery/jquery-mobile/issues/3577
/ * i f ( $ e l [ 0 ] . c l a s s N a m e . l e n g t h ) {
classes = $el [ 0 ] . className ;
} * /
if ( ! ! ~ this . element [ 0 ] . className . indexOf ( "ui-btn-left" ) ) {
classes = " ui-btn-left" ;
}
if ( ! ! ~ this . element [ 0 ] . className . indexOf ( "ui-btn-right" ) ) {
classes = " ui-btn-right" ;
}
this . select = this . element . removeClass ( "ui-btn-left ui-btn-right" ) . wrap ( "<div class='ui-select" + classes + "'>" ) ;
this . selectID = this . select . attr ( "id" ) ;
this . label = $ ( "label[for='" + this . selectID + "']" ) . addClass ( "ui-select" ) ;
this . isMultiple = this . select [ 0 ] . multiple ;
if ( ! this . options . theme ) {
this . options . theme = $ . mobile . getInheritedTheme ( this . select , "c" ) ;
}
} ,
_destroy : function ( ) {
var wrapper = this . element . parents ( ".ui-select" ) ;
if ( wrapper . length > 0 ) {
if ( wrapper . is ( ".ui-btn-left, .ui-btn-right" ) ) {
this . element . addClass ( wrapper . is ( ".ui-btn-left" ) ? "ui-btn-left" : "ui-btn-right" ) ;
}
this . element . insertAfter ( wrapper ) ;
wrapper . remove ( ) ;
}
} ,
_create : function ( ) {
this . _preExtension ( ) ;
// Allows for extension of the native select for custom selects and other plugins
// see select.custom for example extension
// TODO explore plugin registration
this . _trigger ( "beforeCreate" ) ;
this . button = this . _button ( ) ;
var self = this ,
options = this . options ,
inline = options . inline || this . select . jqmData ( "inline" ) ,
mini = options . mini || this . select . jqmData ( "mini" ) ,
iconpos = options . icon ? ( options . iconpos || this . select . jqmData ( "iconpos" ) ) : false ,
// IE throws an exception at options.item() function when
// there is no selected item
// select first in this case
selectedIndex = this . select [ 0 ] . selectedIndex === - 1 ? 0 : this . select [ 0 ] . selectedIndex ,
// TODO values buttonId and menuId are undefined here
button = this . button
. insertBefore ( this . select )
. buttonMarkup ( {
theme : options . theme ,
icon : options . icon ,
iconpos : iconpos ,
inline : inline ,
corners : options . corners ,
shadow : options . shadow ,
iconshadow : options . iconshadow ,
mini : mini
} ) ;
this . setButtonText ( ) ;
// Opera does not properly support opacity on select elements
// In Mini, it hides the element, but not its text
// On the desktop,it seems to do the opposite
// for these reasons, using the nativeMenu option results in a full native select in Opera
if ( options . nativeMenu && window . opera && window . opera . version ) {
button . addClass ( "ui-select-nativeonly" ) ;
}
// Add counter for multi selects
if ( this . isMultiple ) {
this . buttonCount = $ ( "<span>" )
. addClass ( "ui-li-count ui-btn-up-c ui-btn-corner-all" )
. hide ( )
. appendTo ( button . addClass ( 'ui-li-has-count' ) ) ;
}
// Disable if specified
if ( options . disabled || this . element . attr ( 'disabled' ) ) {
this . disable ( ) ;
}
// Events on native select
this . select . change ( function ( ) {
self . refresh ( ) ;
} ) ;
if ( this . _handleFormReset ) {
this . _handleFormReset ( ) ;
}
this . build ( ) ;
} ,
build : function ( ) {
var self = this ;
this . select
. appendTo ( self . button )
. bind ( "vmousedown" , function ( ) {
// Add active class to button
self . button . addClass ( $ . mobile . activeBtnClass ) ;
} )
. bind ( "focus" , function ( ) {
self . button . addClass ( $ . mobile . focusClass ) ;
} )
. bind ( "blur" , function ( ) {
self . button . removeClass ( $ . mobile . focusClass ) ;
} )
. bind ( "focus vmouseover" , function ( ) {
self . button . trigger ( "vmouseover" ) ;
} )
. bind ( "vmousemove" , function ( ) {
// Remove active class on scroll/touchmove
self . button . removeClass ( $ . mobile . activeBtnClass ) ;
} )
. bind ( "change blur vmouseout" , function ( ) {
self . button . trigger ( "vmouseout" )
. removeClass ( $ . mobile . activeBtnClass ) ;
} )
. bind ( "change blur" , function ( ) {
self . button . removeClass ( "ui-btn-down-" + self . options . theme ) ;
} ) ;
// In many situations, iOS will zoom into the select upon tap, this prevents that from happening
self . button . bind ( "vmousedown" , function ( ) {
if ( self . options . preventFocusZoom ) {
$ . mobile . zoom . disable ( true ) ;
}
} ) ;
self . label . bind ( "click focus" , function ( ) {
if ( self . options . preventFocusZoom ) {
$ . mobile . zoom . disable ( true ) ;
}
} ) ;
self . select . bind ( "focus" , function ( ) {
if ( self . options . preventFocusZoom ) {
$ . mobile . zoom . disable ( true ) ;
}
} ) ;
self . button . bind ( "mouseup" , function ( ) {
if ( self . options . preventFocusZoom ) {
setTimeout ( function ( ) {
$ . mobile . zoom . enable ( true ) ;
} , 0 ) ;
}
} ) ;
self . select . bind ( "blur" , function ( ) {
if ( self . options . preventFocusZoom ) {
$ . mobile . zoom . enable ( true ) ;
}
} ) ;
} ,
selected : function ( ) {
return this . _selectOptions ( ) . filter ( ":selected" ) ;
} ,
selectedIndices : function ( ) {
var self = this ;
return this . selected ( ) . map ( function ( ) {
return self . _selectOptions ( ) . index ( this ) ;
} ) . get ( ) ;
} ,
setButtonText : function ( ) {
var self = this ,
selected = this . selected ( ) ,
text = this . placeholder ,
span = $ ( document . createElement ( "span" ) ) ;
this . button . find ( ".ui-btn-text" ) . html ( function ( ) {
if ( selected . length ) {
text = selected . map ( function ( ) {
return $ ( this ) . text ( ) ;
} ) . get ( ) . join ( ", " ) ;
} else {
text = self . placeholder ;
}
// TODO possibly aggregate multiple select option classes
return span . text ( text )
. addClass ( self . select . attr ( "class" ) )
. addClass ( selected . attr ( "class" ) ) ;
} ) ;
} ,
setButtonCount : function ( ) {
var selected = this . selected ( ) ;
// multiple count inside button
if ( this . isMultiple ) {
this . buttonCount [ selected . length > 1 ? "show" : "hide" ] ( ) . text ( selected . length ) ;
}
} ,
_reset : function ( ) {
this . refresh ( ) ;
} ,
refresh : function ( ) {
this . setButtonText ( ) ;
this . setButtonCount ( ) ;
} ,
// open and close preserved in native selects
// to simplify users code when looping over selects
open : $ . noop ,
close : $ . noop ,
disable : function ( ) {
this . _setDisabled ( true ) ;
this . button . addClass ( "ui-disabled" ) ;
} ,
enable : function ( ) {
this . _setDisabled ( false ) ;
this . button . removeClass ( "ui-disabled" ) ;
}
} ) ;
$ . widget ( "mobile.selectmenu" , $ . mobile . selectmenu , $ . mobile . behaviors . formReset ) ;
//auto self-init widgets
$ . mobile . document . bind ( "pagecreate create" , function ( e ) {
$ . mobile . selectmenu . prototype . enhanceWithin ( e . target , true ) ;
} ) ;
} ) ( jQuery ) ;
/ *
* custom "selectmenu" plugin
* /
( function ( $ , undefined ) {
var extendSelect = function ( widget ) {
var select = widget . select ,
origDestroy = widget . _destroy ,
selectID = widget . selectID ,
prefix = ( selectID ? selectID : ( ( $ . mobile . ns || "" ) + "uuid-" + widget . uuid ) ) ,
popupID = prefix + "-listbox" ,
dialogID = prefix + "-dialog" ,
label = widget . label ,
thisPage = widget . select . closest ( ".ui-page" ) ,
selectOptions = widget . _selectOptions ( ) ,
isMultiple = widget . isMultiple = widget . select [ 0 ] . multiple ,
buttonId = selectID + "-button" ,
menuId = selectID + "-menu" ,
menuPage = $ ( "<div data-" + $ . mobile . ns + "role='dialog' id='" + dialogID + "' data-" + $ . mobile . ns + "theme='" + widget . options . theme + "' data-" + $ . mobile . ns + "overlay-theme='" + widget . options . overlayTheme + "'>" +
"<div data-" + $ . mobile . ns + "role='header'>" +
"<div class='ui-title'>" + label . getEncodedText ( ) + "</div>" +
"</div>" +
"<div data-" + $ . mobile . ns + "role='content'></div>" +
"</div>" ) ,
listbox = $ ( "<div id='" + popupID + "' class='ui-selectmenu'>" ) . insertAfter ( widget . select ) . popup ( { theme : widget . options . overlayTheme } ) ,
list = $ ( "<ul>" , {
"class" : "ui-selectmenu-list" ,
"id" : menuId ,
"role" : "listbox" ,
"aria-labelledby" : buttonId
} ) . attr ( "data-" + $ . mobile . ns + "theme" , widget . options . theme )
. attr ( "data-" + $ . mobile . ns + "divider-theme" , widget . options . dividerTheme )
. appendTo ( listbox ) ,
header = $ ( "<div>" , {
"class" : "ui-header ui-bar-" + widget . options . theme
} ) . prependTo ( listbox ) ,
headerTitle = $ ( "<h1>" , {
"class" : "ui-title"
} ) . appendTo ( header ) ,
menuPageContent ,
menuPageClose ,
headerClose ;
if ( widget . isMultiple ) {
headerClose = $ ( "<a>" , {
"text" : widget . options . closeText ,
"href" : "#" ,
"class" : "ui-btn-left"
} ) . attr ( "data-" + $ . mobile . ns + "iconpos" , "notext" ) . attr ( "data-" + $ . mobile . ns + "icon" , "delete" ) . appendTo ( header ) . buttonMarkup ( ) ;
}
$ . extend ( widget , {
select : widget . select ,
selectID : selectID ,
buttonId : buttonId ,
menuId : menuId ,
popupID : popupID ,
dialogID : dialogID ,
thisPage : thisPage ,
menuPage : menuPage ,
label : label ,
selectOptions : selectOptions ,
isMultiple : isMultiple ,
theme : widget . options . theme ,
listbox : listbox ,
list : list ,
header : header ,
headerTitle : headerTitle ,
headerClose : headerClose ,
menuPageContent : menuPageContent ,
menuPageClose : menuPageClose ,
placeholder : "" ,
build : function ( ) {
var self = this ;
// Create list from select, update state
self . refresh ( ) ;
if ( self . _origTabIndex === undefined ) {
// Map undefined to false, because self._origTabIndex === undefined
// indicates that we have not yet checked whether the select has
// originally had a tabindex attribute, whereas false indicates that
// we have checked the select for such an attribute, and have found
// none present.
self . _origTabIndex = ( self . select [ 0 ] . getAttribute ( "tabindex" ) === null ) ? false : self . select . attr ( "tabindex" ) ;
}
self . select . attr ( "tabindex" , "-1" ) . focus ( function ( ) {
$ ( this ) . blur ( ) ;
self . button . focus ( ) ;
} ) ;
// Button events
self . button . bind ( "vclick keydown" , function ( event ) {
if ( self . options . disabled || self . isOpen ) {
return ;
}
if ( event . type === "vclick" ||
event . keyCode && ( event . keyCode === $ . mobile . keyCode . ENTER ||
event . keyCode === $ . mobile . keyCode . SPACE ) ) {
self . _decideFormat ( ) ;
if ( self . menuType === "overlay" ) {
self . button . attr ( "href" , "#" + self . popupID ) . attr ( "data-" + ( $ . mobile . ns || "" ) + "rel" , "popup" ) ;
} else {
self . button . attr ( "href" , "#" + self . dialogID ) . attr ( "data-" + ( $ . mobile . ns || "" ) + "rel" , "dialog" ) ;
}
self . isOpen = true ;
// Do not prevent default, so the navigation may have a chance to actually open the chosen format
}
} ) ;
// Events for list items
self . list . attr ( "role" , "listbox" )
. bind ( "focusin" , function ( e ) {
$ ( e . target )
. attr ( "tabindex" , "0" )
. trigger ( "vmouseover" ) ;
} )
. bind ( "focusout" , function ( e ) {
$ ( e . target )
. attr ( "tabindex" , "-1" )
. trigger ( "vmouseout" ) ;
} )
. delegate ( "li:not(.ui-disabled, .ui-li-divider)" , "click" , function ( event ) {
// index of option tag to be selected
var oldIndex = self . select [ 0 ] . selectedIndex ,
newIndex = self . list . find ( "li:not(.ui-li-divider)" ) . index ( this ) ,
option = self . _selectOptions ( ) . eq ( newIndex ) [ 0 ] ;
// toggle selected status on the tag for multi selects
option . selected = self . isMultiple ? ! option . selected : true ;
// toggle checkbox class for multiple selects
if ( self . isMultiple ) {
$ ( this ) . find ( ".ui-icon" )
. toggleClass ( "ui-icon-checkbox-on" , option . selected )
. toggleClass ( "ui-icon-checkbox-off" , ! option . selected ) ;
}
// trigger change if value changed
if ( self . isMultiple || oldIndex !== newIndex ) {
self . select . trigger ( "change" ) ;
}
// hide custom select for single selects only - otherwise focus clicked item
// We need to grab the clicked item the hard way, because the list may have been rebuilt
if ( self . isMultiple ) {
self . list . find ( "li:not(.ui-li-divider)" ) . eq ( newIndex )
. addClass ( "ui-btn-down-" + widget . options . theme ) . find ( "a" ) . first ( ) . focus ( ) ;
}
else {
self . close ( ) ;
}
event . preventDefault ( ) ;
} )
. keydown ( function ( event ) { //keyboard events for menu items
var target = $ ( event . target ) ,
li = target . closest ( "li" ) ,
prev , next ;
// switch logic based on which key was pressed
switch ( event . keyCode ) {
// up or left arrow keys
case 38 :
prev = li . prev ( ) . not ( ".ui-selectmenu-placeholder" ) ;
if ( prev . is ( ".ui-li-divider" ) ) {
prev = prev . prev ( ) ;
}
// if there's a previous option, focus it
if ( prev . length ) {
target
. blur ( )
. attr ( "tabindex" , "-1" ) ;
prev . addClass ( "ui-btn-down-" + widget . options . theme ) . find ( "a" ) . first ( ) . focus ( ) ;
}
return false ;
// down or right arrow keys
case 40 :
next = li . next ( ) ;
if ( next . is ( ".ui-li-divider" ) ) {
next = next . next ( ) ;
}
// if there's a next option, focus it
if ( next . length ) {
target
. blur ( )
. attr ( "tabindex" , "-1" ) ;
next . addClass ( "ui-btn-down-" + widget . options . theme ) . find ( "a" ) . first ( ) . focus ( ) ;
}
return false ;
// If enter or space is pressed, trigger click
case 13 :
case 32 :
target . trigger ( "click" ) ;
return false ;
}
} ) ;
// button refocus ensures proper height calculation
// by removing the inline style and ensuring page inclusion
self . menuPage . bind ( "pagehide" , function ( ) {
// TODO centralize page removal binding / handling in the page plugin.
// Suggestion from @jblas to do refcounting
//
// TODO extremely confusing dependency on the open method where the pagehide.remove
// bindings are stripped to prevent the parent page from disappearing. The way
// we're keeping pages in the DOM right now sucks
//
// rebind the page remove that was unbound in the open function
// to allow for the parent page removal from actions other than the use
// of a dialog sized custom select
//
// doing this here provides for the back button on the custom select dialog
$ . mobile . _bindPageRemove . call ( self . thisPage ) ;
} ) ;
// Events on the popup
self . listbox . bind ( "popupafterclose" , function ( event ) {
self . close ( ) ;
} ) ;
// Close button on small overlays
if ( self . isMultiple ) {
self . headerClose . click ( function ( ) {
if ( self . menuType === "overlay" ) {
self . close ( ) ;
return false ;
}
} ) ;
}
// track this dependency so that when the parent page
// is removed on pagehide it will also remove the menupage
self . thisPage . addDependents ( this . menuPage ) ;
} ,
_isRebuildRequired : function ( ) {
var list = this . list . find ( "li" ) ,
options = this . _selectOptions ( ) ;
// TODO exceedingly naive method to determine difference
// ignores value changes etc in favor of a forcedRebuild
// from the user in the refresh method
return options . text ( ) !== list . text ( ) ;
} ,
selected : function ( ) {
return this . _selectOptions ( ) . filter ( ":selected:not( :jqmData(placeholder='true') )" ) ;
} ,
refresh : function ( forceRebuild , foo ) {
var self = this ,
select = this . element ,
isMultiple = this . isMultiple ,
indicies ;
if ( forceRebuild || this . _isRebuildRequired ( ) ) {
self . _buildList ( ) ;
}
indicies = this . selectedIndices ( ) ;
self . setButtonText ( ) ;
self . setButtonCount ( ) ;
self . list . find ( "li:not(.ui-li-divider)" )
. removeClass ( $ . mobile . activeBtnClass )
. attr ( "aria-selected" , false )
. each ( function ( i ) {
if ( $ . inArray ( i , indicies ) > - 1 ) {
var item = $ ( this ) ;
// Aria selected attr
item . attr ( "aria-selected" , true ) ;
// Multiple selects: add the "on" checkbox state to the icon
if ( self . isMultiple ) {
item . find ( ".ui-icon" ) . removeClass ( "ui-icon-checkbox-off" ) . addClass ( "ui-icon-checkbox-on" ) ;
} else {
if ( item . is ( ".ui-selectmenu-placeholder" ) ) {
item . next ( ) . addClass ( $ . mobile . activeBtnClass ) ;
} else {
item . addClass ( $ . mobile . activeBtnClass ) ;
}
}
}
} ) ;
} ,
close : function ( ) {
if ( this . options . disabled || ! this . isOpen ) {
return ;
}
var self = this ;
if ( self . menuType === "page" ) {
self . menuPage . dialog ( "close" ) ;
self . list . appendTo ( self . listbox ) ;
} else {
self . listbox . popup ( "close" ) ;
}
self . _focusButton ( ) ;
// allow the dialog to be closed again
self . isOpen = false ;
} ,
open : function ( ) {
this . button . click ( ) ;
} ,
_decideFormat : function ( ) {
var self = this ,
$window = $ . mobile . window ,
selfListParent = self . list . parent ( ) ,
menuHeight = selfListParent . outerHeight ( ) ,
menuWidth = selfListParent . outerWidth ( ) ,
activePage = $ ( "." + $ . mobile . activePageClass ) ,
scrollTop = $window . scrollTop ( ) ,
btnOffset = self . button . offset ( ) . top ,
screenHeight = $window . height ( ) ,
screenWidth = $window . width ( ) ;
function focusMenuItem ( ) {
var selector = self . list . find ( "." + $ . mobile . activeBtnClass + " a" ) ;
if ( selector . length === 0 ) {
selector = self . list . find ( "li.ui-btn:not( :jqmData(placeholder='true') ) a" ) ;
}
selector . first ( ) . focus ( ) . closest ( "li" ) . addClass ( "ui-btn-down-" + widget . options . theme ) ;
}
if ( menuHeight > screenHeight - 80 || ! $ . support . scrollTop ) {
self . menuPage . appendTo ( $ . mobile . pageContainer ) . page ( ) ;
self . menuPageContent = menuPage . find ( ".ui-content" ) ;
self . menuPageClose = menuPage . find ( ".ui-header a" ) ;
// prevent the parent page from being removed from the DOM,
// otherwise the results of selecting a list item in the dialog
// fall into a black hole
self . thisPage . unbind ( "pagehide.remove" ) ;
//for WebOS/Opera Mini (set lastscroll using button offset)
if ( scrollTop === 0 && btnOffset > screenHeight ) {
self . thisPage . one ( "pagehide" , function ( ) {
$ ( this ) . jqmData ( "lastScroll" , btnOffset ) ;
} ) ;
}
self . menuPage
. one ( "pageshow" , function ( ) {
focusMenuItem ( ) ;
} )
. one ( "pagehide" , function ( ) {
self . close ( ) ;
} ) ;
self . menuType = "page" ;
self . menuPageContent . append ( self . list ) ;
self . menuPage . find ( "div .ui-title" ) . text ( self . label . text ( ) ) ;
} else {
self . menuType = "overlay" ;
self . listbox . one ( "popupafteropen" , focusMenuItem ) ;
}
} ,
_buildList : function ( ) {
var self = this ,
o = this . options ,
placeholder = this . placeholder ,
needPlaceholder = true ,
optgroups = [ ] ,
lis = [ ] ,
dataIcon = self . isMultiple ? "checkbox-off" : "false" ;
self . list . empty ( ) . filter ( ".ui-listview" ) . listview ( "destroy" ) ;
var $options = self . select . find ( "option" ) ,
numOptions = $options . length ,
select = this . select [ 0 ] ,
dataPrefix = 'data-' + $ . mobile . ns ,
dataIndexAttr = dataPrefix + 'option-index' ,
dataIconAttr = dataPrefix + 'icon' ,
dataRoleAttr = dataPrefix + 'role' ,
dataPlaceholderAttr = dataPrefix + 'placeholder' ,
fragment = document . createDocumentFragment ( ) ,
isPlaceholderItem = false ,
optGroup ;
for ( var i = 0 ; i < numOptions ; i ++ , isPlaceholderItem = false ) {
var option = $options [ i ] ,
$option = $ ( option ) ,
parent = option . parentNode ,
text = $option . text ( ) ,
anchor = document . createElement ( 'a' ) ,
classes = [ ] ;
anchor . setAttribute ( 'href' , '#' ) ;
anchor . appendChild ( document . createTextNode ( text ) ) ;
// Are we inside an optgroup?
if ( parent !== select && parent . nodeName . toLowerCase ( ) === "optgroup" ) {
var optLabel = parent . getAttribute ( 'label' ) ;
if ( optLabel !== optGroup ) {
var divider = document . createElement ( 'li' ) ;
divider . setAttribute ( dataRoleAttr , 'list-divider' ) ;
divider . setAttribute ( 'role' , 'option' ) ;
divider . setAttribute ( 'tabindex' , '-1' ) ;
divider . appendChild ( document . createTextNode ( optLabel ) ) ;
fragment . appendChild ( divider ) ;
optGroup = optLabel ;
}
}
if ( needPlaceholder && ( ! option . getAttribute ( "value" ) || text . length === 0 || $option . jqmData ( "placeholder" ) ) ) {
needPlaceholder = false ;
isPlaceholderItem = true ;
// If we have identified a placeholder, record the fact that it was
// us who have added the placeholder to the option and mark it
// retroactively in the select as well
if ( null === option . getAttribute ( dataPlaceholderAttr ) ) {
this . _removePlaceholderAttr = true ;
}
option . setAttribute ( dataPlaceholderAttr , true ) ;
if ( o . hidePlaceholderMenuItems ) {
classes . push ( "ui-selectmenu-placeholder" ) ;
}
if ( placeholder !== text ) {
placeholder = self . placeholder = text ;
}
}
var item = document . createElement ( 'li' ) ;
if ( option . disabled ) {
classes . push ( "ui-disabled" ) ;
item . setAttribute ( 'aria-disabled' , true ) ;
}
item . setAttribute ( dataIndexAttr , i ) ;
item . setAttribute ( dataIconAttr , dataIcon ) ;
if ( isPlaceholderItem ) {
item . setAttribute ( dataPlaceholderAttr , true ) ;
}
item . className = classes . join ( " " ) ;
item . setAttribute ( 'role' , 'option' ) ;
anchor . setAttribute ( 'tabindex' , '-1' ) ;
item . appendChild ( anchor ) ;
fragment . appendChild ( item ) ;
}
self . list [ 0 ] . appendChild ( fragment ) ;
// Hide header if it's not a multiselect and there's no placeholder
if ( ! this . isMultiple && ! placeholder . length ) {
this . header . hide ( ) ;
} else {
this . headerTitle . text ( this . placeholder ) ;
}
// Now populated, create listview
self . list . listview ( ) ;
} ,
_button : function ( ) {
return $ ( "<a>" , {
"href" : "#" ,
"role" : "button" ,
// TODO value is undefined at creation
"id" : this . buttonId ,
"aria-haspopup" : "true" ,
// TODO value is undefined at creation
"aria-owns" : this . menuId
} ) ;
} ,
_destroy : function ( ) {
this . close ( ) ;
// Restore the tabindex attribute to its original value
if ( this . _origTabIndex !== undefined ) {
if ( this . _origTabIndex !== false ) {
this . select . attr ( "tabindex" , this . _origTabIndex ) ;
} else {
this . select . removeAttr ( "tabindex" ) ;
}
}
// Remove the placeholder attribute if we were the ones to add it
if ( this . _removePlaceholderAttr ) {
this . _selectOptions ( ) . removeAttr ( "data-" + $ . mobile . ns + "placeholder" ) ;
}
// Remove the popup
this . listbox . remove ( ) ;
// Chain up
origDestroy . apply ( this , arguments ) ;
}
} ) ;
} ;
// issue #3894 - core doesn't trigger events on disabled delegates
$ . mobile . document . bind ( "selectmenubeforecreate" , function ( event ) {
var selectmenuWidget = $ ( event . target ) . data ( "mobile-selectmenu" ) ;
if ( ! selectmenuWidget . options . nativeMenu &&
selectmenuWidget . element . parents ( ":jqmData(role='popup')" ) . length === 0 ) {
extendSelect ( selectmenuWidget ) ;
}
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ . widget ( "mobile.fixedtoolbar" , $ . mobile . widget , {
options : {
visibleOnPageShow : true ,
disablePageZoom : true ,
transition : "slide" , //can be none, fade, slide (slide maps to slideup or slidedown)
fullscreen : false ,
tapToggle : true ,
tapToggleBlacklist : "a, button, input, select, textarea, .ui-header-fixed, .ui-footer-fixed, .ui-popup, .ui-panel, .ui-panel-dismiss-open" ,
hideDuringFocus : "input, textarea, select" ,
updatePagePadding : true ,
trackPersistentToolbars : true ,
// Browser detection! Weeee, here we go...
// Unfortunately, position:fixed is costly, not to mention probably impossible, to feature-detect accurately.
// Some tests exist, but they currently return false results in critical devices and browsers, which could lead to a broken experience.
// Testing fixed positioning is also pretty obtrusive to page load, requiring injected elements and scrolling the window
// The following function serves to rule out some popular browsers with known fixed-positioning issues
// This is a plugin option like any other, so feel free to improve or overwrite it
supportBlacklist : function ( ) {
return ! $ . support . fixedPosition ;
} ,
initSelector : ":jqmData(position='fixed')"
} ,
_create : function ( ) {
var self = this ,
o = self . options ,
$el = self . element ,
tbtype = $el . is ( ":jqmData(role='header')" ) ? "header" : "footer" ,
$page = $el . closest ( ".ui-page" ) ;
// Feature detecting support for
if ( o . supportBlacklist ( ) ) {
self . destroy ( ) ;
return ;
}
$el . addClass ( "ui-" + tbtype + "-fixed" ) ;
// "fullscreen" overlay positioning
if ( o . fullscreen ) {
$el . addClass ( "ui-" + tbtype + "-fullscreen" ) ;
$page . addClass ( "ui-page-" + tbtype + "-fullscreen" ) ;
}
// If not fullscreen, add class to page to set top or bottom padding
else {
$page . addClass ( "ui-page-" + tbtype + "-fixed" ) ;
}
$ . extend ( this , {
_thisPage : null
} ) ;
self . _addTransitionClass ( ) ;
self . _bindPageEvents ( ) ;
self . _bindToggleHandlers ( ) ;
} ,
_addTransitionClass : function ( ) {
var tclass = this . options . transition ;
if ( tclass && tclass !== "none" ) {
// use appropriate slide for header or footer
if ( tclass === "slide" ) {
tclass = this . element . is ( ".ui-header" ) ? "slidedown" : "slideup" ;
}
this . element . addClass ( tclass ) ;
}
} ,
_bindPageEvents : function ( ) {
this . _thisPage = this . element . closest ( ".ui-page" ) ;
//page event bindings
// Fixed toolbars require page zoom to be disabled, otherwise usability issues crop up
// This method is meant to disable zoom while a fixed-positioned toolbar page is visible
this . _on ( this . _thisPage , {
"pagebeforeshow" : "_handlePageBeforeShow" ,
"webkitAnimationStart" : "_handleAnimationStart" ,
"animationstart" : "_handleAnimationStart" ,
"updatelayout" : "_handleAnimationStart" ,
"pageshow" : "_handlePageShow" ,
"pagebeforehide" : "_handlePageBeforeHide"
} ) ;
} ,
_handlePageBeforeShow : function ( ) {
var o = this . options ;
if ( o . disablePageZoom ) {
$ . mobile . zoom . disable ( true ) ;
}
if ( ! o . visibleOnPageShow ) {
this . hide ( true ) ;
}
} ,
_handleAnimationStart : function ( ) {
if ( this . options . updatePagePadding ) {
this . updatePagePadding ( this . _thisPage ) ;
}
} ,
_handlePageShow : function ( ) {
this . updatePagePadding ( this . _thisPage ) ;
if ( this . options . updatePagePadding ) {
this . _on ( $ . mobile . window , { "throttledresize" : "updatePagePadding" } ) ;
}
} ,
_handlePageBeforeHide : function ( e , ui ) {
var o = this . options ;
if ( o . disablePageZoom ) {
$ . mobile . zoom . enable ( true ) ;
}
if ( o . updatePagePadding ) {
this . _off ( $ . mobile . window , "throttledresize" ) ;
}
if ( o . trackPersistentToolbars ) {
var thisFooter = $ ( ".ui-footer-fixed:jqmData(id)" , this . _thisPage ) ,
thisHeader = $ ( ".ui-header-fixed:jqmData(id)" , this . _thisPage ) ,
nextFooter = thisFooter . length && ui . nextPage && $ ( ".ui-footer-fixed:jqmData(id='" + thisFooter . jqmData ( "id" ) + "')" , ui . nextPage ) || $ ( ) ,
nextHeader = thisHeader . length && ui . nextPage && $ ( ".ui-header-fixed:jqmData(id='" + thisHeader . jqmData ( "id" ) + "')" , ui . nextPage ) || $ ( ) ;
if ( nextFooter . length || nextHeader . length ) {
nextFooter . add ( nextHeader ) . appendTo ( $ . mobile . pageContainer ) ;
ui . nextPage . one ( "pageshow" , function ( ) {
nextHeader . prependTo ( this ) ;
nextFooter . appendTo ( this ) ;
} ) ;
}
}
} ,
_visible : true ,
// This will set the content element's top or bottom padding equal to the toolbar's height
updatePagePadding : function ( tbPage ) {
var $el = this . element ,
header = $el . is ( ".ui-header" ) ,
pos = parseFloat ( $el . css ( header ? "top" : "bottom" ) ) ;
// This behavior only applies to "fixed", not "fullscreen"
if ( this . options . fullscreen ) { return ; }
tbPage = tbPage || this . _thisPage || $el . closest ( ".ui-page" ) ;
$ ( tbPage ) . css ( "padding-" + ( header ? "top" : "bottom" ) , $el . outerHeight ( ) + pos ) ;
} ,
_useTransition : function ( notransition ) {
var $win = $ . mobile . window ,
$el = this . element ,
scroll = $win . scrollTop ( ) ,
elHeight = $el . height ( ) ,
pHeight = $el . closest ( ".ui-page" ) . height ( ) ,
viewportHeight = $ . mobile . getScreenHeight ( ) ,
tbtype = $el . is ( ":jqmData(role='header')" ) ? "header" : "footer" ;
return ! notransition &&
( this . options . transition && this . options . transition !== "none" &&
(
( tbtype === "header" && ! this . options . fullscreen && scroll > elHeight ) ||
( tbtype === "footer" && ! this . options . fullscreen && scroll + viewportHeight < pHeight - elHeight )
) || this . options . fullscreen
) ;
} ,
show : function ( notransition ) {
var hideClass = "ui-fixed-hidden" ,
$el = this . element ;
if ( this . _useTransition ( notransition ) ) {
$el
. removeClass ( "out " + hideClass )
. addClass ( "in" )
. animationComplete ( function ( ) {
$el . removeClass ( 'in' ) ;
} ) ;
}
else {
$el . removeClass ( hideClass ) ;
}
this . _visible = true ;
} ,
hide : function ( notransition ) {
var hideClass = "ui-fixed-hidden" ,
$el = this . element ,
// if it's a slide transition, our new transitions need the reverse class as well to slide outward
outclass = "out" + ( this . options . transition === "slide" ? " reverse" : "" ) ;
if ( this . _useTransition ( notransition ) ) {
$el
. addClass ( outclass )
. removeClass ( "in" )
. animationComplete ( function ( ) {
$el . addClass ( hideClass ) . removeClass ( outclass ) ;
} ) ;
}
else {
$el . addClass ( hideClass ) . removeClass ( outclass ) ;
}
this . _visible = false ;
} ,
toggle : function ( ) {
this [ this . _visible ? "hide" : "show" ] ( ) ;
} ,
_bindToggleHandlers : function ( ) {
var self = this , delay ,
o = self . options ,
$el = self . element ;
// tap toggle
$el . closest ( ".ui-page" )
. bind ( "vclick" , function ( e ) {
if ( o . tapToggle && ! $ ( e . target ) . closest ( o . tapToggleBlacklist ) . length ) {
self . toggle ( ) ;
}
} )
. bind ( "focusin focusout" , function ( e ) {
//this hides the toolbars on a keyboard pop to give more screen room and prevent ios bug which
//positions fixed toolbars in the middle of the screen on pop if the input is near the top or
//bottom of the screen addresses issues #4410 Footer navbar moves up when clicking on a textbox in an Android environment
//and issue #4113 Header and footer change their position after keyboard popup - iOS
//and issue #4410 Footer navbar moves up when clicking on a textbox in an Android environment
if ( screen . width < 1025 && $ ( e . target ) . is ( o . hideDuringFocus ) && ! $ ( e . target ) . closest ( ".ui-header-fixed, .ui-footer-fixed" ) . length ) {
//Fix for issue #4724 Moving through form in Mobile Safari with "Next" and "Previous" system
//controls causes fixed position, tap-toggle false Header to reveal itself
if ( e . type === "focusout" && ! self . _visible ) {
//wait for the stack to unwind and see if we have jumped to another input
delay = setTimeout ( function ( ) {
self . show ( ) ;
} , 0 ) ;
} else if ( e . type === "focusin" && self . _visible ) {
//if we have jumped to another input clear the time out to cancel the show.
clearTimeout ( delay ) ;
self . hide ( ) ;
}
}
} ) ;
} ,
_destroy : function ( ) {
var $el = this . element ,
header = $el . is ( ".ui-header" ) ;
$el . closest ( ".ui-page" ) . css ( "padding-" + ( header ? "top" : "bottom" ) , "" ) ;
$el . removeClass ( "ui-header-fixed ui-footer-fixed ui-header-fullscreen ui-footer-fullscreen in out fade slidedown slideup ui-fixed-hidden" ) ;
$el . closest ( ".ui-page" ) . removeClass ( "ui-page-header-fixed ui-page-footer-fixed ui-page-header-fullscreen ui-page-footer-fullscreen" ) ;
}
} ) ;
//auto self-init widgets
$ . mobile . document
. bind ( "pagecreate create" , function ( e ) {
// DEPRECATED in 1.1: support for data-fullscreen=true|false on the page element.
// This line ensures it still works, but we recommend moving the attribute to the toolbars themselves.
if ( $ ( e . target ) . jqmData ( "fullscreen" ) ) {
$ ( $ . mobile . fixedtoolbar . prototype . options . initSelector , e . target ) . not ( ":jqmData(fullscreen)" ) . jqmData ( "fullscreen" , true ) ;
}
$ . mobile . fixedtoolbar . prototype . enhanceWithin ( e . target ) ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ . widget ( "mobile.fixedtoolbar" , $ . mobile . fixedtoolbar , {
_create : function ( ) {
this . _super ( ) ;
this . _workarounds ( ) ;
} ,
//check the browser and version and run needed workarounds
_workarounds : function ( ) {
var ua = navigator . userAgent ,
platform = navigator . platform ,
// Rendering engine is Webkit, and capture major version
wkmatch = ua . match ( /AppleWebKit\/([0-9]+)/ ) ,
wkversion = ! ! wkmatch && wkmatch [ 1 ] ,
os = null ,
self = this ;
//set the os we are working in if it dosent match one with workarounds return
if ( platform . indexOf ( "iPhone" ) > - 1 || platform . indexOf ( "iPad" ) > - 1 || platform . indexOf ( "iPod" ) > - 1 ) {
os = "ios" ;
} else if ( ua . indexOf ( "Android" ) > - 1 ) {
os = "android" ;
} else {
return ;
}
//check os version if it dosent match one with workarounds return
if ( os === "ios" ) {
//iOS workarounds
self . _bindScrollWorkaround ( ) ;
} else if ( os === "android" && wkversion && wkversion < 534 ) {
//Android 2.3 run all Android 2.3 workaround
self . _bindScrollWorkaround ( ) ;
self . _bindListThumbWorkaround ( ) ;
} else {
return ;
}
} ,
//Utility class for checking header and footer positions relative to viewport
_viewportOffset : function ( ) {
var $el = this . element ,
header = $el . is ( ".ui-header" ) ,
offset = Math . abs ( $el . offset ( ) . top - $ . mobile . window . scrollTop ( ) ) ;
if ( ! header ) {
offset = Math . round ( offset - $ . mobile . window . height ( ) + $el . outerHeight ( ) ) - 60 ;
}
return offset ;
} ,
//bind events for _triggerRedraw() function
_bindScrollWorkaround : function ( ) {
var self = this ;
//bind to scrollstop and check if the toolbars are correctly positioned
this . _on ( $ . mobile . window , { scrollstop : function ( ) {
var viewportOffset = self . _viewportOffset ( ) ;
//check if the header is visible and if its in the right place
if ( viewportOffset > 2 && self . _visible ) {
self . _triggerRedraw ( ) ;
}
} } ) ;
} ,
//this addresses issue #4250 Persistent footer instability in v1.1 with long select lists in Android 2.3.3
//and issue #3748 Android 2.x: Page transitions broken when fixed toolbars used
//the absolutely positioned thumbnail in a list view causes problems with fixed position buttons above in a nav bar
//setting the li's to -webkit-transform:translate3d(0,0,0); solves this problem to avoide potential issues in other
//platforms we scope this with the class ui-android-2x-fix
_bindListThumbWorkaround : function ( ) {
this . element . closest ( ".ui-page" ) . addClass ( "ui-android-2x-fixed" ) ;
} ,
//this addresses issues #4337 Fixed header problem after scrolling content on iOS and Android
//and device bugs project issue #1 Form elements can lose click hit area in position: fixed containers.
//this also addresses not on fixed toolbars page in docs
//adding 1px of padding to the bottom then removing it causes a "redraw"
//which positions the toolbars correctly (they will always be visually correct)
_triggerRedraw : function ( ) {
var paddingBottom = parseFloat ( $ ( ".ui-page-active" ) . css ( "padding-bottom" ) ) ;
//trigger page redraw to fix incorrectly positioned fixed elements
$ ( ".ui-page-active" ) . css ( "padding-bottom" , ( paddingBottom + 1 ) + "px" ) ;
//if the padding is reset with out a timeout the reposition will not occure.
//this is independant of JQM the browser seems to need the time to react.
setTimeout ( function ( ) {
$ ( ".ui-page-active" ) . css ( "padding-bottom" , paddingBottom + "px" ) ;
} , 0 ) ;
} ,
destroy : function ( ) {
this . _super ( ) ;
//Remove the class we added to the page previously in android 2.x
this . element . closest ( ".ui-page-active" ) . removeClass ( "ui-android-2x-fix" ) ;
}
} ) ;
} ) ( jQuery ) ;
( function ( $ , window ) {
$ . mobile . iosorientationfixEnabled = true ;
// This fix addresses an iOS bug, so return early if the UA claims it's something else.
var ua = navigator . userAgent ;
if ( ! ( /iPhone|iPad|iPod/ . test ( navigator . platform ) && /OS [1-5]_[0-9_]* like Mac OS X/i . test ( ua ) && ua . indexOf ( "AppleWebKit" ) > - 1 ) ) {
$ . mobile . iosorientationfixEnabled = false ;
return ;
}
var zoom = $ . mobile . zoom ,
evt , x , y , z , aig ;
function checkTilt ( e ) {
evt = e . originalEvent ;
aig = evt . accelerationIncludingGravity ;
x = Math . abs ( aig . x ) ;
y = Math . abs ( aig . y ) ;
z = Math . abs ( aig . z ) ;
// If portrait orientation and in one of the danger zones
if ( ! window . orientation && ( x > 7 || ( ( z > 6 && y < 8 || z < 8 && y > 6 ) && x > 5 ) ) ) {
if ( zoom . enabled ) {
zoom . disable ( ) ;
}
} else if ( ! zoom . enabled ) {
zoom . enable ( ) ;
}
}
$ . mobile . document . on ( "mobileinit" , function ( ) {
if ( $ . mobile . iosorientationfixEnabled ) {
$ . mobile . window
. bind ( "orientationchange.iosorientationfix" , zoom . enable )
. bind ( "devicemotion.iosorientationfix" , checkTilt ) ;
}
} ) ;
} ( jQuery , this ) ) ;
( function ( $ , window , undefined ) {
var $html = $ ( "html" ) ,
$head = $ ( "head" ) ,
$window = $ . mobile . window ;
//remove initial build class (only present on first pageshow)
function hideRenderingClass ( ) {
$html . removeClass ( "ui-mobile-rendering" ) ;
}
// trigger mobileinit event - useful hook for configuring $.mobile settings before they're used
$ ( window . document ) . trigger ( "mobileinit" ) ;
// support conditions
// if device support condition(s) aren't met, leave things as they are -> a basic, usable experience,
// otherwise, proceed with the enhancements
if ( ! $ . mobile . gradeA ( ) ) {
return ;
}
// override ajaxEnabled on platforms that have known conflicts with hash history updates
// or generally work better browsing in regular http for full page refreshes (BB5, Opera Mini)
if ( $ . mobile . ajaxBlacklist ) {
$ . mobile . ajaxEnabled = false ;
}
// Add mobile, initial load "rendering" classes to docEl
$html . addClass ( "ui-mobile ui-mobile-rendering" ) ;
// This is a fallback. If anything goes wrong (JS errors, etc), or events don't fire,
// this ensures the rendering class is removed after 5 seconds, so content is visible and accessible
setTimeout ( hideRenderingClass , 5000 ) ;
$ . extend ( $ . mobile , {
// find and enhance the pages in the dom and transition to the first page.
initializePage : function ( ) {
// find present pages
var path = $ . mobile . path ,
$pages = $ ( ":jqmData(role='page'), :jqmData(role='dialog')" ) ,
hash = path . stripHash ( path . stripQueryParams ( path . parseLocation ( ) . hash ) ) ,
hashPage = document . getElementById ( hash ) ;
// if no pages are found, create one with body's inner html
if ( ! $pages . length ) {
$pages = $ ( "body" ) . wrapInner ( "<div data-" + $ . mobile . ns + "role='page'></div>" ) . children ( 0 ) ;
}
// add dialogs, set data-url attrs
$pages . each ( function ( ) {
var $this = $ ( this ) ;
// unless the data url is already set set it to the pathname
if ( ! $this . jqmData ( "url" ) ) {
$this . attr ( "data-" + $ . mobile . ns + "url" , $this . attr ( "id" ) || location . pathname + location . search ) ;
}
} ) ;
// define first page in dom case one backs out to the directory root (not always the first page visited, but defined as fallback)
$ . mobile . firstPage = $pages . first ( ) ;
// define page container
$ . mobile . pageContainer = $ . mobile . firstPage . parent ( ) . addClass ( "ui-mobile-viewport" ) ;
// alert listeners that the pagecontainer has been determined for binding
// to events triggered on it
$window . trigger ( "pagecontainercreate" ) ;
// cue page loading message
$ . mobile . showPageLoadingMsg ( ) ;
//remove initial build class (only present on first pageshow)
hideRenderingClass ( ) ;
// if hashchange listening is disabled, there's no hash deeplink,
// the hash is not valid (contains more than one # or does not start with #)
// or there is no page with that hash, change to the first page in the DOM
// Remember, however, that the hash can also be a path!
if ( ! ( $ . mobile . hashListeningEnabled &&
$ . mobile . path . isHashValid ( location . hash ) &&
( $ ( hashPage ) . is ( ':jqmData(role="page")' ) ||
$ . mobile . path . isPath ( hash ) ||
hash === $ . mobile . dialogHashKey ) ) ) {
// Store the initial destination
if ( $ . mobile . path . isHashValid ( location . hash ) ) {
$ . mobile . urlHistory . initialDst = hash . replace ( "#" , "" ) ;
}
// make sure to set initial popstate state if it exists
// so that navigation back to the initial page works properly
if ( $ . event . special . navigate . isPushStateEnabled ( ) ) {
$ . mobile . navigate . navigator . squash ( path . parseLocation ( ) . href ) ;
}
$ . mobile . changePage ( $ . mobile . firstPage , {
transition : "none" ,
reverse : true ,
changeHash : false ,
fromHashChange : true
} ) ;
} else {
// trigger hashchange or navigate to squash and record the correct
// history entry for an initial hash path
if ( ! $ . event . special . navigate . isPushStateEnabled ( ) ) {
$window . trigger ( "hashchange" , [ true ] ) ;
} else {
// TODO figure out how to simplify this interaction with the initial history entry
// at the bottom js/navigate/navigate.js
$ . mobile . navigate . history . stack = [ ] ;
$ . mobile . navigate ( $ . mobile . path . isPath ( location . hash ) ? location . hash : location . href ) ;
}
}
}
} ) ;
// initialize events now, after mobileinit has occurred
$ . mobile . navreadyDeferred . resolve ( ) ;
// check which scrollTop value should be used by scrolling to 1 immediately at domready
// then check what the scroll top is. Android will report 0... others 1
// note that this initial scroll won't hide the address bar. It's just for the check.
$ ( function ( ) {
window . scrollTo ( 0 , 1 ) ;
// if defaultHomeScroll hasn't been set yet, see if scrollTop is 1
// it should be 1 in most browsers, but android treats 1 as 0 (for hiding addr bar)
// so if it's 1, use 0 from now on
$ . mobile . defaultHomeScroll = ( ! $ . support . scrollTop || $ . mobile . window . scrollTop ( ) === 1 ) ? 0 : 1 ;
//dom-ready inits
if ( $ . mobile . autoInitializePage ) {
$ . mobile . initializePage ( ) ;
}
// window load event
// hide iOS browser chrome on load
$window . load ( $ . mobile . silentScroll ) ;
if ( ! $ . support . cssPointerEvents ) {
// IE and Opera don't support CSS pointer-events: none that we use to disable link-based buttons
// by adding the 'ui-disabled' class to them. Using a JavaScript workaround for those browser.
// https://github.com/jquery/jquery-mobile/issues/3558
$ . mobile . document . delegate ( ".ui-disabled" , "vclick" ,
function ( e ) {
e . preventDefault ( ) ;
e . stopImmediatePropagation ( ) ;
}
) ;
}
} ) ;
} ( jQuery , this ) ) ;
} ) ) ;