2015-06-19 22:31:44 -07:00
/ *
* jQuery Mobile v1 . 4.5
* http : //jquerymobile.com
*
* Copyright 2010 , 2014 jQuery Foundation , Inc . and other contributors
* Released under the MIT license .
* http : //jquery.org/license
*
* /
( function ( root , doc , factory ) {
2015-07-27 11:18:10 -07:00
// Browser globals
factory ( root . jQuery , root , doc ) ;
} ( this , document , function ( jQuery , window , document , undefined ) { / * !
2015-06-19 22:31:44 -07:00
* jQuery hashchange event - v1 . 3 - 7 / 21 / 2010
* http : //benalman.com/projects/jquery-hashchange-plugin/
*
* Copyright ( c ) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses .
* http : //benalman.com/about/license/
* /
// 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 ) {
'$:nomunge' ; // Used by YUI compressor.
// 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 ) ;
} ;
return self ;
} ) ( ) ;
} ) ( jQuery , this ) ;
( function ( $ ) {
$ . mobile = { } ;
} ( jQuery ) ) ;
( function ( $ , window , undefined ) {
$ . extend ( $ . mobile , {
// Version of the jQuery Mobile Framework
version : "1.4.5" ,
// Deprecated and no longer used in 1.4 remove in 1.5
// Define the url parameter used for referencing widget-generated sub-pages.
// Translates to example.html&ui-page=subpageIdentifier
// hash segment before &ui-page= is used to make Ajax request
subPageUrlKey : "ui-page" ,
hideUrlBar : true ,
// Keepnative Selector
keepNative : ":jqmData(role='none'), :jqmData(role='nojs')" ,
// Deprecated in 1.4 remove in 1.5
// Class assigned to page currently in view, and during transitions
activePageClass : "ui-page-active" ,
// Deprecated in 1.4 remove in 1.5
// Class used for "active" button state, from CSS framework
activeBtnClass : "ui-btn-active" ,
// Deprecated in 1.4 remove in 1.5
// Class used for "focus" form element state, from CSS framework
focusClass : "ui-focus" ,
// Automatically handle clicks and form submissions through Ajax, when same-domain
ajaxEnabled : true ,
// Automatically load and show pages based on location.hash
hashListeningEnabled : true ,
// disable to prevent jquery from bothering with links
linkBindingEnabled : true ,
// Set default page transition - 'none' for no transitions
defaultPageTransition : "fade" ,
// Set maximum window width for transitions to apply - 'false' for no limit
maxTransitionWidth : false ,
// Minimum scroll distance that will be remembered when returning to a page
// Deprecated remove in 1.5
minScrollBack : 0 ,
// Set default dialog transition - 'none' for no transitions
defaultDialogTransition : "pop" ,
// Error response message - appears when an Ajax page request fails
pageLoadErrorMessage : "Error Loading Page" ,
// For error messages, which theme does the box use?
pageLoadErrorMessageTheme : "a" ,
// replace calls to window.history.back with phonegaps navigation helper
// where it is provided on the window object
phonegapNavigationEnabled : false ,
//automatically initialize the DOM when it's ready
autoInitializePage : true ,
pushStateEnabled : true ,
// allows users to opt in to ignoring content by marking a parent element as
// data-ignored
ignoreContentEnabled : false ,
buttonMarkup : {
hoverDelay : 200
} ,
// disable the alteration of the dynamic base tag or links in the case
// that a dynamic base tag isn't supported
dynamicBaseEnabled : true ,
// default the property to remove dependency on assignment in init module
pageContainer : $ ( ) ,
//enable cross-domain page support
allowCrossDomainPages : false ,
dialogHashKey : "&ui-state=dialog"
} ) ;
} ) ( jQuery , this ) ;
( function ( $ , window , undefined ) {
var nsNormalizeDict = { } ,
oldFind = $ . find ,
rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/ ,
jqmDataRE = /:jqmData\(([^)]*)\)/g ;
$ . extend ( $ . mobile , {
// Namespace used framework-wide for data-attrs. Default is no namespace
ns : "" ,
// Retrieve an attribute from an element and perform some massaging of the value
getAttribute : function ( element , key ) {
var data ;
element = element . jquery ? element [ 0 ] : element ;
if ( element && element . getAttribute ) {
data = element . getAttribute ( "data-" + $ . mobile . ns + key ) ;
}
// Copied from core's src/data.js:dataAttr()
// Convert from a string to a proper data type
try {
data = data === "true" ? true :
data === "false" ? false :
data === "null" ? null :
// Only convert to a number if it doesn't change the string
+ data + "" === data ? + data :
rbrace . test ( data ) ? JSON . parse ( data ) :
data ;
} catch ( err ) { }
return data ;
} ,
// Expose our cache for testing purposes.
nsNormalizeDict : nsNormalizeDict ,
// Take a data attribute property, prepend the namespace
// and then camel case the attribute string. Add the result
// to our nsNormalizeDict so we don't have to do this again.
nsNormalize : function ( prop ) {
return nsNormalizeDict [ prop ] ||
( nsNormalizeDict [ prop ] = $ . camelCase ( $ . mobile . ns + prop ) ) ;
} ,
// Find the closest javascript page element to gather settings data jsperf test
// http://jsperf.com/single-complex-selector-vs-many-complex-selectors/edit
// possibly naive, but it shows that the parsing overhead for *just* the page selector vs
// the page and dialog selector is negligable. This could probably be speed up by
// doing a similar parent node traversal to the one found in the inherited theme code above
closestPageData : function ( $target ) {
return $target
. closest ( ":jqmData(role='page'), :jqmData(role='dialog')" )
. data ( "mobile-page" ) ;
}
} ) ;
// Mobile version of data and removeData and hasData methods
// ensures all data is set and retrieved using jQuery Mobile's data namespace
$ . fn . jqmData = function ( prop , value ) {
var result ;
if ( typeof prop !== "undefined" ) {
if ( prop ) {
prop = $ . mobile . nsNormalize ( prop ) ;
}
// undefined is permitted as an explicit input for the second param
// in this case it returns the value and does not set it to undefined
if ( arguments . length < 2 || value === undefined ) {
result = this . data ( prop ) ;
} else {
result = this . data ( prop , value ) ;
}
}
return result ;
} ;
$ . jqmData = function ( elem , prop , value ) {
var result ;
if ( typeof prop !== "undefined" ) {
result = $ . data ( elem , prop ? $ . mobile . nsNormalize ( prop ) : prop , value ) ;
}
return result ;
} ;
$ . fn . jqmRemoveData = function ( prop ) {
return this . removeData ( $ . mobile . nsNormalize ( prop ) ) ;
} ;
$ . jqmRemoveData = function ( elem , prop ) {
return $ . removeData ( elem , $ . mobile . nsNormalize ( prop ) ) ;
} ;
$ . find = function ( selector , context , ret , extra ) {
if ( selector . indexOf ( ":jqmData" ) > - 1 ) {
selector = selector . replace ( jqmDataRE , "[data-" + ( $ . mobile . ns || "" ) + "$1]" ) ;
}
return oldFind . call ( this , selector , context , ret , extra ) ;
} ;
$ . extend ( $ . find , oldFind ) ;
} ) ( jQuery , this ) ;
/ * !
* jQuery UI Core c0ab71056b936627e8a7821f03c044aec6280a40
* http : //jqueryui.com
*
* Copyright 2013 jQuery Foundation and other contributors
* Released under the MIT license .
* http : //jquery.org/license
*
* http : //api.jqueryui.com/category/ui-core/
* /
( function ( $ , undefined ) {
var uuid = 0 ,
runiqueId = /^ui-id-\d+$/ ;
// $.ui might exist from components with no dependencies, e.g., $.ui.position
$ . ui = $ . ui || { } ;
$ . extend ( $ . ui , {
version : "c0ab71056b936627e8a7821f03c044aec6280a40" ,
keyCode : {
BACKSPACE : 8 ,
COMMA : 188 ,
DELETE : 46 ,
DOWN : 40 ,
END : 35 ,
ENTER : 13 ,
ESCAPE : 27 ,
HOME : 36 ,
LEFT : 37 ,
PAGE _DOWN : 34 ,
PAGE _UP : 33 ,
PERIOD : 190 ,
RIGHT : 39 ,
SPACE : 32 ,
TAB : 9 ,
UP : 38
}
} ) ;
// plugins
$ . fn . extend ( {
focus : ( function ( orig ) {
return function ( delay , fn ) {
return typeof delay === "number" ?
this . each ( function ( ) {
var elem = this ;
setTimeout ( function ( ) {
$ ( elem ) . focus ( ) ;
if ( fn ) {
fn . call ( elem ) ;
}
} , delay ) ;
} ) :
orig . apply ( this , arguments ) ;
} ;
} ) ( $ . fn . focus ) ,
scrollParent : function ( ) {
var scrollParent ;
if ( ( $ . ui . ie && ( /(static|relative)/ ) . test ( this . css ( "position" ) ) ) || ( /absolute/ ) . test ( this . css ( "position" ) ) ) {
scrollParent = this . parents ( ) . filter ( function ( ) {
return ( /(relative|absolute|fixed)/ ) . test ( $ . css ( this , "position" ) ) && ( /(auto|scroll)/ ) . test ( $ . css ( this , "overflow" ) + $ . css ( this , "overflow-y" ) + $ . css ( this , "overflow-x" ) ) ;
} ) . eq ( 0 ) ;
} else {
scrollParent = this . parents ( ) . filter ( function ( ) {
return ( /(auto|scroll)/ ) . test ( $ . css ( this , "overflow" ) + $ . css ( this , "overflow-y" ) + $ . css ( this , "overflow-x" ) ) ;
} ) . eq ( 0 ) ;
}
return ( /fixed/ ) . test ( this . css ( "position" ) ) || ! scrollParent . length ? $ ( this [ 0 ] . ownerDocument || document ) : scrollParent ;
} ,
uniqueId : function ( ) {
return this . each ( function ( ) {
if ( ! this . id ) {
this . id = "ui-id-" + ( ++ uuid ) ;
}
} ) ;
} ,
removeUniqueId : function ( ) {
return this . each ( function ( ) {
if ( runiqueId . test ( this . id ) ) {
$ ( this ) . removeAttr ( "id" ) ;
}
} ) ;
}
} ) ;
// selectors
function focusable ( element , isTabIndexNotNaN ) {
var map , mapName , img ,
nodeName = element . nodeName . toLowerCase ( ) ;
if ( "area" === nodeName ) {
map = element . parentNode ;
mapName = map . name ;
if ( ! element . href || ! mapName || map . nodeName . toLowerCase ( ) !== "map" ) {
return false ;
}
img = $ ( "img[usemap=#" + mapName + "]" ) [ 0 ] ;
return ! ! img && visible ( img ) ;
}
return ( /input|select|textarea|button|object/ . test ( nodeName ) ?
! element . disabled :
"a" === nodeName ?
element . href || isTabIndexNotNaN :
isTabIndexNotNaN ) &&
// the element and all of its ancestors must be visible
visible ( element ) ;
}
function visible ( element ) {
return $ . expr . filters . visible ( element ) &&
! $ ( element ) . parents ( ) . addBack ( ) . filter ( function ( ) {
return $ . css ( this , "visibility" ) === "hidden" ;
} ) . length ;
}
$ . extend ( $ . expr [ ":" ] , {
data : $ . expr . createPseudo ?
$ . expr . createPseudo ( function ( dataName ) {
return function ( elem ) {
return ! ! $ . data ( elem , dataName ) ;
} ;
} ) :
// support: jQuery <1.8
function ( elem , i , match ) {
return ! ! $ . data ( elem , match [ 3 ] ) ;
} ,
focusable : function ( element ) {
return focusable ( element , ! isNaN ( $ . attr ( element , "tabindex" ) ) ) ;
} ,
tabbable : function ( element ) {
var tabIndex = $ . attr ( element , "tabindex" ) ,
isTabIndexNaN = isNaN ( tabIndex ) ;
return ( isTabIndexNaN || tabIndex >= 0 ) && focusable ( element , ! isTabIndexNaN ) ;
}
} ) ;
// deprecated
$ . ui . ie = ! ! /msie [\w.]+/ . exec ( navigator . userAgent . toLowerCase ( ) ) ;
$ . support . selectstart = "onselectstart" in document . createElement ( "div" ) ;
$ . fn . extend ( {
disableSelection : function ( ) {
return this . bind ( ( $ . support . selectstart ? "selectstart" : "mousedown" ) +
".ui-disableSelection" , function ( event ) {
event . preventDefault ( ) ;
} ) ;
} ,
enableSelection : function ( ) {
return this . unbind ( ".ui-disableSelection" ) ;
} ,
zIndex : function ( zIndex ) {
if ( zIndex !== undefined ) {
return this . css ( "zIndex" , zIndex ) ;
}
if ( this . length ) {
var elem = $ ( this [ 0 ] ) , position , value ;
while ( elem . length && elem [ 0 ] !== document ) {
// Ignore z-index if position is set to a value where z-index is ignored by the browser
// This makes behavior of this function consistent across browsers
// WebKit always returns auto if the element is positioned
position = elem . css ( "position" ) ;
if ( position === "absolute" || position === "relative" || position === "fixed" ) {
// IE returns 0 when zIndex is not specified
// other browsers return a string
// we ignore the case of nested elements with an explicit value of 0
// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
value = parseInt ( elem . css ( "zIndex" ) , 10 ) ;
if ( ! isNaN ( value ) && value !== 0 ) {
return value ;
}
}
elem = elem . parent ( ) ;
}
}
return 0 ;
}
} ) ;
// $.ui.plugin is deprecated. Use $.widget() extensions instead.
$ . ui . plugin = {
add : function ( module , option , set ) {
var i ,
proto = $ . ui [ module ] . prototype ;
for ( i in set ) {
proto . plugins [ i ] = proto . plugins [ i ] || [ ] ;
proto . plugins [ i ] . push ( [ option , set [ i ] ] ) ;
}
} ,
call : function ( instance , name , args , allowDisconnected ) {
var i ,
set = instance . plugins [ name ] ;
if ( ! set ) {
return ;
}
if ( ! allowDisconnected && ( ! instance . element [ 0 ] . parentNode || instance . element [ 0 ] . parentNode . nodeType === 11 ) ) {
return ;
}
for ( i = 0 ; i < set . length ; i ++ ) {
if ( instance . options [ set [ i ] [ 0 ] ] ) {
set [ i ] [ 1 ] . apply ( instance . element , args ) ;
}
}
}
} ;
} ) ( jQuery ) ;
( function ( $ , window , undefined ) {
2015-09-03 09:39:24 -07:00
function parentWithClass ( elem , className ) {
while ( ! elem . classList || ! elem . classList . contains ( className ) ) {
elem = elem . parentNode ;
if ( ! elem ) {
return null ;
}
}
return elem ;
}
$ . extend ( $ . mobile , {
2015-06-19 22:31:44 -07:00
// define the window and the document objects
window : $ ( window ) ,
document : $ ( document ) ,
// TODO: Remove and use $.ui.keyCode directly
keyCode : $ . ui . keyCode ,
// Place to store various widget extensions
behaviors : { } ,
// Scroll page vertically: scroll to 0 to hide iOS address bar, or pass a Y value
silentScroll : function ( ypos ) {
if ( $ . type ( ypos ) !== "number" ) {
ypos = $ . mobile . defaultHomeScroll ;
}
2015-09-01 12:18:25 -07:00
window . scrollTo ( 0 , ypos ) ;
2015-06-19 22:31:44 -07:00
} ,
2015-09-05 09:58:27 -07:00
getClosestBaseUrl : function ( ele ) {
var page = parentWithClass ( ele , 'ui-page' ) ;
2015-06-19 22:31:44 -07:00
// Find the closest page and extract out its url.
2015-09-05 09:58:27 -07:00
var url = ( page ? page . getAttribute ( "data-url" ) : null ) ,
2015-06-19 22:31:44 -07:00
base = $ . mobile . path . documentBase . hrefNoHash ;
if ( ! $ . mobile . dynamicBaseEnabled || ! url || ! $ . mobile . path . isPath ( url ) ) {
url = base ;
}
return $ . mobile . path . makeUrlAbsolute ( url , base ) ;
} ,
removeActiveLinkClass : function ( forceRemoval ) {
} ,
// DEPRECATED in 1.4
// Find the closest parent with a theme class on it. Note that
// we are not using $.fn.closest() on purpose here because this
// method gets called quite a bit and we need it to be as fast
// as possible.
getInheritedTheme : function ( el , defaultTheme ) {
var e = el [ 0 ] ,
ltr = "" ,
re = /ui-(bar|body|overlay)-([a-z])\b/ ,
c , m ;
while ( e ) {
c = e . className || "" ;
if ( c && ( m = re . exec ( c ) ) && ( ltr = m [ 2 ] ) ) {
// We found a parent with a theme class
// on it so bail from this loop.
break ;
}
e = e . parentNode ;
}
// Return the theme letter we found, if none, return the
// specified default.
return ltr || defaultTheme || "a" ;
} ,
enhanceable : function ( elements ) {
return this . haveParents ( elements , "enhance" ) ;
} ,
hijackable : function ( elements ) {
return this . haveParents ( elements , "ajax" ) ;
} ,
haveParents : function ( elements , attr ) {
if ( ! $ . mobile . ignoreContentEnabled ) {
return elements ;
}
var count = elements . length ,
$newSet = $ ( ) ,
e , $element , excluded ,
i , c ;
for ( i = 0 ; i < count ; i ++ ) {
$element = elements . eq ( i ) ;
excluded = false ;
e = elements [ i ] ;
while ( e ) {
c = e . getAttribute ? e . getAttribute ( "data-" + $ . mobile . ns + attr ) : "" ;
if ( c === "false" ) {
excluded = true ;
break ;
}
e = e . parentNode ;
}
if ( ! excluded ) {
$newSet = $newSet . add ( $element ) ;
}
}
return $newSet ;
} ,
getScreenHeight : function ( ) {
// Native innerHeight returns more accurate value for this across platforms,
// jQuery version is here as a normalized fallback for platforms like Symbian
return window . innerHeight || $ . mobile . window . height ( ) ;
} ,
//simply set the active page's minimum height to screen height, depending on orientation
resetActivePageHeight : function ( height ) {
} ,
2015-08-30 15:55:58 -07:00
loading : function ( ) {
2015-06-19 22:31:44 -07:00
}
} ) ;
$ . addDependents = function ( elem , newDependents ) {
var $elem = $ ( elem ) ,
dependents = $elem . jqmData ( "dependents" ) || $ ( ) ;
$elem . jqmData ( "dependents" , $ ( dependents ) . add ( newDependents ) ) ;
} ;
// plugins
$ . fn . extend ( {
removeWithDependents : function ( ) {
$ . removeWithDependents ( this ) ;
} ,
// Enhance child elements
enhanceWithin : function ( ) {
var index ,
widgetElements = { } ,
keepNative = $ . mobile . page . prototype . keepNativeSelector ( ) ,
that = this ;
// Add no js class to elements
if ( $ . mobile . nojs ) {
$ . mobile . nojs ( this ) ;
}
// Bind links for ajax nav
if ( $ . mobile . links ) {
$ . mobile . links ( this ) ;
}
2015-08-31 07:57:04 -07:00
var thisElem = this [ 0 ] ;
2015-06-19 22:31:44 -07:00
// Run buttonmarkup
if ( $ . fn . buttonMarkup ) {
2015-08-31 07:57:04 -07:00
$ ( thisElem . querySelectorAll ( $ . fn . buttonMarkup . initSelector ) ) . not ( keepNative ) . jqmEnhanceable ( ) . buttonMarkup ( ) ;
2015-06-19 22:31:44 -07:00
}
// Add classes for fieldContain
if ( $ . fn . fieldcontain ) {
2015-08-31 07:57:04 -07:00
$ ( thisElem . querySelectorAll ( "*[data-role='fieldcontain']" ) ) . not ( keepNative ) . jqmEnhanceable ( ) . fieldcontain ( ) ;
2015-06-19 22:31:44 -07:00
}
// Enhance widgets
$ . each ( $ . mobile . widgets , function ( name , constructor ) {
// If initSelector not false find elements
if ( constructor . initSelector ) {
// Filter elements that should not be enhanced based on parents
2015-08-31 07:57:04 -07:00
var elements = $ . mobile . enhanceable ( that . find ( constructor . initSelector ) ) ;
2015-06-19 22:31:44 -07:00
// If any matching elements remain filter ones with keepNativeSelector
if ( elements . length > 0 ) {
// $.mobile.page.prototype.keepNativeSelector is deprecated this is just for backcompat
// Switch to $.mobile.keepNative in 1.5 which is just a value not a function
elements = elements . not ( keepNative ) ;
}
// Enhance whatever is left
if ( elements . length > 0 ) {
widgetElements [ constructor . prototype . widgetName ] = elements ;
}
}
} ) ;
for ( index in widgetElements ) {
widgetElements [ index ] [ index ] ( ) ;
}
return this ;
} ,
addDependents : function ( newDependents ) {
$ . addDependents ( this , newDependents ) ;
} ,
// note that this helper doesn't attempt to handle the callback
// or setting of an html element's text, its only purpose is
// to return the html encoded version of the text in all cases. (thus the name)
getEncodedText : function ( ) {
return $ ( "<a>" ) . text ( this . text ( ) ) . html ( ) ;
} ,
// fluent helper function for the mobile namespaced equivalent
jqmEnhanceable : function ( ) {
return $ . mobile . enhanceable ( this ) ;
} ,
jqmHijackable : function ( ) {
return $ . mobile . hijackable ( this ) ;
}
} ) ;
$ . removeWithDependents = function ( nativeElement ) {
var element = $ ( nativeElement ) ;
( element . jqmData ( "dependents" ) || $ ( ) ) . remove ( ) ;
element . remove ( ) ;
} ;
$ . addDependents = function ( nativeElement , newDependents ) {
var element = $ ( nativeElement ) ,
dependents = element . jqmData ( "dependents" ) || $ ( ) ;
element . jqmData ( "dependents" , $ ( dependents ) . add ( newDependents ) ) ;
} ;
$ . find . matches = function ( expr , set ) {
return $ . find ( expr , null , null , set ) ;
} ;
$ . find . matchesSelector = function ( node , expr ) {
return $ . find ( expr , null , null , [ node ] ) . length > 0 ;
} ;
} ) ( jQuery , this ) ;
( function ( $ , undefined ) {
/*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */
window . matchMedia = window . matchMedia || ( function ( doc , undefined ) {
var bool ,
docElem = doc . documentElement ,
refNode = docElem . firstElementChild || docElem . firstChild ,
// fakeBody required for <FF4 when executed in <head>
fakeBody = doc . createElement ( "body" ) ,
div = doc . createElement ( "div" ) ;
div . id = "mq-test-1" ;
div . style . cssText = "position:absolute;top:-100em" ;
fakeBody . style . background = "none" ;
fakeBody . appendChild ( div ) ;
return function ( q ) {
div . innerHTML = "­<style media=\"" + q + "\"> #mq-test-1 { width: 42px; }</style>" ;
docElem . insertBefore ( fakeBody , refNode ) ;
bool = div . offsetWidth === 42 ;
docElem . removeChild ( fakeBody ) ;
return {
matches : bool ,
media : q
} ;
} ;
} ( document ) ) ;
// $.mobile.media uses matchMedia to return a boolean.
$ . mobile . media = function ( q ) {
return window . matchMedia ( q ) . matches ;
} ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
var support = {
touch : "ontouchend" in document
} ;
$ . mobile . support = $ . mobile . support || { } ;
$ . extend ( $ . support , support ) ;
$ . extend ( $ . mobile . support , support ) ;
} ( jQuery ) ) ;
( function ( $ , undefined ) {
$ . extend ( $ . support , {
orientation : "orientation" in window && "onorientationchange" in window
} ) ;
} ( jQuery ) ) ;
( function ( $ , undefined ) {
// thx Modernizr
function propExists ( prop ) {
var uc _prop = prop . charAt ( 0 ) . toUpperCase ( ) + prop . substr ( 1 ) ,
props = ( prop + " " + vendors . join ( uc _prop + " " ) + uc _prop ) . split ( " " ) ,
v ;
for ( v in props ) {
if ( fbCSS [ props [ v ] ] !== undefined ) {
return true ;
}
}
}
var fakeBody = $ ( "<body>" ) . prependTo ( "html" ) ,
fbCSS = fakeBody [ 0 ] . style ,
vendors = [ "Webkit" , "Moz" , "O" ] ,
webos = "palmGetResource" in window , //only used to rule out scrollTop
operamini = window . operamini && ( { } ) . toString . call ( window . operamini ) === "[object OperaMini]" ,
bb = window . blackberry && ! propExists ( "-webkit-transform" ) , //only used to rule out box shadow, as it's filled opaque on BB 5 and lower
nokiaLTE7 _3 ;
// inline SVG support test
function inlineSVG ( ) {
// Thanks Modernizr & Erik Dahlstrom
var w = window ,
svg = ! ! w . document . createElementNS && ! ! w . document . createElementNS ( "http://www.w3.org/2000/svg" , "svg" ) . createSVGRect && ! ( w . opera && navigator . userAgent . indexOf ( "Chrome" ) === - 1 ) ,
support = function ( data ) {
if ( ! ( data && svg ) ) {
$ ( "html" ) . addClass ( "ui-nosvg" ) ;
}
} ,
img = new w . Image ( ) ;
img . onerror = function ( ) {
support ( false ) ;
} ;
img . onload = function ( ) {
support ( img . width === 1 && img . height === 1 ) ;
} ;
img . src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==" ;
}
function transform3dTest ( ) {
var mqProp = "transform-3d" ,
// Because the `translate3d` test below throws false positives in Android:
ret = $ . mobile . media ( "(-" + vendors . join ( "-" + mqProp + "),(-" ) + "-" + mqProp + "),(" + mqProp + ")" ) ,
el , transforms , t ;
if ( ret ) {
return ! ! ret ;
}
el = document . createElement ( "div" ) ;
transforms = {
// We’ re omitting Opera for the time being; MS uses unprefixed.
"MozTransform" : "-moz-transform" ,
"transform" : "transform"
} ;
fakeBody . append ( el ) ;
for ( t in transforms ) {
if ( el . style [ t ] !== undefined ) {
el . style [ t ] = "translate3d( 100px, 1px, 1px )" ;
ret = window . getComputedStyle ( el ) . getPropertyValue ( transforms [ t ] ) ;
}
}
return ( ! ! ret && ret !== "none" ) ;
}
// Test for dynamic-updating base tag support ( allows us to avoid href,src attr rewriting )
function baseTagTest ( ) {
var fauxBase = location . protocol + "//" + location . host + location . pathname + "ui-dir/" ,
base = $ ( "head base" ) ,
fauxEle = null ,
href = "" ,
link , rebase ;
if ( ! base . length ) {
base = fauxEle = $ ( "<base>" , { "href" : fauxBase } ) . appendTo ( "head" ) ;
} else {
href = base . attr ( "href" ) ;
}
link = $ ( "<a href='testurl' />" ) . prependTo ( fakeBody ) ;
rebase = link [ 0 ] . href ;
base [ 0 ] . href = href || location . pathname ;
if ( fauxEle ) {
fauxEle . remove ( ) ;
}
return rebase . indexOf ( fauxBase ) === 0 ;
}
// Thanks Modernizr
function cssPointerEventsTest ( ) {
var element = document . createElement ( "x" ) ,
documentElement = document . documentElement ,
getComputedStyle = window . getComputedStyle ,
supports ;
if ( ! ( "pointerEvents" in element . style ) ) {
return false ;
}
element . style . pointerEvents = "auto" ;
element . style . pointerEvents = "x" ;
documentElement . appendChild ( element ) ;
supports = getComputedStyle &&
getComputedStyle ( element , "" ) . pointerEvents === "auto" ;
documentElement . removeChild ( element ) ;
return ! ! supports ;
}
function boundingRect ( ) {
var div = document . createElement ( "div" ) ;
return typeof div . getBoundingClientRect !== "undefined" ;
}
// non-UA-based IE version check by James Padolsey, modified by jdalton - from http://gist.github.com/527683
// allows for inclusion of IE 6+, including Windows Mobile 7
$ . extend ( $ . mobile , { browser : { } } ) ;
$ . mobile . browser . oldIE = ( function ( ) {
var v = 3 ,
div = document . createElement ( "div" ) ,
a = div . all || [ ] ;
do {
div . innerHTML = "<!--[if gt IE " + ( ++ v ) + "]><br><![endif]-->" ;
} while ( a [ 0 ] ) ;
return v > 4 ? v : ! v ;
} ) ( ) ;
$ . extend ( $ . support , {
// Note, Chrome for iOS has an extremely quirky implementation of popstate.
// We've chosen to take the shortest path to a bug fix here for issue #5426
// See the following link for information about the regex chosen
// https://developers.google.com/chrome/mobile/docs/user-agent#chrome_for_ios_user-agent
pushState : "pushState" in history &&
"replaceState" in history &&
// When running inside a FF iframe, calling replaceState causes an error
! ( window . navigator . userAgent . indexOf ( "Firefox" ) >= 0 && window . top !== window ) &&
( window . navigator . userAgent . search ( /CriOS/ ) === - 1 ) ,
mediaquery : $ . mobile . media ( "only all" ) ,
cssPseudoElement : ! ! propExists ( "content" ) ,
touchOverflow : ! ! propExists ( "overflowScrolling" ) ,
cssTransform3d : transform3dTest ( ) ,
boxShadow : ! ! propExists ( "boxShadow" ) && ! bb ,
2015-09-01 09:28:52 -07:00
fixedPosition : true ,
2015-06-19 22:31:44 -07:00
scrollTop : ( "pageXOffset" in window ||
"scrollTop" in document . documentElement ||
"scrollTop" in fakeBody [ 0 ] ) && ! webos && ! operamini ,
dynamicBaseTag : baseTagTest ( ) ,
cssPointerEvents : cssPointerEventsTest ( ) ,
boundingRect : boundingRect ( ) ,
inlineSVG : inlineSVG
} ) ;
fakeBody . remove ( ) ;
// Support conditions that must be met in order to proceed
// default enhanced qualifications are media query support OR IE 7+
$ . mobile . gradeA = function ( ) {
return ( ( $ . support . mediaquery && $ . support . cssPseudoElement ) || $ . mobile . browser . oldIE && $ . mobile . browser . oldIE >= 8 ) && ( $ . support . boundingRect || $ . fn . jquery . match ( /1\.[0-7+]\.[0-9+]?/ ) !== null ) ;
} ;
$ . mobile . ajaxBlacklist =
// BlackBerry browsers, pre-webkit
window . blackberry && ! window . WebKitPoint ||
// Opera Mini
2015-09-01 09:28:52 -07:00
operamini ;
2015-06-19 22:31:44 -07:00
// For ruling out shadows via css
if ( ! $ . support . boxShadow ) {
$ ( "html" ) . addClass ( "ui-noboxshadow" ) ;
}
} ) ( jQuery ) ;
( function ( $ , undefined ) {
var $win = $ . mobile . window , self ,
dummyFnToInitNavigate = function ( ) {
} ;
$ . event . special . beforenavigate = {
setup : function ( ) {
$win . on ( "navigate" , dummyFnToInitNavigate ) ;
} ,
teardown : function ( ) {
$win . off ( "navigate" , dummyFnToInitNavigate ) ;
}
} ;
$ . event . special . navigate = self = {
bound : false ,
pushStateEnabled : true ,
originalEventName : undefined ,
// If pushstate support is present and push state support is defined to
// be true on the mobile namespace.
isPushStateEnabled : function ( ) {
return $ . support . pushState &&
$ . mobile . pushStateEnabled === true &&
this . isHashChangeEnabled ( ) ;
} ,
// !! assumes mobile namespace is present
isHashChangeEnabled : function ( ) {
return $ . mobile . hashListeningEnabled === true ;
} ,
// TODO a lot of duplication between popstate and hashchange
popstate : function ( event ) {
var newEvent = new $ . Event ( "navigate" ) ,
beforeNavigate = new $ . Event ( "beforenavigate" ) ,
state = event . originalEvent . state || { } ;
beforeNavigate . originalEvent = event ;
$win . trigger ( beforeNavigate ) ;
if ( beforeNavigate . isDefaultPrevented ( ) ) {
return ;
}
if ( event . historyState ) {
$ . extend ( state , event . historyState ) ;
}
// Make sure the original event is tracked for the end
// user to inspect incase they want to do something special
newEvent . originalEvent = event ;
// NOTE we let the current stack unwind because any assignment to
// location.hash will stop the world and run this event handler. By
// doing this we create a similar behavior to hashchange on hash
// assignment
setTimeout ( function ( ) {
$win . trigger ( newEvent , {
state : state
} ) ;
} , 0 ) ;
} ,
hashchange : function ( event /*, data */ ) {
var newEvent = new $ . Event ( "navigate" ) ,
beforeNavigate = new $ . Event ( "beforenavigate" ) ;
beforeNavigate . originalEvent = event ;
$win . trigger ( beforeNavigate ) ;
if ( beforeNavigate . isDefaultPrevented ( ) ) {
return ;
}
// Make sure the original event is tracked for the end
// user to inspect incase they want to do something special
newEvent . originalEvent = event ;
// Trigger the hashchange with state provided by the user
// that altered the hash
$win . trigger ( newEvent , {
// Users that want to fully normalize the two events
// will need to do history management down the stack and
// add the state to the event before this binding is fired
// TODO consider allowing for the explicit addition of callbacks
// to be fired before this value is set to avoid event timing issues
state : event . hashchangeState || { }
} ) ;
} ,
// TODO We really only want to set this up once
// but I'm not clear if there's a beter way to achieve
// this with the jQuery special event structure
setup : function ( /* data, namespaces */ ) {
if ( self . bound ) {
return ;
}
self . bound = true ;
if ( self . isPushStateEnabled ( ) ) {
self . originalEventName = "popstate" ;
$win . bind ( "popstate.navigate" , self . popstate ) ;
} else if ( self . isHashChangeEnabled ( ) ) {
self . originalEventName = "hashchange" ;
$win . bind ( "hashchange.navigate" , self . hashchange ) ;
}
}
} ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
var props = {
"animation" : { } ,
"transition" : { }
} ,
testElement = document . createElement ( "a" ) ,
vendorPrefixes = [ "" , "webkit-" , "moz-" , "o-" ] ;
$ . each ( [ "animation" , "transition" ] , function ( i , test ) {
// Get correct name for test
var testName = ( i === 0 ) ? test + "-" + "name" : test ;
$ . each ( vendorPrefixes , function ( j , prefix ) {
if ( testElement . style [ $ . camelCase ( prefix + testName ) ] !== undefined ) {
props [ test ] [ "prefix" ] = prefix ;
return false ;
}
} ) ;
// Set event and duration names for later use
props [ test ] [ "duration" ] =
$ . camelCase ( props [ test ] [ "prefix" ] + test + "-" + "duration" ) ;
props [ test ] [ "event" ] =
$ . camelCase ( props [ test ] [ "prefix" ] + test + "-" + "end" ) ;
// All lower case if not a vendor prop
if ( props [ test ] [ "prefix" ] === "" ) {
props [ test ] [ "event" ] = props [ test ] [ "event" ] . toLowerCase ( ) ;
}
} ) ;
// If a valid prefix was found then the it is supported by the browser
$ . support . cssTransitions = ( props [ "transition" ] [ "prefix" ] !== undefined ) ;
$ . support . cssAnimations = ( props [ "animation" ] [ "prefix" ] !== undefined ) ;
// Remove the testElement
$ ( testElement ) . remove ( ) ;
// Animation complete callback
$ . fn . animationComplete = function ( callback , type , fallbackTime ) {
var timer , duration ,
that = this ,
eventBinding = function ( ) {
// Clear the timer so we don't call callback twice
clearTimeout ( timer ) ;
callback . apply ( this , arguments ) ;
} ,
animationType = ( ! type || type === "animation" ) ? "animation" : "transition" ;
// Make sure selected type is supported by browser
if ( ( $ . support . cssTransitions && animationType === "transition" ) ||
( $ . support . cssAnimations && animationType === "animation" ) ) {
// If a fallback time was not passed set one
if ( fallbackTime === undefined ) {
// Make sure the was not bound to document before checking .css
if ( $ ( this ) . context !== document ) {
// Parse the durration since its in second multiple by 1000 for milliseconds
// Multiply by 3 to make sure we give the animation plenty of time.
duration = parseFloat (
$ ( this ) . css ( props [ animationType ] . duration )
) * 3000 ;
}
// If we could not read a duration use the default
if ( duration === 0 || duration === undefined || isNaN ( duration ) ) {
duration = $ . fn . animationComplete . defaultDuration ;
}
}
// Sets up the fallback if event never comes
timer = setTimeout ( function ( ) {
$ ( that ) . off ( props [ animationType ] . event , eventBinding ) ;
callback . apply ( that ) ;
} , duration ) ;
// Bind the event
return $ ( this ) . one ( props [ animationType ] . event , eventBinding ) ;
} else {
// CSS animation / transitions not supported
// Defer execution for consistency between webkit/non webkit
setTimeout ( $ . proxy ( callback , this ) , 0 ) ;
return $ ( this ) ;
}
} ;
// Allow default callback to be configured on mobileInit
$ . fn . animationComplete . defaultDuration = 1000 ;
} ) ( jQuery ) ;
// buttonMarkup is deprecated as of 1.4.0 and will be removed in 1.5.0.
( function ( $ , undefined ) {
// General policy: Do not access data-* attributes except during enhancement.
// In all other cases we determine the state of the button exclusively from its
// className. That's why optionsToClasses expects a full complement of options,
// and the jQuery plugin completes the set of options from the default values.
// Map classes to buttonMarkup boolean options - used in classNameToOptions()
var reverseBoolOptionMap = {
"ui-shadow" : "shadow" ,
"ui-corner-all" : "corners" ,
"ui-btn-inline" : "inline" ,
"ui-shadow-icon" : "iconshadow" , /* TODO: Remove in 1.5 */
"ui-mini" : "mini"
} ,
getAttrFixed = function ( ) {
var ret = $ . mobile . getAttribute . apply ( this , arguments ) ;
return ( ret == null ? undefined : ret ) ;
} ,
capitalLettersRE = /[A-Z]/g ;
// optionsToClasses:
// @options: A complete set of options to convert to class names.
// @existingClasses: extra classes to add to the result
//
// Converts @options to buttonMarkup classes and returns the result as an array
// that can be converted to an element's className with .join( " " ). All
// possible options must be set inside @options. Use $.fn.buttonMarkup.defaults
// to get a complete set and use $.extend to override your choice of options
// from that set.
function optionsToClasses ( options , existingClasses ) {
var classes = existingClasses ? existingClasses : [ ] ;
// Add classes to the array - first ui-btn
classes . push ( "ui-btn" ) ;
// If there is a theme
if ( options . theme ) {
classes . push ( "ui-btn-" + options . theme ) ;
}
// If there's an icon, add the icon-related classes
if ( options . icon ) {
classes = classes . concat ( [
"ui-icon-" + options . icon ,
"ui-btn-icon-" + options . iconpos
] ) ;
if ( options . iconshadow ) {
classes . push ( "ui-shadow-icon" ) ; /* TODO: Remove in 1.5 */
}
}
// Add the appropriate class for each boolean option
if ( options . inline ) {
classes . push ( "ui-btn-inline" ) ;
}
if ( options . shadow ) {
classes . push ( "ui-shadow" ) ;
}
if ( options . corners ) {
classes . push ( "ui-corner-all" ) ;
}
if ( options . mini ) {
classes . push ( "ui-mini" ) ;
}
// Create a string from the array and return it
return classes ;
}
// classNameToOptions:
// @classes: A string containing a .className-style space-separated class list
//
// Loops over @classes and calculates an options object based on the
// buttonMarkup-related classes it finds. It records unrecognized classes in an
// array.
//
// Returns: An object containing the following items:
//
// "options": buttonMarkup options found to be present because of the
// presence/absence of corresponding classes
//
// "unknownClasses": a string containing all the non-buttonMarkup-related
// classes found in @classes
//
// "alreadyEnhanced": A boolean indicating whether the ui-btn class was among
// those found to be present
function classNameToOptions ( classes ) {
var idx , map , unknownClass ,
alreadyEnhanced = false ,
noIcon = true ,
o = {
icon : "" ,
inline : false ,
shadow : false ,
corners : false ,
iconshadow : false ,
mini : false
} ,
unknownClasses = [ ] ;
classes = classes . split ( " " ) ;
// Loop over the classes
for ( idx = 0 ; idx < classes . length ; idx ++ ) {
// Assume it's an unrecognized class
unknownClass = true ;
// Recognize boolean options from the presence of classes
map = reverseBoolOptionMap [ classes [ idx ] ] ;
if ( map !== undefined ) {
unknownClass = false ;
o [ map ] = true ;
// Recognize the presence of an icon and establish the icon position
} else if ( classes [ idx ] . indexOf ( "ui-btn-icon-" ) === 0 ) {
unknownClass = false ;
noIcon = false ;
o . iconpos = classes [ idx ] . substring ( 12 ) ;
// Establish which icon is present
} else if ( classes [ idx ] . indexOf ( "ui-icon-" ) === 0 ) {
unknownClass = false ;
o . icon = classes [ idx ] . substring ( 8 ) ;
// Establish the theme - this recognizes one-letter theme swatch names
} else if ( classes [ idx ] . indexOf ( "ui-btn-" ) === 0 && classes [ idx ] . length === 8 ) {
unknownClass = false ;
o . theme = classes [ idx ] . substring ( 7 ) ;
// Recognize that this element has already been buttonMarkup-enhanced
} else if ( classes [ idx ] === "ui-btn" ) {
unknownClass = false ;
alreadyEnhanced = true ;
}
// If this class has not been recognized, add it to the list
if ( unknownClass ) {
unknownClasses . push ( classes [ idx ] ) ;
}
}
// If a "ui-btn-icon-*" icon position class is absent there cannot be an icon
if ( noIcon ) {
o . icon = "" ;
}
return {
options : o ,
unknownClasses : unknownClasses ,
alreadyEnhanced : alreadyEnhanced
} ;
}
function camelCase2Hyphenated ( c ) {
return "-" + c . toLowerCase ( ) ;
}
// $.fn.buttonMarkup:
// DOM: gets/sets .className
//
// @options: options to apply to the elements in the jQuery object
// @overwriteClasses: boolean indicating whether to honour existing classes
//
// Calculates the classes to apply to the elements in the jQuery object based on
// the options passed in. If @overwriteClasses is true, it sets the className
// property of each element in the jQuery object to the buttonMarkup classes
// it calculates based on the options passed in.
//
// If you wish to preserve any classes that are already present on the elements
// inside the jQuery object, including buttonMarkup-related classes that were
// added by a previous call to $.fn.buttonMarkup() or during page enhancement
// then you should omit @overwriteClasses or set it to false.
$ . fn . buttonMarkup = function ( options , overwriteClasses ) {
var idx , data , el , retrievedOptions , optionKey ,
defaults = $ . fn . buttonMarkup . defaults ;
for ( idx = 0 ; idx < this . length ; idx ++ ) {
el = this [ idx ] ;
data = overwriteClasses ?
// Assume this element is not enhanced and ignore its classes
{ alreadyEnhanced : false , unknownClasses : [ ] } :
// Otherwise analyze existing classes to establish existing options and
// classes
classNameToOptions ( el . className ) ;
retrievedOptions = $ . extend ( { } ,
// If the element already has the class ui-btn, then we assume that
// it has passed through buttonMarkup before - otherwise, the options
// returned by classNameToOptions do not correctly reflect the state of
// the element
( data . alreadyEnhanced ? data . options : { } ) ,
// Finally, apply the options passed in
options ) ;
// If this is the first call on this element, retrieve remaining options
// from the data-attributes
if ( ! data . alreadyEnhanced ) {
for ( optionKey in defaults ) {
if ( retrievedOptions [ optionKey ] === undefined ) {
retrievedOptions [ optionKey ] = getAttrFixed ( el ,
optionKey . replace ( capitalLettersRE , camelCase2Hyphenated )
) ;
}
}
}
el . className = optionsToClasses (
// Merge all the options and apply them as classes
$ . extend ( { } ,
// The defaults form the basis
defaults ,
// Add the computed options
retrievedOptions
) ,
// ... and re-apply any unrecognized classes that were found
data . unknownClasses ) . join ( " " ) ;
if ( el . tagName . toLowerCase ( ) !== "button" ) {
el . setAttribute ( "role" , "button" ) ;
}
}
return this ;
} ;
// buttonMarkup defaults. This must be a complete set, i.e., a value must be
// given here for all recognized options
$ . fn . buttonMarkup . defaults = {
icon : "" ,
iconpos : "left" ,
theme : null ,
inline : false ,
shadow : true ,
corners : true ,
iconshadow : false , /* TODO: Remove in 1.5. Option deprecated in 1.4. */
mini : false
} ;
$ . extend ( $ . fn . buttonMarkup , {
2015-08-31 07:57:04 -07:00
initSelector : "a[data-role='button'], .ui-bar > a, .ui-bar > *[data-role='controlgroup'] > a, button"
2015-06-19 22:31:44 -07:00
} ) ;
} ) ( jQuery ) ;
/ * !
* jQuery UI Widget c0ab71056b936627e8a7821f03c044aec6280a40
* http : //jqueryui.com
*
* Copyright 2013 jQuery Foundation and other contributors
* Released under the MIT license .
* http : //jquery.org/license
*
* http : //api.jqueryui.com/jQuery.widget/
* /
( function ( $ , undefined ) {
var uuid = 0 ,
slice = Array . prototype . slice ,
_cleanData = $ . cleanData ;
$ . cleanData = function ( elems ) {
for ( var i = 0 , elem ; ( elem = elems [ i ] ) != null ; i ++ ) {
try {
$ ( elem ) . triggerHandler ( "remove" ) ;
// http://bugs.jquery.com/ticket/8235
} catch ( e ) { }
}
_cleanData ( elems ) ;
} ;
$ . widget = function ( name , base , prototype ) {
var fullName , existingConstructor , constructor , basePrototype ,
// proxiedPrototype allows the provided prototype to remain unmodified
// so that it can be used as a mixin for multiple widgets (#8876)
proxiedPrototype = { } ,
namespace = name . split ( "." ) [ 0 ] ;
name = name . split ( "." ) [ 1 ] ;
fullName = namespace + "-" + name ;
if ( ! prototype ) {
prototype = base ;
base = $ . Widget ;
}
// create selector for plugin
$ . expr [ ":" ] [ fullName . toLowerCase ( ) ] = function ( elem ) {
return ! ! $ . data ( elem , fullName ) ;
} ;
$ [ namespace ] = $ [ namespace ] || { } ;
existingConstructor = $ [ namespace ] [ name ] ;
constructor = $ [ namespace ] [ name ] = function ( options , element ) {
// allow instantiation without "new" keyword
if ( ! this . _createWidget ) {
return new constructor ( options , element ) ;
}
// allow instantiation without initializing for simple inheritance
// must use "new" keyword (the code above always passes args)
if ( arguments . length ) {
this . _createWidget ( options , element ) ;
}
} ;
// extend with the existing constructor to carry over any static properties
$ . extend ( constructor , existingConstructor , {
version : prototype . version ,
// copy the object used to create the prototype in case we need to
// redefine the widget later
_proto : $ . extend ( { } , prototype ) ,
// track widgets that inherit from this widget in case this widget is
// redefined after a widget inherits from it
_childConstructors : [ ]
} ) ;
basePrototype = new base ( ) ;
// we need to make the options hash a property directly on the new instance
// otherwise we'll modify the options hash on the prototype that we're
// inheriting from
basePrototype . options = $ . widget . extend ( { } , basePrototype . options ) ;
$ . each ( prototype , function ( prop , value ) {
if ( ! $ . isFunction ( value ) ) {
proxiedPrototype [ prop ] = value ;
return ;
}
proxiedPrototype [ prop ] = ( function ( ) {
var _super = function ( ) {
return base . prototype [ prop ] . apply ( this , arguments ) ;
} ,
_superApply = function ( args ) {
return base . prototype [ prop ] . apply ( this , args ) ;
} ;
return function ( ) {
var _ _super = this . _super ,
_ _superApply = this . _superApply ,
returnValue ;
this . _super = _super ;
this . _superApply = _superApply ;
returnValue = value . apply ( this , arguments ) ;
this . _super = _ _super ;
this . _superApply = _ _superApply ;
return returnValue ;
} ;
} ) ( ) ;
} ) ;
constructor . prototype = $ . widget . extend ( basePrototype , {
// TODO: remove support for widgetEventPrefix
// always use the name + a colon as the prefix, e.g., draggable:start
// don't prefix for widgets that aren't DOM-based
widgetEventPrefix : existingConstructor ? ( basePrototype . widgetEventPrefix || name ) : name
} , proxiedPrototype , {
constructor : constructor ,
namespace : namespace ,
widgetName : name ,
widgetFullName : fullName
} ) ;
// If this widget is being redefined then we need to find all widgets that
// are inheriting from it and redefine all of them so that they inherit from
// the new version of this widget. We're essentially trying to replace one
// level in the prototype chain.
if ( existingConstructor ) {
$ . each ( existingConstructor . _childConstructors , function ( i , child ) {
var childPrototype = child . prototype ;
// redefine the child widget using the same prototype that was
// originally used, but inherit from the new version of the base
$ . widget ( childPrototype . namespace + "." + childPrototype . widgetName , constructor , child . _proto ) ;
} ) ;
// remove the list of existing child constructors from the old constructor
// so the old child constructors can be garbage collected
delete existingConstructor . _childConstructors ;
} else {
base . _childConstructors . push ( constructor ) ;
}
$ . widget . bridge ( name , constructor ) ;
return constructor ;
} ;
$ . widget . extend = function ( target ) {
var input = slice . call ( arguments , 1 ) ,
inputIndex = 0 ,
inputLength = input . length ,
key ,
value ;
for ( ; inputIndex < inputLength ; inputIndex ++ ) {
for ( key in input [ inputIndex ] ) {
value = input [ inputIndex ] [ key ] ;
if ( input [ inputIndex ] . hasOwnProperty ( key ) && value !== undefined ) {
// Clone objects
if ( $ . isPlainObject ( value ) ) {
target [ key ] = $ . isPlainObject ( target [ key ] ) ?
$ . widget . extend ( { } , target [ key ] , value ) :
// Don't extend strings, arrays, etc. with objects
$ . widget . extend ( { } , value ) ;
// Copy everything else by reference
} else {
target [ key ] = value ;
}
}
}
}
return target ;
} ;
2015-08-30 15:55:58 -07:00
$ . widget . bridge = function ( name , object ) {
var fullName = object . prototype . widgetFullName || name ;
2015-06-19 22:31:44 -07:00
$ . fn [ name ] = function ( options ) {
var isMethodCall = typeof options === "string" ,
args = slice . call ( arguments , 1 ) ,
returnValue = this ;
// allow multiple hashes to be passed on init
options = ! isMethodCall && args . length ?
$ . widget . extend . apply ( null , [ options ] . concat ( args ) ) :
options ;
if ( isMethodCall ) {
this . each ( function ( ) {
var methodValue ,
instance = $ . data ( this , fullName ) ;
if ( options === "instance" ) {
returnValue = instance ;
return false ;
}
if ( ! instance ) {
return $ . error ( "cannot call methods on " + name + " prior to initialization; " +
"attempted to call method '" + options + "'" ) ;
}
if ( ! $ . isFunction ( instance [ options ] ) || options . charAt ( 0 ) === "_" ) {
return $ . error ( "no such method '" + options + "' for " + name + " widget instance" ) ;
}
methodValue = instance [ options ] . apply ( instance , args ) ;
if ( methodValue !== instance && methodValue !== undefined ) {
returnValue = methodValue && methodValue . jquery ?
returnValue . pushStack ( methodValue . get ( ) ) :
methodValue ;
return false ;
}
} ) ;
} else {
this . each ( function ( ) {
var instance = $ . data ( this , fullName ) ;
if ( instance ) {
instance . option ( options || { } ) . _init ( ) ;
} else {
$ . data ( this , fullName , new object ( options , this ) ) ;
}
} ) ;
}
return returnValue ;
} ;
} ;
$ . Widget = function ( /* options, element */ ) { } ;
$ . Widget . _childConstructors = [ ] ;
$ . Widget . prototype = {
widgetName : "widget" ,
widgetEventPrefix : "" ,
defaultElement : "<div>" ,
options : {
disabled : false ,
// callbacks
create : null
} ,
_createWidget : function ( options , element ) {
element = $ ( element || this . defaultElement || this ) [ 0 ] ;
this . element = $ ( element ) ;
this . uuid = uuid ++ ;
this . eventNamespace = "." + this . widgetName + this . uuid ;
this . options = $ . widget . extend ( { } ,
this . options ,
this . _getCreateOptions ( ) ,
options ) ;
this . bindings = $ ( ) ;
this . hoverable = $ ( ) ;
this . focusable = $ ( ) ;
if ( element !== this ) {
$ . data ( element , this . widgetFullName , this ) ;
this . _on ( true , this . element , {
remove : function ( event ) {
if ( event . target === element ) {
this . destroy ( ) ;
}
}
} ) ;
this . document = $ ( element . style ?
// element within the document
element . ownerDocument :
// element is window or document
element . document || element ) ;
this . window = $ ( this . document [ 0 ] . defaultView || this . document [ 0 ] . parentWindow ) ;
}
this . _create ( ) ;
this . _trigger ( "create" , null , this . _getCreateEventData ( ) ) ;
this . _init ( ) ;
} ,
_getCreateOptions : $ . noop ,
_getCreateEventData : $ . noop ,
_create : $ . noop ,
_init : $ . noop ,
destroy : function ( ) {
this . _destroy ( ) ;
// we can probably remove the unbind calls in 2.0
// all event bindings should go through this._on()
this . element
. unbind ( this . eventNamespace )
. removeData ( this . widgetFullName )
// support: jquery <1.6.3
// http://bugs.jquery.com/ticket/9413
. removeData ( $ . camelCase ( this . widgetFullName ) ) ;
this . widget ( )
. unbind ( this . eventNamespace )
. removeAttr ( "aria-disabled" )
. removeClass (
this . widgetFullName + "-disabled " +
"ui-state-disabled" ) ;
// clean up events and states
this . bindings . unbind ( this . eventNamespace ) ;
this . hoverable . removeClass ( "ui-state-hover" ) ;
this . focusable . removeClass ( "ui-state-focus" ) ;
} ,
_destroy : $ . noop ,
widget : function ( ) {
return this . element ;
} ,
option : function ( key , value ) {
var options = key ,
parts ,
curOption ,
i ;
if ( arguments . length === 0 ) {
// don't return a reference to the internal hash
return $ . widget . extend ( { } , this . options ) ;
}
if ( typeof key === "string" ) {
// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
options = { } ;
parts = key . split ( "." ) ;
key = parts . shift ( ) ;
if ( parts . length ) {
curOption = options [ key ] = $ . widget . extend ( { } , this . options [ key ] ) ;
for ( i = 0 ; i < parts . length - 1 ; i ++ ) {
curOption [ parts [ i ] ] = curOption [ parts [ i ] ] || { } ;
curOption = curOption [ parts [ i ] ] ;
}
key = parts . pop ( ) ;
if ( value === undefined ) {
return curOption [ key ] === undefined ? null : curOption [ key ] ;
}
curOption [ key ] = value ;
} else {
if ( value === undefined ) {
return this . options [ key ] === undefined ? null : this . options [ key ] ;
}
options [ key ] = value ;
}
}
this . _setOptions ( options ) ;
return this ;
} ,
_setOptions : function ( options ) {
var key ;
for ( key in options ) {
this . _setOption ( key , options [ key ] ) ;
}
return this ;
} ,
_setOption : function ( key , value ) {
this . options [ key ] = value ;
if ( key === "disabled" ) {
this . widget ( )
. toggleClass ( this . widgetFullName + "-disabled" , ! ! value ) ;
this . hoverable . removeClass ( "ui-state-hover" ) ;
this . focusable . removeClass ( "ui-state-focus" ) ;
}
return this ;
} ,
enable : function ( ) {
return this . _setOptions ( { disabled : false } ) ;
} ,
disable : function ( ) {
return this . _setOptions ( { disabled : true } ) ;
} ,
_on : function ( suppressDisabledCheck , element , handlers ) {
var delegateElement ,
instance = this ;
// no suppressDisabledCheck flag, shuffle arguments
if ( typeof suppressDisabledCheck !== "boolean" ) {
handlers = element ;
element = suppressDisabledCheck ;
suppressDisabledCheck = false ;
}
// no element argument, shuffle and use this.element
if ( ! handlers ) {
handlers = element ;
element = this . element ;
delegateElement = this . widget ( ) ;
} else {
// accept selectors, DOM elements
element = delegateElement = $ ( element ) ;
this . bindings = this . bindings . add ( element ) ;
}
$ . each ( handlers , function ( event , handler ) {
function handlerProxy ( ) {
// allow widgets to customize the disabled handling
// - disabled as an array instead of boolean
// - disabled class as method for disabling individual parts
if ( ! suppressDisabledCheck &&
( instance . options . disabled === true ||
$ ( this ) . hasClass ( "ui-state-disabled" ) ) ) {
return ;
}
return ( typeof handler === "string" ? instance [ handler ] : handler )
. apply ( instance , arguments ) ;
}
// copy the guid so direct unbinding works
if ( typeof handler !== "string" ) {
handlerProxy . guid = handler . guid =
handler . guid || handlerProxy . guid || $ . guid ++ ;
}
var match = event . match ( /^(\w+)\s*(.*)$/ ) ,
eventName = match [ 1 ] + instance . eventNamespace ,
selector = match [ 2 ] ;
if ( selector ) {
delegateElement . delegate ( selector , eventName , handlerProxy ) ;
} else {
element . bind ( eventName , handlerProxy ) ;
}
} ) ;
} ,
_off : function ( element , eventName ) {
eventName = ( eventName || "" ) . split ( " " ) . join ( this . eventNamespace + " " ) + this . eventNamespace ;
element . unbind ( eventName ) . undelegate ( eventName ) ;
} ,
_delay : function ( handler , delay ) {
function handlerProxy ( ) {
return ( typeof handler === "string" ? instance [ handler ] : handler )
. apply ( instance , arguments ) ;
}
var instance = this ;
return setTimeout ( handlerProxy , delay || 0 ) ;
} ,
_hoverable : function ( element ) {
this . hoverable = this . hoverable . add ( element ) ;
this . _on ( element , {
mouseenter : function ( event ) {
$ ( event . currentTarget ) . addClass ( "ui-state-hover" ) ;
} ,
mouseleave : function ( event ) {
$ ( event . currentTarget ) . removeClass ( "ui-state-hover" ) ;
}
} ) ;
} ,
_focusable : function ( element ) {
this . focusable = this . focusable . add ( element ) ;
this . _on ( element , {
focusin : function ( event ) {
$ ( event . currentTarget ) . addClass ( "ui-state-focus" ) ;
} ,
focusout : function ( event ) {
$ ( event . currentTarget ) . removeClass ( "ui-state-focus" ) ;
}
} ) ;
} ,
_trigger : function ( type , event , data ) {
var prop , orig ,
callback = this . options [ type ] ;
data = data || { } ;
event = $ . Event ( event ) ;
event . type = ( type === this . widgetEventPrefix ?
type :
this . widgetEventPrefix + type ) . toLowerCase ( ) ;
// the original event may come from any element
// so we need to reset the target on the new event
event . target = this . element [ 0 ] ;
// copy original event properties over to the new event
orig = event . originalEvent ;
if ( orig ) {
for ( prop in orig ) {
if ( ! ( prop in event ) ) {
event [ prop ] = orig [ prop ] ;
}
}
}
this . element . trigger ( event , data ) ;
return ! ( $ . isFunction ( callback ) &&
callback . apply ( this . element [ 0 ] , [ event ] . concat ( data ) ) === false ||
event . isDefaultPrevented ( ) ) ;
}
} ;
$ . each ( { show : "fadeIn" , hide : "fadeOut" } , function ( method , defaultEffect ) {
$ . Widget . prototype [ "_" + method ] = function ( element , options , callback ) {
if ( typeof options === "string" ) {
options = { effect : options } ;
}
var hasOptions ,
effectName = ! options ?
method :
options === true || typeof options === "number" ?
defaultEffect :
options . effect || defaultEffect ;
options = options || { } ;
if ( typeof options === "number" ) {
options = { duration : options } ;
}
hasOptions = ! $ . isEmptyObject ( options ) ;
options . complete = callback ;
if ( options . delay ) {
element . delay ( options . delay ) ;
}
if ( hasOptions && $ . effects && $ . effects . effect [ effectName ] ) {
element [ method ] ( options ) ;
} else if ( effectName !== method && element [ effectName ] ) {
element [ effectName ] ( options . duration , options . easing , callback ) ;
} else {
element . queue ( function ( next ) {
$ ( this ) [ method ] ( ) ;
if ( callback ) {
callback . call ( element [ 0 ] ) ;
}
next ( ) ;
} ) ;
}
} ;
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
var rcapitals = /[A-Z]/g ,
replaceFunction = function ( c ) {
return "-" + c . toLowerCase ( ) ;
} ;
$ . extend ( $ . Widget . prototype , {
_getCreateOptions : function ( ) {
var option , value ,
elem = this . element [ 0 ] ,
options = { } ;
//
if ( ! $ . mobile . getAttribute ( elem , "defaults" ) ) {
for ( option in this . options ) {
value = $ . mobile . getAttribute ( elem , option . replace ( rcapitals , replaceFunction ) ) ;
if ( value != null ) {
options [ option ] = value ;
}
}
}
return options ;
}
} ) ;
//TODO: Remove in 1.5 for backcompat only
$ . mobile . widget = $ . Widget ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ . mobile . widgets = { } ;
var originalWidget = $ . widget ,
// Record the original, non-mobileinit-modified version of $.mobile.keepNative
// so we can later determine whether someone has modified $.mobile.keepNative
keepNativeFactoryDefault = $ . mobile . keepNative ;
$ . widget = ( function ( orig ) {
return function ( ) {
var constructor = orig . apply ( this , arguments ) ,
name = constructor . prototype . widgetName ;
constructor . initSelector = ( ( constructor . prototype . initSelector !== undefined ) ?
2015-08-31 07:57:04 -07:00
constructor . prototype . initSelector : "*[data-role='" + name + "']" ) ;
2015-06-19 22:31:44 -07:00
$ . mobile . widgets [ name ] = constructor ;
return constructor ;
} ;
} ) ( $ . widget ) ;
// Make sure $.widget still has bridge and extend methods
$ . extend ( $ . widget , originalWidget ) ;
// For backcompat remove in 1.5
$ . mobile . document . on ( "create" , function ( event ) {
$ ( event . target ) . enhanceWithin ( ) ;
} ) ;
$ . widget ( "mobile.page" , {
options : {
theme : "a" ,
domCache : false ,
// Deprecated in 1.4 remove in 1.5
keepNativeDefault : $ . mobile . keepNative ,
// Deprecated in 1.4 remove in 1.5
contentTheme : null ,
enhanced : false
} ,
// DEPRECATED for > 1.4
// TODO remove at 1.5
2015-08-31 21:15:10 -07:00
_createWidget : function ( ) {
$ . Widget . prototype . _createWidget . apply ( this , arguments ) ;
this . _trigger ( "init" ) ;
2015-06-19 22:31:44 -07:00
} ,
2015-08-31 21:15:10 -07:00
_create : function ( ) {
2015-06-19 22:31:44 -07:00
// If false is returned by the callbacks do not create the page
if ( this . _trigger ( "beforecreate" ) === false ) {
return false ;
}
if ( ! this . options . enhanced ) {
this . _enhance ( ) ;
}
this . _on ( this . element , {
pagebeforehide : "removeContainerBackground" ,
pagebeforeshow : "_handlePageBeforeShow"
} ) ;
this . element . enhanceWithin ( ) ;
} ,
_enhance : function ( ) {
var attrPrefix = "data-" + $ . mobile . ns ,
self = this ;
2015-08-30 15:55:58 -07:00
var element = this . element [ 0 ] ;
2015-06-19 22:31:44 -07:00
if ( this . options . role ) {
2015-08-30 15:55:58 -07:00
element . setAttribute ( "data-" + $ . mobile . ns + "role" , this . options . role ) ;
2015-06-19 22:31:44 -07:00
}
2015-08-30 15:55:58 -07:00
element . setAttribute ( "tabindex" , "0" ) ;
element . classList . add ( "ui-page" ) ;
element . classList . add ( "ui-page-theme-" + this . options . theme ) ;
2015-09-01 18:50:02 -07:00
var contents = element . querySelectorAll ( "div[data-role='content']" ) ;
for ( var i = 0 , length = contents . length ; i < length ; i ++ ) {
var content = contents [ i ] ;
var theme = content . getAttribute ( attrPrefix + "theme" ) || undefined ;
self . options . contentTheme = theme || self . options . contentTheme || ( self . options . dialog && self . options . theme ) || ( self . element . jqmData ( "role" ) === "dialog" && self . options . theme ) ;
content . classList . add ( "ui-content" ) ;
if ( self . options . contentTheme ) {
content . classList . add ( "ui-body-" + ( self . options . contentTheme ) ) ;
}
// Add ARIA role
content . setAttribute ( "role" , "main" ) ;
content . classList . add ( "ui-content" ) ;
}
2015-06-19 22:31:44 -07:00
} ,
2015-08-31 12:00:39 -07:00
_setOptions : function ( o ) {
var elem = this . element [ 0 ] ;
2015-06-19 22:31:44 -07:00
if ( o . theme !== undefined ) {
2015-08-31 12:00:39 -07:00
elem . classList . remove ( "ui-page-theme-" + this . options . theme ) ;
elem . classList . add ( "ui-page-theme-" + o . theme ) ;
2015-06-19 22:31:44 -07:00
}
2015-08-31 12:00:39 -07:00
if ( o . contentTheme !== undefined ) {
var elems = elem . querySelectorAll ( "*[data-" + $ . mobile . ns + "='content']" ) ;
for ( var i = 0 , length = elems . length ; i < length ; i ++ ) {
var el = elems [ i ] ;
el . classList . remove ( "ui-body-" + this . options . contentTheme ) ;
el . classList . add ( "ui-body-" + o . contentTheme ) ;
}
2015-06-19 22:31:44 -07:00
}
} ,
_handlePageBeforeShow : function ( /* e */ ) {
this . setContainerBackground ( ) ;
} ,
// Deprecated in 1.4 remove in 1.5
2015-08-30 15:55:58 -07:00
removeContainerBackground : function ( ) {
$ ( this . element [ 0 ] . parentNode ) . pagecontainer ( { "theme" : "none" } ) ;
2015-06-19 22:31:44 -07:00
} ,
// Deprecated in 1.4 remove in 1.5
// set the page container background to the page theme
setContainerBackground : function ( theme ) {
2015-08-30 15:55:58 -07:00
$ ( this . element [ 0 ] . parentNode ) . pagecontainer ( { "theme" : theme || this . options . theme } ) ;
2015-06-19 22:31:44 -07:00
} ,
// Deprecated in 1.4 remove in 1.5
keepNativeSelector : function ( ) {
var options = this . options ,
keepNative = $ . trim ( options . keepNative || "" ) ,
globalValue = $ . trim ( $ . mobile . keepNative ) ,
optionValue = $ . trim ( options . keepNativeDefault ) ,
// Check if $.mobile.keepNative has changed from the factory default
newDefault = ( keepNativeFactoryDefault === globalValue ?
"" : globalValue ) ,
// If $.mobile.keepNative has not changed, use options.keepNativeDefault
oldDefault = ( newDefault === "" ? optionValue : "" ) ;
// Concatenate keepNative selectors from all sources where the value has
// changed or, if nothing has changed, return the default
return ( ( keepNative ? [ keepNative ] : [ ] )
. concat ( newDefault ? [ newDefault ] : [ ] )
. concat ( oldDefault ? [ oldDefault ] : [ ] )
. join ( ", " ) ) ;
}
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
// Deprecated in 1.4
$ . fn . fieldcontain = function ( /* options */ ) {
return this . addClass ( "ui-field-contain" ) ;
} ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
var path , $base , dialogHashKey = "&ui-state=dialog" ;
$ . mobile . path = path = {
uiStateKey : "&ui-state" ,
// This scary looking regular expression parses an absolute URL or its relative
// variants (protocol, site, document, query, and hash), into the various
// components (protocol, host, path, query, fragment, etc that make up the
// URL as well as some other commonly used sub-parts. When used with RegExp.exec()
// or String.match, it parses the URL into a results array that looks like this:
//
// [0]: http://jblas:password@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 parsedUrl = this . parseUrl ( url || location . href ) ,
uri = url ? parsedUrl : location ,
// 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
hash = parsedUrl . hash ;
// mimic the browser with an empty string when the hash is empty
hash = hash === "#" ? "" : hash ;
return uri . protocol +
parsedUrl . doubleSlash +
uri . host +
// The pathname must start with a slash if there's a protocol, because you
// can't have a protocol followed by a relative path. Also, it's impossible to
// calculate absolute URLs from relative ones if the absolute one doesn't have
// a leading "/".
( ( uri . protocol !== "" && uri . pathname . substring ( 0 , 1 ) !== "/" ) ?
"/" : "" ) +
uri . pathname +
uri . search +
hash ;
} ,
//return the original document url
getDocumentUrl : function ( asParsedObject ) {
return asParsedObject ? $ . extend ( { } , path . documentUrl ) : path . documentUrl . href ;
} ,
parseLocation : function ( ) {
return this . parseUrl ( this . getLocation ( ) ) ;
} ,
//Parse a URL into a structure that allows easy access to
//all of the URL components by name.
parseUrl : function ( url ) {
// If we're passed an object, we'll assume that it is
// a parsed url object and just return it back to the caller.
if ( $ . type ( url ) === "object" ) {
return url ;
}
var matches = path . urlParseRE . exec ( url || "" ) || [ ] ;
// Create an object that allows the caller to access the sub-matches
// by name. Note that IE returns an empty string instead of undefined,
// like all other browsers do, so we normalize everything so its consistent
// no matter what browser we're running on.
return {
href : matches [ 0 ] || "" ,
hrefNoHash : matches [ 1 ] || "" ,
hrefNoSearch : matches [ 2 ] || "" ,
domain : matches [ 3 ] || "" ,
protocol : matches [ 4 ] || "" ,
doubleSlash : matches [ 5 ] || "" ,
authority : matches [ 6 ] || "" ,
username : matches [ 8 ] || "" ,
password : matches [ 9 ] || "" ,
host : matches [ 10 ] || "" ,
hostname : matches [ 11 ] || "" ,
port : matches [ 12 ] || "" ,
pathname : matches [ 13 ] || "" ,
directory : matches [ 14 ] || "" ,
filename : matches [ 15 ] || "" ,
search : matches [ 16 ] || "" ,
hash : matches [ 17 ] || ""
} ;
} ,
//Turn relPath into an asbolute path. absPath is
//an optional absolute path which describes what
//relPath is relative to.
makePathAbsolute : function ( relPath , absPath ) {
var absStack ,
relStack ,
i , d ;
if ( relPath && relPath . charAt ( 0 ) === "/" ) {
return relPath ;
}
relPath = relPath || "" ;
absPath = absPath ? absPath . replace ( /^\/|(\/[^\/]*|[^\/]+)$/g , "" ) : "" ;
absStack = absPath ? absPath . split ( "/" ) : [ ] ;
relStack = relPath . split ( "/" ) ;
for ( i = 0 ; i < relStack . length ; i ++ ) {
d = relStack [ i ] ;
switch ( d ) {
case "." :
break ;
case ".." :
if ( absStack . length ) {
absStack . pop ( ) ;
}
break ;
default :
absStack . push ( d ) ;
break ;
}
}
return "/" + absStack . join ( "/" ) ;
} ,
//Returns true if both urls have the same domain.
isSameDomain : function ( absUrl1 , absUrl2 ) {
return path . parseUrl ( absUrl1 ) . domain . toLowerCase ( ) ===
path . parseUrl ( absUrl2 ) . domain . toLowerCase ( ) ;
} ,
//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 result = absUrl ,
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.
result = u . hash
. split ( dialogHashKey ) [ 0 ]
. replace ( /^#/ , "" )
. replace ( /\?.*$/ , "" ) ;
} else if ( path . isSameDomain ( u , this . documentBase ) ) {
result = u . hrefNoHash . replace ( this . documentBase . domain , "" ) . split ( dialogHashKey ) [ 0 ] ;
}
return window . decodeURIComponent ( result ) ;
} ,
//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 . toLowerCase ( ) !== this . documentUrl . domain . toLowerCase ( ) ) ) ;
} ,
hasProtocol : function ( url ) {
return ( /^(:?\w+:)/ ) . test ( url ) ;
} ,
isEmbeddedPage : function ( url ) {
var u = path . parseUrl ( url ) ;
//if the path is absolute, then we need to compare the url against
//both the this.documentUrl and the documentBase. The main reason for this
//is that links embedded within external documents will refer to the
//application document, whereas links embedded within the application
//document will be resolved against the document base.
if ( u . protocol !== "" ) {
return ( ! this . isPath ( u . hash ) && u . hash && ( u . hrefNoHash === this . documentUrl . hrefNoHash || ( this . documentBaseDiffers && u . hrefNoHash === this . documentBase . hrefNoHash ) ) ) ;
}
return ( /^#/ ) . test ( u . href ) ;
} ,
squash : function ( url , resolutionUrl ) {
var href , cleanedUrl , search , stateIndex , docUrl ,
isPath = this . isPath ( url ) ,
uri = this . parseUrl ( url ) ,
preservedHash = uri . hash ,
uiState = "" ;
// produce a url against which we can resolve the provided path
if ( ! resolutionUrl ) {
if ( isPath ) {
resolutionUrl = path . getLocation ( ) ;
} else {
docUrl = path . getDocumentUrl ( true ) ;
if ( path . isPath ( docUrl . hash ) ) {
resolutionUrl = path . squash ( docUrl . href ) ;
} else {
resolutionUrl = docUrl . href ;
}
}
}
// 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 . doubleSlash + href . host + href . pathname + search +
preservedHash ;
} else {
href += href . indexOf ( "#" ) > - 1 ? uiState : "#" + uiState ;
}
return href ;
} ,
isPreservableHash : function ( hash ) {
return hash . replace ( "#" , "" ) . indexOf ( this . uiStateKey ) === 0 ;
} ,
// Escape weird characters in the hash if it is to be used as a selector
hashToSelector : function ( hash ) {
var hasHash = ( hash . substring ( 0 , 1 ) === "#" ) ;
if ( hasHash ) {
hash = hash . substring ( 1 ) ;
}
return ( hasHash ? "#" : "" ) + hash . replace ( /([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g , "\\$1" ) ;
} ,
// return the substring of a filepath before the dialogHashKey, for making a server
// request
getFilePath : function ( path ) {
return path && path . split ( dialogHashKey ) [ 0 ] ;
} ,
// check if the specified url refers to the first page in the main
// application document.
isFirstPageUrl : function ( url ) {
// We only deal with absolute paths.
var u = path . parseUrl ( path . makeUrlAbsolute ( url , this . documentBase ) ) ,
// Does the url have the same path as the document?
samePath = u . hrefNoHash === this . documentUrl . hrefNoHash ||
( this . documentBaseDiffers &&
u . hrefNoHash === this . documentBase . hrefNoHash ) ,
// Get the first page element.
fp = $ . mobile . firstPage ,
// Get the id of the first page element if it has one.
fpId = fp && fp [ 0 ] ? fp [ 0 ] . id : undefined ;
// The url refers to the first page if the path matches the document and
// it either has no hash value, or the hash is exactly equal to the id
// of the first page element.
return samePath &&
( ! u . hash ||
u . hash === "#" ||
( fpId && u . hash . replace ( /^#/ , "" ) === fpId ) ) ;
} ,
// Some embedded browsers, like the web view in Phone Gap, allow
// cross-domain XHR requests if the document doing the request was loaded
// via the file:// protocol. This is usually to allow the application to
// "phone home" and fetch app specific data. We normally let the browser
// handle external/cross-domain urls, but if the allowCrossDomainPages
// option is true, we will allow cross-domain http/https requests to go
// through our page loading logic.
isPermittedCrossDomainRequest : function ( docUrl , reqUrl ) {
return $ . mobile . allowCrossDomainPages &&
( docUrl . protocol === "file:" || docUrl . protocol === "content:" ) &&
reqUrl . search ( /^https?:/ ) !== - 1 ;
}
} ;
path . documentUrl = path . parseLocation ( ) ;
$base = $ ( "head" ) . find ( "base" ) ;
path . documentBase = $base . length ?
path . parseUrl ( path . makeUrlAbsolute ( $base . attr ( "href" ) , path . documentUrl . href ) ) :
path . documentUrl ;
path . documentBaseDiffers = ( path . documentUrl . hrefNoHash !== path . documentBase . hrefNoHash ) ;
//return the original document base url
path . getDocumentBase = function ( asParsedObject ) {
return asParsedObject ? $ . extend ( { } , path . documentBase ) : path . documentBase . href ;
} ;
// DEPRECATED as of 1.4.0 - remove in 1.5.0
$ . extend ( $ . mobile , {
//return the original document url
getDocumentUrl : path . getDocumentUrl ,
//return the original document base url
getDocumentBase : path . getDocumentBase
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ . mobile . History = function ( stack , index ) {
this . stack = stack || [ ] ;
this . activeIndex = index || 0 ;
} ;
$ . extend ( $ . mobile . History . prototype , {
getActive : function ( ) {
return this . stack [ this . activeIndex ] ;
} ,
getLast : function ( ) {
return this . stack [ this . previousIndex ] ;
} ,
getNext : function ( ) {
return this . stack [ this . activeIndex + 1 ] ;
} ,
getPrev : function ( ) {
return this . stack [ this . activeIndex - 1 ] ;
} ,
// addNew is used whenever a new page is added
add : function ( url , data ) {
data = data || { } ;
//if there's forward history, wipe it
if ( this . getNext ( ) ) {
this . clearForward ( ) ;
}
// if the hash is included in the data make sure the shape
// is consistent for comparison
if ( data . hash && data . hash . indexOf ( "#" ) === - 1 ) {
data . hash = "#" + data . hash ;
}
data . url = url ;
this . stack . push ( data ) ;
this . activeIndex = this . stack . length - 1 ;
} ,
//wipe urls ahead of active index
clearForward : function ( ) {
this . stack = this . stack . slice ( 0 , this . activeIndex + 1 ) ;
} ,
find : function ( url , stack , earlyReturn ) {
stack = stack || this . stack ;
var entry , i , length = stack . length , index ;
for ( i = 0 ; i < length ; i ++ ) {
entry = stack [ i ] ;
if ( decodeURIComponent ( url ) === decodeURIComponent ( entry . url ) ||
decodeURIComponent ( url ) === decodeURIComponent ( entry . hash ) ) {
index = i ;
if ( earlyReturn ) {
return index ;
}
}
}
return index ;
} ,
closest : function ( url ) {
var closest , a = this . activeIndex ;
// First, take the slice of the history stack before the current index and search
// for a url match. If one is found, we'll avoid avoid looking through forward history
// NOTE the preference for backward history movement is driven by the fact that
// most mobile browsers only have a dedicated back button, and users rarely use
// the forward button in desktop browser anyhow
closest = this . find ( url , this . stack . slice ( 0 , a ) ) ;
// If nothing was found in backward history check forward. The `true`
// value passed as the third parameter causes the find method to break
// on the first match in the forward history slice. The starting index
// of the slice must then be added to the result to get the element index
// in the original history stack :( :(
//
// TODO this is hyper confusing and should be cleaned up (ugh so bad)
if ( closest === undefined ) {
closest = this . find ( url , this . stack . slice ( a ) , true ) ;
closest = closest === undefined ? closest : closest + a ;
}
return closest ;
} ,
direct : function ( opts ) {
var newActiveIndex = this . closest ( opts . url ) , a = this . activeIndex ;
// save new page index, null check to prevent falsey 0 result
// record the previous index for reference
if ( newActiveIndex !== undefined ) {
this . activeIndex = newActiveIndex ;
this . previousIndex = a ;
}
// invoke callbacks where appropriate
//
// TODO this is also convoluted and confusing
if ( newActiveIndex < a ) {
( opts . present || opts . back || $ . noop ) ( this . getActive ( ) , "back" ) ;
} else if ( newActiveIndex > a ) {
( opts . present || opts . forward || $ . noop ) ( this . getActive ( ) , "forward" ) ;
} else if ( newActiveIndex === undefined && opts . missing ) {
opts . missing ( this . getActive ( ) ) ;
}
}
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
var path = $ . mobile . path ,
initialHref = location . href ;
$ . mobile . Navigator = function ( history ) {
this . history = history ;
this . ignoreInitialHashChange = true ;
$ . mobile . window . bind ( {
"popstate.history" : $ . proxy ( this . popstate , this ) ,
"hashchange.history" : $ . proxy ( this . hashchange , this )
} ) ;
} ;
$ . extend ( $ . mobile . Navigator . prototype , {
squash : function ( url , data ) {
var state , href , hash = path . isPath ( url ) ? path . stripHash ( url ) : url ;
href = path . squash ( url ) ;
// make sure to provide this information when it isn't explicitly set in the
// data object that was passed to the squash method
state = $ . extend ( {
hash : hash ,
url : href
} , data ) ;
// replace the current url with the new href and store the state
// Note that in some cases we might be replacing an url with the
// same url. We do this anyways because we need to make sure that
// all of our history entries have a state object associated with
// them. This allows us to work around the case where $.mobile.back()
// is called to transition from an external page to an embedded page.
// In that particular case, a hashchange event is *NOT* generated by the browser.
// Ensuring each history entry has a state object means that onPopState()
// will always trigger our hashchange callback even when a hashchange event
// is not fired.
window . history . replaceState ( state , state . title || document . title , href ) ;
return state ;
} ,
hash : function ( url , href ) {
var parsed , loc , hash , resolved ;
// 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 ) ) {
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 hash , state ;
// Partly to support our test suite which manually alters the support
// value to test hashchange. Partly to prevent all around weirdness
if ( ! $ . event . special . navigate . isPushStateEnabled ( ) ) {
return ;
}
// If this is the popstate triggered by the actual alteration of the hash
// prevent it completely. History is tracked manually
if ( this . preventHashAssignPopState ) {
this . preventHashAssignPopState = false ;
event . stopImmediatePropagation ( ) ;
return ;
}
// if this is the popstate triggered after the `replaceState` call in the go
// method, then simply ignore it. The history entry has already been captured
if ( this . ignorePopState ) {
this . ignorePopState = false ;
return ;
}
// If there is no state, and the history stack length is one were
// probably getting the page load popstate fired by browsers like chrome
// avoid it and set the one time flag to false.
// TODO: Do we really need all these conditions? Comparing location hrefs
// should be sufficient.
if ( ! event . originalEvent . state &&
this . history . stack . length === 1 &&
this . ignoreInitialHashChange ) {
this . ignoreInitialHashChange = false ;
if ( location . href === initialHref ) {
event . preventDefault ( ) ;
return ;
}
}
// account for direct manipulation of the hash. That is, we will receive a popstate
// when the hash is changed by assignment, and it won't have a state associated. We
// then need to squash the hash. See below for handling of hash assignment that
// matches an existing history entry
// TODO it might be better to only add to the history stack
// when the hash is adjacent to the active history entry
hash = path . parseLocation ( ) . hash ;
if ( ! event . originalEvent . state && hash ) {
// squash the hash that's been assigned on the URL with replaceState
// also grab the resulting state object for storage
state = this . squash ( hash ) ;
// record the new hash as an additional history entry
// to match the browser's treatment of hash assignment
this . history . add ( state . url , state ) ;
// pass the newly created state information
// along with the event
event . historyState = state ;
// do not alter history, we've added a new history entry
// so we know where we are
return ;
}
// If all else fails this is a popstate that comes from the back or forward buttons
// make sure to set the state of our history stack properly, and record the directionality
this . history . direct ( {
url : ( event . originalEvent . state || { } ) . url || hash ,
// When the url is either forward or backward in history include the entry
// as data on the event object for merging as data in the navigate event
present : function ( historyEntry , direction ) {
// make sure to create a new object to pass down as the navigate event data
event . historyState = $ . extend ( { } , historyEntry ) ;
event . historyState . direction = direction ;
}
} ) ;
} ,
// NOTE must bind before `navigate` special event hashchange binding otherwise the
// navigation data won't be attached to the hashchange event in time for those
// bindings to attach it to the `navigate` special event
// TODO add a check here that `hashchange.navigate` is bound already otherwise it's
// broken (exception?)
hashchange : function ( event ) {
var history , hash ;
// If hashchange listening is explicitly disabled or pushstate is supported
// avoid making use of the hashchange handler.
if ( ! $ . event . special . navigate . isHashChangeEnabled ( ) ||
$ . event . special . navigate . isPushStateEnabled ( ) ) {
return ;
}
// On occasion explicitly want to prevent the next hash from propogating because we only
// with to alter the url to represent the new state do so here
if ( this . preventNextHashChange ) {
this . preventNextHashChange = false ;
event . stopImmediatePropagation ( ) ;
return ;
}
history = this . history ;
hash = path . parseLocation ( ) . hash ;
// If this is a hashchange caused by the back or forward button
// make sure to set the state of our history stack properly
this . history . direct ( {
url : hash ,
// When the url is either forward or backward in history include the entry
// as data on the event object for merging as data in the navigate event
present : function ( historyEntry , direction ) {
// make sure to create a new object to pass down as the navigate event data
event . hashchangeState = $ . extend ( { } , historyEntry ) ;
event . hashchangeState . direction = direction ;
} ,
// When we don't find a hash in our history clearly we're aiming to go there
// record the entry as new for future traversal
//
// NOTE it's not entirely clear that this is the right thing to do given that we
// can't know the users intention. It might be better to explicitly _not_
// support location.hash assignment in preference to $.navigate calls
// TODO first arg to add should be the href, but it causes issues in identifying
// embeded pages
missing : function ( ) {
history . add ( hash , {
hash : hash ,
title : document . title
} ) ;
}
} ) ;
}
} ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
// TODO consider queueing navigation activity until previous activities have completed
// so that end users don't have to think about it. Punting for now
// TODO !! move the event bindings into callbacks on the navigate event
$ . mobile . navigate = function ( url , data , noEvents ) {
$ . mobile . navigate . navigator . go ( url , data , noEvents ) ;
} ;
// expose the history on the navigate method in anticipation of full integration with
// existing navigation functionalty that is tightly coupled to the history information
$ . mobile . navigate . history = new $ . mobile . History ( ) ;
// instantiate an instance of the navigator for use within the $.navigate method
$ . mobile . navigate . navigator = new $ . mobile . Navigator ( $ . mobile . navigate . history ) ;
var loc = $ . mobile . path . parseLocation ( ) ;
$ . mobile . navigate . history . add ( loc . href , { hash : loc . hash } ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
// existing base tag?
2015-08-31 12:00:39 -07:00
var baseElement = document . querySelector ( 'head base' ) ;
2015-06-19 22:31:44 -07:00
2015-08-31 12:00:39 -07:00
if ( ! baseElement ) {
baseElement = $ ( "<base>" , { href : $ . mobile . path . documentBase . hrefNoHash } ) . prependTo ( $ ( "head" ) ) ;
baseElement = baseElement [ 0 ] ;
}
2015-06-19 22:31:44 -07:00
// base element management, defined depending on dynamic base tag support
// TODO move to external widget
2015-08-31 12:00:39 -07:00
var base = {
2015-06-19 22:31:44 -07:00
// define base element, for use in routing asset urls that are referenced
// in Ajax-requested markup
2015-08-31 12:00:39 -07:00
element : baseElement ,
2015-06-19 22:31:44 -07:00
2015-08-31 12:00:39 -07:00
linkSelector : "*[src], link[href], a[rel='external'], *[data-ajax='false'], a[target]" ,
2015-06-19 22:31:44 -07:00
// set the generated BASE element's href to a new page's base path
set : function ( href ) {
// we should do nothing if the user wants to manage their url base
// manually
if ( ! $ . mobile . dynamicBaseEnabled ) {
return ;
}
// we should use the base tag if we can manipulate it dynamically
if ( $ . support . dynamicBaseTag ) {
2015-08-31 12:00:39 -07:00
base . element . setAttribute ( "href" ,
2015-06-19 22:31:44 -07:00
$ . mobile . path . makeUrlAbsolute ( href , $ . mobile . path . documentBase ) ) ;
}
} ,
rewrite : function ( href , page ) {
} ,
// set the generated BASE element's href to a new page's base path
reset : function ( /* href */ ) {
2015-08-31 12:00:39 -07:00
base . element . setAttribute ( "href" , $ . mobile . path . documentBase . hrefNoSearch ) ;
2015-06-19 22:31:44 -07:00
}
} ;
$ . mobile . base = base ;
} ) ( jQuery ) ;
( function ( $ , window , undefined ) {
// TODO remove direct references to $.mobile and properties, we should
// favor injection with params to the constructor
$ . mobile . Transition = function ( ) {
this . init . apply ( this , arguments ) ;
} ;
$ . extend ( $ . mobile . Transition . prototype , {
toPreClass : " ui-page-pre-in" ,
init : function ( name , reverse , $to , $from ) {
$ . extend ( this , {
name : name ,
reverse : reverse ,
$to : $to ,
$from : $from ,
deferred : new $ . Deferred ( )
} ) ;
} ,
cleanFrom : function ( ) {
this . $from
. removeClass ( $ . mobile . activePageClass + " out in reverse " + this . name )
. height ( "" ) ;
} ,
// NOTE overridden by child object prototypes, noop'd here as defaults
beforeDoneIn : function ( ) { } ,
beforeDoneOut : function ( ) { } ,
beforeStartOut : function ( ) { } ,
doneIn : function ( ) {
this . beforeDoneIn ( ) ;
this . $to . removeClass ( "out in reverse " + this . name ) . height ( "" ) ;
this . 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 ( ) !== this . toScroll ) {
this . scrollPage ( ) ;
}
if ( ! this . sequential ) {
this . $to . addClass ( $ . mobile . activePageClass ) ;
}
this . deferred . resolve ( this . name , this . reverse , this . $to , this . $from , true ) ;
} ,
doneOut : function ( screenHeight , reverseClass , none , preventFocus ) {
this . beforeDoneOut ( ) ;
this . startIn ( screenHeight , reverseClass , none , preventFocus ) ;
} ,
hideIn : function ( callback ) {
// Prevent flickering in phonegap container: see comments at #4024 regarding iOS
this . $to . css ( "z-index" , - 10 ) ;
callback . call ( this ) ;
this . $to . css ( "z-index" , "" ) ;
} ,
scrollPage : function ( ) {
2015-09-01 12:18:25 -07:00
//if we are hiding the url bar or the page was previously scrolled scroll to hide or return to position
2015-06-19 22:31:44 -07:00
if ( $ . mobile . hideUrlBar || this . toScroll !== $ . mobile . defaultHomeScroll ) {
window . scrollTo ( 0 , this . toScroll ) ;
}
} ,
startIn : function ( screenHeight , reverseClass , none , preventFocus ) {
this . hideIn ( function ( ) {
this . $to . addClass ( $ . mobile . activePageClass + this . toPreClass ) ;
// Send focus to page as it is now display: block
if ( ! preventFocus ) {
$ . mobile . focusPage ( this . $to ) ;
}
// Set to page height
this . $to . height ( screenHeight + this . toScroll ) ;
if ( ! none ) {
this . scrollPage ( ) ;
}
} ) ;
this . $to
. removeClass ( this . toPreClass )
. addClass ( this . name + " in " + reverseClass ) ;
if ( ! none ) {
this . $to . animationComplete ( $ . proxy ( function ( ) {
this . doneIn ( ) ;
} , this ) ) ;
} else {
this . doneIn ( ) ;
}
} ,
startOut : function ( screenHeight , reverseClass , none ) {
this . beforeStartOut ( screenHeight , reverseClass , none ) ;
// Set the from page's height and start it transitioning out
// Note: setting an explicit height helps eliminate tiling in the transitions
this . $from
. height ( screenHeight + $ . mobile . window . scrollTop ( ) )
. addClass ( this . name + " out" + reverseClass ) ;
} ,
toggleViewportClass : function ( ) {
$ . mobile . pageContainer . toggleClass ( "ui-mobile-viewport-transitioning viewport-" + this . name ) ;
} ,
transition : function ( ) {
// NOTE many of these could be calculated/recorded in the constructor, it's my
// opinion that binding them as late as possible has value with regards to
// better transitions with fewer bugs. Ie, it's not guaranteed that the
// object will be created and transition will be run immediately after as
// it is today. So we wait until transition is invoked to gather the following
var none ,
reverseClass = this . reverse ? " reverse" : "" ,
screenHeight = $ . mobile . getScreenHeight ( ) ,
maxTransitionOverride = $ . mobile . maxTransitionWidth !== false &&
$ . mobile . window . width ( ) > $ . mobile . maxTransitionWidth ;
this . toScroll = $ . mobile . navigate . history . getActive ( ) . lastScroll || $ . mobile . defaultHomeScroll ;
none = ! $ . support . cssTransitions || ! $ . support . cssAnimations ||
maxTransitionOverride || ! this . name || this . name === "none" ||
Math . max ( $ . mobile . window . scrollTop ( ) , this . toScroll ) >
$ . mobile . getMaxScrollForTransition ( ) ;
this . toggleViewportClass ( ) ;
2015-06-29 11:08:07 -07:00
if ( this . $from && ! none ) {
this . startOut ( screenHeight , reverseClass , none ) ;
} else {
this . doneOut ( screenHeight , reverseClass , none , true ) ;
}
2015-06-19 22:31:44 -07:00
return this . deferred . promise ( ) ;
}
} ) ;
} ) ( jQuery , this ) ;
( function ( $ ) {
$ . mobile . SerialTransition = function ( ) {
this . init . apply ( this , arguments ) ;
} ;
$ . extend ( $ . mobile . SerialTransition . prototype , $ . mobile . Transition . prototype , {
sequential : true ,
beforeDoneOut : function ( ) {
if ( this . $from ) {
this . cleanFrom ( ) ;
}
} ,
beforeStartOut : function ( screenHeight , reverseClass , none ) {
this . $from . animationComplete ( $ . proxy ( function ( ) {
this . doneOut ( screenHeight , reverseClass , none ) ;
} , this ) ) ;
}
} ) ;
} ) ( jQuery ) ;
( function ( $ ) {
$ . mobile . ConcurrentTransition = function ( ) {
this . init . apply ( this , arguments ) ;
} ;
$ . extend ( $ . mobile . ConcurrentTransition . prototype , $ . mobile . Transition . prototype , {
sequential : false ,
beforeDoneIn : function ( ) {
if ( this . $from ) {
this . cleanFrom ( ) ;
}
} ,
beforeStartOut : function ( screenHeight , reverseClass , none ) {
this . doneOut ( screenHeight , reverseClass , none ) ;
}
} ) ;
} ) ( jQuery ) ;
( function ( $ ) {
// generate the handlers from the above
var defaultGetMaxScrollForTransition = function ( ) {
return $ . mobile . getScreenHeight ( ) * 3 ;
} ;
//transition handler dictionary for 3rd party transitions
$ . mobile . transitionHandlers = {
"sequential" : $ . mobile . SerialTransition ,
"simultaneous" : $ . mobile . ConcurrentTransition
} ;
// Make our transition handler the public default.
$ . mobile . defaultTransitionHandler = $ . mobile . transitionHandlers . sequential ;
$ . 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 ) ;
( function ( $ , undefined ) {
2015-09-05 09:58:27 -07:00
var pageCache = { } ;
2015-06-19 22:31:44 -07:00
$ . widget ( "mobile.pagecontainer" , {
options : {
theme : "a"
} ,
initSelector : false ,
_create : function ( ) {
this . _trigger ( "beforecreate" ) ;
this . setLastScrollEnabled = true ;
this . _on ( this . window , {
// 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
navigate : "_disableRecordScroll" ,
// bind to scrollstop for the first page, "pagechange" won't be
// fired in that case
scrollstop : "_delayedRecordScroll"
} ) ;
// TODO consider moving the navigation handler OUT of widget into
// some other object as glue between the navigate event and the
// content widget load and change methods
this . _on ( this . window , { navigate : "_filterNavigateEvents" } ) ;
// TODO move from page* events to content* events
this . _on ( { pagechange : "_afterContentChange" } ) ;
// handle initial hashchange from chrome :(
this . window . one ( "navigate" , $ . proxy ( function ( ) {
this . setLastScrollEnabled = true ;
} , this ) ) ;
} ,
_setOptions : function ( options ) {
if ( options . theme !== undefined && options . theme !== "none" ) {
this . element . removeClass ( "ui-overlay-" + this . options . theme )
. addClass ( "ui-overlay-" + options . theme ) ;
} else if ( options . theme !== undefined ) {
this . element . removeClass ( "ui-overlay-" + this . options . theme ) ;
}
this . _super ( options ) ;
} ,
_disableRecordScroll : function ( ) {
this . setLastScrollEnabled = false ;
} ,
_enableRecordScroll : function ( ) {
this . setLastScrollEnabled = true ;
} ,
// TODO consider the name here, since it's purpose specific
_afterContentChange : function ( ) {
// once the page has changed, re-enable the scroll recording
this . 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
this . _off ( this . window , "scrollstop" ) ;
// determine and bind to the current scoll element which may be the
// window or in the case of touch overflow the element touch overflow
this . _on ( this . window , { scrollstop : "_delayedRecordScroll" } ) ;
} ,
_recordScroll : function ( ) {
// this barrier prevents setting the scroll value based on
// the browser scrolling the window based on a hashchange
if ( ! this . setLastScrollEnabled ) {
return ;
}
var active = this . _getActiveHistory ( ) ,
currentScroll , minScroll , defaultScroll ;
if ( active ) {
currentScroll = this . _getScroll ( ) ;
minScroll = this . _getMinScroll ( ) ;
defaultScroll = this . _getDefaultScroll ( ) ;
// Set active page's lastScroll prop. If the location we're
// scrolling to is less than minScrollBack, let it go.
active . lastScroll = currentScroll < minScroll ? defaultScroll : currentScroll ;
}
} ,
_delayedRecordScroll : function ( ) {
setTimeout ( $ . proxy ( this , "_recordScroll" ) , 100 ) ;
} ,
_getScroll : function ( ) {
return this . window . scrollTop ( ) ;
} ,
_getMinScroll : function ( ) {
return $ . mobile . minScrollBack ;
} ,
_getDefaultScroll : function ( ) {
return $ . mobile . defaultHomeScroll ;
} ,
_filterNavigateEvents : function ( e , data ) {
var url ;
if ( e . originalEvent && e . originalEvent . isDefaultPrevented ( ) ) {
return ;
}
url = e . originalEvent . type . indexOf ( "hashchange" ) > - 1 ? data . state . hash : data . state . url ;
if ( ! url ) {
url = this . _getHash ( ) ;
}
if ( ! url || url === "#" || url . indexOf ( "#" + $ . mobile . path . uiStateKey ) === 0 ) {
url = location . href ;
}
this . _handleNavigate ( url , data . state ) ;
} ,
_getHash : function ( ) {
return $ . mobile . path . parseLocation ( ) . hash ;
} ,
// TODO active page should be managed by the container (ie, it should be a property)
getActivePage : function ( ) {
return this . activePage ;
} ,
// TODO the first page should be a property set during _create using the logic
// that currently resides in init
_getInitialContent : function ( ) {
return $ . mobile . firstPage ;
} ,
// TODO each content container should have a history object
_getHistory : function ( ) {
return $ . mobile . navigate . history ;
} ,
_getActiveHistory : function ( ) {
return this . _getHistory ( ) . getActive ( ) ;
} ,
// TODO the document base should be determined at creation
_getDocumentBase : function ( ) {
return $ . mobile . path . documentBase ;
} ,
back : function ( ) {
this . go ( - 1 ) ;
} ,
forward : function ( ) {
this . go ( 1 ) ;
} ,
go : function ( steps ) {
//if hashlistening is enabled use native history method
if ( $ . mobile . hashListeningEnabled ) {
window . history . go ( steps ) ;
} else {
//we are not listening to the hash so handle history internally
var activeIndex = $ . mobile . navigate . history . activeIndex ,
index = activeIndex + parseInt ( steps , 10 ) ,
url = $ . mobile . navigate . history . stack [ index ] . url ,
direction = ( steps >= 1 ) ? "forward" : "back" ;
//update the history object
$ . mobile . navigate . history . activeIndex = index ;
$ . mobile . navigate . history . previousIndex = activeIndex ;
//change to the new page
this . change ( url , { direction : direction , changeHash : false , fromHashChange : true } ) ;
}
} ,
// TODO rename _handleDestination
_handleDestination : function ( to ) {
var history ;
// clean the hash for comparison if it's a url
if ( $ . type ( to ) === "string" ) {
to = $ . mobile . path . stripHash ( to ) ;
}
if ( to ) {
history = this . _getHistory ( ) ;
// 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.
//
// TODO move check to history object or path object?
to = ! $ . mobile . path . isPath ( to ) ? ( $ . mobile . path . makeUrlAbsolute ( "#" + to , this . _getDocumentBase ( ) ) ) : to ;
}
return to || this . _getInitialContent ( ) ;
} ,
_transitionFromHistory : function ( direction , defaultTransition ) {
var history = this . _getHistory ( ) ,
entry = ( direction === "back" ? history . getLast ( ) : history . getActive ( ) ) ;
return ( entry && entry . transition ) || defaultTransition ;
} ,
_handleDialog : function ( changePageOptions , data ) {
var to , active , activeContent = this . getActivePage ( ) ;
// If current active page is not a dialog skip the dialog and continue
// in the same direction
// Note: The dialog widget is deprecated as of 1.4.0 and will be removed in 1.5.0.
// Thus, as of 1.5.0 activeContent.data( "mobile-dialog" ) will always evaluate to
// falsy, so the second condition in the if-statement below can be removed altogether.
if ( activeContent && ! activeContent . data ( "mobile-dialog" ) ) {
// determine if we're heading forward or backward and continue
// accordingly past the current dialog
if ( data . direction === "back" ) {
this . back ( ) ;
} else {
this . forward ( ) ;
}
// prevent changePage call
return false ;
} 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 ;
active = this . _getActiveHistory ( ) ;
// 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 : this . _transitionFromHistory (
data . direction ,
changePageOptions . transition ) ,
reverse : data . direction === "back"
} ) ;
}
return to ;
} ,
_handleNavigate : function ( url , data ) {
//find first page via hash
// TODO stripping the hash twice with handleUrl
var to = $ . mobile . path . stripHash ( url ) , history = this . _getHistory ( ) ,
// transition is false if it's the first page, undefined
// otherwise (and may be overridden by default)
transition = history . stack . length === 0 ? "none" :
this . _transitionFromHistory ( data . direction ) ,
// 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 : transition
} ) ;
// TODO move to _handleDestination ?
// If this isn't the first page, if the current url is a dialog hash
// key, and the initial destination isn't equal to the current target
// page, use the special dialog handling
if ( history . activeIndex > 0 &&
to . indexOf ( $ . mobile . dialogHashKey ) > - 1 ) {
to = this . _handleDialog ( changePageOptions , data ) ;
if ( to === false ) {
return ;
}
}
this . _changeContent ( this . _handleDestination ( to ) , changePageOptions ) ;
} ,
_changeContent : function ( to , opts ) {
$ . mobile . changePage ( to , opts ) ;
} ,
_getBase : function ( ) {
return $ . mobile . base ;
} ,
_getNs : function ( ) {
return $ . mobile . ns ;
} ,
_enhance : function ( content , role ) {
// TODO consider supporting a custom callback, and passing in
// the settings which includes the role
return content . page ( { role : role } ) ;
} ,
2015-08-30 15:55:58 -07:00
_include : function ( page , jPage , settings ) {
// append to page and enhance
2015-08-31 18:38:15 -07:00
jPage . appendTo ( this . element ) ;
//alert(jPage[0].parentNode == this.element[0]);
//this.element[0].appendChild(page);
2015-06-19 22:31:44 -07:00
2015-08-31 18:38:15 -07:00
// use the page widget to enhance
2015-08-30 15:55:58 -07:00
this . _enhance ( jPage , settings . role ) ;
2015-06-19 22:31:44 -07:00
} ,
_find : function ( absUrl ) {
// TODO consider supporting a custom callback
var fileUrl = this . _createFileUrl ( absUrl ) ,
dataUrl = this . _createDataUrl ( absUrl ) ,
page , initialContent = this . _getInitialContent ( ) ;
// Check to see if the page already exists in the DOM.
// NOTE do _not_ use the :jqmData pseudo selector because parenthesis
// are a valid url char and it breaks on the first occurence
page = this . element
. children ( "[data-" + this . _getNs ( ) +
"url='" + $ . mobile . path . hashToSelector ( 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 && ! $ . mobile . path . isPath ( dataUrl ) ) {
page = this . element . children ( $ . mobile . path . hashToSelector ( "#" + dataUrl ) )
. attr ( "data-" + this . _getNs ( ) + "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. Also 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 ( page . length === 0 &&
$ . mobile . path . isFirstPageUrl ( fileUrl ) &&
initialContent &&
initialContent . parent ( ) . length ) {
page = $ ( initialContent ) ;
}
return page ;
} ,
_getLoader : function ( ) {
return $ . mobile . loading ( ) ;
} ,
_showLoading : function ( delay , theme , msg , textonly ) {
} ,
_hideLoading : function ( ) {
} ,
_showError : function ( ) {
// make sure to remove the current loading message
this . _hideLoading ( ) ;
// show the error message
this . _showLoading ( 0 , $ . mobile . pageLoadErrorMessageTheme , $ . mobile . pageLoadErrorMessage , true ) ;
// hide the error message after a delay
// TODO configuration
setTimeout ( $ . proxy ( this , "_hideLoading" ) , 1500 ) ;
} ,
_parse : function ( html , fileUrl ) {
2015-07-27 11:18:10 -07:00
// TODO consider allowing customization of this method. It's very JQM specific
2015-08-30 15:55:58 -07:00
var page , all = document . createElement ( 'div' ) ;
2015-06-19 22:31:44 -07:00
2015-07-27 11:18:10 -07:00
//workaround to allow scripts to execute when included in page divs
2015-08-30 15:55:58 -07:00
all . innerHTML = html ;
2015-06-19 22:31:44 -07:00
2015-09-05 10:43:53 -07:00
page = all . querySelector ( "div[data-role='page']" ) ;
2015-06-19 22:31:44 -07:00
2015-07-27 11:18:10 -07:00
//if page elem couldn't be found, create one and insert the body element's contents
2015-08-30 15:55:58 -07:00
if ( ! page ) {
2015-07-27 11:18:10 -07:00
page = $ ( "<div data-" + this . _getNs ( ) + "role='page'>" +
( html . split ( /<\/?body[^>]*>/gmi ) [ 1 ] || "" ) +
2015-08-30 15:55:58 -07:00
"</div>" ) [ 0 ] ;
2015-07-27 11:18:10 -07:00
}
2015-06-19 22:31:44 -07:00
2015-07-27 11:18:10 -07:00
// TODO tagging 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
2015-08-30 15:55:58 -07:00
page . setAttribute ( "data-" + this . _getNs ( ) + "url" , this . _createDataUrl ( fileUrl ) ) ;
page . setAttribute ( "data-" + this . _getNs ( ) + "external-page" , true ) ;
2015-06-19 22:31:44 -07:00
2015-07-27 11:18:10 -07:00
return page ;
2015-06-19 22:31:44 -07:00
} ,
_setLoadedTitle : function ( page , html ) {
//page title regexp
2015-08-30 15:55:58 -07:00
if ( ! page . jqmData ( "title" ) ) {
var newPageTitle = html . match ( /<title[^>]*>([^<]*)/ ) && RegExp . $1 ;
2015-06-19 22:31:44 -07:00
2015-08-30 15:55:58 -07:00
if ( newPageTitle ) {
page . jqmData ( "title" , newPageTitle ) ;
}
2015-06-19 22:31:44 -07:00
}
} ,
_isRewritableBaseTag : function ( ) {
return $ . mobile . dynamicBaseEnabled && ! $ . support . dynamicBaseTag ;
} ,
_createDataUrl : function ( absoluteUrl ) {
return $ . mobile . path . convertUrlToDataUrl ( absoluteUrl ) ;
} ,
_createFileUrl : function ( absoluteUrl ) {
return $ . mobile . path . getFilePath ( absoluteUrl ) ;
} ,
_triggerWithDeprecated : function ( name , data , page ) {
var deprecatedEvent = $ . Event ( "page" + name ) ,
newEvent = $ . Event ( this . widgetName + name ) ;
// DEPRECATED
// trigger the old deprecated event on the page if it's provided
( page || this . element ) . trigger ( deprecatedEvent , data ) ;
// use the widget trigger method for the new content* event
this . _trigger ( name , newEvent , data ) ;
return {
deprecatedEvent : deprecatedEvent ,
event : newEvent
} ;
} ,
// TODO it would be nice to split this up more but everything appears to be "one off"
// or require ordering such that other bits are sprinkled in between parts that
// could be abstracted out as a group
_loadSuccess : function ( absUrl , triggerData , settings , deferred ) {
var fileUrl = this . _createFileUrl ( absUrl ) ;
2015-09-05 09:58:27 -07:00
return $ . proxy ( function ( html , wasCached ) {
2015-07-27 11:18:10 -07:00
2015-09-05 09:58:27 -07:00
if ( ! wasCached || typeof ( wasCached ) != 'boolean' ) {
if ( $ . mobile . filterHtml ) {
html = $ . mobile . filterHtml ( html ) ;
}
pageCache [ absUrl . split ( '?' ) [ 0 ] ] = html ;
2015-07-27 11:18:10 -07:00
}
2015-06-19 22:31:44 -07:00
//dont update the base tag if we are prefetching
if ( settings . prefetch === undefined ) {
this . _getBase ( ) . set ( fileUrl ) ;
}
2015-08-30 15:55:58 -07:00
var contentElem = this . _parse ( html , fileUrl ) ;
var content = $ ( contentElem ) ;
2015-07-27 11:18:10 -07:00
2015-06-19 22:31:44 -07:00
this . _setLoadedTitle ( content , html ) ;
// DEPRECATED
triggerData . page = content ;
triggerData . content = content ;
triggerData . toPage = content ;
// rewrite src and href attrs to use a base url if the base tag won't work
if ( this . _isRewritableBaseTag ( ) && content ) {
this . _getBase ( ) . rewrite ( fileUrl , content ) ;
}
2015-08-31 21:15:10 -07:00
var dependencies = contentElem . getAttribute ( 'data-require' ) ;
dependencies = dependencies ? dependencies . split ( ',' ) : null ;
if ( contentElem . classList . contains ( 'type-interior' ) ) {
dependencies = dependencies || [ ] ;
dependencies . push ( 'jqmicons' ) ;
dependencies . push ( 'jqmpopup' ) ;
2015-09-04 13:32:20 -07:00
dependencies . push ( 'jqmlistview' ) ;
2015-09-05 09:58:27 -07:00
dependencies . push ( 'jqmcollapsible' ) ;
2015-09-05 22:06:16 -07:00
dependencies . push ( 'jqmcontrolgroup' ) ;
2015-09-04 13:32:20 -07:00
}
2015-08-31 21:15:10 -07:00
var currentSelf = this ;
require ( dependencies , function ( ) {
currentSelf . _include ( contentElem , content , settings ) ;
deferred . resolve ( absUrl , settings , content ) ;
} ) ;
2015-06-19 22:31:44 -07:00
} , this ) ;
} ,
_loadDefaults : {
type : "get" ,
data : undefined ,
// DEPRECATED
reloadPage : false ,
reload : false ,
// By default we rely on the role defined by the @data-role attribute.
role : undefined ,
showLoadMsg : false ,
// This delay allows loads that pull from browser cache to
// occur without showing the loading message.
loadMsgDelay : 50
} ,
load : function ( url , options ) {
// This function uses deferred notifications to let callers
// know when the content is done loading, or if an error has occurred.
var deferred = ( options && options . deferred ) || $ . Deferred ( ) ,
// Examining the option "reloadPage" passed by the user is deprecated as of 1.4.0
// and will be removed in 1.5.0.
// Copy option "reloadPage" to "reload", but only if option "reload" is not present
reloadOptionExtension =
( ( options && options . reload === undefined &&
options . reloadPage !== undefined ) ?
{ reload : options . reloadPage } : { } ) ,
// The default load options with overrides specified by the caller.
settings = $ . extend ( { } , this . _loadDefaults , options , reloadOptionExtension ) ,
// The DOM element for the content after it has been loaded.
content = null ,
// The absolute version of the URL passed into the function. This
// version of the URL may contain dialog/subcontent params in it.
absUrl = $ . mobile . path . makeUrlAbsolute ( url , this . _findBaseWithDefault ( ) ) ,
fileUrl , dataUrl , pblEvent , triggerData ;
// If the caller provided data, and we're using "get" request,
// append the data to the URL.
if ( settings . data && settings . type === "get" ) {
absUrl = $ . mobile . path . addSearchParams ( absUrl , settings . data ) ;
settings . data = undefined ;
}
// If the caller is using a "post" request, reload must be true
if ( settings . data && settings . type === "post" ) {
settings . reload = true ;
}
// The absolute version of the URL minus any dialog/subcontent params.
// In otherwords the real URL of the content to be loaded.
fileUrl = this . _createFileUrl ( absUrl ) ;
// The version of the Url actually stored in the data-url attribute of
// the content. For embedded content, it is just the id of the page. For
// content within the same domain as the document base, it is the site
// relative path. For cross-domain content (Phone Gap only) the entire
// absolute Url is used to load the content.
dataUrl = this . _createDataUrl ( absUrl ) ;
content = this . _find ( absUrl ) ;
// If it isn't a reference to the first content and refers to missing
// embedded content reject the deferred and return
if ( content . length === 0 &&
$ . mobile . path . isEmbeddedPage ( fileUrl ) &&
! $ . mobile . path . isFirstPageUrl ( fileUrl ) ) {
deferred . reject ( absUrl , settings ) ;
return deferred . promise ( ) ;
}
// Reset base to the default document base
// TODO figure out why we doe this
this . _getBase ( ) . reset ( ) ;
// If the content 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. Resolve the deferrred so that
// users can bind to .done on the promise
if ( content . length && ! settings . reload ) {
this . _enhance ( content , settings . role ) ;
deferred . resolve ( absUrl , settings , content ) ;
//if we are reloading the content make sure we update
// the base if its not a prefetch
if ( ! settings . prefetch ) {
this . _getBase ( ) . set ( url ) ;
}
return deferred . promise ( ) ;
}
triggerData = {
url : url ,
absUrl : absUrl ,
toPage : url ,
prevPage : options ? options . fromPage : undefined ,
dataUrl : dataUrl ,
deferred : deferred ,
options : settings
} ;
// Let listeners know we're about to load content.
pblEvent = this . _triggerWithDeprecated ( "beforeload" , triggerData ) ;
// If the default behavior is prevented, stop here!
if ( pblEvent . deprecatedEvent . isDefaultPrevented ( ) ||
pblEvent . event . isDefaultPrevented ( ) ) {
return deferred . promise ( ) ;
}
if ( settings . showLoadMsg ) {
this . _showLoading ( settings . loadMsgDelay ) ;
}
// Reset base to the default document base.
// only reset if we are not prefetching
if ( settings . prefetch === undefined ) {
this . _getBase ( ) . reset ( ) ;
}
if ( ! ( $ . mobile . allowCrossDomainPages ||
$ . mobile . path . isSameDomain ( $ . mobile . path . documentUrl , absUrl ) ) ) {
deferred . reject ( absUrl , settings ) ;
return deferred . promise ( ) ;
}
2015-09-05 09:58:27 -07:00
var successFn = this . _loadSuccess ( absUrl , triggerData , settings , deferred ) ;
var cachedResult = pageCache [ absUrl . split ( '?' ) [ 0 ] ] ;
if ( cachedResult ) {
successFn ( cachedResult , true ) ;
return deferred . promise ( ) ;
}
2015-06-19 22:31:44 -07:00
// Load the new content.
$ . ajax ( {
url : fileUrl ,
type : settings . type ,
data : settings . data ,
contentType : settings . contentType ,
dataType : "html" ,
2015-09-05 09:58:27 -07:00
success : successFn ,
2015-06-19 22:31:44 -07:00
error : this . _loadError ( absUrl , triggerData , settings , deferred )
} ) ;
return deferred . promise ( ) ;
} ,
_loadError : function ( absUrl , triggerData , settings , deferred ) {
return $ . proxy ( function ( xhr , textStatus , errorThrown ) {
//set base back to current path
this . _getBase ( ) . set ( $ . mobile . path . get ( ) ) ;
// Add error info to our triggerData.
triggerData . xhr = xhr ;
triggerData . textStatus = textStatus ;
triggerData . errorThrown = errorThrown ;
// Let listeners know the page load failed.
var plfEvent = this . _triggerWithDeprecated ( "loadfailed" , 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 . deprecatedEvent . isDefaultPrevented ( ) ||
plfEvent . event . isDefaultPrevented ( ) ) {
return ;
}
// Remove loading message.
if ( settings . showLoadMsg ) {
this . _showError ( ) ;
}
deferred . reject ( absUrl , settings ) ;
} , this ) ;
} ,
_getTransitionHandler : function ( transition ) {
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.
return $ . mobile . transitionHandlers [ transition ] || $ . mobile . defaultTransitionHandler ;
} ,
// TODO move into transition handlers?
_triggerCssTransitionEvents : function ( to , from , prefix ) {
var samePage = false ;
prefix = prefix || "" ;
// TODO decide if these events should in fact be triggered on the container
if ( from ) {
//Check if this is a same page transition and tell the handler in page
if ( to [ 0 ] === from [ 0 ] ) {
samePage = true ;
}
2015-08-31 07:57:04 -07:00
//trigger before show/hide events
2015-06-19 22:31:44 -07:00
// TODO deprecate nextPage in favor of next
this . _triggerWithDeprecated ( prefix + "hide" , {
// Deprecated in 1.4 remove in 1.5
nextPage : to ,
toPage : to ,
prevPage : from ,
samePage : samePage
} , from ) ;
}
// TODO deprecate prevPage in favor of previous
this . _triggerWithDeprecated ( prefix + "show" , {
prevPage : from || $ ( "" ) ,
toPage : to
} , to ) ;
} ,
// TODO make private once change has been defined in the widget
_cssTransition : function ( to , from , options ) {
var transition = options . transition ,
reverse = options . reverse ,
TransitionHandler ,
promise ;
2015-09-03 18:34:57 -07:00
this . _triggerCssTransitionEvents ( to , from , "before" ) ;
if ( from ) {
from [ 0 ] . style . display = 'none' ;
}
to [ 0 ] . style . display = 'block' ;
this . _triggerCssTransitionEvents ( to , from ) ;
return ;
2015-06-19 22:31:44 -07:00
// TODO put this in a binding to events *outside* the widget
this . _hideLoading ( ) ;
TransitionHandler = this . _getTransitionHandler ( transition ) ;
promise = ( new TransitionHandler ( transition , reverse , to , from ) ) . transition ( ) ;
promise . done ( $ . proxy ( function ( ) {
this . _triggerCssTransitionEvents ( to , from ) ;
} , this ) ) ;
} ,
_removeActiveLinkClass : function ( force ) {
//clear out the active button state
$ . mobile . removeActiveLinkClass ( force ) ;
} ,
_loadUrl : function ( to , triggerData , settings ) {
// 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 = to ;
settings . deferred = $ . Deferred ( ) ;
this . load ( to , settings ) ;
settings . deferred . done ( $ . proxy ( function ( url , options , content ) {
// store the original absolute url so that it can be provided
// to events in the triggerData of the subsequent changePage call
options . absUrl = triggerData . absUrl ;
this . transition ( content , triggerData , options ) ;
} , this ) ) ;
settings . deferred . fail ( $ . proxy ( function ( /* url, options */ ) {
this . _removeActiveLinkClass ( true ) ;
this . _triggerWithDeprecated ( "changefailed" , triggerData ) ;
} , this ) ) ;
} ,
_triggerPageBeforeChange : function ( to , triggerData , settings ) {
var returnEvents ;
triggerData . prevPage = this . activePage ;
$ . extend ( triggerData , {
toPage : to ,
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 ( $ . type ( to ) === "string" ) {
// if the toPage is a string simply convert it
triggerData . absUrl = $ . mobile . path . makeUrlAbsolute ( to , this . _findBaseWithDefault ( ) ) ;
} else {
// if the toPage is a jQuery object grab the absolute url stored
// in the loadPage callback where it exists
triggerData . absUrl = settings . absUrl ;
}
return true ;
} ,
change : function ( to , options ) {
2015-09-03 19:40:54 -07:00
var settings = $ . extend ( { } , $ . mobile . changePage . defaults , options ) ,
2015-06-19 22:31:44 -07:00
triggerData = { } ;
// Make sure we have a fromPage.
settings . fromPage = settings . fromPage || this . activePage ;
// if the page beforechange default is prevented return early
if ( ! this . _triggerPageBeforeChange ( to , triggerData , settings ) ) {
return ;
}
// We allow "pagebeforechange" observers to modify the to in
// the trigger data to allow for redirects. Make sure our to is
// updated. We also need to re-evaluate whether it is a string,
// because an object can also be replaced by a string
to = triggerData . toPage ;
// 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 ( $ . type ( to ) === "string" ) {
this . _loadUrl ( to , triggerData , settings ) ;
} else {
this . transition ( to , triggerData , settings ) ;
}
} ,
transition : function ( toPage , triggerData , settings ) {
var fromPage , url , pageUrl , fileUrl ,
active , activeIsInitialPage ,
historyDir , pageTitle , isDialog ,
alreadyThere , newPageTitle ,
params , cssTransitionDeferred ,
beforeTransition ;
triggerData . prevPage = settings . fromPage ;
// 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 = $ . mobile . path . documentUrl . hrefNoHash ;
}
// The caller passed us a real page DOM element. Update our
// internal state and then trigger a transition to the page.
fromPage = settings . fromPage ;
url = ( settings . dataUrl && $ . mobile . 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 = $ . mobile . path . getFilePath ( url ) ;
active = $ . mobile . navigate . history . getActive ( ) ;
activeIsInitialPage = $ . mobile . navigate . history . activeIndex === 0 ;
historyDir = 0 ;
pageTitle = document . title ;
isDialog = ( settings . role === "dialog" ||
toPage . jqmData ( "role" ) === "dialog" ) &&
toPage . jqmData ( "dialog" ) !== true ;
// 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 ) {
this . _triggerWithDeprecated ( "transition" , triggerData ) ;
this . _triggerWithDeprecated ( "change" , 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 ) {
$ . mobile . navigate . history . direct ( { url : url } ) ;
}
return ;
}
2015-09-03 18:34:57 -07:00
// We need to make sure the page we are given has already been enhanced.
2015-06-19 22:31:44 -07:00
toPage . page ( { role : 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 = settings . direction === "back" ? - 1 : 1 ;
}
// 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
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 ( $ . mobile . dialogHashKey ) > - 1 &&
this . activePage &&
! this . activePage . hasClass ( "ui-dialog" ) &&
$ . mobile . navigate . history . 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 += $ . mobile . dialogHashKey ;
} else {
url += "#" + $ . mobile . 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
2015-08-31 07:57:04 -07:00
newPageTitle = ( ! active ) ? pageTitle : toPage . jqmData ( "title" ) ;
2015-06-19 22:31:44 -07:00
if ( ! ! newPageTitle && pageTitle === document . title ) {
pageTitle = newPageTitle ;
}
if ( ! toPage . jqmData ( "title" ) ) {
toPage . jqmData ( "title" , pageTitle ) ;
}
// Make sure we have a transition defined.
2015-09-03 19:40:54 -07:00
settings . transition = "none" ;
2015-06-19 22:31:44 -07:00
//add page to history stack if it's not back or forward
if ( ! historyDir && alreadyThere ) {
$ . mobile . navigate . history . getActive ( ) . pageUrl = pageUrl ;
}
// Set the location hash.
if ( url && ! settings . fromHashChange ) {
2015-09-03 18:34:57 -07:00
// rebuilding the hash here since we loose it earlier on
2015-06-19 22:31:44 -07:00
// TODO preserve the originally passed in path
if ( ! $ . mobile . 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 ( this . window [ 0 ] . encodeURI ( 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 deprecated in 1.4 remove in 1.5
$ . mobile . activePage = toPage ;
//new way to handle activePage
this . activePage = toPage ;
// If we're navigating back in the URL history, set reverse accordingly.
settings . reverse = settings . reverse || historyDir < 0 ;
this . _cssTransition ( toPage , fromPage , {
transition : settings . transition ,
2015-08-31 07:57:04 -07:00
reverse : settings . reverse
2015-06-19 22:31:44 -07:00
} ) ;
2015-08-31 07:57:04 -07:00
$ . mobile . removeActiveLinkClass ( ) ;
2015-06-19 22:31:44 -07:00
} ,
// determine the current base url
2015-09-03 09:39:24 -07:00
_findBaseWithDefault : function ( ) {
var closestBase = ( this . activePage &&
$ . mobile . getClosestBaseUrl ( this . activePage [ 0 ] ) ) ;
2015-06-19 22:31:44 -07:00
return closestBase || $ . mobile . path . documentBase . hrefNoHash ;
}
} ) ;
// The following handlers should be bound after mobileinit has been triggered
// the following deferred is resolved in the init file
$ . mobile . navreadyDeferred = $ . Deferred ( ) ;
//these variables make all page containers use the same queue and only navigate one at a time
// queue to hold simultanious page transitions
2015-09-03 19:40:54 -07:00
var pageTransitionQueue = [ ] ;
2015-06-19 22:31:44 -07:00
} ) ( jQuery ) ;
( function ( $ , undefined ) {
// resolved on domready
2015-09-03 09:39:24 -07:00
var domreadyDeferred = $ . Deferred ( ) ,
2015-06-19 22:31:44 -07:00
2015-09-03 09:39:24 -07:00
// resolved and nulled on window.load()
loadDeferred = $ . Deferred ( ) ,
2015-06-19 22:31:44 -07:00
2015-09-03 09:39:24 -07:00
// function that resolves the above deferred
pageIsFullyLoaded = function ( ) {
2015-06-19 22:31:44 -07:00
2015-09-03 09:39:24 -07:00
// Resolve and null the deferred
loadDeferred . resolve ( ) ;
loadDeferred = null ;
} ,
2015-06-19 22:31:44 -07:00
2015-09-03 09:39:24 -07:00
documentUrl = $ . mobile . path . documentUrl ;
2015-06-19 22:31:44 -07:00
$ . mobile . loadPage = function ( url , opts ) {
var container ;
opts = opts || { } ;
container = ( opts . pageContainer || $ . mobile . pageContainer ) ;
// create the deferred that will be supplied to loadPage callers
// and resolved by the content widget's load method
opts . deferred = $ . Deferred ( ) ;
// Preferring to allow exceptions for uninitialized opts.pageContainer
// widgets so we know if we need to force init here for users
container . pagecontainer ( "load" , url , opts ) ;
// provide the deferred
return opts . deferred . promise ( ) ;
} ;
//define vars for interal use
/* 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 {
$ . mobile . pageContainer . pagecontainer ( "back" ) ;
}
} ;
// Direct focus to the page title, or otherwise first focusable element
$ . mobile . focusPage = function ( page ) {
} ;
// No-op implementation of transition degradation
$ . mobile . _maybeDegradeTransition = $ . mobile . _maybeDegradeTransition || function ( transition ) {
return transition ;
} ;
// Exposed $.mobile methods
$ . mobile . changePage = function ( to , options ) {
$ . mobile . pageContainer . pagecontainer ( "change" , to , options ) ;
} ;
$ . 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
} ;
2015-09-03 09:39:24 -07:00
function parentWithTag ( elem , tagName ) {
2015-06-19 22:31:44 -07:00
2015-09-03 09:39:24 -07:00
while ( elem . tagName != tagName ) {
elem = elem . parentNode ;
2015-06-19 22:31:44 -07:00
2015-09-03 09:39:24 -07:00
if ( ! elem ) {
return null ;
}
}
2015-06-19 22:31:44 -07:00
2015-09-03 09:39:24 -07:00
return elem ;
}
2015-06-19 22:31:44 -07:00
2015-09-03 09:39:24 -07:00
$ . mobile . _registerInternalEvents = function ( ) {
2015-06-19 22:31:44 -07:00
// click routing - direct to HTTP or Ajax, accordingly
$ . mobile . document . bind ( "click" , function ( event ) {
if ( ! $ . mobile . linkBindingEnabled || event . isDefaultPrevented ( ) ) {
return ;
}
2015-09-03 09:39:24 -07:00
var link = parentWithTag ( event . target , 'A' ) ;
var $link = $ ( link ) ,
2015-06-19 22:31:44 -07:00
baseUrl , href ,
useDefaultUrlHandling , isExternal ,
transition , reverse , role ;
// 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
2015-09-03 09:39:24 -07:00
if ( ! link || event . which > 1 ) {
2015-06-19 22:31:44 -07:00
return ;
}
//if there's a data-rel=back attr, go back in history
2015-09-03 09:39:24 -07:00
if ( link . getAttribute ( 'data-rel' ) == 'back' ) {
2015-06-19 22:31:44 -07:00
$ . mobile . back ( ) ;
return false ;
}
2015-09-03 09:39:24 -07:00
baseUrl = $ . mobile . getClosestBaseUrl ( link ) ;
2015-06-19 22:31:44 -07:00
//get href, if defined, otherwise default to empty hash
2015-09-03 09:39:24 -07:00
href = $ . mobile . path . makeUrlAbsolute ( link . getAttribute ( "href" ) || "#" , baseUrl ) ;
2015-06-19 22:31:44 -07:00
// 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.
if ( href . search ( "#" ) !== - 1 &&
! ( $ . mobile . path . isExternal ( href ) && $ . mobile . path . isAbsoluteUrl ( href ) ) ) {
href = href . replace ( /[^#]*#/ , "" ) ;
if ( ! href ) {
//link was an empty hash meant purely
//for interaction, so we ignore it.
event . preventDefault ( ) ;
return ;
} else if ( $ . mobile . path . isPath ( href ) ) {
//we have apath so make it the href we want to load.
href = $ . mobile . path . makeUrlAbsolute ( href , baseUrl ) ;
} else {
//we have a simple id so use the documentUrl as its base.
href = $ . mobile . path . makeUrlAbsolute ( "#" + href , documentUrl . hrefNoHash ) ;
}
}
// Should we handle this link, or let the browser deal with it?
2015-09-03 09:39:24 -07:00
useDefaultUrlHandling = link . getAttribute ( "rel" ) == "external" || link . getAttribute ( "data-ajax" ) == "false" || link . getAttribute ( 'target' ) ;
2015-06-19 22:31:44 -07:00
// 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 || ( $ . mobile . path . isExternal ( href ) && ! $ . mobile . path . isPermittedCrossDomainRequest ( documentUrl , href ) ) ;
2015-09-03 09:39:24 -07:00
if ( isExternal ) {
2015-06-19 22:31:44 -07:00
//use default click handling
return ;
}
//use ajax
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
2015-09-03 09:39:24 -07:00
role = link . getAttribute ( "data-" + $ . mobile . ns + "rel" ) || undefined ;
2015-06-19 22:31:44 -07:00
$ . mobile . changePage ( href , { transition : transition , reverse : reverse , role : role , link : $link } ) ;
event . preventDefault ( ) ;
} ) ;
// TODO ensure that the navigate binding in the content widget happens at the right time
$ . mobile . pageContainer . pagecontainer ( ) ;
2015-09-03 09:39:24 -07:00
function removePage ( page ) {
2015-09-03 18:34:57 -07:00
page . parentNode . removeChild ( page ) ;
2015-09-03 09:39:24 -07:00
}
2015-09-03 18:34:57 -07:00
function cleanPages ( newPage ) {
2015-09-03 09:39:24 -07:00
var pages = document . querySelectorAll ( "div[data-role='page']" ) ;
if ( pages . length < 5 ) {
2015-09-03 18:34:57 -07:00
//return;
2015-09-03 09:39:24 -07:00
}
for ( var i = 0 , length = pages . length ; i < length ; i ++ ) {
var page = pages [ i ] ;
2015-09-03 18:34:57 -07:00
if ( page != newPage ) {
2015-09-03 09:39:24 -07:00
removePage ( page ) ;
}
}
}
//set page min-heights to be device specific
$ . mobile . document . bind ( "pagehide" , function ( e , data ) {
2015-09-03 18:34:57 -07:00
var toPage = data . toPage ? data . toPage [ 0 ] : null ;
2015-09-03 09:39:24 -07:00
2015-09-03 18:34:57 -07:00
if ( toPage && toPage . getAttribute ( 'data-dom-cache' ) ) {
cleanPages ( toPage ) ;
}
2015-09-03 09:39:24 -07:00
} ) ;
2015-06-19 22:31:44 -07:00
} ; //navreadyDeferred done callback
$ ( function ( ) { domreadyDeferred . resolve ( ) ; } ) ;
// Account for the possibility that the load event has already fired
if ( document . readyState === "complete" ) {
pageIsFullyLoaded ( ) ;
} else {
$ . mobile . window . load ( pageIsFullyLoaded ) ;
}
$ . when ( domreadyDeferred , $ . mobile . navreadyDeferred ) . done ( function ( ) { $ . mobile . _registerInternalEvents ( ) ; } ) ;
} ) ( jQuery ) ;
( function ( $ , window , undefined ) {
var $html = $ ( "html" ) ,
$window = $ . mobile . window ;
// 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 ;
}
$ . 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 ,
2015-09-05 21:53:37 -07:00
$pages = $ ( "div[data-role='page']" ) ,
2015-06-19 22:31:44 -07:00
hash = path . stripHash ( path . stripQueryParams ( path . parseLocation ( ) . hash ) ) ,
theLocation = $ . mobile . path . parseLocation ( ) ,
hashPage = hash ? document . getElementById ( hash ) : undefined ;
// 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 [ 0 ] . getAttribute ( "data-" + $ . mobile . ns + "url" ) ) {
$this . attr ( "data-" + $ . mobile . ns + "url" , $this . attr ( "id" ) ||
path . convertUrlToDataUrl ( theLocation . pathname + theLocation . 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" )
. pagecontainer ( ) ;
// initialize navigation events now, after mobileinit has occurred and the page container
// has been created but before the rest of the library is alerted to that fact
$ . mobile . navreadyDeferred . resolve ( ) ;
// 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 ) &&
2015-08-31 07:57:04 -07:00
( $ ( hashPage ) . is ( "[data-role='page']" ) ||
2015-06-19 22:31:44 -07:00
$ . mobile . path . isPath ( hash ) ||
hash === $ . mobile . dialogHashKey ) ) ) {
// 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 ) ;
}
}
}
} ) ;
$ ( function ( ) {
//Run inlineSVG support test
$ . support . inlineSVG ( ) ;
// 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.
// hide iOS browser chrome on load if hideUrlBar is true this is to try and do it as soon as possible
if ( $ . mobile . hideUrlBar ) {
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 if hideUrlBar is true this is as fall back incase we were too early before
if ( $ . mobile . hideUrlBar ) {
$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
// DEPRECATED as of 1.4.0 - remove ui-disabled after 1.4.0 release
// only ui-state-disabled should be present thereafter
2015-09-03 09:39:24 -07:00
$ . mobile . document . delegate ( ".ui-state-disabled,.ui-disabled" , "click" ,
2015-06-19 22:31:44 -07:00
function ( e ) {
e . preventDefault ( ) ;
e . stopImmediatePropagation ( ) ;
}
) ;
}
} ) ;
} ( jQuery , this ) ) ;
( function ( $ , undefined ) {
$ . mobile . links = function ( target ) {
2015-08-31 12:00:39 -07:00
target = target . length ? target [ 0 ] : target ;
var links = $ ( target . getElementsByTagName ( 'a' ) )
. jqmEnhanceable ( )
. filter ( "[data-rel='popup'][href][href!='']" ) ;
2015-06-19 22:31:44 -07:00
//links within content areas, tests included with page
2015-08-31 12:00:39 -07:00
links . each ( function ( ) {
2015-06-19 22:31:44 -07:00
// Accessibility info for popups
var element = this ,
idref = element . getAttribute ( "href" ) . substring ( 1 ) ;
2015-08-31 12:00:39 -07:00
if ( idref ) {
2015-06-19 22:31:44 -07:00
element . setAttribute ( "aria-haspopup" , true ) ;
element . setAttribute ( "aria-owns" , idref ) ;
element . setAttribute ( "aria-expanded" , false ) ;
}
} )
. end ( )
2015-08-31 12:00:39 -07:00
. not ( ".ui-btn, [data-role='none']" )
2015-06-19 22:31:44 -07:00
. addClass ( "ui-link" ) ;
} ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
var uiScreenHiddenRegex = /\bui-screen-hidden\b/ ;
function noHiddenClass ( elements ) {
var index ,
length = elements . length ,
result = [ ] ;
for ( index = 0 ; index < length ; index ++ ) {
if ( ! elements [ index ] . className . match ( uiScreenHiddenRegex ) ) {
result . push ( elements [ index ] ) ;
}
}
return $ ( result ) ;
}
$ . mobile . behaviors . addFirstLastClasses = {
_getVisibles : function ( $els , create ) {
var visibles ;
if ( create ) {
visibles = noHiddenClass ( $els ) ;
} else {
visibles = $els . filter ( ":visible" ) ;
if ( visibles . length === 0 ) {
visibles = noHiddenClass ( $els ) ;
}
}
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" ) ;
}
} ,
_removeFirstLastClasses : function ( $els ) {
$els . removeClass ( "ui-first-child ui-last-child" ) ;
}
} ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
$ . mobile . behaviors . formReset = {
_handleFormReset : function ( ) {
this . _on ( this . element . closest ( "form" ) , {
reset : function ( ) {
this . _delay ( "_reset" ) ;
}
} ) ;
}
} ;
} ) ( jQuery ) ;
/ *
* "checkboxradio" plugin
* /
( function ( $ , undefined ) {
var escapeId = $ . mobile . path . hashToSelector ;
$ . widget ( "mobile.checkboxradio" , $ . extend ( {
2015-08-31 07:57:04 -07:00
initSelector : "input[type='checkbox'],input[type='radio']" ,
2015-06-19 22:31:44 -07:00
options : {
theme : "inherit" ,
mini : false ,
wrapperClass : null ,
enhanced : false ,
iconpos : "left"
} ,
_create : function ( ) {
var input = this . element ,
o = this . options ,
inheritAttr = function ( input , dataAttr ) {
return input . jqmData ( dataAttr ) ||
input . closest ( "form, fieldset" ) . jqmData ( dataAttr ) ;
} ,
label = this . options . enhanced ?
{
element : this . element . siblings ( "label" ) ,
isParent : false
} :
this . _findLabel ( ) ,
inputtype = input [ 0 ] . type ,
checkedClass = "ui-" + inputtype + "-on" ,
uncheckedClass = "ui-" + inputtype + "-off" ;
if ( inputtype !== "checkbox" && inputtype !== "radio" ) {
return ;
}
if ( this . element [ 0 ] . disabled ) {
this . options . disabled = true ;
}
o . iconpos = inheritAttr ( input , "iconpos" ) ||
label . element . attr ( "data-" + $ . mobile . ns + "iconpos" ) || o . iconpos ,
// Establish options
o . mini = inheritAttr ( input , "mini" ) || o . mini ;
// Expose for other methods
$ . extend ( this , {
input : input ,
label : label . element ,
labelIsParent : label . isParent ,
inputtype : inputtype ,
checkedClass : checkedClass ,
uncheckedClass : uncheckedClass
} ) ;
if ( ! this . options . enhanced ) {
this . _enhance ( ) ;
}
this . _on ( label . element , {
2015-09-03 09:39:24 -07:00
mouseover : "_handleLabelVMouseOver" ,
click : "_handleLabelVClick"
2015-06-19 22:31:44 -07:00
} ) ;
this . _on ( input , {
2015-09-03 09:39:24 -07:00
mousedown : "_cacheVals" ,
click : "_handleInputVClick" ,
2015-06-19 22:31:44 -07:00
focus : "_handleInputFocus" ,
blur : "_handleInputBlur"
} ) ;
this . _handleFormReset ( ) ;
this . refresh ( ) ;
} ,
_findLabel : function ( ) {
var parentLabel , label , isParent ,
input = this . element ,
labelsList = input [ 0 ] . labels ;
if ( labelsList && labelsList . length > 0 ) {
label = $ ( labelsList [ 0 ] ) ;
isParent = $ . contains ( label [ 0 ] , input [ 0 ] ) ;
} else {
parentLabel = input . closest ( "label" ) ;
isParent = ( parentLabel . length > 0 ) ;
// NOTE: Windows Phone could not find the label through a selector
// filter works though.
label = isParent ? parentLabel :
$ ( this . document [ 0 ] . getElementsByTagName ( "label" ) )
. filter ( "[for='" + escapeId ( input [ 0 ] . id ) + "']" )
. first ( ) ;
}
return {
element : label ,
isParent : isParent
} ;
} ,
_enhance : function ( ) {
this . label . addClass ( "ui-btn ui-corner-all" ) ;
if ( this . labelIsParent ) {
this . input . add ( this . label ) . wrapAll ( this . _wrapper ( ) ) ;
} else {
//this.element.replaceWith( this.input.add( this.label ).wrapAll( this._wrapper() ) );
this . element . wrap ( this . _wrapper ( ) ) ;
this . element . parent ( ) . prepend ( this . label ) ;
}
// Wrap the input + label in a div
this . _setOptions ( {
"theme" : this . options . theme ,
"iconpos" : this . options . iconpos ,
"mini" : this . options . mini
} ) ;
} ,
_wrapper : function ( ) {
return $ ( "<div class='" +
( this . options . wrapperClass ? this . options . wrapperClass : "" ) +
" ui-" + this . inputtype +
( this . options . disabled ? " ui-state-disabled" : "" ) + "' ></div>" ) ;
} ,
_handleInputFocus : function ( ) {
this . label . addClass ( $ . mobile . focusClass ) ;
} ,
_handleInputBlur : function ( ) {
this . label . removeClass ( $ . mobile . focusClass ) ;
} ,
_handleInputVClick : function ( ) {
// Adds checked attribute to checked input when keyboard is used
this . element . prop ( "checked" , this . element . is ( ":checked" ) ) ;
this . _getInputSet ( ) . not ( this . element ) . prop ( "checked" , false ) ;
this . _updateAll ( true ) ;
} ,
_handleLabelVMouseOver : function ( event ) {
if ( this . label . parent ( ) . hasClass ( "ui-state-disabled" ) ) {
event . stopPropagation ( ) ;
}
} ,
_handleLabelVClick : function ( event ) {
var input = this . element ;
if ( input . is ( ":disabled" ) ) {
event . preventDefault ( ) ;
return ;
}
this . _cacheVals ( ) ;
input . prop ( "checked" , this . 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
this . _getInputSet ( ) . not ( input ) . prop ( "checked" , false ) ;
this . _updateAll ( ) ;
return false ;
} ,
_cacheVals : function ( ) {
this . _getInputSet ( ) . each ( function ( ) {
$ ( this ) . attr ( "data-" + $ . mobile . ns + "cacheVal" , this . checked ) ;
} ) ;
} ,
// Returns those radio buttons that are supposed to be in the same group as
// this radio button. In the case of a checkbox or a radio lacking a name
// attribute, it returns this.element.
_getInputSet : function ( ) {
var selector , formId ,
radio = this . element [ 0 ] ,
name = radio . name ,
form = radio . form ,
doc = this . element . parents ( ) . last ( ) . get ( 0 ) ,
// A radio is always a member of its own group
radios = this . element ;
// Only start running selectors if this is an attached radio button with a name
if ( name && this . inputtype === "radio" && doc ) {
selector = "input[type='radio'][name='" + escapeId ( name ) + "']" ;
// If we're inside a form
if ( form ) {
formId = form . getAttribute ( "id" ) ;
// If the form has an ID, collect radios scattered throught the document which
// nevertheless are part of the form by way of the value of their form attribute
if ( formId ) {
radios = $ ( selector + "[form='" + escapeId ( formId ) + "']" , doc ) ;
}
// Also add to those the radios in the form itself
radios = $ ( form ) . find ( selector ) . filter ( function ( ) {
// Some radios inside the form may belong to some other form by virtue of
// having a form attribute defined on them, so we must filter them out here
return ( this . form === form ) ;
} ) . add ( radios ) ;
// If we're outside a form
} else {
// Collect all those radios which are also outside of a form and match our name
radios = $ ( selector , doc ) . filter ( function ( ) {
return ! this . form ;
} ) ;
}
}
return radios ;
} ,
_updateAll : function ( changeTriggered ) {
var self = this ;
this . _getInputSet ( ) . each ( function ( ) {
var $this = $ ( this ) ;
if ( ( this . checked || self . inputtype === "checkbox" ) && ! changeTriggered ) {
$this . trigger ( "change" ) ;
}
} )
. checkboxradio ( "refresh" ) ;
} ,
_reset : function ( ) {
this . refresh ( ) ;
} ,
// Is the widget supposed to display an icon?
_hasIcon : function ( ) {
var controlgroup , controlgroupWidget ,
controlgroupConstructor = $ . mobile . controlgroup ;
// If the controlgroup widget is defined ...
if ( controlgroupConstructor ) {
controlgroup = this . element . closest (
":mobile-controlgroup," +
controlgroupConstructor . prototype . initSelector ) ;
// ... and the checkbox is in a controlgroup ...
if ( controlgroup . length > 0 ) {
// ... look for a controlgroup widget instance, and ...
controlgroupWidget = $ . data ( controlgroup [ 0 ] , "mobile-controlgroup" ) ;
// ... if found, decide based on the option value, ...
return ( ( controlgroupWidget ? controlgroupWidget . options . type :
// ... otherwise decide based on the "type" data attribute.
controlgroup . attr ( "data-" + $ . mobile . ns + "type" ) ) !== "horizontal" ) ;
}
}
// Normally, the widget displays an icon.
return true ;
} ,
refresh : function ( ) {
var isChecked = this . element [ 0 ] . checked ,
active = $ . mobile . activeBtnClass ,
iconposClass = "ui-btn-icon-" + this . options . iconpos ,
addClasses = [ ] ,
removeClasses = [ ] ;
if ( this . _hasIcon ( ) ) {
removeClasses . push ( active ) ;
addClasses . push ( iconposClass ) ;
} else {
removeClasses . push ( iconposClass ) ;
( isChecked ? addClasses : removeClasses ) . push ( active ) ;
}
if ( isChecked ) {
addClasses . push ( this . checkedClass ) ;
removeClasses . push ( this . uncheckedClass ) ;
} else {
addClasses . push ( this . uncheckedClass ) ;
removeClasses . push ( this . checkedClass ) ;
}
this . widget ( ) . toggleClass ( "ui-state-disabled" , this . element . prop ( "disabled" ) ) ;
this . label
. addClass ( addClasses . join ( " " ) )
. removeClass ( removeClasses . join ( " " ) ) ;
} ,
widget : function ( ) {
return this . label . parent ( ) ;
} ,
_setOptions : function ( options ) {
var label = this . label ,
currentOptions = this . options ,
outer = this . widget ( ) ,
hasIcon = this . _hasIcon ( ) ;
if ( options . disabled !== undefined ) {
this . input . prop ( "disabled" , ! ! options . disabled ) ;
outer . toggleClass ( "ui-state-disabled" , ! ! options . disabled ) ;
}
if ( options . mini !== undefined ) {
outer . toggleClass ( "ui-mini" , ! ! options . mini ) ;
}
if ( options . theme !== undefined ) {
label
. removeClass ( "ui-btn-" + currentOptions . theme )
. addClass ( "ui-btn-" + options . theme ) ;
}
if ( options . wrapperClass !== undefined ) {
outer
. removeClass ( currentOptions . wrapperClass )
. addClass ( options . wrapperClass ) ;
}
if ( options . iconpos !== undefined && hasIcon ) {
label . removeClass ( "ui-btn-icon-" + currentOptions . iconpos ) . addClass ( "ui-btn-icon-" + options . iconpos ) ;
} else if ( ! hasIcon ) {
label . removeClass ( "ui-btn-icon-" + currentOptions . iconpos ) ;
}
this . _super ( options ) ;
}
} , $ . mobile . behaviors . formReset ) ) ;
} ) ( jQuery ) ;
( function ( $ , undefined ) {
2015-09-03 10:01:51 -07:00
$ . fn . selectmenu = function ( ) {
return this ;
} ;
2015-06-19 22:31:44 -07:00
} ) ( jQuery ) ;
2015-07-27 11:18:10 -07:00
} ) ) ;