/* * Copyright 2012-2013 BlackBerry; * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /*! * jQuery JavaScript Library v1.7.1 * http://jquery.com/ * * Copyright 2011, John Resig * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * Includes Sizzle.js * http://sizzlejs.com/ * Copyright 2011, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * * Date: Mon Nov 21 21:11:03 2011 -0500 */ (function( window, undefined ) { // Use the correct document accordingly with window argument (sandbox) var document = window.document, navigator = window.navigator, location = window.location; var jQuery = (function() { // Define a local copy of jQuery var jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init( selector, context, rootjQuery ); }, // Map over jQuery in case of overwrite _jQuery = window.jQuery, // Map over the $ in case of overwrite _$ = window.$, // A central reference to the root jQuery(document) rootjQuery, // A simple way to check for HTML strings or ID strings // Prioritize #id over <tag> to avoid XSS via location.hash (#9521) quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, // Check if a string has a non-whitespace character in it rnotwhite = /\S/, // Used for trimming whitespace trimLeft = /^\s+/, trimRight = /\s+$/, // Match a standalone tag rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, // JSON RegExp rvalidchars = /^[\],:{}\s]*$/, rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, // Useragent RegExp rwebkit = /(webkit)[ \/]([\w.]+)/, ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, rmsie = /(msie) ([\w.]+)/, rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, // Matches dashed string for camelizing rdashAlpha = /-([a-z]|[0-9])/ig, rmsPrefix = /^-ms-/, // Used by jQuery.camelCase as callback to replace() fcamelCase = function( all, letter ) { return ( letter + "" ).toUpperCase(); }, // Keep a UserAgent string for use with jQuery.browser userAgent = navigator.userAgent, // For matching the engine and version of the browser browserMatch, // The deferred used on DOM ready readyList, // The ready event handler DOMContentLoaded, // Save a reference to some core methods toString = Object.prototype.toString, hasOwn = Object.prototype.hasOwnProperty, push = Array.prototype.push, slice = Array.prototype.slice, trim = String.prototype.trim, indexOf = Array.prototype.indexOf, // [[Class]] -> type pairs class2type = {}; jQuery.fn = jQuery.prototype = { constructor: jQuery, init: function( selector, context, rootjQuery ) { var match, elem, ret, doc; // Handle $(""), $(null), or $(undefined) if ( !selector ) { return this; } // Handle $(DOMElement) if ( selector.nodeType ) { this.context = this[0] = selector; this.length = 1; return this; } // The body element only exists once, optimize finding it if ( selector === "body" && !context && document.body ) { this.context = document; this[0] = document.body; this.selector = selector; this.length = 1; return this; } // Handle HTML strings if ( typeof selector === "string" ) { // Are we dealing with HTML string or an ID? if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { // Assume that strings that start and end with <> are HTML and skip the regex check match = [ null, selector, null ]; } else { match = quickExpr.exec( selector ); } // Verify a match, and that no context was specified for #id if ( match && (match[1] || !context) ) { // HANDLE: $(html) -> $(array) if ( match[1] ) { context = context instanceof jQuery ? context[0] : context; doc = ( context ? context.ownerDocument || context : document ); // If a single string is passed in and it's a single tag // just do a createElement and skip the rest ret = rsingleTag.exec( selector ); if ( ret ) { if ( jQuery.isPlainObject( context ) ) { selector = [ document.createElement( ret[1] ) ]; jQuery.fn.attr.call( selector, context, true ); } else { selector = [ doc.createElement( ret[1] ) ]; } } else { ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; } return jQuery.merge( this, selector ); // HANDLE: $("#id") } else { elem = document.getElementById( match[2] ); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document #6963 if ( elem && elem.parentNode ) { // Handle the case where IE and Opera return items // by name instead of ID if ( elem.id !== match[2] ) { return rootjQuery.find( selector ); } // Otherwise, we inject the element directly into the jQuery object this.length = 1; this[0] = elem; } this.context = document; this.selector = selector; return this; } // HANDLE: $(expr, $(...)) } else if ( !context || context.jquery ) { return ( context || rootjQuery ).find( selector ); // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) } else { return this.constructor( context ).find( selector ); } // HANDLE: $(function) // Shortcut for document ready } else if ( jQuery.isFunction( selector ) ) { return rootjQuery.ready( selector ); } if ( selector.selector !== undefined ) { this.selector = selector.selector; this.context = selector.context; } return jQuery.makeArray( selector, this ); }, // Start with an empty selector selector: "", // The current version of jQuery being used jquery: "1.7.1", // The default length of a jQuery object is 0 length: 0, // The number of elements contained in the matched element set size: function() { return this.length; }, toArray: function() { return slice.call( this, 0 ); }, // Get the Nth element in the matched element set OR // Get the whole matched element set as a clean array get: function( num ) { return num == null ? // Return a 'clean' array this.toArray() : // Return just the object ( num < 0 ? this[ this.length + num ] : this[ num ] ); }, // Take an array of elements and push it onto the stack // (returning the new matched element set) pushStack: function( elems, name, selector ) { // Build a new jQuery matched element set var ret = this.constructor(); if ( jQuery.isArray( elems ) ) { push.apply( ret, elems ); } else { jQuery.merge( ret, elems ); } // Add the old object onto the stack (as a reference) ret.prevObject = this; ret.context = this.context; if ( name === "find" ) { ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; } else if ( name ) { ret.selector = this.selector + "." + name + "(" + selector + ")"; } // Return the newly-formed element set return ret; }, // Execute a callback for every element in the matched set. // (You can seed the arguments with an array of args, but this is // only used internally.) each: function( callback, args ) { return jQuery.each( this, callback, args ); }, ready: function( fn ) { // Attach the listeners jQuery.bindReady(); // Add the callback readyList.add( fn ); return this; }, eq: function( i ) { i = +i; return i === -1 ? this.slice( i ) : this.slice( i, i + 1 ); }, first: function() { return this.eq( 0 ); }, last: function() { return this.eq( -1 ); }, slice: function() { return this.pushStack( slice.apply( this, arguments ), "slice", slice.call(arguments).join(",") ); }, map: function( callback ) { return this.pushStack( jQuery.map(this, function( elem, i ) { return callback.call( elem, i, elem ); })); }, end: function() { return this.prevObject || this.constructor(null); }, // For internal use only. // Behaves like an Array's method, not like a jQuery method. push: push, sort: [].sort, splice: [].splice }; // Give the init function the jQuery prototype for later instantiation jQuery.fn.init.prototype = jQuery.fn; jQuery.extend = jQuery.fn.extend = function() { var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false; // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; target = arguments[1] || {}; // skip the boolean and the target i = 2; } // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !jQuery.isFunction(target) ) { target = {}; } // extend jQuery itself if only one argument is passed if ( length === i ) { target = this; --i; } for ( ; i < length; i++ ) { // Only deal with non-null/undefined values if ( (options = arguments[ i ]) != null ) { // Extend the base object for ( name in options ) { src = target[ name ]; copy = options[ name ]; // Prevent never-ending loop if ( target === copy ) { continue; } // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { if ( copyIsArray ) { copyIsArray = false; clone = src && jQuery.isArray(src) ? src : []; } else { clone = src && jQuery.isPlainObject(src) ? src : {}; } // Never move original objects, clone them target[ name ] = jQuery.extend( deep, clone, copy ); // Don't bring in undefined values } else if ( copy !== undefined ) { target[ name ] = copy; } } } } // Return the modified object return target; }; jQuery.extend({ noConflict: function( deep ) { if ( window.$ === jQuery ) { window.$ = _$; } if ( deep && window.jQuery === jQuery ) { window.jQuery = _jQuery; } return jQuery; }, // Is the DOM ready to be used? Set to true once it occurs. isReady: false, // A counter to track how many items to wait for before // the ready event fires. See #6781 readyWait: 1, // Hold (or release) the ready event holdReady: function( hold ) { if ( hold ) { jQuery.readyWait++; } else { jQuery.ready( true ); } }, // Handle when the DOM is ready ready: function( wait ) { // Either a released hold or an DOMready/load event and not yet ready if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). if ( !document.body ) { return setTimeout( jQuery.ready, 1 ); } // Remember that the DOM is ready jQuery.isReady = true; // If a normal DOM Ready event fired, decrement, and wait if need be if ( wait !== true && --jQuery.readyWait > 0 ) { return; } // If there are functions bound, to execute readyList.fireWith( document, [ jQuery ] ); // Trigger any bound ready events if ( jQuery.fn.trigger ) { jQuery( document ).trigger( "ready" ).off( "ready" ); } } }, bindReady: function() { if ( readyList ) { return; } readyList = jQuery.Callbacks( "once memory" ); // Catch cases where $(document).ready() is called after the // browser event has already occurred. if ( document.readyState === "complete" ) { // Handle it asynchronously to allow scripts the opportunity to delay ready return setTimeout( jQuery.ready, 1 ); } // Mozilla, Opera and webkit nightlies currently support this event if ( document.addEventListener ) { // Use the handy event callback document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); // A fallback to window.onload, that will always work window.addEventListener( "load", jQuery.ready, false ); // If IE event model is used } else if ( document.attachEvent ) { // ensure firing before onload, // maybe late but safe also for iframes document.attachEvent( "onreadystatechange", DOMContentLoaded ); // A fallback to window.onload, that will always work window.attachEvent( "onload", jQuery.ready ); // If IE and not a frame // continually check to see if the document is ready var toplevel = false; try { toplevel = window.frameElement == null; } catch(e) {} if ( document.documentElement.doScroll && toplevel ) { doScrollCheck(); } } }, // See test/unit/core.js for details concerning isFunction. // Since version 1.3, DOM methods and functions like alert // aren't supported. They return false on IE (#2968). isFunction: function( obj ) { return jQuery.type(obj) === "function"; }, isArray: Array.isArray || function( obj ) { return jQuery.type(obj) === "array"; }, // A crude way of determining if an object is a window isWindow: function( obj ) { return obj && typeof obj === "object" && "setInterval" in obj; }, isNumeric: function( obj ) { return !isNaN( parseFloat(obj) ) && isFinite( obj ); }, type: function( obj ) { return obj == null ? String( obj ) : class2type[ toString.call(obj) ] || "object"; }, isPlainObject: function( obj ) { // Must be an Object. // Because of IE, we also have to check the presence of the constructor property. // Make sure that DOM nodes and window objects don't pass through, as well if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { return false; } try { // Not own constructor property must be Object if ( obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { return false; } } catch ( e ) { // IE8,9 Will throw exceptions on certain host objects #9897 return false; } // Own properties are enumerated firstly, so to speed up, // if last one is own, then all properties are own. var key; for ( key in obj ) {} return key === undefined || hasOwn.call( obj, key ); }, isEmptyObject: function( obj ) { for ( var name in obj ) { return false; } return true; }, error: function( msg ) { throw new Error( msg ); }, parseJSON: function( data ) { if ( typeof data !== "string" || !data ) { return null; } // Make sure leading/trailing whitespace is removed (IE can't handle it) data = jQuery.trim( data ); // Attempt to parse using the native JSON parser first if ( window.JSON && window.JSON.parse ) { return window.JSON.parse( data ); } // Make sure the incoming data is actual JSON // Logic borrowed from http://json.org/json2.js if ( rvalidchars.test( data.replace( rvalidescape, "@" ) .replace( rvalidtokens, "]" ) .replace( rvalidbraces, "")) ) { return ( new Function( "return " + data ) )(); } jQuery.error( "Invalid JSON: " + data ); }, // Cross-browser xml parsing parseXML: function( data ) { var xml, tmp; try { if ( window.DOMParser ) { // Standard tmp = new DOMParser(); xml = tmp.parseFromString( data , "text/xml" ); } else { // IE xml = new ActiveXObject( "Microsoft.XMLDOM" ); xml.async = "false"; xml.loadXML( data ); } } catch( e ) { xml = undefined; } if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { jQuery.error( "Invalid XML: " + data ); } return xml; }, noop: function() {}, // Evaluates a script in a global context // Workarounds based on findings by Jim Driscoll // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context globalEval: function( data ) { if ( data && rnotwhite.test( data ) ) { // We use execScript on Internet Explorer // We use an anonymous function so that context is window // rather than jQuery in Firefox ( window.execScript || function( data ) { window[ "eval" ].call( window, data ); } )( data ); } }, // Convert dashed to camelCase; used by the css and data modules // Microsoft forgot to hump their vendor prefix (#9572) camelCase: function( string ) { return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); }, nodeName: function( elem, name ) { return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); }, // args is for internal usage only each: function( object, callback, args ) { var name, i = 0, length = object.length, isObj = length === undefined || jQuery.isFunction( object ); if ( args ) { if ( isObj ) { for ( name in object ) { if ( callback.apply( object[ name ], args ) === false ) { break; } } } else { for ( ; i < length; ) { if ( callback.apply( object[ i++ ], args ) === false ) { break; } } } // A special, fast, case for the most common use of each } else { if ( isObj ) { for ( name in object ) { if ( callback.call( object[ name ], name, object[ name ] ) === false ) { break; } } } else { for ( ; i < length; ) { if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { break; } } } } return object; }, // Use native String.trim function wherever possible trim: trim ? function( text ) { return text == null ? "" : trim.call( text ); } : // Otherwise use our own trimming functionality function( text ) { return text == null ? "" : text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); }, // results is for internal usage only makeArray: function( array, results ) { var ret = results || []; if ( array != null ) { // The window, strings (and functions) also have 'length' // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 var type = jQuery.type( array ); if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { push.call( ret, array ); } else { jQuery.merge( ret, array ); } } return ret; }, inArray: function( elem, array, i ) { var len; if ( array ) { if ( indexOf ) { return indexOf.call( array, elem, i ); } len = array.length; i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; for ( ; i < len; i++ ) { // Skip accessing in sparse arrays if ( i in array && array[ i ] === elem ) { return i; } } } return -1; }, merge: function( first, second ) { var i = first.length, j = 0; if ( typeof second.length === "number" ) { for ( var l = second.length; j < l; j++ ) { first[ i++ ] = second[ j ]; } } else { while ( second[j] !== undefined ) { first[ i++ ] = second[ j++ ]; } } first.length = i; return first; }, grep: function( elems, callback, inv ) { var ret = [], retVal; inv = !!inv; // Go through the array, only saving the items // that pass the validator function for ( var i = 0, length = elems.length; i < length; i++ ) { retVal = !!callback( elems[ i ], i ); if ( inv !== retVal ) { ret.push( elems[ i ] ); } } return ret; }, // arg is for internal usage only map: function( elems, callback, arg ) { var value, key, ret = [], i = 0, length = elems.length, // jquery objects are treated as arrays isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; // Go through the array, translating each of the items to their if ( isArray ) { for ( ; i < length; i++ ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret[ ret.length ] = value; } } // Go through every key on the object, } else { for ( key in elems ) { value = callback( elems[ key ], key, arg ); if ( value != null ) { ret[ ret.length ] = value; } } } // Flatten any nested arrays return ret.concat.apply( [], ret ); }, // A global GUID counter for objects guid: 1, // Bind a function to a context, optionally partially applying any // arguments. proxy: function( fn, context ) { if ( typeof context === "string" ) { var tmp = fn[ context ]; context = fn; fn = tmp; } // Quick check to determine if target is callable, in the spec // this throws a TypeError, but we will just return undefined. if ( !jQuery.isFunction( fn ) ) { return undefined; } // Simulated bind var args = slice.call( arguments, 2 ), proxy = function() { return fn.apply( context, args.concat( slice.call( arguments ) ) ); }; // Set the guid of unique handler to the same of original handler, so it can be removed proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; return proxy; }, // Mutifunctional method to get and set values to a collection // The value/s can optionally be executed if it's a function access: function( elems, key, value, exec, fn, pass ) { var length = elems.length; // Setting many attributes if ( typeof key === "object" ) { for ( var k in key ) { jQuery.access( elems, k, key[k], exec, fn, value ); } return elems; } // Setting one attribute if ( value !== undefined ) { // Optionally, function values get executed if exec is true exec = !pass && exec && jQuery.isFunction(value); for ( var i = 0; i < length; i++ ) { fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); } return elems; } // Getting an attribute return length ? fn( elems[0], key ) : undefined; }, now: function() { return ( new Date() ).getTime(); }, // Use of jQuery.browser is frowned upon. // More details: http://docs.jquery.com/Utilities/jQuery.browser uaMatch: function( ua ) { ua = ua.toLowerCase(); var match = rwebkit.exec( ua ) || ropera.exec( ua ) || rmsie.exec( ua ) || ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || []; return { browser: match[1] || "", version: match[2] || "0" }; }, sub: function() { function jQuerySub( selector, context ) { return new jQuerySub.fn.init( selector, context ); } jQuery.extend( true, jQuerySub, this ); jQuerySub.superclass = this; jQuerySub.fn = jQuerySub.prototype = this(); jQuerySub.fn.constructor = jQuerySub; jQuerySub.sub = this.sub; jQuerySub.fn.init = function init( selector, context ) { if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { context = jQuerySub( context ); } return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); }; jQuerySub.fn.init.prototype = jQuerySub.fn; var rootjQuerySub = jQuerySub(document); return jQuerySub; }, browser: {} }); // Populate the class2type map jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); }); browserMatch = jQuery.uaMatch( userAgent ); if ( browserMatch.browser ) { jQuery.browser[ browserMatch.browser ] = true; jQuery.browser.version = browserMatch.version; } // Deprecated, use jQuery.browser.webkit instead if ( jQuery.browser.webkit ) { jQuery.browser.safari = true; } // IE doesn't match non-breaking spaces with \s if ( rnotwhite.test( "\xA0" ) ) { trimLeft = /^[\s\xA0]+/; trimRight = /[\s\xA0]+$/; } // All jQuery objects should point back to these rootjQuery = jQuery(document); // Cleanup functions for the document ready method if ( document.addEventListener ) { DOMContentLoaded = function() { document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); jQuery.ready(); }; } else if ( document.attachEvent ) { DOMContentLoaded = function() { // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). if ( document.readyState === "complete" ) { document.detachEvent( "onreadystatechange", DOMContentLoaded ); jQuery.ready(); } }; } // The DOM ready check for Internet Explorer function doScrollCheck() { if ( jQuery.isReady ) { return; } try { // If IE is used, use the trick by Diego Perini // http://javascript.nwbox.com/IEContentLoaded/ document.documentElement.doScroll("left"); } catch(e) { setTimeout( doScrollCheck, 1 ); return; } // and execute any waiting functions jQuery.ready(); } return jQuery; })(); // String to Object flags format cache var flagsCache = {}; // Convert String-formatted flags into Object-formatted ones and store in cache function createFlags( flags ) { var object = flagsCache[ flags ] = {}, i, length; flags = flags.split( /\s+/ ); for ( i = 0, length = flags.length; i < length; i++ ) { object[ flags[i] ] = true; } return object; } /* * Create a callback list using the following parameters: * * flags: an optional list of space-separated flags that will change how * the callback list behaves * * By default a callback list will act like an event callback list and can be * "fired" multiple times. * * Possible flags: * * once: will ensure the callback list can only be fired once (like a Deferred) * * memory: will keep track of previous values and will call any callback added * after the list has been fired right away with the latest "memorized" * values (like a Deferred) * * unique: will ensure a callback can only be added once (no duplicate in the list) * * stopOnFalse: interrupt callings when a callback returns false * */ jQuery.Callbacks = function( flags ) { // Convert flags from String-formatted to Object-formatted // (we check in cache first) flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; var // Actual callback list list = [], // Stack of fire calls for repeatable lists stack = [], // Last fire value (for non-forgettable lists) memory, // Flag to know if list is currently firing firing, // First callback to fire (used internally by add and fireWith) firingStart, // End of the loop when firing firingLength, // Index of currently firing callback (modified by remove if needed) firingIndex, // Add one or several callbacks to the list add = function( args ) { var i, length, elem, type, actual; for ( i = 0, length = args.length; i < length; i++ ) { elem = args[ i ]; type = jQuery.type( elem ); if ( type === "array" ) { // Inspect recursively add( elem ); } else if ( type === "function" ) { // Add if not in unique mode and callback is not in if ( !flags.unique || !self.has( elem ) ) { list.push( elem ); } } } }, // Fire callbacks fire = function( context, args ) { args = args || []; memory = !flags.memory || [ context, args ]; firing = true; firingIndex = firingStart || 0; firingStart = 0; firingLength = list.length; for ( ; list && firingIndex < firingLength; firingIndex++ ) { if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { memory = true; // Mark as halted break; } } firing = false; if ( list ) { if ( !flags.once ) { if ( stack && stack.length ) { memory = stack.shift(); self.fireWith( memory[ 0 ], memory[ 1 ] ); } } else if ( memory === true ) { self.disable(); } else { list = []; } } }, // Actual Callbacks object self = { // Add a callback or a collection of callbacks to the list add: function() { if ( list ) { var length = list.length; add( arguments ); // Do we need to add the callbacks to the // current firing batch? if ( firing ) { firingLength = list.length; // With memory, if we're not firing then // we should call right away, unless previous // firing was halted (stopOnFalse) } else if ( memory && memory !== true ) { firingStart = length; fire( memory[ 0 ], memory[ 1 ] ); } } return this; }, // Remove a callback from the list remove: function() { if ( list ) { var args = arguments, argIndex = 0, argLength = args.length; for ( ; argIndex < argLength ; argIndex++ ) { for ( var i = 0; i < list.length; i++ ) { if ( args[ argIndex ] === list[ i ] ) { // Handle firingIndex and firingLength if ( firing ) { if ( i <= firingLength ) { firingLength--; if ( i <= firingIndex ) { firingIndex--; } } } // Remove the element list.splice( i--, 1 ); // If we have some unicity property then // we only need to do this once if ( flags.unique ) { break; } } } } } return this; }, // Control if a given callback is in the list has: function( fn ) { if ( list ) { var i = 0, length = list.length; for ( ; i < length; i++ ) { if ( fn === list[ i ] ) { return true; } } } return false; }, // Remove all callbacks from the list empty: function() { list = []; return this; }, // Have the list do nothing anymore disable: function() { list = stack = memory = undefined; return this; }, // Is it disabled? disabled: function() { return !list; }, // Lock the list in its current state lock: function() { stack = undefined; if ( !memory || memory === true ) { self.disable(); } return this; }, // Is it locked? locked: function() { return !stack; }, // Call all callbacks with the given context and arguments fireWith: function( context, args ) { if ( stack ) { if ( firing ) { if ( !flags.once ) { stack.push( [ context, args ] ); } } else if ( !( flags.once && memory ) ) { fire( context, args ); } } return this; }, // Call all the callbacks with the given arguments fire: function() { self.fireWith( this, arguments ); return this; }, // To know if the callbacks have already been called at least once fired: function() { return !!memory; } }; return self; }; var // Static reference to slice sliceDeferred = [].slice; jQuery.extend({ Deferred: function( func ) { var doneList = jQuery.Callbacks( "once memory" ), failList = jQuery.Callbacks( "once memory" ), progressList = jQuery.Callbacks( "memory" ), state = "pending", lists = { resolve: doneList, reject: failList, notify: progressList }, promise = { done: doneList.add, fail: failList.add, progress: progressList.add, state: function() { return state; }, // Deprecated isResolved: doneList.fired, isRejected: failList.fired, then: function( doneCallbacks, failCallbacks, progressCallbacks ) { deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); return this; }, always: function() { deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); return this; }, pipe: function( fnDone, fnFail, fnProgress ) { return jQuery.Deferred(function( newDefer ) { jQuery.each( { done: [ fnDone, "resolve" ], fail: [ fnFail, "reject" ], progress: [ fnProgress, "notify" ] }, function( handler, data ) { var fn = data[ 0 ], action = data[ 1 ], returned; if ( jQuery.isFunction( fn ) ) { deferred[ handler ](function() { returned = fn.apply( this, arguments ); if ( returned && jQuery.isFunction( returned.promise ) ) { returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify ); } else { newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); } }); } else { deferred[ handler ]( newDefer[ action ] ); } }); }).promise(); }, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function( obj ) { if ( obj == null ) { obj = promise; } else { for ( var key in promise ) { obj[ key ] = promise[ key ]; } } return obj; } }, deferred = promise.promise({}), key; for ( key in lists ) { deferred[ key ] = lists[ key ].fire; deferred[ key + "With" ] = lists[ key ].fireWith; } // Handle state deferred.done( function() { state = "resolved"; }, failList.disable, progressList.lock ).fail( function() { state = "rejected"; }, doneList.disable, progressList.lock ); // Call given func if any if ( func ) { func.call( deferred, deferred ); } // All done! return deferred; }, // Deferred helper when: function( firstParam ) { var args = sliceDeferred.call( arguments, 0 ), i = 0, length = args.length, pValues = new Array( length ), count = length, pCount = length, deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? firstParam : jQuery.Deferred(), promise = deferred.promise(); function resolveFunc( i ) { return function( value ) { args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; if ( !( --count ) ) { deferred.resolveWith( deferred, args ); } }; } function progressFunc( i ) { return function( value ) { pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; deferred.notifyWith( promise, pValues ); }; } if ( length > 1 ) { for ( ; i < length; i++ ) { if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); } else { --count; } } if ( !count ) { deferred.resolveWith( deferred, args ); } } else if ( deferred !== firstParam ) { deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); } return promise; } }); jQuery.support = (function() { var support, all, a, select, opt, input, marginDiv, fragment, tds, events, eventName, i, isSupported, div = document.createElement( "div" ), documentElement = document.documentElement; // Preliminary tests div.setAttribute("className", "t"); div.innerHTML = " <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>"; all = div.getElementsByTagName( "*" ); a = div.getElementsByTagName( "a" )[ 0 ]; // Can't get basic test support if ( !all || !all.length || !a ) { return {}; } // First batch of supports tests select = document.createElement( "select" ); opt = select.appendChild( document.createElement("option") ); input = div.getElementsByTagName( "input" )[ 0 ]; support = { // IE strips leading whitespace when .innerHTML is used leadingWhitespace: ( div.firstChild.nodeType === 3 ), // Make sure that tbody elements aren't automatically inserted // IE will insert them into empty tables tbody: !div.getElementsByTagName("tbody").length, // Make sure that link elements get serialized correctly by innerHTML // This requires a wrapper element in IE htmlSerialize: !!div.getElementsByTagName("link").length, // Get the style information from getAttribute // (IE uses .cssText instead) style: /top/.test( a.getAttribute("style") ), // Make sure that URLs aren't manipulated // (IE normalizes it by default) hrefNormalized: ( a.getAttribute("href") === "/a" ), // Make sure that element opacity exists // (IE uses filter instead) // Use a regex to work around a WebKit issue. See #5145 opacity: /^0.55/.test( a.style.opacity ), // Verify style float existence // (IE uses styleFloat instead of cssFloat) cssFloat: !!a.style.cssFloat, // Make sure that if no value is specified for a checkbox // that it defaults to "on". // (WebKit defaults to "" instead) checkOn: ( input.value === "on" ), // Make sure that a selected-by-default option has a working selected property. // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) optSelected: opt.selected, // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) getSetAttribute: div.className !== "t", // Tests for enctype support on a form(#6743) enctype: !!document.createElement("form").enctype, // Makes sure cloning an html5 element does not cause problems // Where outerHTML is undefined, this still works html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>", // Will be defined later submitBubbles: true, changeBubbles: true, focusinBubbles: false, deleteExpando: true, noCloneEvent: true, inlineBlockNeedsLayout: false, shrinkWrapBlocks: false, reliableMarginRight: true }; // Make sure checked status is properly cloned input.checked = true; support.noCloneChecked = input.cloneNode( true ).checked; // Make sure that the options inside disabled selects aren't marked as disabled // (WebKit marks them as disabled) select.disabled = true; support.optDisabled = !opt.disabled; // Test to see if it's possible to delete an expando from an element // Fails in Internet Explorer try { delete div.test; } catch( e ) { support.deleteExpando = false; } if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { div.attachEvent( "onclick", function() { // Cloning a node shouldn't copy over any // bound event handlers (IE does this) support.noCloneEvent = false; }); div.cloneNode( true ).fireEvent( "onclick" ); } // Check if a radio maintains its value // after being appended to the DOM input = document.createElement("input"); input.value = "t"; input.setAttribute("type", "radio"); support.radioValue = input.value === "t"; input.setAttribute("checked", "checked"); div.appendChild( input ); fragment = document.createDocumentFragment(); fragment.appendChild( div.lastChild ); // WebKit doesn't clone checked state correctly in fragments support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; // Check if a disconnected checkbox will retain its checked // value of true after appended to the DOM (IE6/7) support.appendChecked = input.checked; fragment.removeChild( input ); fragment.appendChild( div ); div.innerHTML = ""; // Check if div with explicit width and no margin-right incorrectly // gets computed margin-right based on width of container. For more // info see bug #3333 // Fails in WebKit before Feb 2011 nightlies // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right if ( window.getComputedStyle ) { marginDiv = document.createElement( "div" ); marginDiv.style.width = "0"; marginDiv.style.marginRight = "0"; div.style.width = "2px"; div.appendChild( marginDiv ); support.reliableMarginRight = ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; } // Technique from Juriy Zaytsev // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ // We only care about the case where non-standard event systems // are used, namely in IE. Short-circuiting here helps us to // avoid an eval call (in setAttribute) which can cause CSP // to go haywire. See: https://developer.mozilla.org/en/Security/CSP if ( div.attachEvent ) { for( i in { submit: 1, change: 1, focusin: 1 }) { eventName = "on" + i; isSupported = ( eventName in div ); if ( !isSupported ) { div.setAttribute( eventName, "return;" ); isSupported = ( typeof div[ eventName ] === "function" ); } support[ i + "Bubbles" ] = isSupported; } } fragment.removeChild( div ); // Null elements to avoid leaks in IE fragment = select = opt = marginDiv = div = input = null; // Run tests that need a body at doc ready jQuery(function() { var container, outer, inner, table, td, offsetSupport, conMarginTop, ptlm, vb, style, html, body = document.getElementsByTagName("body")[0]; if ( !body ) { // Return for frameset docs that don't have a body return; } conMarginTop = 1; ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;"; vb = "visibility:hidden;border:0;"; style = "style='" + ptlm + "border:5px solid #000;padding:0;'"; html = "<div " + style + "><div></div></div>" + "<table " + style + " cellpadding='0' cellspacing='0'>" + "<tr><td></td></tr></table>"; container = document.createElement("div"); container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; body.insertBefore( container, body.firstChild ); // Construct the test element div = document.createElement("div"); container.appendChild( div ); // Check if table cells still have offsetWidth/Height when they are set // to display:none and there are still other visible table cells in a // table row; if so, offsetWidth/Height are not reliable for use when // determining if an element has been hidden directly using // display:none (it is still safe to use offsets if a parent element is // hidden; don safety goggles and see bug #4512 for more information). // (only IE 8 fails this test) div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>"; tds = div.getElementsByTagName( "td" ); isSupported = ( tds[ 0 ].offsetHeight === 0 ); tds[ 0 ].style.display = ""; tds[ 1 ].style.display = "none"; // Check if empty table cells still have offsetWidth/Height // (IE <= 8 fail this test) support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); // Figure out if the W3C box model works as expected div.innerHTML = ""; div.style.width = div.style.paddingLeft = "1px"; jQuery.boxModel = support.boxModel = div.offsetWidth === 2; if ( typeof div.style.zoom !== "undefined" ) { // Check if natively block-level elements act like inline-block // elements when setting their display to 'inline' and giving // them layout // (IE < 8 does this) div.style.display = "inline"; div.style.zoom = 1; support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 ); // Check if elements with layout shrink-wrap their children // (IE 6 does this) div.style.display = ""; div.innerHTML = "<div style='width:4px;'></div>"; support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); } div.style.cssText = ptlm + vb; div.innerHTML = html; outer = div.firstChild; inner = outer.firstChild; td = outer.nextSibling.firstChild.firstChild; offsetSupport = { doesNotAddBorder: ( inner.offsetTop !== 5 ), doesAddBorderForTableAndCells: ( td.offsetTop === 5 ) }; inner.style.position = "fixed"; inner.style.top = "20px"; // safari subtracts parent border width here which is 5px offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 ); inner.style.position = inner.style.top = ""; outer.style.overflow = "hidden"; outer.style.position = "relative"; offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 ); offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop ); body.removeChild( container ); div = container = null; jQuery.extend( support, offsetSupport ); }); return support; })(); var rbrace = /^(?:\{.*\}|\[.*\])$/, rmultiDash = /([A-Z])/g; jQuery.extend({ cache: {}, // Please use with caution uuid: 0, // Unique for each copy of jQuery on the page // Non-digits removed to match rinlinejQuery expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), // The following elements throw uncatchable exceptions if you // attempt to add expando properties to them. noData: { "embed": true, // Ban all objects except for Flash (which handle expandos) "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", "applet": true }, hasData: function( elem ) { elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; return !!elem && !isEmptyDataObject( elem ); }, data: function( elem, name, data, pvt /* Internal Use Only */ ) { if ( !jQuery.acceptData( elem ) ) { return; } var privateCache, thisCache, ret, internalKey = jQuery.expando, getByName = typeof name === "string", // We have to handle DOM nodes and JS objects differently because IE6-7 // can't GC object references properly across the DOM-JS boundary isNode = elem.nodeType, // Only DOM nodes need the global jQuery cache; JS object data is // attached directly to the object so GC can occur automatically cache = isNode ? jQuery.cache : elem, // Only defining an ID for JS objects if its cache already exists allows // the code to shortcut on the same path as a DOM node with no cache id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey, isEvents = name === "events"; // Avoid doing any more work than we need to when trying to get data on an // object that has no data at all if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) { return; } if ( !id ) { // Only DOM nodes need a new unique ID for each element since their data // ends up in the global cache if ( isNode ) { elem[ internalKey ] = id = ++jQuery.uuid; } else { id = internalKey; } } if ( !cache[ id ] ) { cache[ id ] = {}; // Avoids exposing jQuery metadata on plain JS objects when the object // is serialized using JSON.stringify if ( !isNode ) { cache[ id ].toJSON = jQuery.noop; } } // An object can be passed to jQuery.data instead of a key/value pair; this gets // shallow copied over onto the existing cache if ( typeof name === "object" || typeof name === "function" ) { if ( pvt ) { cache[ id ] = jQuery.extend( cache[ id ], name ); } else { cache[ id ].data = jQuery.extend( cache[ id ].data, name ); } } privateCache = thisCache = cache[ id ]; // jQuery data() is stored in a separate object inside the object's internal data // cache in order to avoid key collisions between internal data and user-defined // data. if ( !pvt ) { if ( !thisCache.data ) { thisCache.data = {}; } thisCache = thisCache.data; } if ( data !== undefined ) { thisCache[ jQuery.camelCase( name ) ] = data; } // Users should not attempt to inspect the internal events object using jQuery.data, // it is undocumented and subject to change. But does anyone listen? No. if ( isEvents && !thisCache[ name ] ) { return privateCache.events; } // Check for both converted-to-camel and non-converted data property names // If a data property was specified if ( getByName ) { // First Try to find as-is property data ret = thisCache[ name ]; // Test for null|undefined property data if ( ret == null ) { // Try to find the camelCased property ret = thisCache[ jQuery.camelCase( name ) ]; } } else { ret = thisCache; } return ret; }, removeData: function( elem, name, pvt /* Internal Use Only */ ) { if ( !jQuery.acceptData( elem ) ) { return; } var thisCache, i, l, // Reference to internal data cache key internalKey = jQuery.expando, isNode = elem.nodeType, // See jQuery.data for more information cache = isNode ? jQuery.cache : elem, // See jQuery.data for more information id = isNode ? elem[ internalKey ] : internalKey; // If there is already no cache entry for this object, there is no // purpose in continuing if ( !cache[ id ] ) { return; } if ( name ) { thisCache = pvt ? cache[ id ] : cache[ id ].data; if ( thisCache ) { // Support array or space separated string names for data keys if ( !jQuery.isArray( name ) ) { // try the string as a key before any manipulation if ( name in thisCache ) { name = [ name ]; } else { // split the camel cased version by spaces unless a key with the spaces exists name = jQuery.camelCase( name ); if ( name in thisCache ) { name = [ name ]; } else { name = name.split( " " ); } } } for ( i = 0, l = name.length; i < l; i++ ) { delete thisCache[ name[i] ]; } // If there is no data left in the cache, we want to continue // and let the cache object itself get destroyed if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { return; } } } // See jQuery.data for more information if ( !pvt ) { delete cache[ id ].data; // Don't destroy the parent cache unless the internal data object // had been the only thing left in it if ( !isEmptyDataObject(cache[ id ]) ) { return; } } // Browsers that fail expando deletion also refuse to delete expandos on // the window, but it will allow it on all other JS objects; other browsers // don't care // Ensure that `cache` is not a window object #10080 if ( jQuery.support.deleteExpando || !cache.setInterval ) { delete cache[ id ]; } else { cache[ id ] = null; } // We destroyed the cache and need to eliminate the expando on the node to avoid // false lookups in the cache for entries that no longer exist if ( isNode ) { // IE does not allow us to delete expando properties from nodes, // nor does it have a removeAttribute function on Document nodes; // we must handle all of these cases if ( jQuery.support.deleteExpando ) { delete elem[ internalKey ]; } else if ( elem.removeAttribute ) { elem.removeAttribute( internalKey ); } else { elem[ internalKey ] = null; } } }, // For internal use only. _data: function( elem, name, data ) { return jQuery.data( elem, name, data, true ); }, // A method for determining if a DOM node can handle the data expando acceptData: function( elem ) { if ( elem.nodeName ) { var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; if ( match ) { return !(match === true || elem.getAttribute("classid") !== match); } } return true; } }); jQuery.fn.extend({ data: function( key, value ) { var parts, attr, name, data = null; if ( typeof key === "undefined" ) { if ( this.length ) { data = jQuery.data( this[0] ); if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) { attr = this[0].attributes; for ( var i = 0, l = attr.length; i < l; i++ ) { name = attr[i].name; if ( name.indexOf( "data-" ) === 0 ) { name = jQuery.camelCase( name.substring(5) ); dataAttr( this[0], name, data[ name ] ); } } jQuery._data( this[0], "parsedAttrs", true ); } } return data; } else if ( typeof key === "object" ) { return this.each(function() { jQuery.data( this, key ); }); } parts = key.split("."); parts[1] = parts[1] ? "." + parts[1] : ""; if ( value === undefined ) { data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); // Try to fetch any internally stored data first if ( data === undefined && this.length ) { data = jQuery.data( this[0], key ); data = dataAttr( this[0], key, data ); } return data === undefined && parts[1] ? this.data( parts[0] ) : data; } else { return this.each(function() { var self = jQuery( this ), args = [ parts[0], value ]; self.triggerHandler( "setData" + parts[1] + "!", args ); jQuery.data( this, key, value ); self.triggerHandler( "changeData" + parts[1] + "!", args ); }); } }, removeData: function( key ) { return this.each(function() { jQuery.removeData( this, key ); }); } }); function dataAttr( elem, key, data ) { // If nothing was found internally, try to fetch any // data from the HTML5 data-* attribute if ( data === undefined && elem.nodeType === 1 ) { var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); data = elem.getAttribute( name ); if ( typeof data === "string" ) { try { data = data === "true" ? true : data === "false" ? false : data === "null" ? null : jQuery.isNumeric( data ) ? parseFloat( data ) : rbrace.test( data ) ? jQuery.parseJSON( data ) : data; } catch( e ) {} // Make sure we set the data so it isn't changed later jQuery.data( elem, key, data ); } else { data = undefined; } } return data; } // checks a cache object for emptiness function isEmptyDataObject( obj ) { for ( var name in obj ) { // if the public data object is empty, the private is still empty if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { continue; } if ( name !== "toJSON" ) { return false; } } return true; } function handleQueueMarkDefer( elem, type, src ) { var deferDataKey = type + "defer", queueDataKey = type + "queue", markDataKey = type + "mark", defer = jQuery._data( elem, deferDataKey ); if ( defer && ( src === "queue" || !jQuery._data(elem, queueDataKey) ) && ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) { // Give room for hard-coded callbacks to fire first // and eventually mark/queue something else on the element setTimeout( function() { if ( !jQuery._data( elem, queueDataKey ) && !jQuery._data( elem, markDataKey ) ) { jQuery.removeData( elem, deferDataKey, true ); defer.fire(); } }, 0 ); } } jQuery.extend({ _mark: function( elem, type ) { if ( elem ) { type = ( type || "fx" ) + "mark"; jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 ); } }, _unmark: function( force, elem, type ) { if ( force !== true ) { type = elem; elem = force; force = false; } if ( elem ) { type = type || "fx"; var key = type + "mark", count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 ); if ( count ) { jQuery._data( elem, key, count ); } else { jQuery.removeData( elem, key, true ); handleQueueMarkDefer( elem, type, "mark" ); } } }, queue: function( elem, type, data ) { var q; if ( elem ) { type = ( type || "fx" ) + "queue"; q = jQuery._data( elem, type ); // Speed up dequeue by getting out quickly if this is just a lookup if ( data ) { if ( !q || jQuery.isArray(data) ) { q = jQuery._data( elem, type, jQuery.makeArray(data) ); } else { q.push( data ); } } return q || []; } }, dequeue: function( elem, type ) { type = type || "fx"; var queue = jQuery.queue( elem, type ), fn = queue.shift(), hooks = {}; // If the fx queue is dequeued, always remove the progress sentinel if ( fn === "inprogress" ) { fn = queue.shift(); } if ( fn ) { // Add a progress sentinel to prevent the fx queue from being // automatically dequeued if ( type === "fx" ) { queue.unshift( "inprogress" ); } jQuery._data( elem, type + ".run", hooks ); fn.call( elem, function() { jQuery.dequeue( elem, type ); }, hooks ); } if ( !queue.length ) { jQuery.removeData( elem, type + "queue " + type + ".run", true ); handleQueueMarkDefer( elem, type, "queue" ); } } }); jQuery.fn.extend({ queue: function( type, data ) { if ( typeof type !== "string" ) { data = type; type = "fx"; } if ( data === undefined ) { return jQuery.queue( this[0], type ); } return this.each(function() { var queue = jQuery.queue( this, type, data ); if ( type === "fx" && queue[0] !== "inprogress" ) { jQuery.dequeue( this, type ); } }); }, dequeue: function( type ) { return this.each(function() { jQuery.dequeue( this, type ); }); }, // Based off of the plugin by Clint Helfers, with permission. // http://blindsignals.com/index.php/2009/07/jquery-delay/ delay: function( time, type ) { time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; type = type || "fx"; return this.queue( type, function( next, hooks ) { var timeout = setTimeout( next, time ); hooks.stop = function() { clearTimeout( timeout ); }; }); }, clearQueue: function( type ) { return this.queue( type || "fx", [] ); }, // Get a promise resolved when queues of a certain type // are emptied (fx is the type by default) promise: function( type, object ) { if ( typeof type !== "string" ) { object = type; type = undefined; } type = type || "fx"; var defer = jQuery.Deferred(), elements = this, i = elements.length, count = 1, deferDataKey = type + "defer", queueDataKey = type + "queue", markDataKey = type + "mark", tmp; function resolve() { if ( !( --count ) ) { defer.resolveWith( elements, [ elements ] ); } } while( i-- ) { if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) { count++; tmp.add( resolve ); } } resolve(); return defer.promise(); } }); var rclass = /[\n\t\r]/g, rspace = /\s+/, rreturn = /\r/g, rtype = /^(?:button|input)$/i, rfocusable = /^(?:button|input|object|select|textarea)$/i, rclickable = /^a(?:rea)?$/i, rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, getSetAttribute = jQuery.support.getSetAttribute, nodeHook, boolHook, fixSpecified; jQuery.fn.extend({ attr: function( name, value ) { return jQuery.access( this, name, value, true, jQuery.attr ); }, removeAttr: function( name ) { return this.each(function() { jQuery.removeAttr( this, name ); }); }, prop: function( name, value ) { return jQuery.access( this, name, value, true, jQuery.prop ); }, removeProp: function( name ) { name = jQuery.propFix[ name ] || name; return this.each(function() { // try/catch handles cases where IE balks (such as removing a property on window) try { this[ name ] = undefined; delete this[ name ]; } catch( e ) {} }); }, addClass: function( value ) { var classNames, i, l, elem, setClass, c, cl; if ( jQuery.isFunction( value ) ) { return this.each(function( j ) { jQuery( this ).addClass( value.call(this, j, this.className) ); }); } if ( value && typeof value === "string" ) { classNames = value.split( rspace ); for ( i = 0, l = this.length; i < l; i++ ) { elem = this[ i ]; if ( elem.nodeType === 1 ) { if ( !elem.className && classNames.length === 1 ) { elem.className = value; } else { setClass = " " + elem.className + " "; for ( c = 0, cl = classNames.length; c < cl; c++ ) { if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { setClass += classNames[ c ] + " "; } } elem.className = jQuery.trim( setClass ); } } } } return this; }, removeClass: function( value ) { var classNames, i, l, elem, className, c, cl; if ( jQuery.isFunction( value ) ) { return this.each(function( j ) { jQuery( this ).removeClass( value.call(this, j, this.className) ); }); } if ( (value && typeof value === "string") || value === undefined ) { classNames = ( value || "" ).split( rspace ); for ( i = 0, l = this.length; i < l; i++ ) { elem = this[ i ]; if ( elem.nodeType === 1 && elem.className ) { if ( value ) { className = (" " + elem.className + " ").replace( rclass, " " ); for ( c = 0, cl = classNames.length; c < cl; c++ ) { className = className.replace(" " + classNames[ c ] + " ", " "); } elem.className = jQuery.trim( className ); } else { elem.className = ""; } } } } return this; }, toggleClass: function( value, stateVal ) { var type = typeof value, isBool = typeof stateVal === "boolean"; if ( jQuery.isFunction( value ) ) { return this.each(function( i ) { jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); }); } return this.each(function() { if ( type === "string" ) { // toggle individual class names var className, i = 0, self = jQuery( this ), state = stateVal, classNames = value.split( rspace ); while ( (className = classNames[ i++ ]) ) { // check each className given, space seperated list state = isBool ? state : !self.hasClass( className ); self[ state ? "addClass" : "removeClass" ]( className ); } } else if ( type === "undefined" || type === "boolean" ) { if ( this.className ) { // store className if set jQuery._data( this, "__className__", this.className ); } // toggle whole className this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; } }); }, hasClass: function( selector ) { var className = " " + selector + " ", i = 0, l = this.length; for ( ; i < l; i++ ) { if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { return true; } } return false; }, val: function( value ) { var hooks, ret, isFunction, elem = this[0]; if ( !arguments.length ) { if ( elem ) { hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ]; if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { return ret; } ret = elem.value; return typeof ret === "string" ? // handle most common string cases ret.replace(rreturn, "") : // handle cases where value is null/undef or number ret == null ? "" : ret; } return; } isFunction = jQuery.isFunction( value ); return this.each(function( i ) { var self = jQuery(this), val; if ( this.nodeType !== 1 ) { return; } if ( isFunction ) { val = value.call( this, i, self.val() ); } else { val = value; } // Treat null/undefined as ""; convert numbers to string if ( val == null ) { val = ""; } else if ( typeof val === "number" ) { val += ""; } else if ( jQuery.isArray( val ) ) { val = jQuery.map(val, function ( value ) { return value == null ? "" : value + ""; }); } hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ]; // If set returns undefined, fall back to normal setting if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { this.value = val; } }); } }); jQuery.extend({ valHooks: { option: { get: function( elem ) { // attributes.value is undefined in Blackberry 4.7 but // uses .value. See #6932 var val = elem.attributes.value; return !val || val.specified ? elem.value : elem.text; } }, select: { get: function( elem ) { var value, i, max, option, index = elem.selectedIndex, values = [], options = elem.options, one = elem.type === "select-one"; // Nothing was selected if ( index < 0 ) { return null; } // Loop through all the selected options i = one ? index : 0; max = one ? index + 1 : options.length; for ( ; i < max; i++ ) { option = options[ i ]; // Don't return options that are disabled or in a disabled optgroup if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { // Get the specific value for the option value = jQuery( option ).val(); // We don't need an array for one selects if ( one ) { return value; } // Multi-Selects return an array values.push( value ); } } // Fixes Bug #2551 -- select.val() broken in IE after form.reset() if ( one && !values.length && options.length ) { return jQuery( options[ index ] ).val(); } return values; }, set: function( elem, value ) { var values = jQuery.makeArray( value ); jQuery(elem).find("option").each(function() { this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; }); if ( !values.length ) { elem.selectedIndex = -1; } return values; } } }, attrFn: { val: true, css: true, html: true, text: true, data: true, width: true, height: true, offset: true }, attr: function( elem, name, value, pass ) { var ret, hooks, notxml, nType = elem.nodeType; // don't get/set attributes on text, comment and attribute nodes if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { return; } if ( pass && name in jQuery.attrFn ) { return jQuery( elem )[ name ]( value ); } // Fallback to prop when attributes are not supported if ( typeof elem.getAttribute === "undefined" ) { return jQuery.prop( elem, name, value ); } notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); // All attributes are lowercase // Grab necessary hook if one is defined if ( notxml ) { name = name.toLowerCase(); hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); } if ( value !== undefined ) { if ( value === null ) { jQuery.removeAttr( elem, name ); return; } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { return ret; } else { elem.setAttribute( name, "" + value ); return value; } } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { return ret; } else { ret = elem.getAttribute( name ); // Non-existent attributes return null, we normalize to undefined return ret === null ? undefined : ret; } }, removeAttr: function( elem, value ) { var propName, attrNames, name, l, i = 0; if ( value && elem.nodeType === 1 ) { attrNames = value.toLowerCase().split( rspace ); l = attrNames.length; for ( ; i < l; i++ ) { name = attrNames[ i ]; if ( name ) { propName = jQuery.propFix[ name ] || name; // See #9699 for explanation of this approach (setting first, then removal) jQuery.attr( elem, name, "" ); elem.removeAttribute( getSetAttribute ? name : propName ); // Set corresponding property to false for boolean attributes if ( rboolean.test( name ) && propName in elem ) { elem[ propName ] = false; } } } } }, attrHooks: { type: { set: function( elem, value ) { // We can't allow the type property to be changed (since it causes problems in IE) if ( rtype.test( elem.nodeName ) && elem.parentNode ) { jQuery.error( "type property can't be changed" ); } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { // Setting the type on a radio button after the value resets the value in IE6-9 // Reset value to it's default in case type is set after value // This is for element creation var val = elem.value; elem.setAttribute( "type", value ); if ( val ) { elem.value = val; } return value; } } }, // Use the value property for back compat // Use the nodeHook for button elements in IE6/7 (#1954) value: { get: function( elem, name ) { if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { return nodeHook.get( elem, name ); } return name in elem ? elem.value : null; }, set: function( elem, value, name ) { if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { return nodeHook.set( elem, value, name ); } // Does not return so that setAttribute is also used elem.value = value; } } }, propFix: { tabindex: "tabIndex", readonly: "readOnly", "for": "htmlFor", "class": "className", maxlength: "maxLength", cellspacing: "cellSpacing", cellpadding: "cellPadding", rowspan: "rowSpan", colspan: "colSpan", usemap: "useMap", frameborder: "frameBorder", contenteditable: "contentEditable" }, prop: function( elem, name, value ) { var ret, hooks, notxml, nType = elem.nodeType; // don't get/set properties on text, comment and attribute nodes if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { return; } notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); if ( notxml ) { // Fix name and attach hooks name = jQuery.propFix[ name ] || name; hooks = jQuery.propHooks[ name ]; } if ( value !== undefined ) { if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { return ret; } else { return ( elem[ name ] = value ); } } else { if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { return ret; } else { return elem[ name ]; } } }, propHooks: { tabIndex: { get: function( elem ) { // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ var attributeNode = elem.getAttributeNode("tabindex"); return attributeNode && attributeNode.specified ? parseInt( attributeNode.value, 10 ) : rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? 0 : undefined; } } } }); // Add the tabIndex propHook to attrHooks for back-compat (different case is intentional) jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex; // Hook for boolean attributes boolHook = { get: function( elem, name ) { // Align boolean attributes with corresponding properties // Fall back to attribute presence where some booleans are not supported var attrNode, property = jQuery.prop( elem, name ); return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? name.toLowerCase() : undefined; }, set: function( elem, value, name ) { var propName; if ( value === false ) { // Remove boolean attributes when set to false jQuery.removeAttr( elem, name ); } else { // value is true since we know at this point it's type boolean and not false // Set boolean attributes to the same name and set the DOM property propName = jQuery.propFix[ name ] || name; if ( propName in elem ) { // Only set the IDL specifically if it already exists on the element elem[ propName ] = true; } elem.setAttribute( name, name.toLowerCase() ); } return name; } }; // IE6/7 do not support getting/setting some attributes with get/setAttribute if ( !getSetAttribute ) { fixSpecified = { name: true, id: true }; // Use this for any attribute in IE6/7 // This fixes almost every IE6/7 issue nodeHook = jQuery.valHooks.button = { get: function( elem, name ) { var ret; ret = elem.getAttributeNode( name ); return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ? ret.nodeValue : undefined; }, set: function( elem, value, name ) { // Set the existing or create a new attribute node var ret = elem.getAttributeNode( name ); if ( !ret ) { ret = document.createAttribute( name ); elem.setAttributeNode( ret ); } return ( ret.nodeValue = value + "" ); } }; // Apply the nodeHook to tabindex jQuery.attrHooks.tabindex.set = nodeHook.set; // Set width and height to auto instead of 0 on empty string( Bug #8150 ) // This is for removals jQuery.each([ "width", "height" ], function( i, name ) { jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { set: function( elem, value ) { if ( value === "" ) { elem.setAttribute( name, "auto" ); return value; } } }); }); // Set contenteditable to false on removals(#10429) // Setting to empty string throws an error as an invalid value jQuery.attrHooks.contenteditable = { get: nodeHook.get, set: function( elem, value, name ) { if ( value === "" ) { value = "false"; } nodeHook.set( elem, value, name ); } }; } // Some attributes require a special call on IE if ( !jQuery.support.hrefNormalized ) { jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { get: function( elem ) { var ret = elem.getAttribute( name, 2 ); return ret === null ? undefined : ret; } }); }); } if ( !jQuery.support.style ) { jQuery.attrHooks.style = { get: function( elem ) { // Return undefined in the case of empty string // Normalize to lowercase since IE uppercases css property names return elem.style.cssText.toLowerCase() || undefined; }, set: function( elem, value ) { return ( elem.style.cssText = "" + value ); } }; } // Safari mis-reports the default selected property of an option // Accessing the parent's selectedIndex property fixes it if ( !jQuery.support.optSelected ) { jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { get: function( elem ) { var parent = elem.parentNode; if ( parent ) { parent.selectedIndex; // Make sure that it also works with optgroups, see #5701 if ( parent.parentNode ) { parent.parentNode.selectedIndex; } } return null; } }); } // IE6/7 call enctype encoding if ( !jQuery.support.enctype ) { jQuery.propFix.enctype = "encoding"; } // Radios and checkboxes getter/setter if ( !jQuery.support.checkOn ) { jQuery.each([ "radio", "checkbox" ], function() { jQuery.valHooks[ this ] = { get: function( elem ) { // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified return elem.getAttribute("value") === null ? "on" : elem.value; } }; }); } jQuery.each([ "radio", "checkbox" ], function() { jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { set: function( elem, value ) { if ( jQuery.isArray( value ) ) { return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); } } }); }); var rformElems = /^(?:textarea|input|select)$/i, rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, rhoverHack = /\bhover(\.\S+)?\b/, rkeyEvent = /^key/, rmouseEvent = /^(?:mouse|contextmenu)|click/, rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, quickParse = function( selector ) { var quick = rquickIs.exec( selector ); if ( quick ) { // 0 1 2 3 // [ _, tag, id, class ] quick[1] = ( quick[1] || "" ).toLowerCase(); quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" ); } return quick; }, quickIs = function( elem, m ) { var attrs = elem.attributes || {}; return ( (!m[1] || elem.nodeName.toLowerCase() === m[1]) && (!m[2] || (attrs.id || {}).value === m[2]) && (!m[3] || m[3].test( (attrs[ "class" ] || {}).value )) ); }, hoverHack = function( events ) { return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); }; /* * Helper functions for managing events -- not part of the public interface. * Props to Dean Edwards' addEvent library for many of the ideas. */ jQuery.event = { add: function( elem, types, handler, data, selector ) { var elemData, eventHandle, events, t, tns, type, namespaces, handleObj, handleObjIn, quick, handlers, special; // Don't attach events to noData or text/comment nodes (allow plain objects tho) if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { return; } // Caller can pass in an object of custom data in lieu of the handler if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; } // Make sure that the handler has a unique ID, used to find/remove it later if ( !handler.guid ) { handler.guid = jQuery.guid++; } // Init the element's event structure and main handler, if this is the first events = elemData.events; if ( !events ) { elemData.events = events = {}; } eventHandle = elemData.handle; if ( !eventHandle ) { elemData.handle = eventHandle = function( e ) { // Discard the second event of a jQuery.event.trigger() and // when an event is called after a page has unloaded return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : undefined; }; // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events eventHandle.elem = elem; } // Handle multiple events separated by a space // jQuery(...).bind("mouseover mouseout", fn); types = jQuery.trim( hoverHack(types) ).split( " " ); for ( t = 0; t < types.length; t++ ) { tns = rtypenamespace.exec( types[t] ) || []; type = tns[1]; namespaces = ( tns[2] || "" ).split( "." ).sort(); // If event changes its type, use the special event handlers for the changed type special = jQuery.event.special[ type ] || {}; // If selector defined, determine special event api type, otherwise given type type = ( selector ? special.delegateType : special.bindType ) || type; // Update special based on newly reset type special = jQuery.event.special[ type ] || {}; // handleObj is passed to all event handlers handleObj = jQuery.extend({ type: type, origType: tns[1], data: data, handler: handler, guid: handler.guid, selector: selector, quick: quickParse( selector ), namespace: namespaces.join(".") }, handleObjIn ); // Init the event handler queue if we're the first handlers = events[ type ]; if ( !handlers ) { handlers = events[ type ] = []; handlers.delegateCount = 0; // Only use addEventListener/attachEvent if the special events handler returns false if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { // Bind the global event handler to the element if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle, false ); } else if ( elem.attachEvent ) { elem.attachEvent( "on" + type, eventHandle ); } } } if ( special.add ) { special.add.call( elem, handleObj ); if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; } } // Add to the element's handler list, delegates in front if ( selector ) { handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { handlers.push( handleObj ); } // Keep track of which events have ever been used, for event optimization jQuery.event.global[ type ] = true; } // Nullify elem to prevent memory leaks in IE elem = null; }, global: {}, // Detach an event or set of events from an element remove: function( elem, types, handler, selector, mappedTypes ) { var elemData = jQuery.hasData( elem ) && jQuery._data( elem ), t, tns, type, origType, namespaces, origCount, j, events, special, handle, eventType, handleObj; if ( !elemData || !(events = elemData.events) ) { return; } // Once for each type.namespace in types; type may be omitted types = jQuery.trim( hoverHack( types || "" ) ).split(" "); for ( t = 0; t < types.length; t++ ) { tns = rtypenamespace.exec( types[t] ) || []; type = origType = tns[1]; namespaces = tns[2]; // Unbind all events (on this namespace, if provided) for the element if ( !type ) { for ( type in events ) { jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); } continue; } special = jQuery.event.special[ type ] || {}; type = ( selector? special.delegateType : special.bindType ) || type; eventType = events[ type ] || []; origCount = eventType.length; namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; // Remove matching events for ( j = 0; j < eventType.length; j++ ) { handleObj = eventType[ j ]; if ( ( mappedTypes || origType === handleObj.origType ) && ( !handler || handler.guid === handleObj.guid ) && ( !namespaces || namespaces.test( handleObj.namespace ) ) && ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { eventType.splice( j--, 1 ); if ( handleObj.selector ) { eventType.delegateCount--; } if ( special.remove ) { special.remove.call( elem, handleObj ); } } } // Remove generic event handler if we removed something and no more handlers exist // (avoids potential for endless recursion during removal of special event handlers) if ( eventType.length === 0 && origCount !== eventType.length ) { if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { jQuery.removeEvent( elem, type, elemData.handle ); } delete events[ type ]; } } // Remove the expando if it's no longer used if ( jQuery.isEmptyObject( events ) ) { handle = elemData.handle; if ( handle ) { handle.elem = null; } // removeData also checks for emptiness and clears the expando if empty // so use it instead of delete jQuery.removeData( elem, [ "events", "handle" ], true ); } }, // Events that are safe to short-circuit if no handlers are attached. // Native DOM events should not be added, they may have inline handlers. customEvent: { "getData": true, "setData": true, "changeData": true }, trigger: function( event, data, elem, onlyHandlers ) { // Don't do events on text and comment nodes if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { return; } // Event object or event type var type = event.type || event, namespaces = [], cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; // focus/blur morphs to focusin/out; ensure we're not firing them right now if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { return; } if ( type.indexOf( "!" ) >= 0 ) { // Exclusive events trigger only for the exact event (no namespaces) type = type.slice(0, -1); exclusive = true; } if ( type.indexOf( "." ) >= 0 ) { // Namespaced trigger; create a regexp to match event type in handle() namespaces = type.split("."); type = namespaces.shift(); namespaces.sort(); } if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { // No jQuery handlers for this event type, and it can't have inline handlers return; } // Caller can pass in an Event, Object, or just an event type string event = typeof event === "object" ? // jQuery.Event object event[ jQuery.expando ] ? event : // Object literal new jQuery.Event( type, event ) : // Just the event type (string) new jQuery.Event( type ); event.type = type; event.isTrigger = true; event.exclusive = exclusive; event.namespace = namespaces.join( "." ); event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; // Handle a global trigger if ( !elem ) { // TODO: Stop taunting the data cache; remove global events and always attach to document cache = jQuery.cache; for ( i in cache ) { if ( cache[ i ].events && cache[ i ].events[ type ] ) { jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); } } return; } // Clean up the event in case it is being reused event.result = undefined; if ( !event.target ) { event.target = elem; } // Clone any incoming data and prepend the event, creating the handler arg list data = data != null ? jQuery.makeArray( data ) : []; data.unshift( event ); // Allow special events to draw outside the lines special = jQuery.event.special[ type ] || {}; if ( special.trigger && special.trigger.apply( elem, data ) === false ) { return; } // Determine event propagation path in advance, per W3C events spec (#9951) // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) eventPath = [[ elem, special.bindType || type ]]; if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { bubbleType = special.delegateType || type; cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; old = null; for ( ; cur; cur = cur.parentNode ) { eventPath.push([ cur, bubbleType ]); old = cur; } // Only add window if we got to document (e.g., not plain obj or detached DOM) if ( old && old === elem.ownerDocument ) { eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); } } // Fire handlers on the event path for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { cur = eventPath[i][0]; event.type = eventPath[i][1]; handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); if ( handle ) { handle.apply( cur, data ); } // Note that this is a bare JS function and not a jQuery handler handle = ontype && cur[ ontype ]; if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { event.preventDefault(); } } event.type = type; // If nobody prevented the default action, do it now if ( !onlyHandlers && !event.isDefaultPrevented() ) { if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { // Call a native DOM method on the target with the same name name as the event. // Can't use an .isFunction() check here because IE6/7 fails that test. // Don't do default actions on window, that's where global variables be (#6170) // IE<9 dies on focus/blur to hidden element (#1486) if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { // Don't re-trigger an onFOO event when we call its FOO() method old = elem[ ontype ]; if ( old ) { elem[ ontype ] = null; } // Prevent re-triggering of the same event, since we already bubbled it above jQuery.event.triggered = type; elem[ type ](); jQuery.event.triggered = undefined; if ( old ) { elem[ ontype ] = old; } } } } return event.result; }, dispatch: function( event ) { // Make a writable jQuery.Event from the native event object event = jQuery.event.fix( event || window.event ); var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), delegateCount = handlers.delegateCount, args = [].slice.call( arguments, 0 ), run_all = !event.exclusive && !event.namespace, handlerQueue = [], i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; // Use the fix-ed jQuery.Event rather than the (read-only) native event args[0] = event; event.delegateTarget = this; // Determine handlers that should run if there are delegated events // Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861) if ( delegateCount && !event.target.disabled && !(event.button && event.type === "click") ) { // Pregenerate a single jQuery object for reuse with .is() jqcur = jQuery(this); jqcur.context = this.ownerDocument || this; for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { selMatch = {}; matches = []; jqcur[0] = cur; for ( i = 0; i < delegateCount; i++ ) { handleObj = handlers[ i ]; sel = handleObj.selector; if ( selMatch[ sel ] === undefined ) { selMatch[ sel ] = ( handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel ) ); } if ( selMatch[ sel ] ) { matches.push( handleObj ); } } if ( matches.length ) { handlerQueue.push({ elem: cur, matches: matches }); } } } // Add the remaining (directly-bound) handlers if ( handlers.length > delegateCount ) { handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); } // Run delegates first; they may want to stop propagation beneath us for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { matched = handlerQueue[ i ]; event.currentTarget = matched.elem; for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { handleObj = matched.matches[ j ]; // Triggered event must either 1) be non-exclusive and have no namespace, or // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { event.data = handleObj.data; event.handleObj = handleObj; ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) .apply( matched.elem, args ); if ( ret !== undefined ) { event.result = ret; if ( ret === false ) { event.preventDefault(); event.stopPropagation(); } } } } } return event.result; }, // Includes some event props shared by KeyEvent and MouseEvent // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), fixHooks: {}, keyHooks: { props: "char charCode key keyCode".split(" "), filter: function( event, original ) { // Add which for key events if ( event.which == null ) { event.which = original.charCode != null ? original.charCode : original.keyCode; } return event; } }, mouseHooks: { props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), filter: function( event, original ) { var eventDoc, doc, body, button = original.button, fromElement = original.fromElement; // Calculate pageX/Y if missing and clientX/Y available if ( event.pageX == null && original.clientX != null ) { eventDoc = event.target.ownerDocument || document; doc = eventDoc.documentElement; body = eventDoc.body; event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); } // Add relatedTarget, if necessary if ( !event.relatedTarget && fromElement ) { event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; } // Add which for click: 1 === left; 2 === middle; 3 === right // Note: button is not normalized, so don't use it if ( !event.which && button !== undefined ) { event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); } return event; } }, fix: function( event ) { if ( event[ jQuery.expando ] ) { return event; } // Create a writable copy of the event object and normalize some properties var i, prop, originalEvent = event, fixHook = jQuery.event.fixHooks[ event.type ] || {}, copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; event = jQuery.Event( originalEvent ); for ( i = copy.length; i; ) { prop = copy[ --i ]; event[ prop ] = originalEvent[ prop ]; } // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) if ( !event.target ) { event.target = originalEvent.srcElement || document; } // Target should not be a text node (#504, Safari) if ( event.target.nodeType === 3 ) { event.target = event.target.parentNode; } // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) if ( event.metaKey === undefined ) { event.metaKey = event.ctrlKey; } return fixHook.filter? fixHook.filter( event, originalEvent ) : event; }, special: { ready: { // Make sure the ready event is setup setup: jQuery.bindReady }, load: { // Prevent triggered image.load events from bubbling to window.load noBubble: true }, focus: { delegateType: "focusin" }, blur: { delegateType: "focusout" }, beforeunload: { setup: function( data, namespaces, eventHandle ) { // We only want to do this special case on windows if ( jQuery.isWindow( this ) ) { this.onbeforeunload = eventHandle; } }, teardown: function( namespaces, eventHandle ) { if ( this.onbeforeunload === eventHandle ) { this.onbeforeunload = null; } } } }, simulate: function( type, elem, event, bubble ) { // Piggyback on a donor event to simulate a different one. // Fake originalEvent to avoid donor's stopPropagation, but if the // simulated event prevents default then we do the same on the donor. var e = jQuery.extend( new jQuery.Event(), event, { type: type, isSimulated: true, originalEvent: {} } ); if ( bubble ) { jQuery.event.trigger( e, null, elem ); } else { jQuery.event.dispatch.call( elem, e ); } if ( e.isDefaultPrevented() ) { event.preventDefault(); } } }; // Some plugins are using, but it's undocumented/deprecated and will be removed. // The 1.7 special event interface should provide all the hooks needed now. jQuery.event.handle = jQuery.event.dispatch; jQuery.removeEvent = document.removeEventListener ? function( elem, type, handle ) { if ( elem.removeEventListener ) { elem.removeEventListener( type, handle, false ); } } : function( elem, type, handle ) { if ( elem.detachEvent ) { elem.detachEvent( "on" + type, handle ); } }; jQuery.Event = function( src, props ) { // Allow instantiation without the 'new' keyword if ( !(this instanceof jQuery.Event) ) { return new jQuery.Event( src, props ); } // Event object if ( src && src.type ) { this.originalEvent = src; this.type = src.type; // Events bubbling up the document may have been marked as prevented // by a handler lower down the tree; reflect the correct value. this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; // Event type } else { this.type = src; } // Put explicitly provided properties onto the event object if ( props ) { jQuery.extend( this, props ); } // Create a timestamp if incoming event doesn't have one this.timeStamp = src && src.timeStamp || jQuery.now(); // Mark it as fixed this[ jQuery.expando ] = true; }; function returnFalse() { return false; } function returnTrue() { return true; } // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html jQuery.Event.prototype = { preventDefault: function() { this.isDefaultPrevented = returnTrue; var e = this.originalEvent; if ( !e ) { return; } // if preventDefault exists run it on the original event if ( e.preventDefault ) { e.preventDefault(); // otherwise set the returnValue property of the original event to false (IE) } else { e.returnValue = false; } }, stopPropagation: function() { this.isPropagationStopped = returnTrue; var e = this.originalEvent; if ( !e ) { return; } // if stopPropagation exists run it on the original event if ( e.stopPropagation ) { e.stopPropagation(); } // otherwise set the cancelBubble property of the original event to true (IE) e.cancelBubble = true; }, stopImmediatePropagation: function() { this.isImmediatePropagationStopped = returnTrue; this.stopPropagation(); }, isDefaultPrevented: returnFalse, isPropagationStopped: returnFalse, isImmediatePropagationStopped: returnFalse }; // Create mouseenter/leave events using mouseover/out and event-time checks jQuery.each({ mouseenter: "mouseover", mouseleave: "mouseout" }, function( orig, fix ) { jQuery.event.special[ orig ] = { delegateType: fix, bindType: fix, handle: function( event ) { var target = this, related = event.relatedTarget, handleObj = event.handleObj, selector = handleObj.selector, ret; // For mousenter/leave call the handler if related is outside the target. // NB: No relatedTarget if the mouse left/entered the browser window if ( !related || (related !== target && !jQuery.contains( target, related )) ) { event.type = handleObj.origType; ret = handleObj.handler.apply( this, arguments ); event.type = fix; } return ret; } }; }); // IE submit delegation if ( !jQuery.support.submitBubbles ) { jQuery.event.special.submit = { setup: function() { // Only need this for delegated form submit events if ( jQuery.nodeName( this, "form" ) ) { return false; } // Lazy-add a submit handler when a descendant form may potentially be submitted jQuery.event.add( this, "click._submit keypress._submit", function( e ) { // Node name check avoids a VML-related crash in IE (#9807) var elem = e.target, form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; if ( form && !form._submit_attached ) { jQuery.event.add( form, "submit._submit", function( event ) { // If form was submitted by the user, bubble the event up the tree if ( this.parentNode && !event.isTrigger ) { jQuery.event.simulate( "submit", this.parentNode, event, true ); } }); form._submit_attached = true; } }); // return undefined since we don't need an event listener }, teardown: function() { // Only need this for delegated form submit events if ( jQuery.nodeName( this, "form" ) ) { return false; } // Remove delegated handlers; cleanData eventually reaps submit handlers attached above jQuery.event.remove( this, "._submit" ); } }; } // IE change delegation and checkbox/radio fix if ( !jQuery.support.changeBubbles ) { jQuery.event.special.change = { setup: function() { if ( rformElems.test( this.nodeName ) ) { // IE doesn't fire change on a check/radio until blur; trigger it on click // after a propertychange. Eat the blur-change in special.change.handle. // This still fires onchange a second time for check/radio after blur. if ( this.type === "checkbox" || this.type === "radio" ) { jQuery.event.add( this, "propertychange._change", function( event ) { if ( event.originalEvent.propertyName === "checked" ) { this._just_changed = true; } }); jQuery.event.add( this, "click._change", function( event ) { if ( this._just_changed && !event.isTrigger ) { this._just_changed = false; jQuery.event.simulate( "change", this, event, true ); } }); } return false; } // Delegated event; lazy-add a change handler on descendant inputs jQuery.event.add( this, "beforeactivate._change", function( e ) { var elem = e.target; if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) { jQuery.event.add( elem, "change._change", function( event ) { if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { jQuery.event.simulate( "change", this.parentNode, event, true ); } }); elem._change_attached = true; } }); }, handle: function( event ) { var elem = event.target; // Swallow native change events from checkbox/radio, we already triggered them above if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { return event.handleObj.handler.apply( this, arguments ); } }, teardown: function() { jQuery.event.remove( this, "._change" ); return rformElems.test( this.nodeName ); } }; } // Create "bubbling" focus and blur events if ( !jQuery.support.focusinBubbles ) { jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { // Attach a single capturing handler while someone wants focusin/focusout var attaches = 0, handler = function( event ) { jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); }; jQuery.event.special[ fix ] = { setup: function() { if ( attaches++ === 0 ) { document.addEventListener( orig, handler, true ); } }, teardown: function() { if ( --attaches === 0 ) { document.removeEventListener( orig, handler, true ); } } }; }); } jQuery.fn.extend({ on: function( types, selector, data, fn, /*INTERNAL*/ one ) { var origFn, type; // Types can be a map of types/handlers if ( typeof types === "object" ) { // ( types-Object, selector, data ) if ( typeof selector !== "string" ) { // ( types-Object, data ) data = selector; selector = undefined; } for ( type in types ) { this.on( type, selector, data, types[ type ], one ); } return this; } if ( data == null && fn == null ) { // ( types, fn ) fn = selector; data = selector = undefined; } else if ( fn == null ) { if ( typeof selector === "string" ) { // ( types, selector, fn ) fn = data; data = undefined; } else { // ( types, data, fn ) fn = data; data = selector; selector = undefined; } } if ( fn === false ) { fn = returnFalse; } else if ( !fn ) { return this; } if ( one === 1 ) { origFn = fn; fn = function( event ) { // Can use an empty set, since event contains the info jQuery().off( event ); return origFn.apply( this, arguments ); }; // Use same guid so caller can remove using origFn fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); } return this.each( function() { jQuery.event.add( this, types, fn, data, selector ); }); }, one: function( types, selector, data, fn ) { return this.on.call( this, types, selector, data, fn, 1 ); }, off: function( types, selector, fn ) { if ( types && types.preventDefault && types.handleObj ) { // ( event ) dispatched jQuery.Event var handleObj = types.handleObj; jQuery( types.delegateTarget ).off( handleObj.namespace? handleObj.type + "." + handleObj.namespace : handleObj.type, handleObj.selector, handleObj.handler ); return this; } if ( typeof types === "object" ) { // ( types-object [, selector] ) for ( var type in types ) { this.off( type, selector, types[ type ] ); } return this; } if ( selector === false || typeof selector === "function" ) { // ( types [, fn] ) fn = selector; selector = undefined; } if ( fn === false ) { fn = returnFalse; } return this.each(function() { jQuery.event.remove( this, types, fn, selector ); }); }, bind: function( types, data, fn ) { return this.on( types, null, data, fn ); }, unbind: function( types, fn ) { return this.off( types, null, fn ); }, live: function( types, data, fn ) { jQuery( this.context ).on( types, this.selector, data, fn ); return this; }, die: function( types, fn ) { jQuery( this.context ).off( types, this.selector || "**", fn ); return this; }, delegate: function( selector, types, data, fn ) { return this.on( types, selector, data, fn ); }, undelegate: function( selector, types, fn ) { // ( namespace ) or ( selector, types [, fn] ) return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn ); }, trigger: function( type, data ) { return this.each(function() { jQuery.event.trigger( type, data, this ); }); }, triggerHandler: function( type, data ) { if ( this[0] ) { return jQuery.event.trigger( type, data, this[0], true ); } }, toggle: function( fn ) { // Save reference to arguments for access in closure var args = arguments, guid = fn.guid || jQuery.guid++, i = 0, toggler = function( event ) { // Figure out which function to execute var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); // Make sure that clicks stop event.preventDefault(); // and execute the function return args[ lastToggle ].apply( this, arguments ) || false; }; // link all the functions, so any of them can unbind this click handler toggler.guid = guid; while ( i < args.length ) { args[ i++ ].guid = guid; } return this.click( toggler ); }, hover: function( fnOver, fnOut ) { return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); } }); jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { // Handle event binding jQuery.fn[ name ] = function( data, fn ) { if ( fn == null ) { fn = data; data = null; } return arguments.length > 0 ? this.on( name, null, data, fn ) : this.trigger( name ); }; if ( jQuery.attrFn ) { jQuery.attrFn[ name ] = true; } if ( rkeyEvent.test( name ) ) { jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; } if ( rmouseEvent.test( name ) ) { jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; } }); /*! * Sizzle CSS Selector Engine * Copyright 2011, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * More information: http://sizzlejs.com/ */ (function(){ var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, expando = "sizcache" + (Math.random() + '').replace('.', ''), done = 0, toString = Object.prototype.toString, hasDuplicate = false, baseHasDuplicate = true, rBackslash = /\\/g, rReturn = /\r\n/g, rNonWord = /\W/; // Here we check if the JavaScript engine is using some sort of // optimization where it does not always call our comparision // function. If that is the case, discard the hasDuplicate value. // Thus far that includes Google Chrome. [0, 0].sort(function() { baseHasDuplicate = false; return 0; }); var Sizzle = function( selector, context, results, seed ) { results = results || []; context = context || document; var origContext = context; if ( context.nodeType !== 1 && context.nodeType !== 9 ) { return []; } if ( !selector || typeof selector !== "string" ) { return results; } var m, set, checkSet, extra, ret, cur, pop, i, prune = true, contextXML = Sizzle.isXML( context ), parts = [], soFar = selector; // Reset the position of the chunker regexp (start from head) do { chunker.exec( "" ); m = chunker.exec( soFar ); if ( m ) { soFar = m[3]; parts.push( m[1] ); if ( m[2] ) { extra = m[3]; break; } } } while ( m ); if ( parts.length > 1 && origPOS.exec( selector ) ) { if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { set = posProcess( parts[0] + parts[1], context, seed ); } else { set = Expr.relative[ parts[0] ] ? [ context ] : Sizzle( parts.shift(), context ); while ( parts.length ) { selector = parts.shift(); if ( Expr.relative[ selector ] ) { selector += parts.shift(); } set = posProcess( selector, set, seed ); } } } else { // Take a shortcut and set the context if the root selector is an ID // (but not if it'll be faster if the inner selector is an ID) if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { ret = Sizzle.find( parts.shift(), context, contextXML ); context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; } if ( context ) { ret = seed ? { expr: parts.pop(), set: makeArray(seed) } : Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; if ( parts.length > 0 ) { checkSet = makeArray( set ); } else { prune = false; } while ( parts.length ) { cur = parts.pop(); pop = cur; if ( !Expr.relative[ cur ] ) { cur = ""; } else { pop = parts.pop(); } if ( pop == null ) { pop = context; } Expr.relative[ cur ]( checkSet, pop, contextXML ); } } else { checkSet = parts = []; } } if ( !checkSet ) { checkSet = set; } if ( !checkSet ) { Sizzle.error( cur || selector ); } if ( toString.call(checkSet) === "[object Array]" ) { if ( !prune ) { results.push.apply( results, checkSet ); } else if ( context && context.nodeType === 1 ) { for ( i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { results.push( set[i] ); } } } else { for ( i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && checkSet[i].nodeType === 1 ) { results.push( set[i] ); } } } } else { makeArray( checkSet, results ); } if ( extra ) { Sizzle( extra, origContext, results, seed ); Sizzle.uniqueSort( results ); } return results; }; Sizzle.uniqueSort = function( results ) { if ( sortOrder ) { hasDuplicate = baseHasDuplicate; results.sort( sortOrder ); if ( hasDuplicate ) { for ( var i = 1; i < results.length; i++ ) { if ( results[i] === results[ i - 1 ] ) { results.splice( i--, 1 ); } } } } return results; }; Sizzle.matches = function( expr, set ) { return Sizzle( expr, null, null, set ); }; Sizzle.matchesSelector = function( node, expr ) { return Sizzle( expr, null, null, [node] ).length > 0; }; Sizzle.find = function( expr, context, isXML ) { var set, i, len, match, type, left; if ( !expr ) { return []; } for ( i = 0, len = Expr.order.length; i < len; i++ ) { type = Expr.order[i]; if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { left = match[1]; match.splice( 1, 1 ); if ( left.substr( left.length - 1 ) !== "\\" ) { match[1] = (match[1] || "").replace( rBackslash, "" ); set = Expr.find[ type ]( match, context, isXML ); if ( set != null ) { expr = expr.replace( Expr.match[ type ], "" ); break; } } } } if ( !set ) { set = typeof context.getElementsByTagName !== "undefined" ? context.getElementsByTagName( "*" ) : []; } return { set: set, expr: expr }; }; Sizzle.filter = function( expr, set, inplace, not ) { var match, anyFound, type, found, item, filter, left, i, pass, old = expr, result = [], curLoop = set, isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); while ( expr && set.length ) { for ( type in Expr.filter ) { if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { filter = Expr.filter[ type ]; left = match[1]; anyFound = false; match.splice(1,1); if ( left.substr( left.length - 1 ) === "\\" ) { continue; } if ( curLoop === result ) { result = []; } if ( Expr.preFilter[ type ] ) { match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); if ( !match ) { anyFound = found = true; } else if ( match === true ) { continue; } } if ( match ) { for ( i = 0; (item = curLoop[i]) != null; i++ ) { if ( item ) { found = filter( item, match, i, curLoop ); pass = not ^ found; if ( inplace && found != null ) { if ( pass ) { anyFound = true; } else { curLoop[i] = false; } } else if ( pass ) { result.push( item ); anyFound = true; } } } } if ( found !== undefined ) { if ( !inplace ) { curLoop = result; } expr = expr.replace( Expr.match[ type ], "" ); if ( !anyFound ) { return []; } break; } } } // Improper expression if ( expr === old ) { if ( anyFound == null ) { Sizzle.error( expr ); } else { break; } } old = expr; } return curLoop; }; Sizzle.error = function( msg ) { throw new Error( "Syntax error, unrecognized expression: " + msg ); }; /** * Utility function for retreiving the text value of an array of DOM nodes * @param {Array|Element} elem */ var getText = Sizzle.getText = function( elem ) { var i, node, nodeType = elem.nodeType, ret = ""; if ( nodeType ) { if ( nodeType === 1 || nodeType === 9 ) { // Use textContent || innerText for elements if ( typeof elem.textContent === 'string' ) { return elem.textContent; } else if ( typeof elem.innerText === 'string' ) { // Replace IE's carriage returns return elem.innerText.replace( rReturn, '' ); } else { // Traverse it's children for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { ret += getText( elem ); } } } else if ( nodeType === 3 || nodeType === 4 ) { return elem.nodeValue; } } else { // If no nodeType, this is expected to be an array for ( i = 0; (node = elem[i]); i++ ) { // Do not traverse comment nodes if ( node.nodeType !== 8 ) { ret += getText( node ); } } } return ret; }; var Expr = Sizzle.selectors = { order: [ "ID", "NAME", "TAG" ], match: { ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ }, leftMatch: {}, attrMap: { "class": "className", "for": "htmlFor" }, attrHandle: { href: function( elem ) { return elem.getAttribute( "href" ); }, type: function( elem ) { return elem.getAttribute( "type" ); } }, relative: { "+": function(checkSet, part){ var isPartStr = typeof part === "string", isTag = isPartStr && !rNonWord.test( part ), isPartStrNotTag = isPartStr && !isTag; if ( isTag ) { part = part.toLowerCase(); } for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { if ( (elem = checkSet[i]) ) { while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? elem || false : elem === part; } } if ( isPartStrNotTag ) { Sizzle.filter( part, checkSet, true ); } }, ">": function( checkSet, part ) { var elem, isPartStr = typeof part === "string", i = 0, l = checkSet.length; if ( isPartStr && !rNonWord.test( part ) ) { part = part.toLowerCase(); for ( ; i < l; i++ ) { elem = checkSet[i]; if ( elem ) { var parent = elem.parentNode; checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; } } } else { for ( ; i < l; i++ ) { elem = checkSet[i]; if ( elem ) { checkSet[i] = isPartStr ? elem.parentNode : elem.parentNode === part; } } if ( isPartStr ) { Sizzle.filter( part, checkSet, true ); } } }, "": function(checkSet, part, isXML){ var nodeCheck, doneName = done++, checkFn = dirCheck; if ( typeof part === "string" && !rNonWord.test( part ) ) { part = part.toLowerCase(); nodeCheck = part; checkFn = dirNodeCheck; } checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); }, "~": function( checkSet, part, isXML ) { var nodeCheck, doneName = done++, checkFn = dirCheck; if ( typeof part === "string" && !rNonWord.test( part ) ) { part = part.toLowerCase(); nodeCheck = part; checkFn = dirNodeCheck; } checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); } }, find: { ID: function( match, context, isXML ) { if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document #6963 return m && m.parentNode ? [m] : []; } }, NAME: function( match, context ) { if ( typeof context.getElementsByName !== "undefined" ) { var ret = [], results = context.getElementsByName( match[1] ); for ( var i = 0, l = results.length; i < l; i++ ) { if ( results[i].getAttribute("name") === match[1] ) { ret.push( results[i] ); } } return ret.length === 0 ? null : ret; } }, TAG: function( match, context ) { if ( typeof context.getElementsByTagName !== "undefined" ) { return context.getElementsByTagName( match[1] ); } } }, preFilter: { CLASS: function( match, curLoop, inplace, result, not, isXML ) { match = " " + match[1].replace( rBackslash, "" ) + " "; if ( isXML ) { return match; } for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { if ( elem ) { if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { if ( !inplace ) { result.push( elem ); } } else if ( inplace ) { curLoop[i] = false; } } } return false; }, ID: function( match ) { return match[1].replace( rBackslash, "" ); }, TAG: function( match, curLoop ) { return match[1].replace( rBackslash, "" ).toLowerCase(); }, CHILD: function( match ) { if ( match[1] === "nth" ) { if ( !match[2] ) { Sizzle.error( match[0] ); } match[2] = match[2].replace(/^\+|\s*/g, ''); // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); // calculate the numbers (first)n+(last) including if they are negative match[2] = (test[1] + (test[2] || 1)) - 0; match[3] = test[3] - 0; } else if ( match[2] ) { Sizzle.error( match[0] ); } // TODO: Move to normal caching system match[0] = done++; return match; }, ATTR: function( match, curLoop, inplace, result, not, isXML ) { var name = match[1] = match[1].replace( rBackslash, "" ); if ( !isXML && Expr.attrMap[name] ) { match[1] = Expr.attrMap[name]; } // Handle if an un-quoted value was used match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); if ( match[2] === "~=" ) { match[4] = " " + match[4] + " "; } return match; }, PSEUDO: function( match, curLoop, inplace, result, not ) { if ( match[1] === "not" ) { // If we're dealing with a complex expression, or a simple one if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { match[3] = Sizzle(match[3], null, null, curLoop); } else { var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); if ( !inplace ) { result.push.apply( result, ret ); } return false; } } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { return true; } return match; }, POS: function( match ) { match.unshift( true ); return match; } }, filters: { enabled: function( elem ) { return elem.disabled === false && elem.type !== "hidden"; }, disabled: function( elem ) { return elem.disabled === true; }, checked: function( elem ) { return elem.checked === true; }, selected: function( elem ) { // Accessing this property makes selected-by-default // options in Safari work properly if ( elem.parentNode ) { elem.parentNode.selectedIndex; } return elem.selected === true; }, parent: function( elem ) { return !!elem.firstChild; }, empty: function( elem ) { return !elem.firstChild; }, has: function( elem, i, match ) { return !!Sizzle( match[3], elem ).length; }, header: function( elem ) { return (/h\d/i).test( elem.nodeName ); }, text: function( elem ) { var attr = elem.getAttribute( "type" ), type = elem.type; // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) // use getAttribute instead to test this case return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); }, radio: function( elem ) { return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; }, checkbox: function( elem ) { return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; }, file: function( elem ) { return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; }, password: function( elem ) { return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; }, submit: function( elem ) { var name = elem.nodeName.toLowerCase(); return (name === "input" || name === "button") && "submit" === elem.type; }, image: function( elem ) { return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; }, reset: function( elem ) { var name = elem.nodeName.toLowerCase(); return (name === "input" || name === "button") && "reset" === elem.type; }, button: function( elem ) { var name = elem.nodeName.toLowerCase(); return name === "input" && "button" === elem.type || name === "button"; }, input: function( elem ) { return (/input|select|textarea|button/i).test( elem.nodeName ); }, focus: function( elem ) { return elem === elem.ownerDocument.activeElement; } }, setFilters: { first: function( elem, i ) { return i === 0; }, last: function( elem, i, match, array ) { return i === array.length - 1; }, even: function( elem, i ) { return i % 2 === 0; }, odd: function( elem, i ) { return i % 2 === 1; }, lt: function( elem, i, match ) { return i < match[3] - 0; }, gt: function( elem, i, match ) { return i > match[3] - 0; }, nth: function( elem, i, match ) { return match[3] - 0 === i; }, eq: function( elem, i, match ) { return match[3] - 0 === i; } }, filter: { PSEUDO: function( elem, match, i, array ) { var name = match[1], filter = Expr.filters[ name ]; if ( filter ) { return filter( elem, i, match, array ); } else if ( name === "contains" ) { return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; } else if ( name === "not" ) { var not = match[3]; for ( var j = 0, l = not.length; j < l; j++ ) { if ( not[j] === elem ) { return false; } } return true; } else { Sizzle.error( name ); } }, CHILD: function( elem, match ) { var first, last, doneName, parent, cache, count, diff, type = match[1], node = elem; switch ( type ) { case "only": case "first": while ( (node = node.previousSibling) ) { if ( node.nodeType === 1 ) { return false; } } if ( type === "first" ) { return true; } node = elem; case "last": while ( (node = node.nextSibling) ) { if ( node.nodeType === 1 ) { return false; } } return true; case "nth": first = match[2]; last = match[3]; if ( first === 1 && last === 0 ) { return true; } doneName = match[0]; parent = elem.parentNode; if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { count = 0; for ( node = parent.firstChild; node; node = node.nextSibling ) { if ( node.nodeType === 1 ) { node.nodeIndex = ++count; } } parent[ expando ] = doneName; } diff = elem.nodeIndex - last; if ( first === 0 ) { return diff === 0; } else { return ( diff % first === 0 && diff / first >= 0 ); } } }, ID: function( elem, match ) { return elem.nodeType === 1 && elem.getAttribute("id") === match; }, TAG: function( elem, match ) { return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; }, CLASS: function( elem, match ) { return (" " + (elem.className || elem.getAttribute("class")) + " ") .indexOf( match ) > -1; }, ATTR: function( elem, match ) { var name = match[1], result = Sizzle.attr ? Sizzle.attr( elem, name ) : Expr.attrHandle[ name ] ? Expr.attrHandle[ name ]( elem ) : elem[ name ] != null ? elem[ name ] : elem.getAttribute( name ), value = result + "", type = match[2], check = match[4]; return result == null ? type === "!=" : !type && Sizzle.attr ? result != null : type === "=" ? value === check : type === "*=" ? value.indexOf(check) >= 0 : type === "~=" ? (" " + value + " ").indexOf(check) >= 0 : !check ? value && result !== false : type === "!=" ? value !== check : type === "^=" ? value.indexOf(check) === 0 : type === "$=" ? value.substr(value.length - check.length) === check : type === "|=" ? value === check || value.substr(0, check.length + 1) === check + "-" : false; }, POS: function( elem, match, i, array ) { var name = match[2], filter = Expr.setFilters[ name ]; if ( filter ) { return filter( elem, i, match, array ); } } } }; var origPOS = Expr.match.POS, fescape = function(all, num){ return "\\" + (num - 0 + 1); }; for ( var type in Expr.match ) { Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); } var makeArray = function( array, results ) { array = Array.prototype.slice.call( array, 0 ); if ( results ) { results.push.apply( results, array ); return results; } return array; }; // Perform a simple check to determine if the browser is capable of // converting a NodeList to an array using builtin methods. // Also verifies that the returned array holds DOM nodes // (which is not the case in the Blackberry browser) try { Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; // Provide a fallback method if it does not work } catch( e ) { makeArray = function( array, results ) { var i = 0, ret = results || []; if ( toString.call(array) === "[object Array]" ) { Array.prototype.push.apply( ret, array ); } else { if ( typeof array.length === "number" ) { for ( var l = array.length; i < l; i++ ) { ret.push( array[i] ); } } else { for ( ; array[i]; i++ ) { ret.push( array[i] ); } } } return ret; }; } var sortOrder, siblingCheck; if ( document.documentElement.compareDocumentPosition ) { sortOrder = function( a, b ) { if ( a === b ) { hasDuplicate = true; return 0; } if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { return a.compareDocumentPosition ? -1 : 1; } return a.compareDocumentPosition(b) & 4 ? -1 : 1; }; } else { sortOrder = function( a, b ) { // The nodes are identical, we can exit early if ( a === b ) { hasDuplicate = true; return 0; // Fallback to using sourceIndex (in IE) if it's available on both nodes } else if ( a.sourceIndex && b.sourceIndex ) { return a.sourceIndex - b.sourceIndex; } var al, bl, ap = [], bp = [], aup = a.parentNode, bup = b.parentNode, cur = aup; // If the nodes are siblings (or identical) we can do a quick check if ( aup === bup ) { return siblingCheck( a, b ); // If no parents were found then the nodes are disconnected } else if ( !aup ) { return -1; } else if ( !bup ) { return 1; } // Otherwise they're somewhere else in the tree so we need // to build up a full list of the parentNodes for comparison while ( cur ) { ap.unshift( cur ); cur = cur.parentNode; } cur = bup; while ( cur ) { bp.unshift( cur ); cur = cur.parentNode; } al = ap.length; bl = bp.length; // Start walking down the tree looking for a discrepancy for ( var i = 0; i < al && i < bl; i++ ) { if ( ap[i] !== bp[i] ) { return siblingCheck( ap[i], bp[i] ); } } // We ended someplace up the tree so do a sibling check return i === al ? siblingCheck( a, bp[i], -1 ) : siblingCheck( ap[i], b, 1 ); }; siblingCheck = function( a, b, ret ) { if ( a === b ) { return ret; } var cur = a.nextSibling; while ( cur ) { if ( cur === b ) { return -1; } cur = cur.nextSibling; } return 1; }; } // Check to see if the browser returns elements by name when // querying by getElementById (and provide a workaround) (function(){ // We're going to inject a fake input element with a specified name var form = document.createElement("div"), id = "script" + (new Date()).getTime(), root = document.documentElement; form.innerHTML = "<a name='" + id + "'/>"; // Inject it into the root element, check its status, and remove it quickly root.insertBefore( form, root.firstChild ); // The workaround has to do additional checks after a getElementById // Which slows things down for other browsers (hence the branching) if ( document.getElementById( id ) ) { Expr.find.ID = function( match, context, isXML ) { if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; } }; Expr.filter.ID = function( elem, match ) { var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); return elem.nodeType === 1 && node && node.nodeValue === match; }; } root.removeChild( form ); // release memory in IE root = form = null; })(); (function(){ // Check to see if the browser returns only elements // when doing getElementsByTagName("*") // Create a fake element var div = document.createElement("div"); div.appendChild( document.createComment("") ); // Make sure no comments are found if ( div.getElementsByTagName("*").length > 0 ) { Expr.find.TAG = function( match, context ) { var results = context.getElementsByTagName( match[1] ); // Filter out possible comments if ( match[1] === "*" ) { var tmp = []; for ( var i = 0; results[i]; i++ ) { if ( results[i].nodeType === 1 ) { tmp.push( results[i] ); } } results = tmp; } return results; }; } // Check to see if an attribute returns normalized href attributes div.innerHTML = "<a href='#'></a>"; if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && div.firstChild.getAttribute("href") !== "#" ) { Expr.attrHandle.href = function( elem ) { return elem.getAttribute( "href", 2 ); }; } // release memory in IE div = null; })(); if ( document.querySelectorAll ) { (function(){ var oldSizzle = Sizzle, div = document.createElement("div"), id = "__sizzle__"; div.innerHTML = "<p class='TEST'></p>"; // Safari can't handle uppercase or unicode characters when // in quirks mode. if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { return; } Sizzle = function( query, context, extra, seed ) { context = context || document; // Only use querySelectorAll on non-XML documents // (ID selectors don't work in non-HTML documents) if ( !seed && !Sizzle.isXML(context) ) { // See if we find a selector to speed up var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { // Speed-up: Sizzle("TAG") if ( match[1] ) { return makeArray( context.getElementsByTagName( query ), extra ); // Speed-up: Sizzle(".CLASS") } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { return makeArray( context.getElementsByClassName( match[2] ), extra ); } } if ( context.nodeType === 9 ) { // Speed-up: Sizzle("body") // The body element only exists once, optimize finding it if ( query === "body" && context.body ) { return makeArray( [ context.body ], extra ); // Speed-up: Sizzle("#ID") } else if ( match && match[3] ) { var elem = context.getElementById( match[3] ); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document #6963 if ( elem && elem.parentNode ) { // Handle the case where IE and Opera return items // by name instead of ID if ( elem.id === match[3] ) { return makeArray( [ elem ], extra ); } } else { return makeArray( [], extra ); } } try { return makeArray( context.querySelectorAll(query), extra ); } catch(qsaError) {} // qSA works strangely on Element-rooted queries // We can work around this by specifying an extra ID on the root // and working up from there (Thanks to Andrew Dupont for the technique) // IE 8 doesn't work on object elements } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { var oldContext = context, old = context.getAttribute( "id" ), nid = old || id, hasParent = context.parentNode, relativeHierarchySelector = /^\s*[+~]/.test( query ); if ( !old ) { context.setAttribute( "id", nid ); } else { nid = nid.replace( /'/g, "\\$&" ); } if ( relativeHierarchySelector && hasParent ) { context = context.parentNode; } try { if ( !relativeHierarchySelector || hasParent ) { return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); } } catch(pseudoError) { } finally { if ( !old ) { oldContext.removeAttribute( "id" ); } } } } return oldSizzle(query, context, extra, seed); }; for ( var prop in oldSizzle ) { Sizzle[ prop ] = oldSizzle[ prop ]; } // release memory in IE div = null; })(); } (function(){ var html = document.documentElement, matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; if ( matches ) { // Check to see if it's possible to do matchesSelector // on a disconnected node (IE 9 fails this) var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), pseudoWorks = false; try { // This should fail with an exception // Gecko does not error, returns false instead matches.call( document.documentElement, "[test!='']:sizzle" ); } catch( pseudoError ) { pseudoWorks = true; } Sizzle.matchesSelector = function( node, expr ) { // Make sure that attribute selectors are quoted expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); if ( !Sizzle.isXML( node ) ) { try { if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { var ret = matches.call( node, expr ); // IE 9's matchesSelector returns false on disconnected nodes if ( ret || !disconnectedMatch || // As well, disconnected nodes are said to be in a document // fragment in IE 9, so check for that node.document && node.document.nodeType !== 11 ) { return ret; } } } catch(e) {} } return Sizzle(expr, null, null, [node]).length > 0; }; } })(); (function(){ var div = document.createElement("div"); div.innerHTML = "<div class='test e'></div><div class='test'></div>"; // Opera can't find a second classname (in 9.6) // Also, make sure that getElementsByClassName actually exists if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { return; } // Safari caches class attributes, doesn't catch changes (in 3.2) div.lastChild.className = "e"; if ( div.getElementsByClassName("e").length === 1 ) { return; } Expr.order.splice(1, 0, "CLASS"); Expr.find.CLASS = function( match, context, isXML ) { if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { return context.getElementsByClassName(match[1]); } }; // release memory in IE div = null; })(); function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { var match = false; elem = elem[dir]; while ( elem ) { if ( elem[ expando ] === doneName ) { match = checkSet[elem.sizset]; break; } if ( elem.nodeType === 1 && !isXML ){ elem[ expando ] = doneName; elem.sizset = i; } if ( elem.nodeName.toLowerCase() === cur ) { match = elem; break; } elem = elem[dir]; } checkSet[i] = match; } } } function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { var match = false; elem = elem[dir]; while ( elem ) { if ( elem[ expando ] === doneName ) { match = checkSet[elem.sizset]; break; } if ( elem.nodeType === 1 ) { if ( !isXML ) { elem[ expando ] = doneName; elem.sizset = i; } if ( typeof cur !== "string" ) { if ( elem === cur ) { match = true; break; } } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { match = elem; break; } } elem = elem[dir]; } checkSet[i] = match; } } } if ( document.documentElement.contains ) { Sizzle.contains = function( a, b ) { return a !== b && (a.contains ? a.contains(b) : true); }; } else if ( document.documentElement.compareDocumentPosition ) { Sizzle.contains = function( a, b ) { return !!(a.compareDocumentPosition(b) & 16); }; } else { Sizzle.contains = function() { return false; }; } Sizzle.isXML = function( elem ) { // documentElement is verified for cases where it doesn't yet exist // (such as loading iframes in IE - #4833) var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; return documentElement ? documentElement.nodeName !== "HTML" : false; }; var posProcess = function( selector, context, seed ) { var match, tmpSet = [], later = "", root = context.nodeType ? [context] : context; // Position selectors must be done after the filter // And so must :not(positional) so we move all PSEUDOs to the end while ( (match = Expr.match.PSEUDO.exec( selector )) ) { later += match[0]; selector = selector.replace( Expr.match.PSEUDO, "" ); } selector = Expr.relative[selector] ? selector + "*" : selector; for ( var i = 0, l = root.length; i < l; i++ ) { Sizzle( selector, root[i], tmpSet, seed ); } return Sizzle.filter( later, tmpSet ); }; // EXPOSE // Override sizzle attribute retrieval Sizzle.attr = jQuery.attr; Sizzle.selectors.attrMap = {}; jQuery.find = Sizzle; jQuery.expr = Sizzle.selectors; jQuery.expr[":"] = jQuery.expr.filters; jQuery.unique = Sizzle.uniqueSort; jQuery.text = Sizzle.getText; jQuery.isXMLDoc = Sizzle.isXML; jQuery.contains = Sizzle.contains; })(); var runtil = /Until$/, rparentsprev = /^(?:parents|prevUntil|prevAll)/, // Note: This RegExp should be improved, or likely pulled from Sizzle rmultiselector = /,/, isSimple = /^.[^:#\[\.,]*$/, slice = Array.prototype.slice, POS = jQuery.expr.match.POS, // methods guaranteed to produce a unique set when starting from a unique set guaranteedUnique = { children: true, contents: true, next: true, prev: true }; jQuery.fn.extend({ find: function( selector ) { var self = this, i, l; if ( typeof selector !== "string" ) { return jQuery( selector ).filter(function() { for ( i = 0, l = self.length; i < l; i++ ) { if ( jQuery.contains( self[ i ], this ) ) { return true; } } }); } var ret = this.pushStack( "", "find", selector ), length, n, r; for ( i = 0, l = this.length; i < l; i++ ) { length = ret.length; jQuery.find( selector, this[i], ret ); if ( i > 0 ) { // Make sure that the results are unique for ( n = length; n < ret.length; n++ ) { for ( r = 0; r < length; r++ ) { if ( ret[r] === ret[n] ) { ret.splice(n--, 1); break; } } } } } return ret; }, has: function( target ) { var targets = jQuery( target ); return this.filter(function() { for ( var i = 0, l = targets.length; i < l; i++ ) { if ( jQuery.contains( this, targets[i] ) ) { return true; } } }); }, not: function( selector ) { return this.pushStack( winnow(this, selector, false), "not", selector); }, filter: function( selector ) { return this.pushStack( winnow(this, selector, true), "filter", selector ); }, is: function( selector ) { return !!selector && ( typeof selector === "string" ? // If this is a positional selector, check membership in the returned set // so $("p:first").is("p:last") won't return true for a doc with two "p". POS.test( selector ) ? jQuery( selector, this.context ).index( this[0] ) >= 0 : jQuery.filter( selector, this ).length > 0 : this.filter( selector ).length > 0 ); }, closest: function( selectors, context ) { var ret = [], i, l, cur = this[0]; // Array (deprecated as of jQuery 1.7) if ( jQuery.isArray( selectors ) ) { var level = 1; while ( cur && cur.ownerDocument && cur !== context ) { for ( i = 0; i < selectors.length; i++ ) { if ( jQuery( cur ).is( selectors[ i ] ) ) { ret.push({ selector: selectors[ i ], elem: cur, level: level }); } } cur = cur.parentNode; level++; } return ret; } // String var pos = POS.test( selectors ) || typeof selectors !== "string" ? jQuery( selectors, context || this.context ) : 0; for ( i = 0, l = this.length; i < l; i++ ) { cur = this[i]; while ( cur ) { if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { ret.push( cur ); break; } else { cur = cur.parentNode; if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) { break; } } } } ret = ret.length > 1 ? jQuery.unique( ret ) : ret; return this.pushStack( ret, "closest", selectors ); }, // Determine the position of an element within // the matched set of elements index: function( elem ) { // No argument, return index in parent if ( !elem ) { return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1; } // index in selector if ( typeof elem === "string" ) { return jQuery.inArray( this[0], jQuery( elem ) ); } // Locate the position of the desired element return jQuery.inArray( // If it receives a jQuery object, the first element is used elem.jquery ? elem[0] : elem, this ); }, add: function( selector, context ) { var set = typeof selector === "string" ? jQuery( selector, context ) : jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), all = jQuery.merge( this.get(), set ); return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? all : jQuery.unique( all ) ); }, andSelf: function() { return this.add( this.prevObject ); } }); // A painfully simple check to see if an element is disconnected // from a document (should be improved, where feasible). function isDisconnected( node ) { return !node || !node.parentNode || node.parentNode.nodeType === 11; } jQuery.each({ parent: function( elem ) { var parent = elem.parentNode; return parent && parent.nodeType !== 11 ? parent : null; }, parents: function( elem ) { return jQuery.dir( elem, "parentNode" ); }, parentsUntil: function( elem, i, until ) { return jQuery.dir( elem, "parentNode", until ); }, next: function( elem ) { return jQuery.nth( elem, 2, "nextSibling" ); }, prev: function( elem ) { return jQuery.nth( elem, 2, "previousSibling" ); }, nextAll: function( elem ) { return jQuery.dir( elem, "nextSibling" ); }, prevAll: function( elem ) { return jQuery.dir( elem, "previousSibling" ); }, nextUntil: function( elem, i, until ) { return jQuery.dir( elem, "nextSibling", until ); }, prevUntil: function( elem, i, until ) { return jQuery.dir( elem, "previousSibling", until ); }, siblings: function( elem ) { return jQuery.sibling( elem.parentNode.firstChild, elem ); }, children: function( elem ) { return jQuery.sibling( elem.firstChild ); }, contents: function( elem ) { return jQuery.nodeName( elem, "iframe" ) ? elem.contentDocument || elem.contentWindow.document : jQuery.makeArray( elem.childNodes ); } }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { var ret = jQuery.map( this, fn, until ); if ( !runtil.test( name ) ) { selector = until; } if ( selector && typeof selector === "string" ) { ret = jQuery.filter( selector, ret ); } ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { ret = ret.reverse(); } return this.pushStack( ret, name, slice.call( arguments ).join(",") ); }; }); jQuery.extend({ filter: function( expr, elems, not ) { if ( not ) { expr = ":not(" + expr + ")"; } return elems.length === 1 ? jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : jQuery.find.matches(expr, elems); }, dir: function( elem, dir, until ) { var matched = [], cur = elem[ dir ]; while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { if ( cur.nodeType === 1 ) { matched.push( cur ); } cur = cur[dir]; } return matched; }, nth: function( cur, result, dir, elem ) { result = result || 1; var num = 0; for ( ; cur; cur = cur[dir] ) { if ( cur.nodeType === 1 && ++num === result ) { break; } } return cur; }, sibling: function( n, elem ) { var r = []; for ( ; n; n = n.nextSibling ) { if ( n.nodeType === 1 && n !== elem ) { r.push( n ); } } return r; } }); // Implement the identical functionality for filter and not function winnow( elements, qualifier, keep ) { // Can't pass null or undefined to indexOf in Firefox 4 // Set to 0 to skip string check qualifier = qualifier || 0; if ( jQuery.isFunction( qualifier ) ) { return jQuery.grep(elements, function( elem, i ) { var retVal = !!qualifier.call( elem, i, elem ); return retVal === keep; }); } else if ( qualifier.nodeType ) { return jQuery.grep(elements, function( elem, i ) { return ( elem === qualifier ) === keep; }); } else if ( typeof qualifier === "string" ) { var filtered = jQuery.grep(elements, function( elem ) { return elem.nodeType === 1; }); if ( isSimple.test( qualifier ) ) { return jQuery.filter(qualifier, filtered, !keep); } else { qualifier = jQuery.filter( qualifier, filtered ); } } return jQuery.grep(elements, function( elem, i ) { return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; }); } function createSafeFragment( document ) { var list = nodeNames.split( "|" ), safeFrag = document.createDocumentFragment(); if ( safeFrag.createElement ) { while ( list.length ) { safeFrag.createElement( list.pop() ); } } return safeFrag; } var nodeNames = "abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|" + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, rleadingWhitespace = /^\s+/, rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, rtagName = /<([\w:]+)/, rtbody = /<tbody/i, rhtml = /<|&#?\w+;/, rnoInnerhtml = /<(?:script|style)/i, rnocache = /<(?:script|object|embed|option|style)/i, rnoshimcache = new RegExp("<(?:" + nodeNames + ")", "i"), // checked="checked" or checked rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, rscriptType = /\/(java|ecma)script/i, rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)/, wrapMap = { option: [ 1, "<select multiple='multiple'>", "</select>" ], legend: [ 1, "<fieldset>", "</fieldset>" ], thead: [ 1, "<table>", "</table>" ], tr: [ 2, "<table><tbody>", "</tbody></table>" ], td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ], area: [ 1, "<map>", "</map>" ], _default: [ 0, "", "" ] }, safeFragment = createSafeFragment( document ); wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; // IE can't serialize <link> and <script> tags normally if ( !jQuery.support.htmlSerialize ) { wrapMap._default = [ 1, "div<div>", "</div>" ]; } jQuery.fn.extend({ text: function( text ) { if ( jQuery.isFunction(text) ) { return this.each(function(i) { var self = jQuery( this ); self.text( text.call(this, i, self.text()) ); }); } if ( typeof text !== "object" && text !== undefined ) { return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); } return jQuery.text( this ); }, wrapAll: function( html ) { if ( jQuery.isFunction( html ) ) { return this.each(function(i) { jQuery(this).wrapAll( html.call(this, i) ); }); } if ( this[0] ) { // The elements to wrap the target around var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); if ( this[0].parentNode ) { wrap.insertBefore( this[0] ); } wrap.map(function() { var elem = this; while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { elem = elem.firstChild; } return elem; }).append( this ); } return this; }, wrapInner: function( html ) { if ( jQuery.isFunction( html ) ) { return this.each(function(i) { jQuery(this).wrapInner( html.call(this, i) ); }); } return this.each(function() { var self = jQuery( this ), contents = self.contents(); if ( contents.length ) { contents.wrapAll( html ); } else { self.append( html ); } }); }, wrap: function( html ) { var isFunction = jQuery.isFunction( html ); return this.each(function(i) { jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); }); }, unwrap: function() { return this.parent().each(function() { if ( !jQuery.nodeName( this, "body" ) ) { jQuery( this ).replaceWith( this.childNodes ); } }).end(); }, append: function() { return this.domManip(arguments, true, function( elem ) { if ( this.nodeType === 1 ) { this.appendChild( elem ); } }); }, prepend: function() { return this.domManip(arguments, true, function( elem ) { if ( this.nodeType === 1 ) { this.insertBefore( elem, this.firstChild ); } }); }, before: function() { if ( this[0] && this[0].parentNode ) { return this.domManip(arguments, false, function( elem ) { this.parentNode.insertBefore( elem, this ); }); } else if ( arguments.length ) { var set = jQuery.clean( arguments ); set.push.apply( set, this.toArray() ); return this.pushStack( set, "before", arguments ); } }, after: function() { if ( this[0] && this[0].parentNode ) { return this.domManip(arguments, false, function( elem ) { this.parentNode.insertBefore( elem, this.nextSibling ); }); } else if ( arguments.length ) { var set = this.pushStack( this, "after", arguments ); set.push.apply( set, jQuery.clean(arguments) ); return set; } }, // keepData is for internal use only--do not document remove: function( selector, keepData ) { for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { if ( !selector || jQuery.filter( selector, [ elem ] ).length ) { if ( !keepData && elem.nodeType === 1 ) { jQuery.cleanData( elem.getElementsByTagName("*") ); jQuery.cleanData( [ elem ] ); } if ( elem.parentNode ) { elem.parentNode.removeChild( elem ); } } } return this; }, empty: function() { for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { // Remove element nodes and prevent memory leaks if ( elem.nodeType === 1 ) { jQuery.cleanData( elem.getElementsByTagName("*") ); } // Remove any remaining nodes while ( elem.firstChild ) { elem.removeChild( elem.firstChild ); } } return this; }, clone: function( dataAndEvents, deepDataAndEvents ) { dataAndEvents = dataAndEvents == null ? false : dataAndEvents; deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; return this.map( function () { return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); }); }, html: function( value ) { if ( value === undefined ) { return this[0] && this[0].nodeType === 1 ? this[0].innerHTML.replace(rinlinejQuery, "") : null; // See if we can take a shortcut and just use innerHTML } else if ( typeof value === "string" && !rnoInnerhtml.test( value ) && (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) && !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) { value = value.replace(rxhtmlTag, "<$1></$2>"); try { for ( var i = 0, l = this.length; i < l; i++ ) { // Remove element nodes and prevent memory leaks if ( this[i].nodeType === 1 ) { jQuery.cleanData( this[i].getElementsByTagName("*") ); this[i].innerHTML = value; } } // If using innerHTML throws an exception, use the fallback method } catch(e) { this.empty().append( value ); } } else if ( jQuery.isFunction( value ) ) { this.each(function(i){ var self = jQuery( this ); self.html( value.call(this, i, self.html()) ); }); } else { this.empty().append( value ); } return this; }, replaceWith: function( value ) { if ( this[0] && this[0].parentNode ) { // Make sure that the elements are removed from the DOM before they are inserted // this can help fix replacing a parent with child elements if ( jQuery.isFunction( value ) ) { return this.each(function(i) { var self = jQuery(this), old = self.html(); self.replaceWith( value.call( this, i, old ) ); }); } if ( typeof value !== "string" ) { value = jQuery( value ).detach(); } return this.each(function() { var next = this.nextSibling, parent = this.parentNode; jQuery( this ).remove(); if ( next ) { jQuery(next).before( value ); } else { jQuery(parent).append( value ); } }); } else { return this.length ? this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) : this; } }, detach: function( selector ) { return this.remove( selector, true ); }, domManip: function( args, table, callback ) { var results, first, fragment, parent, value = args[0], scripts = []; // We can't cloneNode fragments that contain checked, in WebKit if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) { return this.each(function() { jQuery(this).domManip( args, table, callback, true ); }); } if ( jQuery.isFunction(value) ) { return this.each(function(i) { var self = jQuery(this); args[0] = value.call(this, i, table ? self.html() : undefined); self.domManip( args, table, callback ); }); } if ( this[0] ) { parent = value && value.parentNode; // If we're in a fragment, just use that instead of building a new one if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) { results = { fragment: parent }; } else { results = jQuery.buildFragment( args, this, scripts ); } fragment = results.fragment; if ( fragment.childNodes.length === 1 ) { first = fragment = fragment.firstChild; } else { first = fragment.firstChild; } if ( first ) { table = table && jQuery.nodeName( first, "tr" ); for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) { callback.call( table ? root(this[i], first) : this[i], // Make sure that we do not leak memory by inadvertently discarding // the original fragment (which might have attached data) instead of // using it; in addition, use the original fragment object for the last // item instead of first because it can end up being emptied incorrectly // in certain situations (Bug #8070). // Fragments from the fragment cache must always be cloned and never used // in place. results.cacheable || ( l > 1 && i < lastIndex ) ? jQuery.clone( fragment, true, true ) : fragment ); } } if ( scripts.length ) { jQuery.each( scripts, evalScript ); } } return this; } }); function root( elem, cur ) { return jQuery.nodeName(elem, "table") ? (elem.getElementsByTagName("tbody")[0] || elem.appendChild(elem.ownerDocument.createElement("tbody"))) : elem; } function cloneCopyEvent( src, dest ) { if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { return; } var type, i, l, oldData = jQuery._data( src ), curData = jQuery._data( dest, oldData ), events = oldData.events; if ( events ) { delete curData.handle; curData.events = {}; for ( type in events ) { for ( i = 0, l = events[ type ].length; i < l; i++ ) { jQuery.event.add( dest, type + ( events[ type ][ i ].namespace ? "." : "" ) + events[ type ][ i ].namespace, events[ type ][ i ], events[ type ][ i ].data ); } } } // make the cloned public data object a copy from the original if ( curData.data ) { curData.data = jQuery.extend( {}, curData.data ); } } function cloneFixAttributes( src, dest ) { var nodeName; // We do not need to do anything for non-Elements if ( dest.nodeType !== 1 ) { return; } // clearAttributes removes the attributes, which we don't want, // but also removes the attachEvent events, which we *do* want if ( dest.clearAttributes ) { dest.clearAttributes(); } // mergeAttributes, in contrast, only merges back on the // original attributes, not the events if ( dest.mergeAttributes ) { dest.mergeAttributes( src ); } nodeName = dest.nodeName.toLowerCase(); // IE6-8 fail to clone children inside object elements that use // the proprietary classid attribute value (rather than the type // attribute) to identify the type of content to display if ( nodeName === "object" ) { dest.outerHTML = src.outerHTML; } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) { // IE6-8 fails to persist the checked state of a cloned checkbox // or radio button. Worse, IE6-7 fail to give the cloned element // a checked appearance if the defaultChecked value isn't also set if ( src.checked ) { dest.defaultChecked = dest.checked = src.checked; } // IE6-7 get confused and end up setting the value of a cloned // checkbox/radio button to an empty string instead of "on" if ( dest.value !== src.value ) { dest.value = src.value; } // IE6-8 fails to return the selected option to the default selected // state when cloning options } else if ( nodeName === "option" ) { dest.selected = src.defaultSelected; // IE6-8 fails to set the defaultValue to the correct value when // cloning other types of input fields } else if ( nodeName === "input" || nodeName === "textarea" ) { dest.defaultValue = src.defaultValue; } // Event data gets referenced instead of copied if the expando // gets copied too dest.removeAttribute( jQuery.expando ); } jQuery.buildFragment = function( args, nodes, scripts ) { var fragment, cacheable, cacheresults, doc, first = args[ 0 ]; // nodes may contain either an explicit document object, // a jQuery collection or context object. // If nodes[0] contains a valid object to assign to doc if ( nodes && nodes[0] ) { doc = nodes[0].ownerDocument || nodes[0]; } // Ensure that an attr object doesn't incorrectly stand in as a document object // Chrome and Firefox seem to allow this to occur and will throw exception // Fixes #8950 if ( !doc.createDocumentFragment ) { doc = document; } // Only cache "small" (1/2 KB) HTML strings that are associated with the main document // Cloning options loses the selected state, so don't cache them // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501 if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document && first.charAt(0) === "<" && !rnocache.test( first ) && (jQuery.support.checkClone || !rchecked.test( first )) && (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) { cacheable = true; cacheresults = jQuery.fragments[ first ]; if ( cacheresults && cacheresults !== 1 ) { fragment = cacheresults; } } if ( !fragment ) { fragment = doc.createDocumentFragment(); jQuery.clean( args, doc, fragment, scripts ); } if ( cacheable ) { jQuery.fragments[ first ] = cacheresults ? fragment : 1; } return { fragment: fragment, cacheable: cacheable }; }; jQuery.fragments = {}; jQuery.each({ appendTo: "append", prependTo: "prepend", insertBefore: "before", insertAfter: "after", replaceAll: "replaceWith" }, function( name, original ) { jQuery.fn[ name ] = function( selector ) { var ret = [], insert = jQuery( selector ), parent = this.length === 1 && this[0].parentNode; if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { insert[ original ]( this[0] ); return this; } else { for ( var i = 0, l = insert.length; i < l; i++ ) { var elems = ( i > 0 ? this.clone(true) : this ).get(); jQuery( insert[i] )[ original ]( elems ); ret = ret.concat( elems ); } return this.pushStack( ret, name, insert.selector ); } }; }); function getAll( elem ) { if ( typeof elem.getElementsByTagName !== "undefined" ) { return elem.getElementsByTagName( "*" ); } else if ( typeof elem.querySelectorAll !== "undefined" ) { return elem.querySelectorAll( "*" ); } else { return []; } } // Used in clean, fixes the defaultChecked property function fixDefaultChecked( elem ) { if ( elem.type === "checkbox" || elem.type === "radio" ) { elem.defaultChecked = elem.checked; } } // Finds all inputs and passes them to fixDefaultChecked function findInputs( elem ) { var nodeName = ( elem.nodeName || "" ).toLowerCase(); if ( nodeName === "input" ) { fixDefaultChecked( elem ); // Skip scripts, get other children } else if ( nodeName !== "script" && typeof elem.getElementsByTagName !== "undefined" ) { jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked ); } } // Derived From: http://www.iecss.com/shimprove/javascript/shimprove.1-0-1.js function shimCloneNode( elem ) { var div = document.createElement( "div" ); safeFragment.appendChild( div ); div.innerHTML = elem.outerHTML; return div.firstChild; } jQuery.extend({ clone: function( elem, dataAndEvents, deepDataAndEvents ) { var srcElements, destElements, i, // IE<=8 does not properly clone detached, unknown element nodes clone = jQuery.support.html5Clone || !rnoshimcache.test( "<" + elem.nodeName ) ? elem.cloneNode( true ) : shimCloneNode( elem ); if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { // IE copies events bound via attachEvent when using cloneNode. // Calling detachEvent on the clone will also remove the events // from the original. In order to get around this, we use some // proprietary methods to clear the events. Thanks to MooTools // guys for this hotness. cloneFixAttributes( elem, clone ); // Using Sizzle here is crazy slow, so we use getElementsByTagName instead srcElements = getAll( elem ); destElements = getAll( clone ); // Weird iteration because IE will replace the length property // with an element if you are cloning the body and one of the // elements on the page has a name or id of "length" for ( i = 0; srcElements[i]; ++i ) { // Ensure that the destination node is not null; Fixes #9587 if ( destElements[i] ) { cloneFixAttributes( srcElements[i], destElements[i] ); } } } // Copy the events from the original to the clone if ( dataAndEvents ) { cloneCopyEvent( elem, clone ); if ( deepDataAndEvents ) { srcElements = getAll( elem ); destElements = getAll( clone ); for ( i = 0; srcElements[i]; ++i ) { cloneCopyEvent( srcElements[i], destElements[i] ); } } } srcElements = destElements = null; // Return the cloned set return clone; }, clean: function( elems, context, fragment, scripts ) { var checkScriptType; context = context || document; // !context.createElement fails in IE with an error but returns typeof 'object' if ( typeof context.createElement === "undefined" ) { context = context.ownerDocument || context[0] && context[0].ownerDocument || document; } var ret = [], j; for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { if ( typeof elem === "number" ) { elem += ""; } if ( !elem ) { continue; } // Convert html string into DOM nodes if ( typeof elem === "string" ) { if ( !rhtml.test( elem ) ) { elem = context.createTextNode( elem ); } else { // Fix "XHTML"-style tags in all browsers elem = elem.replace(rxhtmlTag, "<$1></$2>"); // Trim whitespace, otherwise indexOf won't work as expected var tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(), wrap = wrapMap[ tag ] || wrapMap._default, depth = wrap[0], div = context.createElement("div"); // Append wrapper element to unknown element safe doc fragment if ( context === document ) { // Use the fragment we've already created for this document safeFragment.appendChild( div ); } else { // Use a fragment created with the owner document createSafeFragment( context ).appendChild( div ); } // Go to html and back, then peel off extra wrappers div.innerHTML = wrap[1] + elem + wrap[2]; // Move to the right depth while ( depth-- ) { div = div.lastChild; } // Remove IE's autoinserted <tbody> from table fragments if ( !jQuery.support.tbody ) { // String was a <table>, *may* have spurious <tbody> var hasBody = rtbody.test(elem), tbody = tag === "table" && !hasBody ? div.firstChild && div.firstChild.childNodes : // String was a bare <thead> or <tfoot> wrap[1] === "<table>" && !hasBody ? div.childNodes : []; for ( j = tbody.length - 1; j >= 0 ; --j ) { if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { tbody[ j ].parentNode.removeChild( tbody[ j ] ); } } } // IE completely kills leading whitespace when innerHTML is used if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); } elem = div.childNodes; } } // Resets defaultChecked for any radios and checkboxes // about to be appended to the DOM in IE 6/7 (#8060) var len; if ( !jQuery.support.appendChecked ) { if ( elem[0] && typeof (len = elem.length) === "number" ) { for ( j = 0; j < len; j++ ) { findInputs( elem[j] ); } } else { findInputs( elem ); } } if ( elem.nodeType ) { ret.push( elem ); } else { ret = jQuery.merge( ret, elem ); } } if ( fragment ) { checkScriptType = function( elem ) { return !elem.type || rscriptType.test( elem.type ); }; for ( i = 0; ret[i]; i++ ) { if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) { scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] ); } else { if ( ret[i].nodeType === 1 ) { var jsTags = jQuery.grep( ret[i].getElementsByTagName( "script" ), checkScriptType ); ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) ); } fragment.appendChild( ret[i] ); } } } return ret; }, cleanData: function( elems ) { var data, id, cache = jQuery.cache, special = jQuery.event.special, deleteExpando = jQuery.support.deleteExpando; for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { continue; } id = elem[ jQuery.expando ]; if ( id ) { data = cache[ id ]; if ( data && data.events ) { for ( var type in data.events ) { if ( special[ type ] ) { jQuery.event.remove( elem, type ); // This is a shortcut to avoid jQuery.event.remove's overhead } else { jQuery.removeEvent( elem, type, data.handle ); } } // Null the DOM reference to avoid IE6/7/8 leak (#7054) if ( data.handle ) { data.handle.elem = null; } } if ( deleteExpando ) { delete elem[ jQuery.expando ]; } else if ( elem.removeAttribute ) { elem.removeAttribute( jQuery.expando ); } delete cache[ id ]; } } } }); function evalScript( i, elem ) { if ( elem.src ) { jQuery.ajax({ url: elem.src, async: false, dataType: "script" }); } else { jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "/*$0*/" ) ); } if ( elem.parentNode ) { elem.parentNode.removeChild( elem ); } } var ralpha = /alpha\([^)]*\)/i, ropacity = /opacity=([^)]*)/, // fixed for IE9, see #8346 rupper = /([A-Z]|^ms)/g, rnumpx = /^-?\d+(?:px)?$/i, rnum = /^-?\d/, rrelNum = /^([\-+])=([\-+.\de]+)/, cssShow = { position: "absolute", visibility: "hidden", display: "block" }, cssWidth = [ "Left", "Right" ], cssHeight = [ "Top", "Bottom" ], curCSS, getComputedStyle, currentStyle; jQuery.fn.css = function( name, value ) { // Setting 'undefined' is a no-op if ( arguments.length === 2 && value === undefined ) { return this; } return jQuery.access( this, name, value, true, function( elem, name, value ) { return value !== undefined ? jQuery.style( elem, name, value ) : jQuery.css( elem, name ); }); }; jQuery.extend({ // Add in style property hooks for overriding the default // behavior of getting and setting a style property cssHooks: { opacity: { get: function( elem, computed ) { if ( computed ) { // We should always get a number back from opacity var ret = curCSS( elem, "opacity", "opacity" ); return ret === "" ? "1" : ret; } else { return elem.style.opacity; } } } }, // Exclude the following css properties to add px cssNumber: { "fillOpacity": true, "fontWeight": true, "lineHeight": true, "opacity": true, "orphans": true, "widows": true, "zIndex": true, "zoom": true }, // Add in properties whose names you wish to fix before // setting or getting the value cssProps: { // normalize float css property "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" }, // Get and set the style property on a DOM Node style: function( elem, name, value, extra ) { // Don't set styles on text and comment nodes if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { return; } // Make sure that we're working with the right name var ret, type, origName = jQuery.camelCase( name ), style = elem.style, hooks = jQuery.cssHooks[ origName ]; name = jQuery.cssProps[ origName ] || origName; // Check if we're setting a value if ( value !== undefined ) { type = typeof value; // convert relative number strings (+= or -=) to relative numbers. #7345 if ( type === "string" && (ret = rrelNum.exec( value )) ) { value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) ); // Fixes bug #9237 type = "number"; } // Make sure that NaN and null values aren't set. See: #7116 if ( value == null || type === "number" && isNaN( value ) ) { return; } // If a number was passed in, add 'px' to the (except for certain CSS properties) if ( type === "number" && !jQuery.cssNumber[ origName ] ) { value += "px"; } // If a hook was provided, use that value, otherwise just set the specified value if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) { // Wrapped to prevent IE from throwing errors when 'invalid' values are provided // Fixes bug #5509 try { style[ name ] = value; } catch(e) {} } } else { // If a hook was provided get the non-computed value from there if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { return ret; } // Otherwise just get the value from the style object return style[ name ]; } }, css: function( elem, name, extra ) { var ret, hooks; // Make sure that we're working with the right name name = jQuery.camelCase( name ); hooks = jQuery.cssHooks[ name ]; name = jQuery.cssProps[ name ] || name; // cssFloat needs a special treatment if ( name === "cssFloat" ) { name = "float"; } // If a hook was provided get the computed value from there if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) { return ret; // Otherwise, if a way to get the computed value exists, use that } else if ( curCSS ) { return curCSS( elem, name ); } }, // A method for quickly swapping in/out CSS properties to get correct calculations swap: function( elem, options, callback ) { var old = {}; // Remember the old values, and insert the new ones for ( var name in options ) { old[ name ] = elem.style[ name ]; elem.style[ name ] = options[ name ]; } callback.call( elem ); // Revert the old values for ( name in options ) { elem.style[ name ] = old[ name ]; } } }); // DEPRECATED, Use jQuery.css() instead jQuery.curCSS = jQuery.css; jQuery.each(["height", "width"], function( i, name ) { jQuery.cssHooks[ name ] = { get: function( elem, computed, extra ) { var val; if ( computed ) { if ( elem.offsetWidth !== 0 ) { return getWH( elem, name, extra ); } else { jQuery.swap( elem, cssShow, function() { val = getWH( elem, name, extra ); }); } return val; } }, set: function( elem, value ) { if ( rnumpx.test( value ) ) { // ignore negative width and height values #1599 value = parseFloat( value ); if ( value >= 0 ) { return value + "px"; } } else { return value; } } }; }); if ( !jQuery.support.opacity ) { jQuery.cssHooks.opacity = { get: function( elem, computed ) { // IE uses filters for opacity return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ? ( parseFloat( RegExp.$1 ) / 100 ) + "" : computed ? "1" : ""; }, set: function( elem, value ) { var style = elem.style, currentStyle = elem.currentStyle, opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "", filter = currentStyle && currentStyle.filter || style.filter || ""; // IE has trouble with opacity if it does not have layout // Force it by setting the zoom level style.zoom = 1; // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652 if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) { // Setting style.filter to null, "" & " " still leave "filter:" in the cssText // if "filter:" is present at all, clearType is disabled, we want to avoid this // style.removeAttribute is IE Only, but so apparently is this code path... style.removeAttribute( "filter" ); // if there there is no filter style applied in a css rule, we are done if ( currentStyle && !currentStyle.filter ) { return; } } // otherwise, set new filter values style.filter = ralpha.test( filter ) ? filter.replace( ralpha, opacity ) : filter + " " + opacity; } }; } jQuery(function() { // This hook cannot be added until DOM ready because the support test // for it is not run until after DOM ready if ( !jQuery.support.reliableMarginRight ) { jQuery.cssHooks.marginRight = { get: function( elem, computed ) { // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right // Work around by temporarily setting element display to inline-block var ret; jQuery.swap( elem, { "display": "inline-block" }, function() { if ( computed ) { ret = curCSS( elem, "margin-right", "marginRight" ); } else { ret = elem.style.marginRight; } }); return ret; } }; } }); if ( document.defaultView && document.defaultView.getComputedStyle ) { getComputedStyle = function( elem, name ) { var ret, defaultView, computedStyle; name = name.replace( rupper, "-$1" ).toLowerCase(); if ( (defaultView = elem.ownerDocument.defaultView) && (computedStyle = defaultView.getComputedStyle( elem, null )) ) { ret = computedStyle.getPropertyValue( name ); if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) { ret = jQuery.style( elem, name ); } } return ret; }; } if ( document.documentElement.currentStyle ) { currentStyle = function( elem, name ) { var left, rsLeft, uncomputed, ret = elem.currentStyle && elem.currentStyle[ name ], style = elem.style; // Avoid setting ret to empty string here // so we don't default to auto if ( ret === null && style && (uncomputed = style[ name ]) ) { ret = uncomputed; } // From the awesome hack by Dean Edwards // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 // If we're not dealing with a regular pixel number // but a number that has a weird ending, we need to convert it to pixels if ( !rnumpx.test( ret ) && rnum.test( ret ) ) { // Remember the original values left = style.left; rsLeft = elem.runtimeStyle && elem.runtimeStyle.left; // Put in the new values to get a computed value out if ( rsLeft ) { elem.runtimeStyle.left = elem.currentStyle.left; } style.left = name === "fontSize" ? "1em" : ( ret || 0 ); ret = style.pixelLeft + "px"; // Revert the changed values style.left = left; if ( rsLeft ) { elem.runtimeStyle.left = rsLeft; } } return ret === "" ? "auto" : ret; }; } curCSS = getComputedStyle || currentStyle; function getWH( elem, name, extra ) { // Start with offset property var val = name === "width" ? elem.offsetWidth : elem.offsetHeight, which = name === "width" ? cssWidth : cssHeight, i = 0, len = which.length; if ( val > 0 ) { if ( extra !== "border" ) { for ( ; i < len; i++ ) { if ( !extra ) { val -= parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0; } if ( extra === "margin" ) { val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0; } else { val -= parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0; } } } return val + "px"; } // Fall back to computed then uncomputed css if necessary val = curCSS( elem, name, name ); if ( val < 0 || val == null ) { val = elem.style[ name ] || 0; } // Normalize "", auto, and prepare for extra val = parseFloat( val ) || 0; // Add padding, border, margin if ( extra ) { for ( ; i < len; i++ ) { val += parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0; if ( extra !== "padding" ) { val += parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0; } if ( extra === "margin" ) { val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0; } } } return val + "px"; } if ( jQuery.expr && jQuery.expr.filters ) { jQuery.expr.filters.hidden = function( elem ) { var width = elem.offsetWidth, height = elem.offsetHeight; return ( width === 0 && height === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none"); }; jQuery.expr.filters.visible = function( elem ) { return !jQuery.expr.filters.hidden( elem ); }; } var r20 = /%20/g, rbracket = /\[\]$/, rCRLF = /\r?\n/g, rhash = /#.*$/, rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, // #7653, #8125, #8152: local protocol detection rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/, rnoContent = /^(?:GET|HEAD)$/, rprotocol = /^\/\//, rquery = /\?/, rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, rselectTextarea = /^(?:select|textarea)/i, rspacesAjax = /\s+/, rts = /([?&])_=[^&]*/, rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/, // Keep a copy of the old load method _load = jQuery.fn.load, /* Prefilters * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) * 2) These are called: * - BEFORE asking for a transport * - AFTER param serialization (s.data is a string if s.processData is true) * 3) key is the dataType * 4) the catchall symbol "*" can be used * 5) execution will start with transport dataType and THEN continue down to "*" if needed */ prefilters = {}, /* Transports bindings * 1) key is the dataType * 2) the catchall symbol "*" can be used * 3) selection will start with transport dataType and THEN go to "*" if needed */ transports = {}, // Document location ajaxLocation, // Document location segments ajaxLocParts, // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression allTypes = ["*/"] + ["*"]; // #8138, IE may throw an exception when accessing // a field from window.location if document.domain has been set try { ajaxLocation = location.href; } catch( e ) { // Use the href attribute of an A element // since IE will modify it given document.location ajaxLocation = document.createElement( "a" ); ajaxLocation.href = ""; ajaxLocation = ajaxLocation.href; } // Segment location into parts ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || []; // Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport function addToPrefiltersOrTransports( structure ) { // dataTypeExpression is optional and defaults to "*" return function( dataTypeExpression, func ) { if ( typeof dataTypeExpression !== "string" ) { func = dataTypeExpression; dataTypeExpression = "*"; } if ( jQuery.isFunction( func ) ) { var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ), i = 0, length = dataTypes.length, dataType, list, placeBefore; // For each dataType in the dataTypeExpression for ( ; i < length; i++ ) { dataType = dataTypes[ i ]; // We control if we're asked to add before // any existing element placeBefore = /^\+/.test( dataType ); if ( placeBefore ) { dataType = dataType.substr( 1 ) || "*"; } list = structure[ dataType ] = structure[ dataType ] || []; // then we add to the structure accordingly list[ placeBefore ? "unshift" : "push" ]( func ); } } }; } // Base inspection function for prefilters and transports function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR, dataType /* internal */, inspected /* internal */ ) { dataType = dataType || options.dataTypes[ 0 ]; inspected = inspected || {}; inspected[ dataType ] = true; var list = structure[ dataType ], i = 0, length = list ? list.length : 0, executeOnly = ( structure === prefilters ), selection; for ( ; i < length && ( executeOnly || !selection ); i++ ) { selection = list[ i ]( options, originalOptions, jqXHR ); // If we got redirected to another dataType // we try there if executing only and not done already if ( typeof selection === "string" ) { if ( !executeOnly || inspected[ selection ] ) { selection = undefined; } else { options.dataTypes.unshift( selection ); selection = inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR, selection, inspected ); } } } // If we're only executing or nothing was selected // we try the catchall dataType if not done already if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) { selection = inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR, "*", inspected ); } // unnecessary when only executing (prefilters) // but it'll be ignored by the caller in that case return selection; } // A special extend for ajax options // that takes "flat" options (not to be deep extended) // Fixes #9887 function ajaxExtend( target, src ) { var key, deep, flatOptions = jQuery.ajaxSettings.flatOptions || {}; for ( key in src ) { if ( src[ key ] !== undefined ) { ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; } } if ( deep ) { jQuery.extend( true, target, deep ); } } jQuery.fn.extend({ load: function( url, params, callback ) { if ( typeof url !== "string" && _load ) { return _load.apply( this, arguments ); // Don't do a request if no elements are being requested } else if ( !this.length ) { return this; } var off = url.indexOf( " " ); if ( off >= 0 ) { var selector = url.slice( off, url.length ); url = url.slice( 0, off ); } // Default to a GET request var type = "GET"; // If the second parameter was provided if ( params ) { // If it's a function if ( jQuery.isFunction( params ) ) { // We assume that it's the callback callback = params; params = undefined; // Otherwise, build a param string } else if ( typeof params === "object" ) { params = jQuery.param( params, jQuery.ajaxSettings.traditional ); type = "POST"; } } var self = this; // Request the remote document jQuery.ajax({ url: url, type: type, dataType: "html", data: params, // Complete callback (responseText is used internally) complete: function( jqXHR, status, responseText ) { // Store the response as specified by the jqXHR object responseText = jqXHR.responseText; // If successful, inject the HTML into all the matched elements if ( jqXHR.isResolved() ) { // #4825: Get the actual response in case // a dataFilter is present in ajaxSettings jqXHR.done(function( r ) { responseText = r; }); // See if a selector was specified self.html( selector ? // Create a dummy div to hold the results jQuery("<div>") // inject the contents of the document in, removing the scripts // to avoid any 'Permission Denied' errors in IE .append(responseText.replace(rscript, "")) // Locate the specified elements .find(selector) : // If not, just inject the full result responseText ); } if ( callback ) { self.each( callback, [ responseText, status, jqXHR ] ); } } }); return this; }, serialize: function() { return jQuery.param( this.serializeArray() ); }, serializeArray: function() { return this.map(function(){ return this.elements ? jQuery.makeArray( this.elements ) : this; }) .filter(function(){ return this.name && !this.disabled && ( this.checked || rselectTextarea.test( this.nodeName ) || rinput.test( this.type ) ); }) .map(function( i, elem ){ var val = jQuery( this ).val(); return val == null ? null : jQuery.isArray( val ) ? jQuery.map( val, function( val, i ){ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; }) : { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; }).get(); } }); // Attach a bunch of functions for handling common AJAX events jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){ jQuery.fn[ o ] = function( f ){ return this.on( o, f ); }; }); jQuery.each( [ "get", "post" ], function( i, method ) { jQuery[ method ] = function( url, data, callback, type ) { // shift arguments if data argument was omitted if ( jQuery.isFunction( data ) ) { type = type || callback; callback = data; data = undefined; } return jQuery.ajax({ type: method, url: url, data: data, success: callback, dataType: type }); }; }); jQuery.extend({ getScript: function( url, callback ) { return jQuery.get( url, undefined, callback, "script" ); }, getJSON: function( url, data, callback ) { return jQuery.get( url, data, callback, "json" ); }, // Creates a full fledged settings object into target // with both ajaxSettings and settings fields. // If target is omitted, writes into ajaxSettings. ajaxSetup: function( target, settings ) { if ( settings ) { // Building a settings object ajaxExtend( target, jQuery.ajaxSettings ); } else { // Extending ajaxSettings settings = target; target = jQuery.ajaxSettings; } ajaxExtend( target, settings ); return target; }, ajaxSettings: { url: ajaxLocation, isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ), global: true, type: "GET", contentType: "application/x-www-form-urlencoded", processData: true, async: true, /* timeout: 0, data: null, dataType: null, username: null, password: null, cache: null, traditional: false, headers: {}, */ accepts: { xml: "application/xml, text/xml", html: "text/html", text: "text/plain", json: "application/json, text/javascript", "*": allTypes }, contents: { xml: /xml/, html: /html/, json: /json/ }, responseFields: { xml: "responseXML", text: "responseText" }, // List of data converters // 1) key format is "source_type destination_type" (a single space in-between) // 2) the catchall symbol "*" can be used for source_type converters: { // Convert anything to text "* text": window.String, // Text to html (true = no transformation) "text html": true, // Evaluate text as a json expression "text json": jQuery.parseJSON, // Parse text as xml "text xml": jQuery.parseXML }, // For options that shouldn't be deep extended: // you can add your own custom options here if // and when you create one that shouldn't be // deep extended (see ajaxExtend) flatOptions: { context: true, url: true } }, ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), ajaxTransport: addToPrefiltersOrTransports( transports ), // Main method ajax: function( url, options ) { // If url is an object, simulate pre-1.5 signature if ( typeof url === "object" ) { options = url; url = undefined; } // Force options to be an object options = options || {}; var // Create the final options object s = jQuery.ajaxSetup( {}, options ), // Callbacks context callbackContext = s.context || s, // Context for global events // It's the callbackContext if one was provided in the options // and if it's a DOM node or a jQuery collection globalEventContext = callbackContext !== s && ( callbackContext.nodeType || callbackContext instanceof jQuery ) ? jQuery( callbackContext ) : jQuery.event, // Deferreds deferred = jQuery.Deferred(), completeDeferred = jQuery.Callbacks( "once memory" ), // Status-dependent callbacks statusCode = s.statusCode || {}, // ifModified key ifModifiedKey, // Headers (they are sent all at once) requestHeaders = {}, requestHeadersNames = {}, // Response headers responseHeadersString, responseHeaders, // transport transport, // timeout handle timeoutTimer, // Cross-domain detection vars parts, // The jqXHR state state = 0, // To know if global events are to be dispatched fireGlobals, // Loop variable i, // Fake xhr jqXHR = { readyState: 0, // Caches the header setRequestHeader: function( name, value ) { if ( !state ) { var lname = name.toLowerCase(); name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name; requestHeaders[ name ] = value; } return this; }, // Raw string getAllResponseHeaders: function() { return state === 2 ? responseHeadersString : null; }, // Builds headers hashtable if needed getResponseHeader: function( key ) { var match; if ( state === 2 ) { if ( !responseHeaders ) { responseHeaders = {}; while( ( match = rheaders.exec( responseHeadersString ) ) ) { responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; } } match = responseHeaders[ key.toLowerCase() ]; } return match === undefined ? null : match; }, // Overrides response content-type header overrideMimeType: function( type ) { if ( !state ) { s.mimeType = type; } return this; }, // Cancel the request abort: function( statusText ) { statusText = statusText || "abort"; if ( transport ) { transport.abort( statusText ); } done( 0, statusText ); return this; } }; // Callback for when everything is done // It is defined here because jslint complains if it is declared // at the end of the function (which would be more logical and readable) function done( status, nativeStatusText, responses, headers ) { // Called once if ( state === 2 ) { return; } // State is "done" now state = 2; // Clear timeout if it exists if ( timeoutTimer ) { clearTimeout( timeoutTimer ); } // Dereference transport for early garbage collection // (no matter how long the jqXHR object will be used) transport = undefined; // Cache response headers responseHeadersString = headers || ""; // Set readyState jqXHR.readyState = status > 0 ? 4 : 0; var isSuccess, success, error, statusText = nativeStatusText, response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined, lastModified, etag; // If successful, handle type chaining if ( status >= 200 && status < 300 || status === 304 ) { // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) { jQuery.lastModified[ ifModifiedKey ] = lastModified; } if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) { jQuery.etag[ ifModifiedKey ] = etag; } } // If not modified if ( status === 304 ) { statusText = "notmodified"; isSuccess = true; // If we have data } else { try { success = ajaxConvert( s, response ); statusText = "success"; isSuccess = true; } catch(e) { // We have a parsererror statusText = "parsererror"; error = e; } } } else { // We extract error from statusText // then normalize statusText and status for non-aborts error = statusText; if ( !statusText || status ) { statusText = "error"; if ( status < 0 ) { status = 0; } } } // Set data for the fake xhr object jqXHR.status = status; jqXHR.statusText = "" + ( nativeStatusText || statusText ); // Success/Error if ( isSuccess ) { deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); } else { deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); } // Status-dependent callbacks jqXHR.statusCode( statusCode ); statusCode = undefined; if ( fireGlobals ) { globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ), [ jqXHR, s, isSuccess ? success : error ] ); } // Complete completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); if ( fireGlobals ) { globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); // Handle the global AJAX counter if ( !( --jQuery.active ) ) { jQuery.event.trigger( "ajaxStop" ); } } } // Attach deferreds deferred.promise( jqXHR ); jqXHR.success = jqXHR.done; jqXHR.error = jqXHR.fail; jqXHR.complete = completeDeferred.add; // Status-dependent callbacks jqXHR.statusCode = function( map ) { if ( map ) { var tmp; if ( state < 2 ) { for ( tmp in map ) { statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ]; } } else { tmp = map[ jqXHR.status ]; jqXHR.then( tmp, tmp ); } } return this; }; // Remove hash character (#7531: and string promotion) // Add protocol if not provided (#5866: IE7 issue with protocol-less urls) // We also use the url parameter if available s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" ); // Extract dataTypes list s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax ); // Determine if a cross-domain request is in order if ( s.crossDomain == null ) { parts = rurl.exec( s.url.toLowerCase() ); s.crossDomain = !!( parts && ( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] || ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) != ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) ) ); } // Convert data if not already a string if ( s.data && s.processData && typeof s.data !== "string" ) { s.data = jQuery.param( s.data, s.traditional ); } // Apply prefilters inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); // If request was aborted inside a prefiler, stop there if ( state === 2 ) { return false; } // We can fire global events as of now if asked to fireGlobals = s.global; // Uppercase the type s.type = s.type.toUpperCase(); // Determine if request has content s.hasContent = !rnoContent.test( s.type ); // Watch for a new set of requests if ( fireGlobals && jQuery.active++ === 0 ) { jQuery.event.trigger( "ajaxStart" ); } // More options handling for requests with no content if ( !s.hasContent ) { // If data is available, append data to url if ( s.data ) { s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; // #9682: remove data so that it's not used in an eventual retry delete s.data; } // Get ifModifiedKey before adding the anti-cache parameter ifModifiedKey = s.url; // Add anti-cache in url if needed if ( s.cache === false ) { var ts = jQuery.now(), // try replacing _= if it is there ret = s.url.replace( rts, "$1_=" + ts ); // if nothing was replaced, add timestamp to the end s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" ); } } // Set the correct header, if data is being sent if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { jqXHR.setRequestHeader( "Content-Type", s.contentType ); } // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { ifModifiedKey = ifModifiedKey || s.url; if ( jQuery.lastModified[ ifModifiedKey ] ) { jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] ); } if ( jQuery.etag[ ifModifiedKey ] ) { jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] ); } } // Set the Accepts header for the server, depending on the dataType jqXHR.setRequestHeader( "Accept", s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ? s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : s.accepts[ "*" ] ); // Check for headers option for ( i in s.headers ) { jqXHR.setRequestHeader( i, s.headers[ i ] ); } // Allow custom headers/mimetypes and early abort if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) { // Abort if not done already jqXHR.abort(); return false; } // Install callbacks on deferreds for ( i in { success: 1, error: 1, complete: 1 } ) { jqXHR[ i ]( s[ i ] ); } // Get transport transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); // If no transport, we auto-abort if ( !transport ) { done( -1, "No Transport" ); } else { jqXHR.readyState = 1; // Send global event if ( fireGlobals ) { globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); } // Timeout if ( s.async && s.timeout > 0 ) { timeoutTimer = setTimeout( function(){ jqXHR.abort( "timeout" ); }, s.timeout ); } try { state = 1; transport.send( requestHeaders, done ); } catch (e) { // Propagate exception as error if not done if ( state < 2 ) { done( -1, e ); // Simply rethrow otherwise } else { throw e; } } } return jqXHR; }, // Serialize an array of form elements or a set of // key/values into a query string param: function( a, traditional ) { var s = [], add = function( key, value ) { // If value is a function, invoke it and return its value value = jQuery.isFunction( value ) ? value() : value; s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value ); }; // Set traditional to true for jQuery <= 1.3.2 behavior. if ( traditional === undefined ) { traditional = jQuery.ajaxSettings.traditional; } // If an array was passed in, assume that it is an array of form elements. if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { // Serialize the form elements jQuery.each( a, function() { add( this.name, this.value ); }); } else { // If traditional, encode the "old" way (the way 1.3.2 or older // did it), otherwise encode params recursively. for ( var prefix in a ) { buildParams( prefix, a[ prefix ], traditional, add ); } } // Return the resulting serialization return s.join( "&" ).replace( r20, "+" ); } }); function buildParams( prefix, obj, traditional, add ) { if ( jQuery.isArray( obj ) ) { // Serialize array item. jQuery.each( obj, function( i, v ) { if ( traditional || rbracket.test( prefix ) ) { // Treat each array item as a scalar. add( prefix, v ); } else { // If array item is non-scalar (array or object), encode its // numeric index to resolve deserialization ambiguity issues. // Note that rack (as of 1.0.0) can't currently deserialize // nested arrays properly, and attempting to do so may cause // a server error. Possible fixes are to modify rack's // deserialization algorithm or to provide an option or flag // to force array serialization to be shallow. buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add ); } }); } else if ( !traditional && obj != null && typeof obj === "object" ) { // Serialize object item. for ( var name in obj ) { buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); } } else { // Serialize scalar item. add( prefix, obj ); } } // This is still on the jQuery object... for now // Want to move this to jQuery.ajax some day jQuery.extend({ // Counter for holding the number of active queries active: 0, // Last-Modified header cache for next request lastModified: {}, etag: {} }); /* Handles responses to an ajax request: * - sets all responseXXX fields accordingly * - finds the right dataType (mediates between content-type and expected dataType) * - returns the corresponding response */ function ajaxHandleResponses( s, jqXHR, responses ) { var contents = s.contents, dataTypes = s.dataTypes, responseFields = s.responseFields, ct, type, finalDataType, firstDataType; // Fill responseXXX fields for ( type in responseFields ) { if ( type in responses ) { jqXHR[ responseFields[type] ] = responses[ type ]; } } // Remove auto dataType and get content-type in the process while( dataTypes[ 0 ] === "*" ) { dataTypes.shift(); if ( ct === undefined ) { ct = s.mimeType || jqXHR.getResponseHeader( "content-type" ); } } // Check if we're dealing with a known content-type if ( ct ) { for ( type in contents ) { if ( contents[ type ] && contents[ type ].test( ct ) ) { dataTypes.unshift( type ); break; } } } // Check to see if we have a response for the expected dataType if ( dataTypes[ 0 ] in responses ) { finalDataType = dataTypes[ 0 ]; } else { // Try convertible dataTypes for ( type in responses ) { if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) { finalDataType = type; break; } if ( !firstDataType ) { firstDataType = type; } } // Or just use first one finalDataType = finalDataType || firstDataType; } // If we found a dataType // We add the dataType to the list if needed // and return the corresponding response if ( finalDataType ) { if ( finalDataType !== dataTypes[ 0 ] ) { dataTypes.unshift( finalDataType ); } return responses[ finalDataType ]; } } // Chain conversions given the request and the original response function ajaxConvert( s, response ) { // Apply the dataFilter if provided if ( s.dataFilter ) { response = s.dataFilter( response, s.dataType ); } var dataTypes = s.dataTypes, converters = {}, i, key, length = dataTypes.length, tmp, // Current and previous dataTypes current = dataTypes[ 0 ], prev, // Conversion expression conversion, // Conversion function conv, // Conversion functions (transitive conversion) conv1, conv2; // For each dataType in the chain for ( i = 1; i < length; i++ ) { // Create converters map // with lowercased keys if ( i === 1 ) { for ( key in s.converters ) { if ( typeof key === "string" ) { converters[ key.toLowerCase() ] = s.converters[ key ]; } } } // Get the dataTypes prev = current; current = dataTypes[ i ]; // If current is auto dataType, update it to prev if ( current === "*" ) { current = prev; // If no auto and dataTypes are actually different } else if ( prev !== "*" && prev !== current ) { // Get the converter conversion = prev + " " + current; conv = converters[ conversion ] || converters[ "* " + current ]; // If there is no direct converter, search transitively if ( !conv ) { conv2 = undefined; for ( conv1 in converters ) { tmp = conv1.split( " " ); if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) { conv2 = converters[ tmp[1] + " " + current ]; if ( conv2 ) { conv1 = converters[ conv1 ]; if ( conv1 === true ) { conv = conv2; } else if ( conv2 === true ) { conv = conv1; } break; } } } } // If we found no converter, dispatch an error if ( !( conv || conv2 ) ) { jQuery.error( "No conversion from " + conversion.replace(" "," to ") ); } // If found converter is not an equivalence if ( conv !== true ) { // Convert with 1 or 2 converters accordingly response = conv ? conv( response ) : conv2( conv1(response) ); } } } return response; } var jsc = jQuery.now(), jsre = /(\=)\?(&|$)|\?\?/i; // Default jsonp settings jQuery.ajaxSetup({ jsonp: "callback", jsonpCallback: function() { return jQuery.expando + "_" + ( jsc++ ); } }); // Detect, normalize options and install callbacks for jsonp requests jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { var inspectData = s.contentType === "application/x-www-form-urlencoded" && ( typeof s.data === "string" ); if ( s.dataTypes[ 0 ] === "jsonp" || s.jsonp !== false && ( jsre.test( s.url ) || inspectData && jsre.test( s.data ) ) ) { var responseContainer, jsonpCallback = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback, previous = window[ jsonpCallback ], url = s.url, data = s.data, replace = "$1" + jsonpCallback + "$2"; if ( s.jsonp !== false ) { url = url.replace( jsre, replace ); if ( s.url === url ) { if ( inspectData ) { data = data.replace( jsre, replace ); } if ( s.data === data ) { // Add callback manually url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback; } } } s.url = url; s.data = data; // Install callback window[ jsonpCallback ] = function( response ) { responseContainer = [ response ]; }; // Clean-up function jqXHR.always(function() { // Set callback back to previous value window[ jsonpCallback ] = previous; // Call if it was a function and we have a response if ( responseContainer && jQuery.isFunction( previous ) ) { window[ jsonpCallback ]( responseContainer[ 0 ] ); } }); // Use data converter to retrieve json after script execution s.converters["script json"] = function() { if ( !responseContainer ) { jQuery.error( jsonpCallback + " was not called" ); } return responseContainer[ 0 ]; }; // force json dataType s.dataTypes[ 0 ] = "json"; // Delegate to script return "script"; } }); // Install script dataType jQuery.ajaxSetup({ accepts: { script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" }, contents: { script: /javascript|ecmascript/ }, converters: { "text script": function( text ) { jQuery.globalEval( text ); return text; } } }); // Handle cache's special case and global jQuery.ajaxPrefilter( "script", function( s ) { if ( s.cache === undefined ) { s.cache = false; } if ( s.crossDomain ) { s.type = "GET"; s.global = false; } }); // Bind script tag hack transport jQuery.ajaxTransport( "script", function(s) { // This transport only deals with cross domain requests if ( s.crossDomain ) { var script, head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement; return { send: function( _, callback ) { script = document.createElement( "script" ); script.async = "async"; if ( s.scriptCharset ) { script.charset = s.scriptCharset; } script.src = s.url; // Attach handlers for all browsers script.onload = script.onreadystatechange = function( _, isAbort ) { if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) { // Handle memory leak in IE script.onload = script.onreadystatechange = null; // Remove the script if ( head && script.parentNode ) { head.removeChild( script ); } // Dereference the script script = undefined; // Callback if not abort if ( !isAbort ) { callback( 200, "success" ); } } }; // Use insertBefore instead of appendChild to circumvent an IE6 bug. // This arises when a base node is used (#2709 and #4378). head.insertBefore( script, head.firstChild ); }, abort: function() { if ( script ) { script.onload( 0, 1 ); } } }; } }); var // #5280: Internet Explorer will keep connections alive if we don't abort on unload xhrOnUnloadAbort = window.ActiveXObject ? function() { // Abort all pending requests for ( var key in xhrCallbacks ) { xhrCallbacks[ key ]( 0, 1 ); } } : false, xhrId = 0, xhrCallbacks; // Functions to create xhrs function createStandardXHR() { try { return new window.XMLHttpRequest(); } catch( e ) {} } function createActiveXHR() { try { return new window.ActiveXObject( "Microsoft.XMLHTTP" ); } catch( e ) {} } // Create the request object // (This is still attached to ajaxSettings for backward compatibility) jQuery.ajaxSettings.xhr = window.ActiveXObject ? /* Microsoft failed to properly * implement the XMLHttpRequest in IE7 (can't request local files), * so we use the ActiveXObject when it is available * Additionally XMLHttpRequest can be disabled in IE7/IE8 so * we need a fallback. */ function() { return !this.isLocal && createStandardXHR() || createActiveXHR(); } : // For all other browsers, use the standard XMLHttpRequest object createStandardXHR; // Determine support properties (function( xhr ) { jQuery.extend( jQuery.support, { ajax: !!xhr, cors: !!xhr && ( "withCredentials" in xhr ) }); })( jQuery.ajaxSettings.xhr() ); // Create transport if the browser can provide an xhr if ( jQuery.support.ajax ) { jQuery.ajaxTransport(function( s ) { // Cross domain only allowed if supported through XMLHttpRequest if ( !s.crossDomain || jQuery.support.cors ) { var callback; return { send: function( headers, complete ) { // Get a new xhr var xhr = s.xhr(), handle, i; // Open the socket // Passing null username, generates a login popup on Opera (#2865) if ( s.username ) { xhr.open( s.type, s.url, s.async, s.username, s.password ); } else { xhr.open( s.type, s.url, s.async ); } // Apply custom fields if provided if ( s.xhrFields ) { for ( i in s.xhrFields ) { xhr[ i ] = s.xhrFields[ i ]; } } // Override mime type if needed if ( s.mimeType && xhr.overrideMimeType ) { xhr.overrideMimeType( s.mimeType ); } // X-Requested-With header // For cross-domain requests, seeing as conditions for a preflight are // akin to a jigsaw puzzle, we simply never set it to be sure. // (it can always be set on a per-request basis or even using ajaxSetup) // For same-domain requests, won't change header if already provided. if ( !s.crossDomain && !headers["X-Requested-With"] ) { headers[ "X-Requested-With" ] = "XMLHttpRequest"; } // Need an extra try/catch for cross domain requests in Firefox 3 try { for ( i in headers ) { xhr.setRequestHeader( i, headers[ i ] ); } } catch( _ ) {} // Do send the request // This may raise an exception which is actually // handled in jQuery.ajax (so no try/catch here) xhr.send( ( s.hasContent && s.data ) || null ); // Listener callback = function( _, isAbort ) { var status, statusText, responseHeaders, responses, xml; // Firefox throws exceptions when accessing properties // of an xhr when a network error occured // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE) try { // Was never called and is aborted or complete if ( callback && ( isAbort || xhr.readyState === 4 ) ) { // Only called once callback = undefined; // Do not keep as active anymore if ( handle ) { xhr.onreadystatechange = jQuery.noop; if ( xhrOnUnloadAbort ) { delete xhrCallbacks[ handle ]; } } // If it's an abort if ( isAbort ) { // Abort it manually if needed if ( xhr.readyState !== 4 ) { xhr.abort(); } } else { status = xhr.status; responseHeaders = xhr.getAllResponseHeaders(); responses = {}; xml = xhr.responseXML; // Construct response list if ( xml && xml.documentElement /* #4958 */ ) { responses.xml = xml; } responses.text = xhr.responseText; // Firefox throws an exception when accessing // statusText for faulty cross-domain requests try { statusText = xhr.statusText; } catch( e ) { // We normalize with Webkit giving an empty statusText statusText = ""; } // Filter status for non standard behaviors // If the request is local and we have data: assume a success // (success with no data won't get notified, that's the best we // can do given current implementations) if ( !status && s.isLocal && !s.crossDomain ) { status = responses.text ? 200 : 404; // IE - #1450: sometimes returns 1223 when it should be 204 } else if ( status === 1223 ) { status = 204; } } } } catch( firefoxAccessException ) { if ( !isAbort ) { complete( -1, firefoxAccessException ); } } // Call complete if needed if ( responses ) { complete( status, statusText, responses, responseHeaders ); } }; // if we're in sync mode or it's in cache // and has been retrieved directly (IE6 & IE7) // we need to manually fire the callback if ( !s.async || xhr.readyState === 4 ) { callback(); } else { handle = ++xhrId; if ( xhrOnUnloadAbort ) { // Create the active xhrs callbacks list if needed // and attach the unload handler if ( !xhrCallbacks ) { xhrCallbacks = {}; jQuery( window ).unload( xhrOnUnloadAbort ); } // Add to list of active xhrs callbacks xhrCallbacks[ handle ] = callback; } xhr.onreadystatechange = callback; } }, abort: function() { if ( callback ) { callback(0,1); } } }; } }); } var elemdisplay = {}, iframe, iframeDoc, rfxtypes = /^(?:toggle|show|hide)$/, rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i, timerId, fxAttrs = [ // height animations [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], // width animations [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], // opacity animations [ "opacity" ] ], fxNow; jQuery.fn.extend({ show: function( speed, easing, callback ) { var elem, display; if ( speed || speed === 0 ) { return this.animate( genFx("show", 3), speed, easing, callback ); } else { for ( var i = 0, j = this.length; i < j; i++ ) { elem = this[ i ]; if ( elem.style ) { display = elem.style.display; // Reset the inline display of this element to learn if it is // being hidden by cascaded rules or not if ( !jQuery._data(elem, "olddisplay") && display === "none" ) { display = elem.style.display = ""; } // Set elements which have been overridden with display: none // in a stylesheet to whatever the default browser style is // for such an element if ( display === "" && jQuery.css(elem, "display") === "none" ) { jQuery._data( elem, "olddisplay", defaultDisplay(elem.nodeName) ); } } } // Set the display of most of the elements in a second loop // to avoid the constant reflow for ( i = 0; i < j; i++ ) { elem = this[ i ]; if ( elem.style ) { display = elem.style.display; if ( display === "" || display === "none" ) { elem.style.display = jQuery._data( elem, "olddisplay" ) || ""; } } } return this; } }, hide: function( speed, easing, callback ) { if ( speed || speed === 0 ) { return this.animate( genFx("hide", 3), speed, easing, callback); } else { var elem, display, i = 0, j = this.length; for ( ; i < j; i++ ) { elem = this[i]; if ( elem.style ) { display = jQuery.css( elem, "display" ); if ( display !== "none" && !jQuery._data( elem, "olddisplay" ) ) { jQuery._data( elem, "olddisplay", display ); } } } // Set the display of the elements in a second loop // to avoid the constant reflow for ( i = 0; i < j; i++ ) { if ( this[i].style ) { this[i].style.display = "none"; } } return this; } }, // Save the old toggle function _toggle: jQuery.fn.toggle, toggle: function( fn, fn2, callback ) { var bool = typeof fn === "boolean"; if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) { this._toggle.apply( this, arguments ); } else if ( fn == null || bool ) { this.each(function() { var state = bool ? fn : jQuery(this).is(":hidden"); jQuery(this)[ state ? "show" : "hide" ](); }); } else { this.animate(genFx("toggle", 3), fn, fn2, callback); } return this; }, fadeTo: function( speed, to, easing, callback ) { return this.filter(":hidden").css("opacity", 0).show().end() .animate({opacity: to}, speed, easing, callback); }, animate: function( prop, speed, easing, callback ) { var optall = jQuery.speed( speed, easing, callback ); if ( jQuery.isEmptyObject( prop ) ) { return this.each( optall.complete, [ false ] ); } // Do not change referenced properties as per-property easing will be lost prop = jQuery.extend( {}, prop ); function doAnimation() { // XXX 'this' does not always have a nodeName when running the // test suite if ( optall.queue === false ) { jQuery._mark( this ); } var opt = jQuery.extend( {}, optall ), isElement = this.nodeType === 1, hidden = isElement && jQuery(this).is(":hidden"), name, val, p, e, parts, start, end, unit, method; // will store per property easing and be used to determine when an animation is complete opt.animatedProperties = {}; for ( p in prop ) { // property name normalization name = jQuery.camelCase( p ); if ( p !== name ) { prop[ name ] = prop[ p ]; delete prop[ p ]; } val = prop[ name ]; // easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default) if ( jQuery.isArray( val ) ) { opt.animatedProperties[ name ] = val[ 1 ]; val = prop[ name ] = val[ 0 ]; } else { opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing'; } if ( val === "hide" && hidden || val === "show" && !hidden ) { return opt.complete.call( this ); } if ( isElement && ( name === "height" || name === "width" ) ) { // Make sure that nothing sneaks out // Record all 3 overflow attributes because IE does not // change the overflow attribute when overflowX and // overflowY are set to the same value opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ]; // Set display property to inline-block for height/width // animations on inline elements that are having width/height animated if ( jQuery.css( this, "display" ) === "inline" && jQuery.css( this, "float" ) === "none" ) { // inline-level elements accept inline-block; // block-level elements need to be inline with layout if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( this.nodeName ) === "inline" ) { this.style.display = "inline-block"; } else { this.style.zoom = 1; } } } } if ( opt.overflow != null ) { this.style.overflow = "hidden"; } for ( p in prop ) { e = new jQuery.fx( this, opt, p ); val = prop[ p ]; if ( rfxtypes.test( val ) ) { // Tracks whether to show or hide based on private // data attached to the element method = jQuery._data( this, "toggle" + p ) || ( val === "toggle" ? hidden ? "show" : "hide" : 0 ); if ( method ) { jQuery._data( this, "toggle" + p, method === "show" ? "hide" : "show" ); e[ method ](); } else { e[ val ](); } } else { parts = rfxnum.exec( val ); start = e.cur(); if ( parts ) { end = parseFloat( parts[2] ); unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" ); // We need to compute starting value if ( unit !== "px" ) { jQuery.style( this, p, (end || 1) + unit); start = ( (end || 1) / e.cur() ) * start; jQuery.style( this, p, start + unit); } // If a +=/-= token was provided, we're doing a relative animation if ( parts[1] ) { end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start; } e.custom( start, end, unit ); } else { e.custom( start, val, "" ); } } } // For JS strict compliance return true; } return optall.queue === false ? this.each( doAnimation ) : this.queue( optall.queue, doAnimation ); }, stop: function( type, clearQueue, gotoEnd ) { if ( typeof type !== "string" ) { gotoEnd = clearQueue; clearQueue = type; type = undefined; } if ( clearQueue && type !== false ) { this.queue( type || "fx", [] ); } return this.each(function() { var index, hadTimers = false, timers = jQuery.timers, data = jQuery._data( this ); // clear marker counters if we know they won't be if ( !gotoEnd ) { jQuery._unmark( true, this ); } function stopQueue( elem, data, index ) { var hooks = data[ index ]; jQuery.removeData( elem, index, true ); hooks.stop( gotoEnd ); } if ( type == null ) { for ( index in data ) { if ( data[ index ] && data[ index ].stop && index.indexOf(".run") === index.length - 4 ) { stopQueue( this, data, index ); } } } else if ( data[ index = type + ".run" ] && data[ index ].stop ){ stopQueue( this, data, index ); } for ( index = timers.length; index--; ) { if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) { if ( gotoEnd ) { // force the next step to be the last timers[ index ]( true ); } else { timers[ index ].saveState(); } hadTimers = true; timers.splice( index, 1 ); } } // start the next in the queue if the last step wasn't forced // timers currently will call their complete callbacks, which will dequeue // but only if they were gotoEnd if ( !( gotoEnd && hadTimers ) ) { jQuery.dequeue( this, type ); } }); } }); // Animations created synchronously will run synchronously function createFxNow() { setTimeout( clearFxNow, 0 ); return ( fxNow = jQuery.now() ); } function clearFxNow() { fxNow = undefined; } // Generate parameters to create a standard animation function genFx( type, num ) { var obj = {}; jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice( 0, num )), function() { obj[ this ] = type; }); return obj; } // Generate shortcuts for custom animations jQuery.each({ slideDown: genFx( "show", 1 ), slideUp: genFx( "hide", 1 ), slideToggle: genFx( "toggle", 1 ), fadeIn: { opacity: "show" }, fadeOut: { opacity: "hide" }, fadeToggle: { opacity: "toggle" } }, function( name, props ) { jQuery.fn[ name ] = function( speed, easing, callback ) { return this.animate( props, speed, easing, callback ); }; }); jQuery.extend({ speed: function( speed, easing, fn ) { var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { complete: fn || !fn && easing || jQuery.isFunction( speed ) && speed, duration: speed, easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing }; opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default; // normalize opt.queue - true/undefined/null -> "fx" if ( opt.queue == null || opt.queue === true ) { opt.queue = "fx"; } // Queueing opt.old = opt.complete; opt.complete = function( noUnmark ) { if ( jQuery.isFunction( opt.old ) ) { opt.old.call( this ); } if ( opt.queue ) { jQuery.dequeue( this, opt.queue ); } else if ( noUnmark !== false ) { jQuery._unmark( this ); } }; return opt; }, easing: { linear: function( p, n, firstNum, diff ) { return firstNum + diff * p; }, swing: function( p, n, firstNum, diff ) { return ( ( -Math.cos( p*Math.PI ) / 2 ) + 0.5 ) * diff + firstNum; } }, timers: [], fx: function( elem, options, prop ) { this.options = options; this.elem = elem; this.prop = prop; options.orig = options.orig || {}; } }); jQuery.fx.prototype = { // Simple function for setting a style value update: function() { if ( this.options.step ) { this.options.step.call( this.elem, this.now, this ); } ( jQuery.fx.step[ this.prop ] || jQuery.fx.step._default )( this ); }, // Get the current size cur: function() { if ( this.elem[ this.prop ] != null && (!this.elem.style || this.elem.style[ this.prop ] == null) ) { return this.elem[ this.prop ]; } var parsed, r = jQuery.css( this.elem, this.prop ); // Empty strings, null, undefined and "auto" are converted to 0, // complex values such as "rotate(1rad)" are returned as is, // simple values such as "10px" are parsed to Float. return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed; }, // Start an animation from one number to another custom: function( from, to, unit ) { var self = this, fx = jQuery.fx; this.startTime = fxNow || createFxNow(); this.end = to; this.now = this.start = from; this.pos = this.state = 0; this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" ); function t( gotoEnd ) { return self.step( gotoEnd ); } t.queue = this.options.queue; t.elem = this.elem; t.saveState = function() { if ( self.options.hide && jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) { jQuery._data( self.elem, "fxshow" + self.prop, self.start ); } }; if ( t() && jQuery.timers.push(t) && !timerId ) { timerId = setInterval( fx.tick, fx.interval ); } }, // Simple 'show' function show: function() { var dataShow = jQuery._data( this.elem, "fxshow" + this.prop ); // Remember where we started, so that we can go back to it later this.options.orig[ this.prop ] = dataShow || jQuery.style( this.elem, this.prop ); this.options.show = true; // Begin the animation // Make sure that we start at a small width/height to avoid any flash of content if ( dataShow !== undefined ) { // This show is picking up where a previous hide or show left off this.custom( this.cur(), dataShow ); } else { this.custom( this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur() ); } // Start by showing the element jQuery( this.elem ).show(); }, // Simple 'hide' function hide: function() { // Remember where we started, so that we can go back to it later this.options.orig[ this.prop ] = jQuery._data( this.elem, "fxshow" + this.prop ) || jQuery.style( this.elem, this.prop ); this.options.hide = true; // Begin the animation this.custom( this.cur(), 0 ); }, // Each step of an animation step: function( gotoEnd ) { var p, n, complete, t = fxNow || createFxNow(), done = true, elem = this.elem, options = this.options; if ( gotoEnd || t >= options.duration + this.startTime ) { this.now = this.end; this.pos = this.state = 1; this.update(); options.animatedProperties[ this.prop ] = true; for ( p in options.animatedProperties ) { if ( options.animatedProperties[ p ] !== true ) { done = false; } } if ( done ) { // Reset the overflow if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { jQuery.each( [ "", "X", "Y" ], function( index, value ) { elem.style[ "overflow" + value ] = options.overflow[ index ]; }); } // Hide the element if the "hide" operation was done if ( options.hide ) { jQuery( elem ).hide(); } // Reset the properties, if the item has been hidden or shown if ( options.hide || options.show ) { for ( p in options.animatedProperties ) { jQuery.style( elem, p, options.orig[ p ] ); jQuery.removeData( elem, "fxshow" + p, true ); // Toggle data is no longer needed jQuery.removeData( elem, "toggle" + p, true ); } } // Execute the complete function // in the event that the complete function throws an exception // we must ensure it won't be called twice. #5684 complete = options.complete; if ( complete ) { options.complete = false; complete.call( elem ); } } return false; } else { // classical easing cannot be used with an Infinity duration if ( options.duration == Infinity ) { this.now = t; } else { n = t - this.startTime; this.state = n / options.duration; // Perform the easing function, defaults to swing this.pos = jQuery.easing[ options.animatedProperties[this.prop] ]( this.state, n, 0, 1, options.duration ); this.now = this.start + ( (this.end - this.start) * this.pos ); } // Perform the next step of the animation this.update(); } return true; } }; jQuery.extend( jQuery.fx, { tick: function() { var timer, timers = jQuery.timers, i = 0; for ( ; i < timers.length; i++ ) { timer = timers[ i ]; // Checks the timer has not already been removed if ( !timer() && timers[ i ] === timer ) { timers.splice( i--, 1 ); } } if ( !timers.length ) { jQuery.fx.stop(); } }, interval: 13, stop: function() { clearInterval( timerId ); timerId = null; }, speeds: { slow: 600, fast: 200, // Default speed _default: 400 }, step: { opacity: function( fx ) { jQuery.style( fx.elem, "opacity", fx.now ); }, _default: function( fx ) { if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) { fx.elem.style[ fx.prop ] = fx.now + fx.unit; } else { fx.elem[ fx.prop ] = fx.now; } } } }); // Adds width/height step functions // Do not set anything below 0 jQuery.each([ "width", "height" ], function( i, prop ) { jQuery.fx.step[ prop ] = function( fx ) { jQuery.style( fx.elem, prop, Math.max(0, fx.now) + fx.unit ); }; }); if ( jQuery.expr && jQuery.expr.filters ) { jQuery.expr.filters.animated = function( elem ) { return jQuery.grep(jQuery.timers, function( fn ) { return elem === fn.elem; }).length; }; } // Try to restore the default display value of an element function defaultDisplay( nodeName ) { if ( !elemdisplay[ nodeName ] ) { var body = document.body, elem = jQuery( "<" + nodeName + ">" ).appendTo( body ), display = elem.css( "display" ); elem.remove(); // If the simple way fails, // get element's real default display by attaching it to a temp iframe if ( display === "none" || display === "" ) { // No iframe to use yet, so create it if ( !iframe ) { iframe = document.createElement( "iframe" ); iframe.frameBorder = iframe.width = iframe.height = 0; } body.appendChild( iframe ); // Create a cacheable copy of the iframe document on first call. // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML // document to it; WebKit & Firefox won't allow reusing the iframe document. if ( !iframeDoc || !iframe.createElement ) { iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document; iframeDoc.write( ( document.compatMode === "CSS1Compat" ? "<!doctype html>" : "" ) + "<html><body>" ); iframeDoc.close(); } elem = iframeDoc.createElement( nodeName ); iframeDoc.body.appendChild( elem ); display = jQuery.css( elem, "display" ); body.removeChild( iframe ); } // Store the correct default display elemdisplay[ nodeName ] = display; } return elemdisplay[ nodeName ]; } var rtable = /^t(?:able|d|h)$/i, rroot = /^(?:body|html)$/i; if ( "getBoundingClientRect" in document.documentElement ) { jQuery.fn.offset = function( options ) { var elem = this[0], box; if ( options ) { return this.each(function( i ) { jQuery.offset.setOffset( this, options, i ); }); } if ( !elem || !elem.ownerDocument ) { return null; } if ( elem === elem.ownerDocument.body ) { return jQuery.offset.bodyOffset( elem ); } try { box = elem.getBoundingClientRect(); } catch(e) {} var doc = elem.ownerDocument, docElem = doc.documentElement; // Make sure we're not dealing with a disconnected DOM node if ( !box || !jQuery.contains( docElem, elem ) ) { return box ? { top: box.top, left: box.left } : { top: 0, left: 0 }; } var body = doc.body, win = getWindow(doc), clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0, scrollTop = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop, scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft, top = box.top + scrollTop - clientTop, left = box.left + scrollLeft - clientLeft; return { top: top, left: left }; }; } else { jQuery.fn.offset = function( options ) { var elem = this[0]; if ( options ) { return this.each(function( i ) { jQuery.offset.setOffset( this, options, i ); }); } if ( !elem || !elem.ownerDocument ) { return null; } if ( elem === elem.ownerDocument.body ) { return jQuery.offset.bodyOffset( elem ); } var computedStyle, offsetParent = elem.offsetParent, prevOffsetParent = elem, doc = elem.ownerDocument, docElem = doc.documentElement, body = doc.body, defaultView = doc.defaultView, prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle, top = elem.offsetTop, left = elem.offsetLeft; while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) { break; } computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle; top -= elem.scrollTop; left -= elem.scrollLeft; if ( elem === offsetParent ) { top += elem.offsetTop; left += elem.offsetLeft; if ( jQuery.support.doesNotAddBorder && !(jQuery.support.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) { top += parseFloat( computedStyle.borderTopWidth ) || 0; left += parseFloat( computedStyle.borderLeftWidth ) || 0; } prevOffsetParent = offsetParent; offsetParent = elem.offsetParent; } if ( jQuery.support.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) { top += parseFloat( computedStyle.borderTopWidth ) || 0; left += parseFloat( computedStyle.borderLeftWidth ) || 0; } prevComputedStyle = computedStyle; } if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) { top += body.offsetTop; left += body.offsetLeft; } if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) { top += Math.max( docElem.scrollTop, body.scrollTop ); left += Math.max( docElem.scrollLeft, body.scrollLeft ); } return { top: top, left: left }; }; } jQuery.offset = { bodyOffset: function( body ) { var top = body.offsetTop, left = body.offsetLeft; if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) { top += parseFloat( jQuery.css(body, "marginTop") ) || 0; left += parseFloat( jQuery.css(body, "marginLeft") ) || 0; } return { top: top, left: left }; }, setOffset: function( elem, options, i ) { var position = jQuery.css( elem, "position" ); // set position first, in-case top/left are set even on static elem if ( position === "static" ) { elem.style.position = "relative"; } var curElem = jQuery( elem ), curOffset = curElem.offset(), curCSSTop = jQuery.css( elem, "top" ), curCSSLeft = jQuery.css( elem, "left" ), calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1, props = {}, curPosition = {}, curTop, curLeft; // need to be able to calculate position if either top or left is auto and position is either absolute or fixed if ( calculatePosition ) { curPosition = curElem.position(); curTop = curPosition.top; curLeft = curPosition.left; } else { curTop = parseFloat( curCSSTop ) || 0; curLeft = parseFloat( curCSSLeft ) || 0; } if ( jQuery.isFunction( options ) ) { options = options.call( elem, i, curOffset ); } if ( options.top != null ) { props.top = ( options.top - curOffset.top ) + curTop; } if ( options.left != null ) { props.left = ( options.left - curOffset.left ) + curLeft; } if ( "using" in options ) { options.using.call( elem, props ); } else { curElem.css( props ); } } }; jQuery.fn.extend({ position: function() { if ( !this[0] ) { return null; } var elem = this[0], // Get *real* offsetParent offsetParent = this.offsetParent(), // Get correct offsets offset = this.offset(), parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset(); // Subtract element margins // note: when an element has margin: auto the offsetLeft and marginLeft // are the same in Safari causing offset.left to incorrectly be 0 offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0; offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0; // Add offsetParent borders parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0; parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0; // Subtract the two offsets return { top: offset.top - parentOffset.top, left: offset.left - parentOffset.left }; }, offsetParent: function() { return this.map(function() { var offsetParent = this.offsetParent || document.body; while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) { offsetParent = offsetParent.offsetParent; } return offsetParent; }); } }); // Create scrollLeft and scrollTop methods jQuery.each( ["Left", "Top"], function( i, name ) { var method = "scroll" + name; jQuery.fn[ method ] = function( val ) { var elem, win; if ( val === undefined ) { elem = this[ 0 ]; if ( !elem ) { return null; } win = getWindow( elem ); // Return the scroll offset return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] : jQuery.support.boxModel && win.document.documentElement[ method ] || win.document.body[ method ] : elem[ method ]; } // Set the scroll offset return this.each(function() { win = getWindow( this ); if ( win ) { win.scrollTo( !i ? val : jQuery( win ).scrollLeft(), i ? val : jQuery( win ).scrollTop() ); } else { this[ method ] = val; } }); }; }); function getWindow( elem ) { return jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 ? elem.defaultView || elem.parentWindow : false; } // Create width, height, innerHeight, innerWidth, outerHeight and outerWidth methods jQuery.each([ "Height", "Width" ], function( i, name ) { var type = name.toLowerCase(); // innerHeight and innerWidth jQuery.fn[ "inner" + name ] = function() { var elem = this[0]; return elem ? elem.style ? parseFloat( jQuery.css( elem, type, "padding" ) ) : this[ type ]() : null; }; // outerHeight and outerWidth jQuery.fn[ "outer" + name ] = function( margin ) { var elem = this[0]; return elem ? elem.style ? parseFloat( jQuery.css( elem, type, margin ? "margin" : "border" ) ) : this[ type ]() : null; }; jQuery.fn[ type ] = function( size ) { // Get window width or height var elem = this[0]; if ( !elem ) { return size == null ? null : this; } if ( jQuery.isFunction( size ) ) { return this.each(function( i ) { var self = jQuery( this ); self[ type ]( size.call( this, i, self[ type ]() ) ); }); } if ( jQuery.isWindow( elem ) ) { // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat var docElemProp = elem.document.documentElement[ "client" + name ], body = elem.document.body; return elem.document.compatMode === "CSS1Compat" && docElemProp || body && body[ "client" + name ] || docElemProp; // Get document width or height } else if ( elem.nodeType === 9 ) { // Either scroll[Width/Height] or offset[Width/Height], whichever is greater return Math.max( elem.documentElement["client" + name], elem.body["scroll" + name], elem.documentElement["scroll" + name], elem.body["offset" + name], elem.documentElement["offset" + name] ); // Get or set width or height on the element } else if ( size === undefined ) { var orig = jQuery.css( elem, type ), ret = parseFloat( orig ); return jQuery.isNumeric( ret ) ? ret : orig; // Set the width or height on the element (default to pixels if value is unitless) } else { return this.css( type, typeof size === "string" ? size : size + "px" ); } }; }); // Expose jQuery to the global object window.jQuery = window.$ = jQuery; // Expose jQuery as an AMD module, but only for AMD loaders that // understand the issues with loading multiple versions of jQuery // in a page that all might call define(). The loader will indicate // they have special allowances for multiple jQuery versions by // specifying define.amd.jQuery = true. Register as a named module, // since jQuery can be concatenated with other files that may use define, // but not use a proper concatenation script that understands anonymous // AMD modules. A named AMD is safest and most robust way to register. // Lowercase jquery is used because AMD module names are derived from // file names, and jQuery is normally delivered in a lowercase file name. // Do this after creating the global so that if an AMD module wants to call // noConflict to hide this version of jQuery, it will work. if ( typeof define === "function" && define.amd && define.amd.jQuery ) { define( "jquery", [], function () { return jQuery; } ); } })( window ); //Blackberry 10 Configuration Settings //Settings here are optimized to provide Blackberry 10 behaviour and feel (function($) { $(document).bind("mobileinit", function(){ $.mobile.buttonMarkup.hoverDelay = 0; // Use the simultaneous transitions handler for slide transitions $.mobile.transitionHandlers.cover = $.mobile.transitionHandlers.simultaneous; // Set the slide transitions's fallback to "fade" $.mobile.transitionFallbacks.cover = "fade"; $.mobile.defaultPageTransition = "cover"; $.mobile.listview.prototype.options.icon = false; $.mobile.selectmenu.prototype.options.nativeMenu = false; $.mobile.fixedtoolbar.prototype.options.tapToggle = false; }); })(jQuery); (function ( root, doc, factory ) { if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. define( [ "jquery" ], function ( $ ) { factory( $, root, doc ); return $.mobile; }); } else { // Browser globals factory( root.jQuery, root, doc ); } }( this, document, function ( jQuery, window, document, undefined ) { (function( $ ) { $.mobile = {}; }( jQuery )); (function( $, window, undefined ) { var nsNormalizeDict = {}; // jQuery.mobile configurable options $.mobile = $.extend($.mobile, { // Version of the jQuery Mobile Framework version: "1.4.0pre", // Namespace used framework-wide for data-attrs. Default is no namespace ns: "", // Define the url parameter used for referencing widget-generated sub-pages. // Translates to to example.html&ui-page=subpageIdentifier // hash segment before &ui-page= is used to make Ajax request subPageUrlKey: "ui-page", // Class assigned to page currently in view, and during transitions activePageClass: "ui-page-active", // Class used for "active" button state, from CSS framework activeBtnClass: "ui-btn-active", // Class used for "focus" form element state, from CSS framework focusClass: "ui-focus", // Automatically handle clicks and form submissions through Ajax, when same-domain ajaxEnabled: true, // Automatically load and show pages based on location.hash hashListeningEnabled: true, // disable to prevent jquery from bothering with links linkBindingEnabled: true, // Set default page transition - 'none' for no transitions defaultPageTransition: "fade", // Set maximum window width for transitions to apply - 'false' for no limit maxTransitionWidth: false, // Minimum scroll distance that will be remembered when returning to a page minScrollBack: 250, // DEPRECATED: the following property is no longer in use, but defined until 2.0 to prevent conflicts touchOverflowEnabled: false, // Set default dialog transition - 'none' for no transitions defaultDialogTransition: "pop", // Error response message - appears when an Ajax page request fails pageLoadErrorMessage: "Error Loading Page", // For error messages, which theme does the box uses? pageLoadErrorMessageTheme: "e", // replace calls to window.history.back with phonegaps navigation helper // where it is provided on the window object phonegapNavigationEnabled: false, //automatically initialize the DOM when it's ready autoInitializePage: true, pushStateEnabled: true, // allows users to opt in to ignoring content by marking a parent element as // data-ignored ignoreContentEnabled: false, // turn of binding to the native orientationchange due to android orientation behavior orientationChangeEnabled: true, buttonMarkup: { hoverDelay: 200 }, // define the window and the document objects window: $( window ), document: $( document ), // TODO might be useful upstream in jquery itself ? keyCode: { ALT: 18, BACKSPACE: 8, CAPS_LOCK: 20, COMMA: 188, COMMAND: 91, COMMAND_LEFT: 91, // COMMAND COMMAND_RIGHT: 93, CONTROL: 17, DELETE: 46, DOWN: 40, END: 35, ENTER: 13, ESCAPE: 27, HOME: 36, INSERT: 45, LEFT: 37, MENU: 93, // COMMAND_RIGHT NUMPAD_ADD: 107, NUMPAD_DECIMAL: 110, NUMPAD_DIVIDE: 111, NUMPAD_ENTER: 108, NUMPAD_MULTIPLY: 106, NUMPAD_SUBTRACT: 109, PAGE_DOWN: 34, PAGE_UP: 33, PERIOD: 190, RIGHT: 39, SHIFT: 16, SPACE: 32, TAB: 9, UP: 38, WINDOWS: 91 // COMMAND }, // Place to store various widget extensions behaviors: {}, // Scroll page vertically: scroll to 0 to hide iOS address bar, or pass a Y value silentScroll: function( ypos ) { if ( $.type( ypos ) !== "number" ) { ypos = $.mobile.defaultHomeScroll; } // prevent scrollstart and scrollstop events $.event.special.scrollstart.enabled = false; setTimeout( function() { window.scrollTo( 0, ypos ); $.mobile.document.trigger( "silentscroll", { x: 0, y: ypos }); }, 20 ); setTimeout( function() { $.event.special.scrollstart.enabled = true; }, 150 ); }, // Expose our cache for testing purposes. nsNormalizeDict: nsNormalizeDict, // Take a data attribute property, prepend the namespace // and then camel case the attribute string. Add the result // to our nsNormalizeDict so we don't have to do this again. nsNormalize: function( prop ) { if ( !prop ) { return; } return nsNormalizeDict[ prop ] || ( nsNormalizeDict[ prop ] = $.camelCase( $.mobile.ns + prop ) ); }, // Find the closest parent with a theme class on it. Note that // we are not using $.fn.closest() on purpose here because this // method gets called quite a bit and we need it to be as fast // as possible. getInheritedTheme: function( el, defaultTheme ) { var e = el[ 0 ], ltr = "", re = /ui-(bar|body|overlay)-([a-z])\b/, c, m; while ( e ) { c = e.className || ""; if ( c && ( m = re.exec( c ) ) && ( ltr = m[ 2 ] ) ) { // We found a parent with a theme class // on it so bail from this loop. break; } e = e.parentNode; } // Return the theme letter we found, if none, return the // specified default. return ltr || defaultTheme || "a"; }, // TODO the following $ and $.fn extensions can/probably should be moved into jquery.mobile.core.helpers // // Find the closest javascript page element to gather settings data jsperf test // http://jsperf.com/single-complex-selector-vs-many-complex-selectors/edit // possibly naive, but it shows that the parsing overhead for *just* the page selector vs // the page and dialog selector is negligable. This could probably be speed up by // doing a similar parent node traversal to the one found in the inherited theme code above closestPageData: function( $target ) { return $target .closest( ':jqmData(role="page"), :jqmData(role="dialog")' ) .data( "mobile-page" ); }, enhanceable: function( $set ) { return this.haveParents( $set, "enhance" ); }, hijackable: function( $set ) { return this.haveParents( $set, "ajax" ); }, haveParents: function( $set, attr ) { if ( !$.mobile.ignoreContentEnabled ) { return $set; } var count = $set.length, $newSet = $(), e, $element, excluded; for ( var i = 0; i < count; i++ ) { $element = $set.eq( i ); excluded = false; e = $set[ i ]; while ( e ) { var c = e.getAttribute ? e.getAttribute( "data-" + $.mobile.ns + attr ) : ""; if ( c === "false" ) { excluded = true; break; } e = e.parentNode; } if ( !excluded ) { $newSet = $newSet.add( $element ); } } return $newSet; }, getScreenHeight: function() { // Native innerHeight returns more accurate value for this across platforms, // jQuery version is here as a normalized fallback for platforms like Symbian return window.innerHeight || $.mobile.window.height(); } }, $.mobile ); // Mobile version of data and removeData and hasData methods // ensures all data is set and retrieved using jQuery Mobile's data namespace $.fn.jqmData = function( prop, value ) { var result; if ( typeof prop !== "undefined" ) { if ( prop ) { prop = $.mobile.nsNormalize( prop ); } // undefined is permitted as an explicit input for the second param // in this case it returns the value and does not set it to undefined if( arguments.length < 2 || value === undefined ){ result = this.data( prop ); } else { result = this.data( prop, value ); } } return result; }; $.jqmData = function( elem, prop, value ) { var result; if ( typeof prop !== "undefined" ) { result = $.data( elem, prop ? $.mobile.nsNormalize( prop ) : prop, value ); } return result; }; $.fn.jqmRemoveData = function( prop ) { return this.removeData( $.mobile.nsNormalize( prop ) ); }; $.jqmRemoveData = function( elem, prop ) { return $.removeData( elem, $.mobile.nsNormalize( prop ) ); }; $.fn.removeWithDependents = function() { $.removeWithDependents( this ); }; $.removeWithDependents = function( elem ) { var $elem = $( elem ); ( $elem.jqmData( 'dependents' ) || $() ).remove(); $elem.remove(); }; $.fn.addDependents = function( newDependents ) { $.addDependents( $( this ), newDependents ); }; $.addDependents = function( elem, newDependents ) { var dependents = $( elem ).jqmData( 'dependents' ) || $(); $( elem ).jqmData( 'dependents', $.merge( dependents, newDependents ) ); }; // note that this helper doesn't attempt to handle the callback // or setting of an html element's text, its only purpose is // to return the html encoded version of the text in all cases. (thus the name) $.fn.getEncodedText = function() { return $( "<div/>" ).text( $( this ).text() ).html(); }; // fluent helper function for the mobile namespaced equivalent $.fn.jqmEnhanceable = function() { return $.mobile.enhanceable( this ); }; $.fn.jqmHijackable = function() { return $.mobile.hijackable( this ); }; // Monkey-patching Sizzle to filter the :jqmData selector var oldFind = $.find, jqmDataRE = /:jqmData\(([^)]*)\)/g; $.find = function( selector, context, ret, extra ) { selector = selector.replace( jqmDataRE, "[data-" + ( $.mobile.ns || "" ) + "$1]" ); return oldFind.call( this, selector, context, ret, extra ); }; $.extend( $.find, oldFind ); $.find.matches = function( expr, set ) { return $.find( expr, null, null, set ); }; $.find.matchesSelector = function( node, expr ) { return $.find( expr, null, null, [ node ] ).length > 0; }; })( jQuery, this ); /*! * jQuery UI Widget 1.10.1 * 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 }, 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 ); }; $.widget.extend = function( target ) { var input = slice.call( arguments, 1 ), inputIndex = 0, inputLength = input.length, key, value; for ( ; inputIndex < inputLength; inputIndex++ ) { for ( key in input[ inputIndex ] ) { value = input[ inputIndex ][ key ]; if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { // Clone objects if ( $.isPlainObject( value ) ) { target[ key ] = $.isPlainObject( target[ key ] ) ? $.widget.extend( {}, target[ key ], value ) : // Don't extend strings, arrays, etc. with objects $.widget.extend( {}, value ); // Copy everything else by reference } else { target[ key ] = value; } } } } return target; }; $.widget.bridge = function( name, object ) { var fullName = object.prototype.widgetFullName || name; $.fn[ name ] = function( options ) { var isMethodCall = typeof options === "string", args = slice.call( arguments, 1 ), returnValue = this; // allow multiple hashes to be passed on init options = !isMethodCall && args.length ? $.widget.extend.apply( null, [ options ].concat(args) ) : options; if ( isMethodCall ) { this.each(function() { var methodValue, instance = $.data( this, fullName ); if ( !instance ) { return $.error( "cannot call methods on " + name + " prior to initialization; " + "attempted to call method '" + options + "'" ); } if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { return $.error( "no such method '" + options + "' for " + name + " widget instance" ); } methodValue = instance[ options ].apply( instance, args ); if ( methodValue !== instance && methodValue !== undefined ) { returnValue = methodValue && methodValue.jquery ? returnValue.pushStack( methodValue.get() ) : methodValue; return false; } }); } else { this.each(function() { var instance = $.data( this, fullName ); if ( instance ) { instance.option( options || {} )._init(); } else { $.data( this, fullName, new object( options, this ) ); } }); } return returnValue; }; }; $.Widget = function( /* options, element */ ) {}; $.Widget._childConstructors = []; $.Widget.prototype = { widgetName: "widget", widgetEventPrefix: "", defaultElement: "<div>", options: { disabled: false, // callbacks create: null }, _createWidget: function( options, element ) { element = $( element || this.defaultElement || this )[ 0 ]; this.element = $( element ); this.uuid = uuid++; this.eventNamespace = "." + this.widgetName + this.uuid; this.options = $.widget.extend( {}, this.options, this._getCreateOptions(), options ); this.bindings = $(); this.hoverable = $(); this.focusable = $(); if ( element !== this ) { $.data( element, this.widgetFullName, this ); this._on( true, this.element, { remove: function( event ) { if ( event.target === element ) { this.destroy(); } } }); this.document = $( element.style ? // element within the document element.ownerDocument : // element is window or document element.document || element ); this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); } this._create(); this._trigger( "create", null, this._getCreateEventData() ); this._init(); }, _getCreateOptions: $.noop, _getCreateEventData: $.noop, _create: $.noop, _init: $.noop, destroy: function() { this._destroy(); // we can probably remove the unbind calls in 2.0 // all event bindings should go through this._on() this.element .unbind( this.eventNamespace ) // 1.9 BC for #7810 // TODO remove dual storage .removeData( this.widgetName ) .removeData( this.widgetFullName ) // support: jquery <1.6.3 // http://bugs.jquery.com/ticket/9413 .removeData( $.camelCase( this.widgetFullName ) ); this.widget() .unbind( this.eventNamespace ) .removeAttr( "aria-disabled" ) .removeClass( this.widgetFullName + "-disabled " + "ui-state-disabled" ); // clean up events and states this.bindings.unbind( this.eventNamespace ); this.hoverable.removeClass( "ui-state-hover" ); this.focusable.removeClass( "ui-state-focus" ); }, _destroy: $.noop, widget: function() { return this.element; }, option: function( key, value ) { var options = key, parts, curOption, i; if ( arguments.length === 0 ) { // don't return a reference to the internal hash return $.widget.extend( {}, this.options ); } if ( typeof key === "string" ) { // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } options = {}; parts = key.split( "." ); key = parts.shift(); if ( parts.length ) { curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); for ( i = 0; i < parts.length - 1; i++ ) { curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; curOption = curOption[ parts[ i ] ]; } key = parts.pop(); if ( value === undefined ) { return curOption[ key ] === undefined ? null : curOption[ key ]; } curOption[ key ] = value; } else { if ( value === undefined ) { return this.options[ key ] === undefined ? null : this.options[ key ]; } options[ key ] = value; } } this._setOptions( options ); return this; }, _setOptions: function( options ) { var key; for ( key in options ) { this._setOption( key, options[ key ] ); } return this; }, _setOption: function( key, value ) { this.options[ key ] = value; if ( key === "disabled" ) { this.widget() .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value ) .attr( "aria-disabled", value ); this.hoverable.removeClass( "ui-state-hover" ); this.focusable.removeClass( "ui-state-focus" ); } return this; }, enable: function() { return this._setOption( "disabled", false ); }, disable: function() { return this._setOption( "disabled", true ); }, _on: function( suppressDisabledCheck, element, handlers ) { var delegateElement, instance = this; // no suppressDisabledCheck flag, shuffle arguments if ( typeof suppressDisabledCheck !== "boolean" ) { handlers = element; element = suppressDisabledCheck; suppressDisabledCheck = false; } // no element argument, shuffle and use this.element if ( !handlers ) { handlers = element; element = this.element; delegateElement = this.widget(); } else { // accept selectors, DOM elements element = delegateElement = $( element ); this.bindings = this.bindings.add( element ); } $.each( handlers, function( event, handler ) { function handlerProxy() { // allow widgets to customize the disabled handling // - disabled as an array instead of boolean // - disabled class as method for disabling individual parts if ( !suppressDisabledCheck && ( instance.options.disabled === true || $( this ).hasClass( "ui-state-disabled" ) ) ) { return; } return ( typeof handler === "string" ? instance[ handler ] : handler ) .apply( instance, arguments ); } // copy the guid so direct unbinding works if ( typeof handler !== "string" ) { handlerProxy.guid = handler.guid = handler.guid || handlerProxy.guid || $.guid++; } var match = event.match( /^(\w+)\s*(.*)$/ ), eventName = match[1] + instance.eventNamespace, selector = match[2]; if ( selector ) { delegateElement.delegate( selector, eventName, handlerProxy ); } else { element.bind( eventName, handlerProxy ); } }); }, _off: function( element, eventName ) { eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; element.unbind( eventName ).undelegate( eventName ); }, _delay: function( handler, delay ) { function handlerProxy() { return ( typeof handler === "string" ? instance[ handler ] : handler ) .apply( instance, arguments ); } var instance = this; return setTimeout( handlerProxy, delay || 0 ); }, _hoverable: function( element ) { this.hoverable = this.hoverable.add( element ); this._on( element, { mouseenter: function( event ) { $( event.currentTarget ).addClass( "ui-state-hover" ); }, mouseleave: function( event ) { $( event.currentTarget ).removeClass( "ui-state-hover" ); } }); }, _focusable: function( element ) { this.focusable = this.focusable.add( element ); this._on( element, { focusin: function( event ) { $( event.currentTarget ).addClass( "ui-state-focus" ); }, focusout: function( event ) { $( event.currentTarget ).removeClass( "ui-state-focus" ); } }); }, _trigger: function( type, event, data ) { var prop, orig, callback = this.options[ type ]; data = data || {}; event = $.Event( event ); event.type = ( type === this.widgetEventPrefix ? type : this.widgetEventPrefix + type ).toLowerCase(); // the original event may come from any element // so we need to reset the target on the new event event.target = this.element[ 0 ]; // copy original event properties over to the new event orig = event.originalEvent; if ( orig ) { for ( prop in orig ) { if ( !( prop in event ) ) { event[ prop ] = orig[ prop ]; } } } this.element.trigger( event, data ); return !( $.isFunction( callback ) && callback.apply( this.element[0], [ event ].concat( data ) ) === false || event.isDefaultPrevented() ); } }; $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { if ( typeof options === "string" ) { options = { effect: options }; } var hasOptions, effectName = !options ? method : options === true || typeof options === "number" ? defaultEffect : options.effect || defaultEffect; options = options || {}; if ( typeof options === "number" ) { options = { duration: options }; } hasOptions = !$.isEmptyObject( options ); options.complete = callback; if ( options.delay ) { element.delay( options.delay ); } if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { element[ method ]( options ); } else if ( effectName !== method && element[ effectName ] ) { element[ effectName ]( options.duration, options.easing, callback ); } else { element.queue(function( next ) { $( this )[ method ](); if ( callback ) { callback.call( element[ 0 ] ); } next(); }); } }; }); })( jQuery ); (function( $, undefined ) { $.widget( "mobile.widget", { // decorate the parent _createWidget to trigger `widgetinit` for users // who wish to do post post `widgetcreate` alterations/additions // // TODO create a pull request for jquery ui to trigger this event // in the original _createWidget _createWidget: function() { $.Widget.prototype._createWidget.apply( this, arguments ); this._trigger( 'init' ); }, _getCreateOptions: function() { var elem = this.element, options = {}; $.each( this.options, function( option ) { var value = elem.jqmData( option.replace( /[A-Z]/g, function( c ) { return "-" + c.toLowerCase(); }) ); if ( value !== undefined ) { options[ option ] = value; } }); return options; }, enhanceWithin: function( target, useKeepNative ) { this.enhance( $( this.options.initSelector, $( target )), useKeepNative ); }, enhance: function( targets, useKeepNative ) { var page, keepNative, $widgetElements = $( targets ), self = this; // if ignoreContentEnabled is set to true the framework should // only enhance the selected elements when they do NOT have a // parent with the data-namespace-ignore attribute $widgetElements = $.mobile.enhanceable( $widgetElements ); if ( useKeepNative && $widgetElements.length ) { // TODO remove dependency on the page widget for the keepNative. // Currently the keepNative value is defined on the page prototype so // the method is as well page = $.mobile.closestPageData( $widgetElements ); keepNative = ( page && page.keepNativeSelector()) || ""; $widgetElements = $widgetElements.not( keepNative ); } $widgetElements[ this.widgetName ](); }, raise: function( msg ) { throw "Widget [" + this.widgetName + "]: " + msg; } }); })( jQuery ); (function( $, window ) { // DEPRECATED // NOTE global mobile object settings $.extend( $.mobile, { // DEPRECATED Should the text be visble in the loading message? loadingMessageTextVisible: undefined, // DEPRECATED When the text is visible, what theme does the loading box use? loadingMessageTheme: undefined, // DEPRECATED default message setting loadingMessage: undefined, // DEPRECATED // Turn on/off page loading message. Theme doubles as an object argument // with the following shape: { theme: '', text: '', html: '', textVisible: '' } // NOTE that the $.mobile.loading* settings and params past the first are deprecated showPageLoadingMsg: function( theme, msgText, textonly ) { $.mobile.loading( 'show', theme, msgText, textonly ); }, // DEPRECATED hidePageLoadingMsg: function() { $.mobile.loading( 'hide' ); }, loading: function() { this.loaderWidget.loader.apply( this.loaderWidget, arguments ); } }); // TODO move loader class down into the widget settings var loaderClass = "ui-loader", $html = $( "html" ), $window = $.mobile.window; $.widget( "mobile.loader", { // NOTE if the global config settings are defined they will override these // options options: { // the theme for the loading message theme: "a", // whether the text in the loading message is shown textVisible: false, // custom html for the inner content of the loading message html: "", // the text to be displayed when the popup is shown text: "loading" }, defaultHtml: "<div class='" + loaderClass + "'>" + "<span class='ui-icon ui-icon-loading'></span>" + "<h1></h1>" + "</div>", // For non-fixed supportin browsers. Position at y center (if scrollTop supported), above the activeBtn (if defined), or just 100px from top fakeFixLoader: function() { var activeBtn = $( "." + $.mobile.activeBtnClass ).first(); this.element .css({ top: $.support.scrollTop && $window.scrollTop() + $window.height() / 2 || activeBtn.length && activeBtn.offset().top || 100 }); }, // check position of loader to see if it appears to be "fixed" to center // if not, use abs positioning checkLoaderPosition: function() { var offset = this.element.offset(), scrollTop = $window.scrollTop(), screenHeight = $.mobile.getScreenHeight(); if ( offset.top < scrollTop || ( offset.top - scrollTop ) > screenHeight ) { this.element.addClass( "ui-loader-fakefix" ); this.fakeFixLoader(); $window .unbind( "scroll", this.checkLoaderPosition ) .bind( "scroll", $.proxy( this.fakeFixLoader, this ) ); } }, resetHtml: function() { this.element.html( $( this.defaultHtml ).html() ); }, // Turn on/off page loading message. Theme doubles as an object argument // with the following shape: { theme: '', text: '', html: '', textVisible: '' } // NOTE that the $.mobile.loading* settings and params past the first are deprecated // TODO sweet jesus we need to break some of this out show: function( theme, msgText, textonly ) { var textVisible, message, $header, loadSettings; this.resetHtml(); // use the prototype options so that people can set them globally at // mobile init. Consistency, it's what's for dinner if ( $.type(theme) === "object" ) { loadSettings = $.extend( {}, this.options, theme ); // prefer object property from the param then the old theme setting theme = loadSettings.theme || $.mobile.loadingMessageTheme; } else { loadSettings = this.options; // here we prefer the them value passed as a string argument, then // we prefer the global option because we can't use undefined default // prototype options, then the prototype option theme = theme || $.mobile.loadingMessageTheme || loadSettings.theme; } // set the message text, prefer the param, then the settings object // then loading message message = msgText || $.mobile.loadingMessage || loadSettings.text; // prepare the dom $html.addClass( "ui-loading" ); if ( $.mobile.loadingMessage !== false || loadSettings.html ) { // boolean values require a bit more work :P, supports object properties // and old settings if ( $.mobile.loadingMessageTextVisible !== undefined ) { textVisible = $.mobile.loadingMessageTextVisible; } else { textVisible = loadSettings.textVisible; } // add the proper css given the options (theme, text, etc) // Force text visibility if the second argument was supplied, or // if the text was explicitly set in the object args this.element.attr("class", loaderClass + " ui-corner-all ui-body-" + theme + " ui-loader-" + ( textVisible || msgText || theme.text ? "verbose" : "default" ) + ( loadSettings.textonly || textonly ? " ui-loader-textonly" : "" ) ); // TODO verify that jquery.fn.html is ok to use in both cases here // this might be overly defensive in preventing unknowing xss // if the html attribute is defined on the loading settings, use that // otherwise use the fallbacks from above if ( loadSettings.html ) { this.element.html( loadSettings.html ); } else { this.element.find( "h1" ).text( message ); } // attach the loader to the DOM this.element.appendTo( $.mobile.pageContainer ); // check that the loader is visible this.checkLoaderPosition(); // on scroll check the loader position $window.bind( "scroll", $.proxy( this.checkLoaderPosition, this ) ); } }, hide: function() { $html.removeClass( "ui-loading" ); if ( $.mobile.loadingMessage ) { this.element.removeClass( "ui-loader-fakefix" ); } $.mobile.window.unbind( "scroll", this.fakeFixLoader ); $.mobile.window.unbind( "scroll", this.checkLoaderPosition ); } }); $window.bind( 'pagecontainercreate', function() { $.mobile.loaderWidget = $.mobile.loaderWidget || $( $.mobile.loader.prototype.defaultHtml ).loader(); }); })(jQuery, this); // Script: jQuery hashchange event // // *Version: 1.3, Last updated: 7/21/2010* // // Project Home - http://benalman.com/projects/jquery-hashchange-plugin/ // GitHub - http://github.com/cowboy/jquery-hashchange/ // Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js // (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (0.8kb gzipped) // // About: License // // Copyright (c) 2010 "Cowboy" Ben Alman, // Dual licensed under the MIT and GPL licenses. // http://benalman.com/about/license/ // // About: Examples // // These working examples, complete with fully commented code, illustrate a few // ways in which this plugin can be used. // // hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/ // document.domain - http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/ // // About: Support and Testing // // Information about what version or versions of jQuery this plugin has been // tested with, what browsers it has been tested in, and where the unit tests // reside (so you can test it yourself). // // jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2 // Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5, // Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5. // Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/ // // About: Known issues // // While this jQuery hashchange event implementation is quite stable and // robust, there are a few unfortunate browser bugs surrounding expected // hashchange event-based behaviors, independent of any JavaScript // window.onhashchange abstraction. See the following examples for more // information: // // Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/ // Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/ // WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/ // Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/ // // Also note that should a browser natively support the window.onhashchange // event, but not report that it does, the fallback polling loop will be used. // // About: Release History // // 1.3 - (7/21/2010) Reorganized IE6/7 Iframe code to make it more // "removable" for mobile-only development. Added IE6/7 document.title // support. Attempted to make Iframe as hidden as possible by using // techniques from http://www.paciellogroup.com/blog/?p=604. Added // support for the "shortcut" format $(window).hashchange( fn ) and // $(window).hashchange() like jQuery provides for built-in events. // Renamed jQuery.hashchangeDelay to <jQuery.fn.hashchange.delay> and // lowered its default value to 50. Added <jQuery.fn.hashchange.domain> // and <jQuery.fn.hashchange.src> properties plus document-domain.html // file to address access denied issues when setting document.domain in // IE6/7. // 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin // from a page on another domain would cause an error in Safari 4. Also, // IE6/7 Iframe is now inserted after the body (this actually works), // which prevents the page from scrolling when the event is first bound. // Event can also now be bound before DOM ready, but it won't be usable // before then in IE6/7. // 1.1 - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug // where browser version is incorrectly reported as 8.0, despite // inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag. // 1.0 - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special // window.onhashchange functionality into a separate plugin for users // who want just the basic event & back button support, without all the // extra awesomeness that BBQ provides. This plugin will be included as // part of jQuery BBQ, but also be available separately. (function( $, window, undefined ) { // Reused string. var str_hashchange = 'hashchange', // Method / object references. doc = document, fake_onhashchange, special = $.event.special, // Does the browser support window.onhashchange? Note that IE8 running in // IE7 compatibility mode reports true for 'onhashchange' in window, even // though the event isn't supported, so also test document.documentMode. doc_mode = doc.documentMode, supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 ); // Get location.hash (or what you'd expect location.hash to be) sans any // leading #. Thanks for making this necessary, Firefox! function get_fragment( url ) { url = url || location.href; return '#' + url.replace( /^[^#]*#?(.*)$/, '$1' ); }; // Method: jQuery.fn.hashchange // // Bind a handler to the window.onhashchange event or trigger all bound // window.onhashchange event handlers. This behavior is consistent with // jQuery's built-in event handlers. // // Usage: // // > jQuery(window).hashchange( [ handler ] ); // // Arguments: // // handler - (Function) Optional handler to be bound to the hashchange // event. This is a "shortcut" for the more verbose form: // jQuery(window).bind( 'hashchange', handler ). If handler is omitted, // all bound window.onhashchange event handlers will be triggered. This // is a shortcut for the more verbose // jQuery(window).trigger( 'hashchange' ). These forms are described in // the <hashchange event> section. // // Returns: // // (jQuery) The initial jQuery collection of elements. // Allow the "shortcut" format $(elem).hashchange( fn ) for binding and // $(elem).hashchange() for triggering, like jQuery does for built-in events. $.fn[ str_hashchange ] = function( fn ) { return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange ); }; // Property: jQuery.fn.hashchange.delay // // The numeric interval (in milliseconds) at which the <hashchange event> // polling loop executes. Defaults to 50. // Property: jQuery.fn.hashchange.domain // // If you're setting document.domain in your JavaScript, and you want hash // history to work in IE6/7, not only must this property be set, but you must // also set document.domain BEFORE jQuery is loaded into the page. This // property is only applicable if you are supporting IE6/7 (or IE8 operating // in "IE7 compatibility" mode). // // In addition, the <jQuery.fn.hashchange.src> property must be set to the // path of the included "document-domain.html" file, which can be renamed or // modified if necessary (note that the document.domain specified must be the // same in both your main JavaScript as well as in this file). // // Usage: // // jQuery.fn.hashchange.domain = document.domain; // Property: jQuery.fn.hashchange.src // // If, for some reason, you need to specify an Iframe src file (for example, // when setting document.domain as in <jQuery.fn.hashchange.domain>), you can // do so using this property. Note that when using this property, history // won't be recorded in IE6/7 until the Iframe src file loads. This property // is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7 // compatibility" mode). // // Usage: // // jQuery.fn.hashchange.src = 'path/to/file.html'; $.fn[ str_hashchange ].delay = 50; /* $.fn[ str_hashchange ].domain = null; $.fn[ str_hashchange ].src = null; */ // Event: hashchange event // // Fired when location.hash changes. In browsers that support it, the native // HTML5 window.onhashchange event is used, otherwise a polling loop is // initialized, running every <jQuery.fn.hashchange.delay> milliseconds to // see if the hash has changed. In IE6/7 (and IE8 operating in "IE7 // compatibility" mode), a hidden Iframe is created to allow the back button // and hash-based history to work. // // Usage as described in <jQuery.fn.hashchange>: // // > // Bind an event handler. // > jQuery(window).hashchange( function(e) { // > var hash = location.hash; // > ... // > }); // > // > // Manually trigger the event handler. // > jQuery(window).hashchange(); // // A more verbose usage that allows for event namespacing: // // > // Bind an event handler. // > jQuery(window).bind( 'hashchange', function(e) { // > var hash = location.hash; // > ... // > }); // > // > // Manually trigger the event handler. // > jQuery(window).trigger( 'hashchange' ); // // Additional Notes: // // * The polling loop and Iframe are not created until at least one handler // is actually bound to the 'hashchange' event. // * If you need the bound handler(s) to execute immediately, in cases where // a location.hash exists on page load, via bookmark or page refresh for // example, use jQuery(window).hashchange() or the more verbose // jQuery(window).trigger( 'hashchange' ). // * The event can be bound before DOM ready, but since it won't be usable // before then in IE6/7 (due to the necessary Iframe), recommended usage is // to bind it inside a DOM ready handler. // Override existing $.event.special.hashchange methods (allowing this plugin // to be defined after jQuery BBQ in BBQ's source code). special[ str_hashchange ] = $.extend( special[ str_hashchange ], { // Called only when the first 'hashchange' event is bound to window. setup: function() { // If window.onhashchange is supported natively, there's nothing to do.. if ( supports_onhashchange ) { return false; } // Otherwise, we need to create our own. And we don't want to call this // until the user binds to the event, just in case they never do, since it // will create a polling loop and possibly even a hidden Iframe. $( fake_onhashchange.start ); }, // Called only when the last 'hashchange' event is unbound from window. teardown: function() { // If window.onhashchange is supported natively, there's nothing to do.. if ( supports_onhashchange ) { return false; } // Otherwise, we need to stop ours (if possible). $( fake_onhashchange.stop ); } }); // fake_onhashchange does all the work of triggering the window.onhashchange // event for browsers that don't natively support it, including creating a // polling loop to watch for hash changes and in IE 6/7 creating a hidden // Iframe to enable back and forward. fake_onhashchange = (function() { var self = {}, timeout_id, // Remember the initial hash so it doesn't get triggered immediately. last_hash = get_fragment(), fn_retval = function( val ) { return val; }, history_set = fn_retval, history_get = fn_retval; // Start the polling loop. self.start = function() { timeout_id || poll(); }; // Stop the polling loop. self.stop = function() { timeout_id && clearTimeout( timeout_id ); timeout_id = undefined; }; // This polling loop checks every $.fn.hashchange.delay milliseconds to see // if location.hash has changed, and triggers the 'hashchange' event on // window when necessary. function poll() { var hash = get_fragment(), history_hash = history_get( last_hash ); if ( hash !== last_hash ) { history_set( last_hash = hash, history_hash ); $(window).trigger( str_hashchange ); } else if ( history_hash !== last_hash ) { location.href = location.href.replace( /#.*/, '' ) + history_hash; } timeout_id = setTimeout( poll, $.fn[ str_hashchange ].delay ); }; // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv // vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv window.attachEvent && !window.addEventListener && !supports_onhashchange && (function() { // Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8 // when running in "IE7 compatibility" mode. var iframe, iframe_src; // When the event is bound and polling starts in IE 6/7, create a hidden // Iframe for history handling. self.start = function() { if ( !iframe ) { iframe_src = $.fn[ str_hashchange ].src; iframe_src = iframe_src && iframe_src + get_fragment(); // Create hidden Iframe. Attempt to make Iframe as hidden as possible // by using techniques from http://www.paciellogroup.com/blog/?p=604. iframe = $('<iframe tabindex="-1" title="empty"/>').hide() // When Iframe has completely loaded, initialize the history and // start polling. .one( 'load', function() { iframe_src || history_set( get_fragment() ); poll(); }) // Load Iframe src if specified, otherwise nothing. .attr( 'src', iframe_src || 'javascript:0' ) // Append Iframe after the end of the body to prevent unnecessary // initial page scrolling (yes, this works). .insertAfter( 'body' )[0].contentWindow; // Whenever `document.title` changes, update the Iframe's title to // prettify the back/next history menu entries. Since IE sometimes // errors with "Unspecified error" the very first time this is set // (yes, very useful) wrap this with a try/catch block. doc.onpropertychange = function() { try { if ( event.propertyName === 'title' ) { iframe.document.title = doc.title; } } catch(e) {} }; } }; // Override the "stop" method since an IE6/7 Iframe was created. Even // if there are no longer any bound event handlers, the polling loop // is still necessary for back/next to work at all! self.stop = fn_retval; // Get history by looking at the hidden Iframe's location.hash. history_get = function() { return get_fragment( iframe.location.href ); }; // Set a new history item by opening and then closing the Iframe // document, *then* setting its location.hash. If document.domain has // been set, update that as well. history_set = function( hash, history_hash ) { var iframe_doc = iframe.document, domain = $.fn[ str_hashchange ].domain; if ( hash !== history_hash ) { // Update Iframe with any initial `document.title` that might be set. iframe_doc.title = doc.title; // Opening the Iframe's document after it has been closed is what // actually adds a history entry. iframe_doc.open(); // Set document.domain for the Iframe document as well, if necessary. domain && iframe_doc.write( '<script>document.domain="' + domain + '"</script>' ); iframe_doc.close(); // Update the Iframe's hash, for great justice. iframe.location.hash = hash; } }; })(); // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // ^^^^^^^^^^^^^^^^^^^ REMOVE IF NOT SUPPORTING IE6/7/8 ^^^^^^^^^^^^^^^^^^^ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ return self; })(); })(jQuery,this); (function( $, undefined ) { /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ window.matchMedia = window.matchMedia || (function( doc, undefined ) { var bool, docElem = doc.documentElement, refNode = docElem.firstElementChild || docElem.firstChild, // fakeBody required for <FF4 when executed in <head> fakeBody = doc.createElement( "body" ), div = doc.createElement( "div" ); div.id = "mq-test-1"; div.style.cssText = "position:absolute;top:-100em"; fakeBody.style.background = "none"; fakeBody.appendChild(div); return function(q){ div.innerHTML = "­<style media=\"" + q + "\"> #mq-test-1 { width: 42px; }</style>"; docElem.insertBefore( fakeBody, refNode ); bool = div.offsetWidth === 42; docElem.removeChild( fakeBody ); return { matches: bool, media: q }; }; }( document )); // $.mobile.media uses matchMedia to return a boolean. $.mobile.media = function( q ) { return window.matchMedia( q ).matches; }; })(jQuery); (function( $, undefined ) { var support = { touch: "ontouchend" in document }; $.mobile.support = $.mobile.support || {}; $.extend( $.support, support ); $.extend( $.mobile.support, support ); }( jQuery )); (function( $, undefined ) { $.extend( $.support, { orientation: "orientation" in window && "onorientationchange" in window }); }( jQuery )); (function( $, undefined ) { // thx Modernizr function propExists( prop ) { var uc_prop = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ), props = ( prop + " " + vendors.join( uc_prop + " " ) + uc_prop ).split( " " ); for ( var v in props ) { if ( fbCSS[ props[ v ] ] !== undefined ) { return true; } } } var fakeBody = $( "<body>" ).prependTo( "html" ), fbCSS = fakeBody[ 0 ].style, vendors = [ "Webkit", "Moz", "O" ], webos = "palmGetResource" in window, //only used to rule out scrollTop opera = window.opera, operamini = window.operamini && ({}).toString.call( window.operamini ) === "[object OperaMini]", bb = window.blackberry && !propExists( "-webkit-transform" ); //only used to rule out box shadow, as it's filled opaque on BB 5 and lower function validStyle( prop, value, check_vend ) { var div = document.createElement( 'div' ), uc = function( txt ) { return txt.charAt( 0 ).toUpperCase() + txt.substr( 1 ); }, vend_pref = function( vend ) { if( vend === "" ) { return ""; } else { return "-" + vend.charAt( 0 ).toLowerCase() + vend.substr( 1 ) + "-"; } }, check_style = function( vend ) { var vend_prop = vend_pref( vend ) + prop + ": " + value + ";", uc_vend = uc( vend ), propStyle = uc_vend + ( uc_vend === "" ? prop : uc( prop ) ); div.setAttribute( "style", vend_prop ); if ( !!div.style[ propStyle ] ) { ret = true; } }, check_vends = check_vend ? check_vend : vendors, ret; for( var i = 0; i < check_vends.length; i++ ) { check_style( check_vends[i] ); } return !!ret; } function transform3dTest() { var mqProp = "transform-3d", // Because the `translate3d` test below throws false positives in Android: ret = $.mobile.media( "(-" + vendors.join( "-" + mqProp + "),(-" ) + "-" + mqProp + "),(" + mqProp + ")" ); if( ret ) { return !!ret; } var el = document.createElement( "div" ), transforms = { // We’re omitting Opera for the time being; MS uses unprefixed. 'MozTransform':'-moz-transform', 'transform':'transform' }; fakeBody.append( el ); for ( var t in transforms ) { if( el.style[ t ] !== undefined ){ el.style[ t ] = 'translate3d( 100px, 1px, 1px )'; ret = window.getComputedStyle( el ).getPropertyValue( transforms[ t ] ); } } return ( !!ret && ret !== "none" ); } // Test for dynamic-updating base tag support ( allows us to avoid href,src attr rewriting ) function baseTagTest() { var fauxBase = location.protocol + "//" + location.host + location.pathname + "ui-dir/", base = $( "head base" ), fauxEle = null, href = "", link, rebase; if ( !base.length ) { base = fauxEle = $( "<base>", { "href": fauxBase }).appendTo( "head" ); } else { href = base.attr( "href" ); } link = $( "<a href='testurl' />" ).prependTo( fakeBody ); rebase = link[ 0 ].href; base[ 0 ].href = href || location.pathname; if ( fauxEle ) { fauxEle.remove(); } return rebase.indexOf( fauxBase ) === 0; } // Thanks Modernizr function cssPointerEventsTest() { var element = document.createElement( 'x' ), documentElement = document.documentElement, getComputedStyle = window.getComputedStyle, supports; if ( !( 'pointerEvents' in element.style ) ) { return false; } element.style.pointerEvents = 'auto'; element.style.pointerEvents = 'x'; documentElement.appendChild( element ); supports = getComputedStyle && getComputedStyle( element, '' ).pointerEvents === 'auto'; documentElement.removeChild( element ); return !!supports; } function boundingRect() { var div = document.createElement( "div" ); return typeof div.getBoundingClientRect !== "undefined"; } // non-UA-based IE version check by James Padolsey, modified by jdalton - from http://gist.github.com/527683 // allows for inclusion of IE 6+, including Windows Mobile 7 $.extend( $.mobile, { browser: {} } ); $.mobile.browser.oldIE = (function() { var v = 3, div = document.createElement( "div" ), a = div.all || []; do { div.innerHTML = "<!--[if gt IE " + ( ++v ) + "]><br><![endif]-->"; } while( a[0] ); return v > 4 ? v : !v; })(); function fixedPosition() { var w = window, ua = navigator.userAgent, platform = navigator.platform, // Rendering engine is Webkit, and capture major version wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ ), wkversion = !!wkmatch && wkmatch[ 1 ], ffmatch = ua.match( /Fennec\/([0-9]+)/ ), ffversion = !!ffmatch && ffmatch[ 1 ], operammobilematch = ua.match( /Opera Mobi\/([0-9]+)/ ), omversion = !!operammobilematch && operammobilematch[ 1 ]; if( // iOS 4.3 and older : Platform is iPhone/Pad/Touch and Webkit version is less than 534 (ios5) ( ( platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1 || platform.indexOf( "iPod" ) > -1 ) && wkversion && wkversion < 534 ) || // Opera Mini ( w.operamini && ({}).toString.call( w.operamini ) === "[object OperaMini]" ) || ( operammobilematch && omversion < 7458 ) || //Android lte 2.1: Platform is Android and Webkit version is less than 533 (Android 2.2) ( ua.indexOf( "Android" ) > -1 && wkversion && wkversion < 533 ) || // Firefox Mobile before 6.0 - ( ffversion && ffversion < 6 ) || // WebOS less than 3 ( "palmGetResource" in window && wkversion && wkversion < 534 ) || // MeeGo ( ua.indexOf( "MeeGo" ) > -1 && ua.indexOf( "NokiaBrowser/8.5.0" ) > -1 ) ) { return false; } return true; } $.extend( $.support, { cssTransitions: "WebKitTransitionEvent" in window || validStyle( 'transition', 'height 100ms linear', [ "Webkit", "Moz", "" ] ) && !$.mobile.browser.oldIE && !opera, // Note, Chrome for iOS has an extremely quirky implementation of popstate. // We've chosen to take the shortest path to a bug fix here for issue #5426 // See the following link for information about the regex chosen // https://developers.google.com/chrome/mobile/docs/user-agent#chrome_for_ios_user-agent pushState: "pushState" in history && "replaceState" in history && // 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, fixedPosition: fixedPosition(), scrollTop: ("pageXOffset" in window || "scrollTop" in document.documentElement || "scrollTop" in fakeBody[ 0 ]) && !webos && !operamini, dynamicBaseTag: baseTagTest(), cssPointerEvents: cssPointerEventsTest(), boundingRect: boundingRect() }); fakeBody.remove(); // $.mobile.ajaxBlacklist is used to override ajaxEnabled on platforms that have known conflicts with hash history updates (BB5, Symbian) // or that generally work better browsing in regular http for full page refreshes (Opera Mini) // Note: This detection below is used as a last resort. // We recommend only using these detection methods when all other more reliable/forward-looking approaches are not possible var nokiaLTE7_3 = (function() { var ua = window.navigator.userAgent; //The following is an attempt to match Nokia browsers that are running Symbian/s60, with webkit, version 7.3 or older return ua.indexOf( "Nokia" ) > -1 && ( ua.indexOf( "Symbian/3" ) > -1 || ua.indexOf( "Series60/5" ) > -1 ) && ua.indexOf( "AppleWebKit" ) > -1 && ua.match( /(BrowserNG|NokiaBrowser)\/7\.[0-3]/ ); })(); // Support conditions that must be met in order to proceed // default enhanced qualifications are media query support OR IE 7+ $.mobile.gradeA = function() { return ( $.support.mediaquery || $.mobile.browser.oldIE && $.mobile.browser.oldIE >= 7 ) && ( $.support.boundingRect || $.fn.jquery.match(/1\.[0-7+]\.[0-9+]?/) !== null ); }; $.mobile.ajaxBlacklist = // BlackBerry browsers, pre-webkit window.blackberry && !window.WebKitPoint || // Opera Mini operamini || // Symbian webkits pre 7.3 nokiaLTE7_3; // Lastly, this workaround is the only way we've found so far to get pre 7.3 Symbian webkit devices // to render the stylesheets when they're referenced before this script, as we'd recommend doing. // This simply reappends the CSS in place, which for some reason makes it apply if ( nokiaLTE7_3 ) { $(function() { $( "head link[rel='stylesheet']" ).attr( "rel", "alternate stylesheet" ).attr( "rel", "stylesheet" ); }); } // For ruling out shadows via css if ( !$.support.boxShadow ) { $( "html" ).addClass( "ui-mobile-nosupport-boxshadow" ); } })( jQuery ); (function( $, undefined ) { var $win = $.mobile.window, self, history, 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 || {}, href = location.href; beforeNavigate.originalEvent = event; $win.trigger( beforeNavigate ); if( beforeNavigate.isDefaultPrevented() ){ return; } if( event.historyState ){ $.extend(state, event.historyState); } // Make sure the original event is tracked for the end // user to inspect incase they want to do something special newEvent.originalEvent = event; // NOTE we let the current stack unwind because any assignment to // location.hash will stop the world and run this event handler. By // doing this we create a similar behavior to hashchange on hash // assignment setTimeout(function() { $win.trigger( newEvent, { state: state }); }, 0); }, hashchange: function( event, data ) { var newEvent = new $.Event( "navigate" ), beforeNavigate = new $.Event( "beforenavigate" ); beforeNavigate.originalEvent = event; $win.trigger( beforeNavigate ); if( beforeNavigate.isDefaultPrevented() ){ return; } // Make sure the original event is tracked for the end // user to inspect incase they want to do something special newEvent.originalEvent = event; // Trigger the hashchange with state provided by the user // that altered the hash $win.trigger( newEvent, { // Users that want to fully normalize the two events // will need to do history management down the stack and // add the state to the event before this binding is fired // TODO consider allowing for the explicit addition of callbacks // to be fired before this value is set to avoid event timing issues state: event.hashchangeState || {} }); }, // TODO We really only want to set this up once // but I'm not clear if there's a beter way to achieve // this with the jQuery special event structure setup: function( data, namespaces ) { if( self.bound ) { return; } self.bound = true; if( self.isPushStateEnabled() ) { self.originalEventName = "popstate"; $win.bind( "popstate.navigate", self.popstate ); } else if ( self.isHashChangeEnabled() ){ self.originalEventName = "hashchange"; $win.bind( "hashchange.navigate", self.hashchange ); } } }; })( jQuery ); (function( $, undefined ) { var path, documentBase, $base, dialogHashKey = "&ui-state=dialog"; $.mobile.path = path = { uiStateKey: "&ui-state", // This scary looking regular expression parses an absolute URL or its relative // variants (protocol, site, document, query, and hash), into the various // components (protocol, host, path, query, fragment, etc that make up the // URL as well as some other commonly used sub-parts. When used with RegExp.exec() // or String.match, it parses the URL into a results array that looks like this: // // [0]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread#msg-content // [1]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread // [2]: http://jblas:password@mycompany.com:8080/mail/inbox // [3]: http://jblas:password@mycompany.com:8080 // [4]: http: // [5]: // // [6]: jblas:password@mycompany.com:8080 // [7]: jblas:password // [8]: jblas // [9]: password // [10]: mycompany.com:8080 // [11]: mycompany.com // [12]: 8080 // [13]: /mail/inbox // [14]: /mail/ // [15]: inbox // [16]: ?msg=1234&type=unread // [17]: #msg-content // urlParseRE: /^\s*(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/, // Abstraction to address xss (Issue #4787) by removing the authority in // browsers that auto decode it. All references to location.href should be // replaced with a call to this method so that it can be dealt with properly here getLocation: function( url ) { var uri = url ? this.parseUrl( url ) : location, hash = this.parseUrl( url || location.href ).hash; // mimic the browser with an empty string when the hash is empty hash = hash === "#" ? "" : hash; // Make sure to parse the url or the location object for the hash because using location.hash // is autodecoded in firefox, the rest of the url should be from the object (location unless // we're testing) to avoid the inclusion of the authority return uri.protocol + "//" + uri.host + uri.pathname + uri.search + hash; }, parseLocation: function() { return this.parseUrl( this.getLocation() ); }, //Parse a URL into a structure that allows easy access to //all of the URL components by name. parseUrl: function( url ) { // If we're passed an object, we'll assume that it is // a parsed url object and just return it back to the caller. if ( $.type( url ) === "object" ) { return url; } var matches = path.urlParseRE.exec( url || "" ) || []; // Create an object that allows the caller to access the sub-matches // by name. Note that IE returns an empty string instead of undefined, // like all other browsers do, so we normalize everything so its consistent // no matter what browser we're running on. return { href: matches[ 0 ] || "", hrefNoHash: matches[ 1 ] || "", hrefNoSearch: matches[ 2 ] || "", domain: matches[ 3 ] || "", protocol: matches[ 4 ] || "", doubleSlash: matches[ 5 ] || "", authority: matches[ 6 ] || "", username: matches[ 8 ] || "", password: matches[ 9 ] || "", host: matches[ 10 ] || "", hostname: matches[ 11 ] || "", port: matches[ 12 ] || "", pathname: matches[ 13 ] || "", directory: matches[ 14 ] || "", filename: matches[ 15 ] || "", search: matches[ 16 ] || "", hash: matches[ 17 ] || "" }; }, //Turn relPath into an asbolute path. absPath is //an optional absolute path which describes what //relPath is relative to. makePathAbsolute: function( relPath, absPath ) { if ( relPath && relPath.charAt( 0 ) === "/" ) { return relPath; } relPath = relPath || ""; absPath = absPath ? absPath.replace( /^\/|(\/[^\/]*|[^\/]+)$/g, "" ) : ""; var absStack = absPath ? absPath.split( "/" ) : [], relStack = relPath.split( "/" ); for ( var i = 0; i < relStack.length; i++ ) { var d = relStack[ i ]; switch ( d ) { case ".": break; case "..": if ( absStack.length ) { absStack.pop(); } break; default: absStack.push( d ); break; } } return "/" + absStack.join( "/" ); }, //Returns true if both urls have the same domain. isSameDomain: function( absUrl1, absUrl2 ) { return path.parseUrl( absUrl1 ).domain === path.parseUrl( absUrl2 ).domain; }, //Returns true for any relative variant. isRelativeUrl: function( url ) { // All relative Url variants have one thing in common, no protocol. return path.parseUrl( url ).protocol === ""; }, //Returns true for an absolute url. isAbsoluteUrl: function( url ) { return path.parseUrl( url ).protocol !== ""; }, //Turn the specified realtive URL into an absolute one. This function //can handle all relative variants (protocol, site, document, query, fragment). makeUrlAbsolute: function( relUrl, absUrl ) { if ( !path.isRelativeUrl( relUrl ) ) { return relUrl; } if ( absUrl === undefined ) { absUrl = this.documentBase; } var relObj = path.parseUrl( relUrl ), absObj = path.parseUrl( absUrl ), protocol = relObj.protocol || absObj.protocol, doubleSlash = relObj.protocol ? relObj.doubleSlash : ( relObj.doubleSlash || absObj.doubleSlash ), authority = relObj.authority || absObj.authority, hasPath = relObj.pathname !== "", pathname = path.makePathAbsolute( relObj.pathname || absObj.filename, absObj.pathname ), search = relObj.search || ( !hasPath && absObj.search ) || "", hash = relObj.hash; return protocol + doubleSlash + authority + pathname + search + hash; }, //Add search (aka query) params to the specified url. addSearchParams: function( url, params ) { var u = path.parseUrl( url ), p = ( typeof params === "object" ) ? $.param( params ) : params, s = u.search || "?"; return u.hrefNoSearch + s + ( s.charAt( s.length - 1 ) !== "?" ? "&" : "" ) + p + ( u.hash || "" ); }, convertUrlToDataUrl: function( absUrl ) { var u = path.parseUrl( absUrl ); if ( path.isEmbeddedPage( u ) ) { // For embedded pages, remove the dialog hash key as in getFilePath(), // and remove otherwise the Data Url won't match the id of the embedded Page. return u.hash .split( dialogHashKey )[0] .replace( /^#/, "" ) .replace( /\?.*$/, "" ); } else if ( path.isSameDomain( u, this.documentBase ) ) { return u.hrefNoHash.replace( this.documentBase.domain, "" ).split( dialogHashKey )[0]; } return window.decodeURIComponent(absUrl); }, //get path from current hash, or from a file path get: function( newPath ) { if ( newPath === undefined ) { newPath = path.parseLocation().hash; } return path.stripHash( newPath ).replace( /[^\/]*\.[^\/*]+$/, '' ); }, //set location hash to path set: function( path ) { location.hash = path; }, //test if a given url (string) is a path //NOTE might be exceptionally naive isPath: function( url ) { return ( /\// ).test( url ); }, //return a url path with the window's location protocol/hostname/pathname removed clean: function( url ) { return url.replace( this.documentBase.domain, "" ); }, //just return the url without an initial # stripHash: function( url ) { return url.replace( /^#/, "" ); }, stripQueryParams: function( url ) { return url.replace( /\?.*$/, "" ); }, //remove the preceding hash, any query params, and dialog notations cleanHash: function( hash ) { return path.stripHash( hash.replace( /\?.*$/, "" ).replace( dialogHashKey, "" ) ); }, isHashValid: function( hash ) { return ( /^#[^#]+$/ ).test( hash ); }, //check whether a url is referencing the same domain, or an external domain or different protocol //could be mailto, etc isExternal: function( url ) { var u = path.parseUrl( url ); return u.protocol && u.domain !== this.documentUrl.domain ? true : false; }, hasProtocol: function( url ) { return ( /^(:?\w+:)/ ).test( url ); }, isEmbeddedPage: function( url ) { var u = path.parseUrl( url ); //if the path is absolute, then we need to compare the url against //both the this.documentUrl and the documentBase. The main reason for this //is that links embedded within external documents will refer to the //application document, whereas links embedded within the application //document will be resolved against the document base. if ( u.protocol !== "" ) { return ( !this.isPath(u.hash) && u.hash && ( u.hrefNoHash === this.documentUrl.hrefNoHash || ( this.documentBaseDiffers && u.hrefNoHash === this.documentBase.hrefNoHash ) ) ); } return ( /^#/ ).test( u.href ); }, squash: function( url, resolutionUrl ) { var state, href, cleanedUrl, search, stateIndex, isPath = this.isPath( url ), uri = this.parseUrl( url ), preservedHash = uri.hash, uiState = ""; // produce a url against which we can resole the provided path resolutionUrl = resolutionUrl || (path.isPath(url) ? path.getLocation() : path.getDocumentUrl()); // If the url is anything but a simple string, remove any preceding hash // eg #foo/bar -> foo/bar // #foo -> #foo cleanedUrl = isPath ? path.stripHash( url ) : url; // If the url is a full url with a hash check if the parsed hash is a path // if it is, strip the #, and use it otherwise continue without change cleanedUrl = path.isPath( uri.hash ) ? path.stripHash( uri.hash ) : cleanedUrl; // Split the UI State keys off the href stateIndex = cleanedUrl.indexOf( this.uiStateKey ); // store the ui state keys for use if( stateIndex > -1 ){ uiState = cleanedUrl.slice( stateIndex ); cleanedUrl = cleanedUrl.slice( 0, stateIndex ); } // make the cleanedUrl absolute relative to the resolution url href = path.makeUrlAbsolute( cleanedUrl, resolutionUrl ); // grab the search from the resolved url since parsing from // the passed url may not yield the correct result search = this.parseUrl( href ).search; // TODO all this crap is terrible, clean it up if ( isPath ) { // reject the hash if it's a path or it's just a dialog key if( path.isPath( preservedHash ) || preservedHash.replace("#", "").indexOf( this.uiStateKey ) === 0) { preservedHash = ""; } // Append the UI State keys where it exists and it's been removed // from the url if( uiState && preservedHash.indexOf( this.uiStateKey ) === -1){ preservedHash += uiState; } // make sure that pound is on the front of the hash if( preservedHash.indexOf( "#" ) === -1 && preservedHash !== "" ){ preservedHash = "#" + preservedHash; } // reconstruct each of the pieces with the new search string and hash href = path.parseUrl( href ); href = href.protocol + "//" + href.host + href.pathname + search + preservedHash; } else { href += href.indexOf( "#" ) > -1 ? uiState : "#" + uiState; } return href; }, isPreservableHash: function( hash ) { return hash.replace( "#", "" ).indexOf( this.uiStateKey ) === 0; } }; path.documentUrl = path.parseLocation(); $base = $( "head" ).find( "base" ); path.documentBase = $base.length ? path.parseUrl( path.makeUrlAbsolute( $base.attr( "href" ), path.documentUrl.href ) ) : path.documentUrl; path.documentBaseDiffers = (path.documentUrl.hrefNoHash !== path.documentBase.hrefNoHash); //return the original document url path.getDocumentUrl = function( asParsedObject ) { return asParsedObject ? $.extend( {}, path.documentUrl ) : path.documentUrl.href; }; //return the original document base url path.getDocumentBase = function( asParsedObject ) { return asParsedObject ? $.extend( {}, path.documentBase ) : path.documentBase.href; }; })( jQuery ); (function( $, undefined ) { var path = $.mobile.path; $.mobile.History = function( stack, index ) { this.stack = stack || []; this.activeIndex = index || 0; }; $.extend($.mobile.History.prototype, { getActive: function() { return this.stack[ this.activeIndex ]; }, getLast: function() { return this.stack[ this.previousIndex ]; }, getNext: function() { return this.stack[ this.activeIndex + 1 ]; }, getPrev: function() { return this.stack[ this.activeIndex - 1 ]; }, // addNew is used whenever a new page is added add: function( url, data ){ data = data || {}; //if there's forward history, wipe it if ( this.getNext() ) { this.clearForward(); } // if the hash is included in the data make sure the shape // is consistent for comparison if( data.hash && data.hash.indexOf( "#" ) === -1) { data.hash = "#" + data.hash; } data.url = url; this.stack.push( data ); this.activeIndex = this.stack.length - 1; }, //wipe urls ahead of active index clearForward: function() { this.stack = this.stack.slice( 0, this.activeIndex + 1 ); }, find: function( url, stack, earlyReturn ) { stack = stack || this.stack; var entry, i, length = stack.length, index; for ( i = 0; i < length; i++ ) { entry = stack[i]; if ( decodeURIComponent(url) === decodeURIComponent(entry.url) || decodeURIComponent(url) === decodeURIComponent(entry.hash) ) { index = i; if( earlyReturn ) { return index; } } } return index; }, closest: function( url ) { var closest, a = this.activeIndex; // First, take the slice of the history stack before the current index and search // for a url match. If one is found, we'll avoid avoid looking through forward history // NOTE the preference for backward history movement is driven by the fact that // most mobile browsers only have a dedicated back button, and users rarely use // the forward button in desktop browser anyhow closest = this.find( url, this.stack.slice(0, a) ); // If nothing was found in backward history check forward. The `true` // value passed as the third parameter causes the find method to break // on the first match in the forward history slice. The starting index // of the slice must then be added to the result to get the element index // in the original history stack :( :( // // TODO this is hyper confusing and should be cleaned up (ugh so bad) if( closest === undefined ) { closest = this.find( url, this.stack.slice(a), true ); closest = closest === undefined ? closest : closest + a; } return closest; }, direct: function( opts ) { var newActiveIndex = this.closest( opts.url ), a = this.activeIndex; // save new page index, null check to prevent falsey 0 result // record the previous index for reference if( newActiveIndex !== undefined ) { this.activeIndex = newActiveIndex; this.previousIndex = a; } // invoke callbacks where appropriate // // TODO this is also convoluted and confusing if ( newActiveIndex < a ) { ( opts.present || opts.back || $.noop )( this.getActive(), 'back' ); } else if ( newActiveIndex > a ) { ( opts.present || opts.forward || $.noop )( this.getActive(), 'forward' ); } else if ( newActiveIndex === undefined && opts.missing ){ opts.missing( this.getActive() ); } } }); })( jQuery ); (function( $, undefined ) { var path = $.mobile.path, 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; // Grab the hash for recording. If the passed url is a path // we used the parsed version of the squashed url to reconstruct, // otherwise we assume it's a hash and store it directly parsed = path.parseUrl( url ); loc = path.parseLocation(); if( loc.pathname + loc.search === parsed.pathname + parsed.search ) { // If the pathname and search of the passed url is identical to the current loc // then we must use the hash. Otherwise there will be no event // eg, url = "/foo/bar?baz#bang", location.href = "http://example.com/foo/bar?baz" hash = parsed.hash ? parsed.hash : parsed.pathname + parsed.search; } else if ( path.isPath(url) ) { var resolved = path.parseUrl( href ); // If the passed url is a path, make it domain relative and remove any trailing hash hash = resolved.pathname + resolved.search + (path.isPreservableHash( resolved.hash )? resolved.hash.replace( "#", "" ) : ""); } else { hash = url; } return hash; }, // TODO reconsider name go: function( url, data, noEvents ) { var state, href, hash, popstateEvent, isPopStateEvent = $.event.special.navigate.isPushStateEnabled(); // Get the url as it would look squashed on to the current resolution url href = path.squash( url ); // sort out what the hash sould be from the url hash = this.hash( url, href ); // Here we prevent the next hash change or popstate event from doing any // history management. In the case of hashchange we don't swallow it // if there will be no hashchange fired (since that won't reset the value) // and will swallow the following hashchange if( noEvents && hash !== path.stripHash(path.parseLocation().hash) ) { this.preventNextHashChange = noEvents; } // IMPORTANT in the case where popstate is supported the event will be triggered // directly, stopping further execution - ie, interupting the flow of this // method call to fire bindings at this expression. Below the navigate method // there is a binding to catch this event and stop its propagation. // // We then trigger a new popstate event on the window with a null state // so that the navigate events can conclude their work properly // // if the url is a path we want to preserve the query params that are available on // the current url. this.preventHashAssignPopState = true; window.location.hash = hash; // If popstate is enabled and the browser triggers `popstate` events when the hash // is set (this often happens immediately in browsers like Chrome), then the // this flag will be set to false already. If it's a browser that does not trigger // a `popstate` on hash assignement or `replaceState` then we need avoid the branch // that swallows the event created by the popstate generated by the hash assignment // At the time of this writing this happens with Opera 12 and some version of IE this.preventHashAssignPopState = false; state = $.extend({ url: href, hash: hash, title: document.title }, data); if( isPopStateEvent ) { popstateEvent = new $.Event( "popstate" ); popstateEvent.originalEvent = { type: "popstate", state: null }; this.squash( url, state ); // Trigger a new faux popstate event to replace the one that we // caught that was triggered by the hash setting above. if( !noEvents ) { this.ignorePopState = true; $.mobile.window.trigger( popstateEvent ); } } // record the history entry so that the information can be included // in hashchange event driven navigate events in a similar fashion to // the state that's provided by popstate this.history.add( state.url, state ); }, // This binding is intended to catch the popstate events that are fired // when execution of the `$.navigate` method stops at window.location.hash = url; // and completely prevent them from propagating. The popstate event will then be // retriggered after execution resumes // // TODO grab the original event here and use it for the synthetic event in the // second half of the navigate execution that will follow this binding popstate: function( event ) { var active, hash, state, closestIndex; // Partly to support our test suite which manually alters the support // value to test hashchange. Partly to prevent all around weirdness if( !$.event.special.navigate.isPushStateEnabled() ){ return; } // If this is the popstate triggered by the actual alteration of the hash // prevent it completely. History is tracked manually if( this.preventHashAssignPopState ) { this.preventHashAssignPopState = false; event.stopImmediatePropagation(); return; } // if this is the popstate triggered after the `replaceState` call in the go // method, then simply ignore it. The history entry has already been captured if( this.ignorePopState ) { this.ignorePopState = false; return; } // If there is no state, and the history stack length is one were // probably getting the page load popstate fired by browsers like chrome // avoid it and set the one time flag to false. // 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 ); // This plugin is an experiment for abstracting away the touch and mouse // events so that developers don't have to worry about which method of input // the device their document is loaded on supports. // // The idea here is to allow the developer to register listeners for the // basic mouse events, such as mousedown, mousemove, mouseup, and click, // and the plugin will take care of registering the correct listeners // behind the scenes to invoke the listener at the fastest possible time // for that device, while still retaining the order of event firing in // the traditional mouse environment, should multiple handlers be registered // on the same element for different events. // // The current version exposes the following virtual events to jQuery bind methods: // "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel" (function( $, window, document, undefined ) { var dataPropertyName = "virtualMouseBindings", touchTargetPropertyName = "virtualTouchID", virtualEventNames = "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split( " " ), touchEventProps = "clientX clientY pageX pageY screenX screenY".split( " " ), mouseHookProps = $.event.mouseHooks ? $.event.mouseHooks.props : [], mouseEventProps = $.event.props.concat( mouseHookProps ), activeDocHandlers = {}, resetTimerID = 0, startX = 0, startY = 0, didScroll = false, clickBlockList = [], blockMouseTriggers = false, blockTouchTriggers = false, eventCaptureSupported = "addEventListener" in document, $document = $( document ), nextTouchID = 1, lastTouchID = 0, threshold; $.vmouse = { moveDistanceThreshold: 10, clickDistanceThreshold: 10, resetTimerDuration: 1500 }; function getNativeEvent( event ) { while ( event && typeof event.originalEvent !== "undefined" ) { event = event.originalEvent; } return event; } function createVirtualEvent( event, eventType ) { var t = event.type, oe, props, ne, prop, ct, touch, i, j, len; event = $.Event( event ); event.type = eventType; oe = event.originalEvent; props = $.event.props; // addresses separation of $.event.props in to $.event.mouseHook.props and Issue 3280 // https://github.com/jquery/jquery-mobile/issues/3280 if ( t.search( /^(mouse|click)/ ) > -1 ) { props = mouseEventProps; } // copy original event properties over to the new event // this would happen if we could call $.event.fix instead of $.Event // but we don't have a way to force an event to be fixed multiple times if ( oe ) { for ( i = props.length, prop; i; ) { prop = props[ --i ]; event[ prop ] = oe[ prop ]; } } // make sure that if the mouse and click virtual events are generated // without a .which one is defined if ( t.search(/mouse(down|up)|click/) > -1 && !event.which ) { event.which = 1; } if ( t.search(/^touch/) !== -1 ) { ne = getNativeEvent( oe ); t = ne.touches; ct = ne.changedTouches; touch = ( t && t.length ) ? t[0] : ( ( ct && ct.length ) ? ct[ 0 ] : undefined ); if ( touch ) { for ( j = 0, len = touchEventProps.length; j < len; j++) { prop = touchEventProps[ j ]; event[ prop ] = touch[ prop ]; } } } return event; } function getVirtualBindingFlags( element ) { var flags = {}, b, k; while ( element ) { b = $.data( element, dataPropertyName ); for ( k in b ) { if ( b[ k ] ) { flags[ k ] = flags.hasVirtualBinding = true; } } element = element.parentNode; } return flags; } function getClosestElementWithVirtualBinding( element, eventType ) { var b; while ( element ) { b = $.data( element, dataPropertyName ); if ( b && ( !eventType || b[ eventType ] ) ) { return element; } element = element.parentNode; } return null; } function enableTouchBindings() { blockTouchTriggers = false; } function disableTouchBindings() { blockTouchTriggers = true; } function enableMouseBindings() { lastTouchID = 0; clickBlockList.length = 0; blockMouseTriggers = false; // When mouse bindings are enabled, our // touch bindings are disabled. disableTouchBindings(); } function disableMouseBindings() { // When mouse bindings are disabled, our // touch bindings are enabled. enableTouchBindings(); } function startResetTimer() { clearResetTimer(); resetTimerID = setTimeout( function() { resetTimerID = 0; enableMouseBindings(); }, $.vmouse.resetTimerDuration ); } function clearResetTimer() { if ( resetTimerID ) { clearTimeout( resetTimerID ); resetTimerID = 0; } } function triggerVirtualEvent( eventType, event, flags ) { var ve; if ( ( flags && flags[ eventType ] ) || ( !flags && getClosestElementWithVirtualBinding( event.target, eventType ) ) ) { ve = createVirtualEvent( event, eventType ); $( event.target).trigger( ve ); } return ve; } function mouseEventCallback( event ) { var touchID = $.data( event.target, touchTargetPropertyName ); if ( !blockMouseTriggers && ( !lastTouchID || lastTouchID !== touchID ) ) { var ve = triggerVirtualEvent( "v" + event.type, event ); if ( ve ) { if ( ve.isDefaultPrevented() ) { event.preventDefault(); } if ( ve.isPropagationStopped() ) { event.stopPropagation(); } if ( ve.isImmediatePropagationStopped() ) { event.stopImmediatePropagation(); } } } } function handleTouchStart( event ) { var touches = getNativeEvent( event ).touches, target, flags; if ( touches && touches.length === 1 ) { target = event.target; flags = getVirtualBindingFlags( target ); if ( flags.hasVirtualBinding ) { lastTouchID = nextTouchID++; $.data( target, touchTargetPropertyName, lastTouchID ); clearResetTimer(); disableMouseBindings(); didScroll = false; var t = getNativeEvent( event ).touches[ 0 ]; startX = t.pageX; startY = t.pageY; triggerVirtualEvent( "vmouseover", event, flags ); triggerVirtualEvent( "vmousedown", event, flags ); } } } function handleScroll( event ) { if ( blockTouchTriggers ) { return; } if ( !didScroll ) { triggerVirtualEvent( "vmousecancel", event, getVirtualBindingFlags( event.target ) ); } didScroll = true; startResetTimer(); } function handleTouchMove( event ) { if ( blockTouchTriggers ) { return; } var t = getNativeEvent( event ).touches[ 0 ], didCancel = didScroll, moveThreshold = $.vmouse.moveDistanceThreshold, flags = getVirtualBindingFlags( event.target ); didScroll = didScroll || ( Math.abs( t.pageX - startX ) > moveThreshold || Math.abs( t.pageY - startY ) > moveThreshold ); if ( didScroll && !didCancel ) { triggerVirtualEvent( "vmousecancel", event, flags ); } triggerVirtualEvent( "vmousemove", event, flags ); startResetTimer(); } function handleTouchEnd( event ) { if ( blockTouchTriggers ) { return; } disableTouchBindings(); var flags = getVirtualBindingFlags( event.target ), t; triggerVirtualEvent( "vmouseup", event, flags ); if ( !didScroll ) { var ve = triggerVirtualEvent( "vclick", event, flags ); if ( ve && ve.isDefaultPrevented() ) { // The target of the mouse events that follow the touchend // event don't necessarily match the target used during the // touch. This means we need to rely on coordinates for blocking // any click that is generated. t = getNativeEvent( event ).changedTouches[ 0 ]; clickBlockList.push({ touchID: lastTouchID, x: t.clientX, y: t.clientY }); // Prevent any mouse events that follow from triggering // virtual event notifications. blockMouseTriggers = true; } } triggerVirtualEvent( "vmouseout", event, flags); didScroll = false; startResetTimer(); } function hasVirtualBindings( ele ) { var bindings = $.data( ele, dataPropertyName ), k; if ( bindings ) { for ( k in bindings ) { if ( bindings[ k ] ) { return true; } } } return false; } function dummyMouseHandler() {} function getSpecialEventObject( eventType ) { var realType = eventType.substr( 1 ); return { setup: function( data, namespace ) { // If this is the first virtual mouse binding for this element, // add a bindings object to its data. if ( !hasVirtualBindings( this ) ) { $.data( this, dataPropertyName, {} ); } // If setup is called, we know it is the first binding for this // eventType, so initialize the count for the eventType to zero. var bindings = $.data( this, dataPropertyName ); bindings[ eventType ] = true; // If this is the first virtual mouse event for this type, // register a global handler on the document. activeDocHandlers[ eventType ] = ( activeDocHandlers[ eventType ] || 0 ) + 1; if ( activeDocHandlers[ eventType ] === 1 ) { $document.bind( realType, mouseEventCallback ); } // Some browsers, like Opera Mini, won't dispatch mouse/click events // for elements unless they actually have handlers registered on them. // To get around this, we register dummy handlers on the elements. $( this ).bind( realType, dummyMouseHandler ); // For now, if event capture is not supported, we rely on mouse handlers. if ( eventCaptureSupported ) { // If this is the first virtual mouse binding for the document, // register our touchstart handler on the document. activeDocHandlers[ "touchstart" ] = ( activeDocHandlers[ "touchstart" ] || 0) + 1; if ( activeDocHandlers[ "touchstart" ] === 1 ) { $document.bind( "touchstart", handleTouchStart ) .bind( "touchend", handleTouchEnd ) // On touch platforms, touching the screen and then dragging your finger // causes the window content to scroll after some distance threshold is // exceeded. On these platforms, a scroll prevents a click event from being // dispatched, and on some platforms, even the touchend is suppressed. To // mimic the suppression of the click event, we need to watch for a scroll // event. Unfortunately, some platforms like iOS don't dispatch scroll // events until *AFTER* the user lifts their finger (touchend). This means // we need to watch both scroll and touchmove events to figure out whether // or not a scroll happenens before the touchend event is fired. .bind( "touchmove", handleTouchMove ) .bind( "scroll", handleScroll ); } } }, teardown: function( data, namespace ) { // If this is the last virtual binding for this eventType, // remove its global handler from the document. --activeDocHandlers[ eventType ]; if ( !activeDocHandlers[ eventType ] ) { $document.unbind( realType, mouseEventCallback ); } if ( eventCaptureSupported ) { // If this is the last virtual mouse binding in existence, // remove our document touchstart listener. --activeDocHandlers[ "touchstart" ]; if ( !activeDocHandlers[ "touchstart" ] ) { $document.unbind( "touchstart", handleTouchStart ) .unbind( "touchmove", handleTouchMove ) .unbind( "touchend", handleTouchEnd ) .unbind( "scroll", handleScroll ); } } var $this = $( this ), bindings = $.data( this, dataPropertyName ); // teardown may be called when an element was // removed from the DOM. If this is the case, // jQuery core may have already stripped the element // of any data bindings so we need to check it before // using it. if ( bindings ) { bindings[ eventType ] = false; } // Unregister the dummy event handler. $this.unbind( realType, dummyMouseHandler ); // If this is the last virtual mouse binding on the // element, remove the binding data from the element. if ( !hasVirtualBindings( this ) ) { $this.removeData( dataPropertyName ); } } }; } // Expose our custom events to the jQuery bind/unbind mechanism. for ( var i = 0; i < virtualEventNames.length; i++ ) { $.event.special[ virtualEventNames[ i ] ] = getSpecialEventObject( virtualEventNames[ i ] ); } // Add a capture click handler to block clicks. // Note that we require event capture support for this so if the device // doesn't support it, we punt for now and rely solely on mouse events. if ( eventCaptureSupported ) { document.addEventListener( "click", function( e ) { var cnt = clickBlockList.length, target = e.target, x, y, ele, i, o, touchID; if ( cnt ) { x = e.clientX; y = e.clientY; threshold = $.vmouse.clickDistanceThreshold; // The idea here is to run through the clickBlockList to see if // the current click event is in the proximity of one of our // vclick events that had preventDefault() called on it. If we find // one, then we block the click. // // Why do we have to rely on proximity? // // Because the target of the touch event that triggered the vclick // can be different from the target of the click event synthesized // by the browser. The target of a mouse/click event that is syntehsized // from a touch event seems to be implementation specific. For example, // some browsers will fire mouse/click events for a link that is near // a touch event, even though the target of the touchstart/touchend event // says the user touched outside the link. Also, it seems that with most // browsers, the target of the mouse/click event is not calculated until the // time it is dispatched, so if you replace an element that you touched // with another element, the target of the mouse/click will be the new // element underneath that point. // // Aside from proximity, we also check to see if the target and any // of its ancestors were the ones that blocked a click. This is necessary // because of the strange mouse/click target calculation done in the // Android 2.1 browser, where if you click on an element, and there is a // mouse/click handler on one of its ancestors, the target will be the // innermost child of the touched element, even if that child is no where // near the point of touch. ele = target; while ( ele ) { for ( i = 0; i < cnt; i++ ) { o = clickBlockList[ i ]; touchID = 0; if ( ( ele === target && Math.abs( o.x - x ) < threshold && Math.abs( o.y - y ) < threshold ) || $.data( ele, touchTargetPropertyName ) === o.touchID ) { // XXX: We may want to consider removing matches from the block list // instead of waiting for the reset timer to fire. e.preventDefault(); e.stopPropagation(); return; } } ele = ele.parentNode; } } }, true); } })( jQuery, window, document ); (function( $, window, undefined ) { var $document = $( document ); // add new event shortcuts $.each( ( "touchstart touchmove touchend " + "tap taphold " + "swipe swipeleft swiperight " + "scrollstart scrollstop" ).split( " " ), function( i, name ) { $.fn[ name ] = function( fn ) { return fn ? this.bind( name, fn ) : this.trigger( name ); }; // jQuery < 1.8 if ( $.attrFn ) { $.attrFn[ name ] = true; } }); var supportTouch = $.mobile.support.touch, scrollEvent = "touchmove scroll", touchStartEvent = supportTouch ? "touchstart" : "mousedown", touchStopEvent = supportTouch ? "touchend" : "mouseup", touchMoveEvent = supportTouch ? "touchmove" : "mousemove"; function triggerCustomEvent( obj, eventType, event ) { var originalType = event.type; event.type = eventType; $.event.dispatch.call( obj, event ); event.type = originalType; } // also handles scrollstop $.event.special.scrollstart = { enabled: true, setup: function() { var thisObject = this, $this = $( thisObject ), scrolling, timer; function trigger( event, state ) { scrolling = state; triggerCustomEvent( thisObject, scrolling ? "scrollstart" : "scrollstop", event ); } // iPhone triggers scroll after a small delay; use touchmove instead $this.bind( scrollEvent, function( event ) { if ( !$.event.special.scrollstart.enabled ) { return; } if ( !scrolling ) { trigger( event, true ); } clearTimeout( timer ); timer = setTimeout( function() { trigger( event, false ); }, 50 ); }); } }; // also handles taphold $.event.special.tap = { tapholdThreshold: 750, setup: function() { var thisObject = this, $this = $( thisObject ); $this.bind( "vmousedown", function( event ) { if ( event.which && event.which !== 1 ) { return false; } var origTarget = event.target, origEvent = event.originalEvent, timer; function clearTapTimer() { clearTimeout( timer ); } function clearTapHandlers() { clearTapTimer(); $this.unbind( "vclick", clickHandler ) .unbind( "vmouseup", clearTapTimer ); $document.unbind( "vmousecancel", clearTapHandlers ); } function clickHandler( event ) { clearTapHandlers(); // ONLY trigger a 'tap' event if the start target is // the same as the stop target. if ( origTarget === event.target ) { triggerCustomEvent( thisObject, "tap", event ); } } $this.bind( "vmouseup", clearTapTimer ) .bind( "vclick", clickHandler ); $document.bind( "vmousecancel", clearTapHandlers ); timer = setTimeout( function() { triggerCustomEvent( thisObject, "taphold", $.Event( "taphold", { target: origTarget } ) ); }, $.event.special.tap.tapholdThreshold ); }); } }; // also handles swipeleft, swiperight $.event.special.swipe = { scrollSupressionThreshold: 30, // More than this horizontal displacement, and we will suppress scrolling. durationThreshold: 1000, // More time than this, and it isn't a swipe. horizontalDistanceThreshold: 30, // Swipe horizontal displacement must be more than this. verticalDistanceThreshold: 75, // Swipe vertical displacement must be less than this. start: function( event ) { var data = event.originalEvent.touches ? event.originalEvent.touches[ 0 ] : event; return { time: ( new Date() ).getTime(), coords: [ data.pageX, data.pageY ], origin: $( event.target ) }; }, stop: function( event ) { var data = event.originalEvent.touches ? event.originalEvent.touches[ 0 ] : event; return { time: ( new Date() ).getTime(), coords: [ data.pageX, data.pageY ] }; }, handleSwipe: function( start, stop ) { if ( stop.time - start.time < $.event.special.swipe.durationThreshold && Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.horizontalDistanceThreshold && Math.abs( start.coords[ 1 ] - stop.coords[ 1 ] ) < $.event.special.swipe.verticalDistanceThreshold ) { start.origin.trigger( "swipe" ) .trigger( start.coords[0] > stop.coords[ 0 ] ? "swipeleft" : "swiperight" ); } }, setup: function() { var thisObject = this, $this = $( thisObject ); $this.bind( touchStartEvent, function( event ) { var start = $.event.special.swipe.start( event ), stop; function moveHandler( event ) { if ( !start ) { return; } stop = $.event.special.swipe.stop( event ); // prevent scrolling if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.scrollSupressionThreshold ) { event.preventDefault(); } } $this.bind( touchMoveEvent, moveHandler ) .one( touchStopEvent, function() { $this.unbind( touchMoveEvent, moveHandler ); if ( start && stop ) { $.event.special.swipe.handleSwipe( start, stop ); } start = stop = undefined; }); }); } }; $.each({ scrollstop: "scrollstart", taphold: "tap", swipeleft: "swipe", swiperight: "swipe" }, function( event, sourceEvent ) { $.event.special[ event ] = { setup: function() { $( this ).bind( sourceEvent, $.noop ); } }; }); })( jQuery, this ); // throttled resize event (function( $ ) { $.event.special.throttledresize = { setup: function() { $( this ).bind( "resize", handler ); }, teardown: function() { $( this ).unbind( "resize", handler ); } }; var throttle = 250, handler = function() { curr = ( new Date() ).getTime(); diff = curr - lastCall; if ( diff >= throttle ) { lastCall = curr; $( this ).trigger( "throttledresize" ); } else { if ( heldCall ) { clearTimeout( heldCall ); } // Promise a held call will still execute heldCall = setTimeout( handler, throttle - diff ); } }, lastCall = 0, heldCall, curr, diff; })( jQuery ); (function( $, window ) { var win = $( window ), event_name = "orientationchange", special_event, get_orientation, last_orientation, initial_orientation_is_landscape, initial_orientation_is_default, portrait_map = { "0": true, "180": true }; // It seems that some device/browser vendors use window.orientation values 0 and 180 to // denote the "default" orientation. For iOS devices, and most other smart-phones tested, // the default orientation is always "portrait", but in some Android and RIM based tablets, // the default orientation is "landscape". The following code attempts to use the window // dimensions to figure out what the current orientation is, and then makes adjustments // to the to the portrait_map if necessary, so that we can properly decode the // window.orientation value whenever get_orientation() is called. // // Note that we used to use a media query to figure out what the orientation the browser // thinks it is in: // // initial_orientation_is_landscape = $.mobile.media("all and (orientation: landscape)"); // // but there was an iPhone/iPod Touch bug beginning with iOS 4.2, up through iOS 5.1, // where the browser *ALWAYS* applied the landscape media query. This bug does not // happen on iPad. if ( $.support.orientation ) { // Check the window width and height to figure out what the current orientation // of the device is at this moment. Note that we've initialized the portrait map // values to 0 and 180, *AND* we purposely check for landscape so that if we guess // wrong, , we default to the assumption that portrait is the default orientation. // We use a threshold check below because on some platforms like iOS, the iPhone // form-factor can report a larger width than height if the user turns on the // developer console. The actual threshold value is somewhat arbitrary, we just // need to make sure it is large enough to exclude the developer console case. var ww = window.innerWidth || win.width(), wh = window.innerHeight || win.height(), landscape_threshold = 50; initial_orientation_is_landscape = ww > wh && ( ww - wh ) > landscape_threshold; // Now check to see if the current window.orientation is 0 or 180. initial_orientation_is_default = portrait_map[ window.orientation ]; // If the initial orientation is landscape, but window.orientation reports 0 or 180, *OR* // if the initial orientation is portrait, but window.orientation reports 90 or -90, we // need to flip our portrait_map values because landscape is the default orientation for // this device/browser. if ( ( initial_orientation_is_landscape && initial_orientation_is_default ) || ( !initial_orientation_is_landscape && !initial_orientation_is_default ) ) { portrait_map = { "-90": true, "90": true }; } } $.event.special.orientationchange = $.extend( {}, $.event.special.orientationchange, { setup: function() { // If the event is supported natively, return false so that jQuery // will bind to the event using DOM methods. if ( $.support.orientation && !$.event.special.orientationchange.disabled ) { return false; } // Get the current orientation to avoid initial double-triggering. last_orientation = get_orientation(); // Because the orientationchange event doesn't exist, simulate the // event by testing window dimensions on resize. win.bind( "throttledresize", handler ); }, teardown: function() { // If the event is not supported natively, return false so that // jQuery will unbind the event using DOM methods. if ( $.support.orientation && !$.event.special.orientationchange.disabled ) { return false; } // Because the orientationchange event doesn't exist, unbind the // resize event handler. win.unbind( "throttledresize", handler ); }, add: function( handleObj ) { // Save a reference to the bound event handler. var old_handler = handleObj.handler; handleObj.handler = function( event ) { // Modify event object, adding the .orientation property. event.orientation = get_orientation(); // Call the originally-bound event handler and return its result. return old_handler.apply( this, arguments ); }; } }); // If the event is not supported natively, this handler will be bound to // the window resize event to simulate the orientationchange event. function handler() { // Get the current orientation. var orientation = get_orientation(); if ( orientation !== last_orientation ) { // The orientation has changed, so trigger the orientationchange event. last_orientation = orientation; win.trigger( event_name ); } } // Get the current page orientation. This method is exposed publicly, should it // be needed, as jQuery.event.special.orientationchange.orientation() $.event.special.orientationchange.orientation = get_orientation = function() { var isPortrait = true, elem = document.documentElement; // prefer window orientation to the calculation based on screensize as // the actual screen resize takes place before or after the orientation change event // has been fired depending on implementation (eg android 2.3 is before, iphone after). // More testing is required to determine if a more reliable method of determining the new screensize // is possible when orientationchange is fired. (eg, use media queries + element + opacity) if ( $.support.orientation ) { // if the window orientation registers as 0 or 180 degrees report // portrait, otherwise landscape isPortrait = portrait_map[ window.orientation ]; } else { isPortrait = elem && elem.clientWidth / elem.clientHeight < 1.1; } return isPortrait ? "portrait" : "landscape"; }; $.fn[ event_name ] = function( fn ) { return fn ? this.bind( event_name, fn ) : this.trigger( event_name ); }; // jQuery < 1.8 if ( $.attrFn ) { $.attrFn[ event_name ] = true; } }( jQuery, this )); (function( $, undefined ) { $.widget( "mobile.page", $.mobile.widget, { options: { theme: "c", domCache: false, keepNativeDefault: ":jqmData(role='none'), :jqmData(role='nojs')" }, _create: function() { // if false is returned by the callbacks do not create the page if ( this._trigger( "beforecreate" ) === false ) { return false; } this.element .attr( "tabindex", "0" ) .addClass( "ui-page ui-body-" + this.options.theme ); this._on( this.element, { pagebeforehide: "removeContainerBackground", pagebeforeshow: "_handlePageBeforeShow" }); }, _handlePageBeforeShow: function( e ) { this.setContainerBackground(); }, removeContainerBackground: function() { $.mobile.pageContainer.removeClass( "ui-overlay-" + $.mobile.getInheritedTheme( this.element.parent() ) ); }, // set the page container background to the page theme setContainerBackground: function( theme ) { if ( this.options.theme ) { $.mobile.pageContainer.addClass( "ui-overlay-" + ( theme || this.options.theme ) ); } }, keepNativeSelector: function() { var options = this.options, keepNativeDefined = options.keepNative && $.trim( options.keepNative ); if ( keepNativeDefined && options.keepNative !== options.keepNativeDefault ) { return [options.keepNative, options.keepNativeDefault].join( ", " ); } return options.keepNativeDefault; } }); })( jQuery ); (function( $, window, undefined ) { var createHandler = function( sequential ) { // Default to sequential if ( sequential === undefined ) { sequential = true; } return function( name, reverse, $to, $from ) { var deferred = new $.Deferred(), reverseClass = reverse ? " reverse" : "", active = $.mobile.urlHistory.getActive(), toScroll = active.lastScroll || $.mobile.defaultHomeScroll, screenHeight = $.mobile.getScreenHeight(), maxTransitionOverride = $.mobile.maxTransitionWidth !== false && $.mobile.window.width() > $.mobile.maxTransitionWidth, none = !$.support.cssTransitions || maxTransitionOverride || !name || name === "none" || Math.max( $.mobile.window.scrollTop(), toScroll ) > $.mobile.getMaxScrollForTransition(), toPreClass = " ui-page-pre-in", toggleViewportClass = function() { $.mobile.pageContainer.toggleClass( "ui-mobile-viewport-transitioning viewport-" + name ); }, scrollPage = function() { // By using scrollTo instead of silentScroll, we can keep things better in order // Just to be precautios, disable scrollstart listening like silentScroll would $.event.special.scrollstart.enabled = false; window.scrollTo( 0, toScroll ); // reenable scrollstart listening like silentScroll would setTimeout( function() { $.event.special.scrollstart.enabled = true; }, 150 ); }, cleanFrom = function() { $from .removeClass( $.mobile.activePageClass + " out in reverse " + name ) .height( "" ); }, startOut = function() { // if it's not sequential, call the doneOut transition to start the TO page animating in simultaneously if ( !sequential ) { doneOut(); } else { $from.animationComplete( doneOut ); } // Set the from page's height and start it transitioning out // Note: setting an explicit height helps eliminate tiling in the transitions $from .height( screenHeight + $.mobile.window.scrollTop() ) .addClass( name + " out" + reverseClass ); }, doneOut = function() { if ( $from && sequential ) { cleanFrom(); } startIn(); }, startIn = function() { // Prevent flickering in phonegap container: see comments at #4024 regarding iOS $to.css( "z-index", -10 ); $to.addClass( $.mobile.activePageClass + toPreClass ); // Send focus to page as it is now display: block $.mobile.focusPage( $to ); // Set to page height $to.height( screenHeight + toScroll ); scrollPage(); // Restores visibility of the new page: added together with $to.css( "z-index", -10 ); $to.css( "z-index", "" ); if ( !none ) { $to.animationComplete( doneIn ); } $to .removeClass( toPreClass ) .addClass( name + " in" + reverseClass ); if ( none ) { doneIn(); } }, doneIn = function() { if ( !sequential ) { if ( $from ) { cleanFrom(); } } $to .removeClass( "out in reverse " + name ) .height( "" ); toggleViewportClass(); // In some browsers (iOS5), 3D transitions block the ability to scroll to the desired location during transition // This ensures we jump to that spot after the fact, if we aren't there already. if ( $.mobile.window.scrollTop() !== toScroll ) { scrollPage(); } deferred.resolve( name, reverse, $to, $from, true ); }; toggleViewportClass(); if ( $from && !none ) { startOut(); } else { doneOut(); } return deferred.promise(); }; }; // generate the handlers from the above var sequentialHandler = createHandler(), simultaneousHandler = createHandler( false ), defaultGetMaxScrollForTransition = function() { return $.mobile.getScreenHeight() * 3; }; // Make our transition handler the public default. $.mobile.defaultTransitionHandler = sequentialHandler; //transition handler dictionary for 3rd party transitions $.mobile.transitionHandlers = { "default": $.mobile.defaultTransitionHandler, "sequential": sequentialHandler, "simultaneous": simultaneousHandler }; $.mobile.transitionFallbacks = {}; // If transition is defined, check if css 3D transforms are supported, and if not, if a fallback is specified $.mobile._maybeDegradeTransition = function( transition ) { if ( transition && !$.support.cssTransform3d && $.mobile.transitionFallbacks[ transition ] ) { transition = $.mobile.transitionFallbacks[ transition ]; } return transition; }; // Set the getMaxScrollForTransition to default if no implementation was set by user $.mobile.getMaxScrollForTransition = $.mobile.getMaxScrollForTransition || defaultGetMaxScrollForTransition; })( jQuery, this ); (function( $, undefined ) { //define vars for interal use var $window = $.mobile.window, $html = $( 'html' ), $head = $( 'head' ), // NOTE: path extensions dependent on core attributes. Moved here to remove deps from // $.mobile.path definition path = $.extend($.mobile.path, { //return the substring of a filepath before the sub-page key, for making a server request getFilePath: function( path ) { var splitkey = '&' + $.mobile.subPageUrlKey; return path && path.split( splitkey )[0].split( dialogHashKey )[0]; }, //check if the specified url refers to the first page in the main application document. isFirstPageUrl: function( url ) { // We only deal with absolute paths. var u = path.parseUrl( path.makeUrlAbsolute( url, this.documentBase ) ), // Does the url have the same path as the document? samePath = u.hrefNoHash === this.documentUrl.hrefNoHash || ( this.documentBaseDiffers && u.hrefNoHash === this.documentBase.hrefNoHash ), // Get the first page element. fp = $.mobile.firstPage, // Get the id of the first page element if it has one. fpId = fp && fp[0] ? fp[0].id : undefined; // The url refers to the first page if the path matches the document and // it either has no hash value, or the hash is exactly equal to the id of the // first page element. return samePath && ( !u.hash || u.hash === "#" || ( fpId && u.hash.replace( /^#/, "" ) === fpId ) ); }, // Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR // requests if the document doing the request was loaded via the file:// protocol. // This is usually to allow the application to "phone home" and fetch app specific // data. We normally let the browser handle external/cross-domain urls, but if the // allowCrossDomainPages option is true, we will allow cross-domain http/https // requests to go through our page loading logic. isPermittedCrossDomainRequest: function( docUrl, reqUrl ) { return $.mobile.allowCrossDomainPages && docUrl.protocol === "file:" && reqUrl.search( /^https?:/ ) !== -1; } }), // used to track last vclicked element to make sure its value is added to form data $lastVClicked = null, //will be defined when a link is clicked and given an active class $activeClickedLink = null, // resolved on domready domreadyDeferred = $.Deferred(), //urlHistory is purely here to make guesses at whether the back or forward button was clicked //and provide an appropriate transition urlHistory = $.mobile.navigate.history, //define first selector to receive focus when a page is shown focusable = "[tabindex],a,button:visible,select:visible,input", //queue to hold simultanious page transitions pageTransitionQueue = [], //indicates whether or not page is in process of transitioning isPageTransitioning = false, //nonsense hash change key for dialogs, so they create a history entry dialogHashKey = "&ui-state=dialog", //existing base tag? $base = $head.children( "base" ), //tuck away the original document URL minus any fragment. documentUrl = path.documentUrl, //if the document has an embedded base tag, documentBase is set to its //initial value. If a base tag does not exist, then we default to the documentUrl. documentBase = path.documentBase, //cache the comparison once. documentBaseDiffers = path.documentBaseDiffers, getScreenHeight = $.mobile.getScreenHeight; //base element management, defined depending on dynamic base tag support var base = $.support.dynamicBaseTag ? { //define base element, for use in routing asset urls that are referenced in Ajax-requested markup element: ( $base.length ? $base : $( "<base>", { href: documentBase.hrefNoHash } ).prependTo( $head ) ), //set the generated BASE element's href attribute to a new page's base path set: function( href ) { href = path.parseUrl(href).hrefNoHash; base.element.attr( "href", path.makeUrlAbsolute( href, documentBase ) ); }, //set the generated BASE element's href attribute to a new page's base path reset: function() { base.element.attr( "href", documentBase.hrefNoSearch ); } } : undefined; //return the original document url $.mobile.getDocumentUrl = path.getDocumentUrl; //return the original document base url $.mobile.getDocumentBase = path.getDocumentBase; /* internal utility functions */ // NOTE Issue #4950 Android phonegap doesn't navigate back properly // when a full page refresh has taken place. It appears that hashchange // and replacestate history alterations work fine but we need to support // both forms of history traversal in our code that uses backward history // movement $.mobile.back = function() { var nav = window.navigator; // if the setting is on and the navigator object is // available use the phonegap navigation capability if( this.phonegapNavigationEnabled && nav && nav.app && nav.app.backHistory ){ nav.app.backHistory(); } else { window.history.back(); } }; //direct focus to the page title, or otherwise first focusable element $.mobile.focusPage = function ( page ) { var autofocus = page.find( "[autofocus]" ), pageTitle = page.find( ".ui-title:eq(0)" ); if ( autofocus.length ) { autofocus.focus(); return; } if ( pageTitle.length ) { pageTitle.focus(); } else{ page.focus(); } }; //remove active classes after page transition or error function removeActiveLinkClass( forceRemoval ) { if ( !!$activeClickedLink && ( !$activeClickedLink.closest( "." + $.mobile.activePageClass ).length || forceRemoval ) ) { $activeClickedLink.removeClass( $.mobile.activeBtnClass ); } $activeClickedLink = null; } function releasePageTransitionLock() { isPageTransitioning = false; if ( pageTransitionQueue.length > 0 ) { $.mobile.changePage.apply( null, pageTransitionQueue.pop() ); } } // Save the last scroll distance per page, before it is hidden var setLastScrollEnabled = true, setLastScroll, delayedSetLastScroll; setLastScroll = function() { // this barrier prevents setting the scroll value based on the browser // scrolling the window based on a hashchange if ( !setLastScrollEnabled ) { return; } var active = $.mobile.urlHistory.getActive(); if ( active ) { var lastScroll = $window.scrollTop(); // Set active page's lastScroll prop. // If the location we're scrolling to is less than minScrollBack, let it go. active.lastScroll = lastScroll < $.mobile.minScrollBack ? $.mobile.defaultHomeScroll : lastScroll; } }; // bind to scrollstop to gather scroll position. The delay allows for the hashchange // event to fire and disable scroll recording in the case where the browser scrolls // to the hash targets location (sometimes the top of the page). once pagechange fires // getLastScroll is again permitted to operate delayedSetLastScroll = function() { setTimeout( setLastScroll, 100 ); }; // disable an scroll setting when a hashchange has been fired, this only works // because the recording of the scroll position is delayed for 100ms after // the browser might have changed the position because of the hashchange $window.bind( $.support.pushState ? "popstate" : "hashchange", function() { setLastScrollEnabled = false; }); // handle initial hashchange from chrome :( $window.one( $.support.pushState ? "popstate" : "hashchange", function() { setLastScrollEnabled = true; }); // wait until the mobile page container has been determined to bind to pagechange $window.one( "pagecontainercreate", function() { // once the page has changed, re-enable the scroll recording $.mobile.pageContainer.bind( "pagechange", function() { setLastScrollEnabled = true; // remove any binding that previously existed on the get scroll // which may or may not be different than the scroll element determined for // this page previously $window.unbind( "scrollstop", delayedSetLastScroll ); // determine and bind to the current scoll element which may be the window // or in the case of touch overflow the element with touch overflow $window.bind( "scrollstop", delayedSetLastScroll ); }); }); // bind to scrollstop for the first page as "pagechange" won't be fired in that case $window.bind( "scrollstop", delayedSetLastScroll ); // No-op implementation of transition degradation $.mobile._maybeDegradeTransition = $.mobile._maybeDegradeTransition || function( transition ) { return transition; }; //function for transitioning between two existing pages function transitionPages( toPage, fromPage, transition, reverse ) { if ( fromPage ) { //trigger before show/hide events fromPage.data( "mobile-page" )._trigger( "beforehide", null, { nextPage: toPage } ); } toPage.data( "mobile-page" )._trigger( "beforeshow", null, { prevPage: fromPage || $( "" ) } ); //clear page loader $.mobile.hidePageLoadingMsg(); transition = $.mobile._maybeDegradeTransition( transition ); //find the transition handler for the specified transition. If there //isn't one in our transitionHandlers dictionary, use the default one. //call the handler immediately to kick-off the transition. var th = $.mobile.transitionHandlers[ transition || "default" ] || $.mobile.defaultTransitionHandler, promise = th( transition, reverse, toPage, fromPage ); promise.done(function() { //trigger show/hide events if ( fromPage ) { fromPage.data( "mobile-page" )._trigger( "hide", null, { nextPage: toPage } ); } //trigger pageshow, define prevPage as either fromPage or empty jQuery obj toPage.data( "mobile-page" )._trigger( "show", null, { prevPage: fromPage || $( "" ) } ); }); return promise; } //simply set the active page's minimum height to screen height, depending on orientation $.mobile.resetActivePageHeight = function resetActivePageHeight( height ) { var aPage = $( "." + $.mobile.activePageClass ), aPagePadT = parseFloat( aPage.css( "padding-top" ) ), aPagePadB = parseFloat( aPage.css( "padding-bottom" ) ), aPageBorderT = parseFloat( aPage.css( "border-top-width" ) ), aPageBorderB = parseFloat( aPage.css( "border-bottom-width" ) ); height = ( typeof height === "number" )? height : getScreenHeight(); aPage.css( "min-height", height - aPagePadT - aPagePadB - aPageBorderT - aPageBorderB ); }; //shared page enhancements function enhancePage( $page, role ) { // If a role was specified, make sure the data-role attribute // on the page element is in sync. if ( role ) { $page.attr( "data-" + $.mobile.ns + "role", role ); } //run page plugin $page.page(); } // determine the current base url function findBaseWithDefault() { var closestBase = ( $.mobile.activePage && getClosestBaseUrl( $.mobile.activePage ) ); return closestBase || documentBase.hrefNoHash; } /* exposed $.mobile methods */ //animation complete callback $.fn.animationComplete = function( callback ) { if ( $.support.cssTransitions ) { return $( this ).one( 'webkitAnimationEnd animationend', callback ); } else{ // defer execution for consistency between webkit/non webkit setTimeout( callback, 0 ); return $( this ); } }; //expose path object on $.mobile $.mobile.path = path; //expose base object on $.mobile $.mobile.base = base; //history stack $.mobile.urlHistory = urlHistory; $.mobile.dialogHashKey = dialogHashKey; //enable cross-domain page support $.mobile.allowCrossDomainPages = false; $.mobile._bindPageRemove = function() { var page = $( this ); // when dom caching is not enabled or the page is embedded bind to remove the page on hide if ( !page.data( "mobile-page" ).options.domCache && page.is( ":jqmData(external-page='true')" ) ) { page.bind( 'pagehide.remove', function( e ) { var $this = $( this ), prEvent = new $.Event( "pageremove" ); $this.trigger( prEvent ); if ( !prEvent.isDefaultPrevented() ) { $this.removeWithDependents(); } }); } }; // Load a page into the DOM. $.mobile.loadPage = function( url, options ) { // This function uses deferred notifications to let callers // know when the page is done loading, or if an error has occurred. var deferred = $.Deferred(), // The default loadPage options with overrides specified by // the caller. settings = $.extend( {}, $.mobile.loadPage.defaults, options ), // The DOM element for the page after it has been loaded. page = null, // If the reloadPage option is true, and the page is already // in the DOM, dupCachedPage will be set to the page element // so that it can be removed after the new version of the // page is loaded off the network. dupCachedPage = null, // The absolute version of the URL passed into the function. This // version of the URL may contain dialog/subpage params in it. absUrl = path.makeUrlAbsolute( url, findBaseWithDefault() ); // If the caller provided data, and we're using "get" request, // append the data to the URL. if ( settings.data && settings.type === "get" ) { absUrl = path.addSearchParams( absUrl, settings.data ); settings.data = undefined; } // If the caller is using a "post" request, reloadPage must be true if ( settings.data && settings.type === "post" ) { settings.reloadPage = true; } // The absolute version of the URL minus any dialog/subpage params. // In otherwords the real URL of the page to be loaded. var fileUrl = path.getFilePath( absUrl ), // The version of the Url actually stored in the data-url attribute of // the page. For embedded pages, it is just the id of the page. For pages // within the same domain as the document base, it is the site relative // path. For cross-domain pages (Phone Gap only) the entire absolute Url // used to load the page. dataUrl = path.convertUrlToDataUrl( absUrl ); // Make sure we have a pageContainer to work with. settings.pageContainer = settings.pageContainer || $.mobile.pageContainer; // Check to see if the page already exists in the DOM. // NOTE do _not_ use the :jqmData psuedo selector because parenthesis // are a valid url char and it breaks on the first occurence page = settings.pageContainer.children( "[data-" + $.mobile.ns +"url='" + dataUrl + "']" ); // If we failed to find the page, check to see if the url is a // reference to an embedded page. If so, it may have been dynamically // injected by a developer, in which case it would be lacking a data-url // attribute and in need of enhancement. if ( page.length === 0 && dataUrl && !path.isPath( dataUrl ) ) { page = settings.pageContainer.children( "#" + dataUrl ) .attr( "data-" + $.mobile.ns + "url", dataUrl ) .jqmData( "url", dataUrl ); } // If we failed to find a page in the DOM, check the URL to see if it // refers to the first page in the application. If it isn't a reference // to the first page and refers to non-existent embedded page, error out. if ( page.length === 0 ) { if ( $.mobile.firstPage && path.isFirstPageUrl( fileUrl ) ) { // Check to make sure our cached-first-page is actually // in the DOM. Some user deployed apps are pruning the first // page from the DOM for various reasons, we check for this // case here because we don't want a first-page with an id // falling through to the non-existent embedded page error // case. If the first-page is not in the DOM, then we let // things fall through to the ajax loading code below so // that it gets reloaded. if ( $.mobile.firstPage.parent().length ) { page = $( $.mobile.firstPage ); } } else if ( path.isEmbeddedPage( fileUrl ) ) { deferred.reject( absUrl, options ); return deferred.promise(); } } // If the page we are interested in is already in the DOM, // and the caller did not indicate that we should force a // reload of the file, we are done. Otherwise, track the // existing page as a duplicated. if ( page.length ) { if ( !settings.reloadPage ) { enhancePage( page, settings.role ); deferred.resolve( absUrl, options, page ); //if we are reloading the page make sure we update the base if its not a prefetch if( base && !options.prefetch ){ base.set(url); } return deferred.promise(); } dupCachedPage = page; } var mpc = settings.pageContainer, pblEvent = new $.Event( "pagebeforeload" ), triggerData = { url: url, absUrl: absUrl, dataUrl: dataUrl, deferred: deferred, options: settings }; // Let listeners know we're about to load a page. mpc.trigger( pblEvent, triggerData ); // If the default behavior is prevented, stop here! if ( pblEvent.isDefaultPrevented() ) { return deferred.promise(); } if ( settings.showLoadMsg ) { // This configurable timeout allows cached pages a brief delay to load without showing a message var loadMsgDelay = setTimeout(function() { $.mobile.showPageLoadingMsg(); }, settings.loadMsgDelay ), // Shared logic for clearing timeout and removing message. hideMsg = function() { // Stop message show timer clearTimeout( loadMsgDelay ); // Hide loading message $.mobile.hidePageLoadingMsg(); }; } // Reset base to the default document base. // only reset if we are not prefetching if ( base && typeof options.prefetch === "undefined" ) { base.reset(); } if ( !( $.mobile.allowCrossDomainPages || path.isSameDomain( documentUrl, absUrl ) ) ) { deferred.reject( absUrl, options ); } else { // Load the new page. $.ajax({ url: fileUrl, type: settings.type, data: settings.data, contentType: settings.contentType, dataType: "html", success: function( html, textStatus, xhr ) { //pre-parse html to check for a data-url, //use it as the new fileUrl, base path, etc var all = $( "<div></div>" ), //page title regexp newPageTitle = html.match( /<title[^>]*>([^<]*)/ ) && RegExp.$1, // TODO handle dialogs again pageElemRegex = new RegExp( "(<[^>]+\\bdata-" + $.mobile.ns + "role=[\"']?page[\"']?[^>]*>)" ), dataUrlRegex = new RegExp( "\\bdata-" + $.mobile.ns + "url=[\"']?([^\"'>]*)[\"']?" ); // data-url must be provided for the base tag so resource requests can be directed to the // correct url. loading into a temprorary element makes these requests immediately if ( pageElemRegex.test( html ) && RegExp.$1 && dataUrlRegex.test( RegExp.$1 ) && RegExp.$1 ) { url = fileUrl = path.getFilePath( $( "<div>" + RegExp.$1 + "</div>" ).text() ); } //dont update the base tag if we are prefetching if ( base && typeof options.prefetch === "undefined") { base.set( fileUrl ); } //workaround to allow scripts to execute when included in page divs all.get( 0 ).innerHTML = html; page = all.find( ":jqmData(role='page'), :jqmData(role='dialog')" ).first(); //if page elem couldn't be found, create one and insert the body element's contents if ( !page.length ) { page = $( "<div data-" + $.mobile.ns + "role='page'>" + ( html.split( /<\/?body[^>]*>/gmi )[1] || "" ) + "</div>" ); } if ( newPageTitle && !page.jqmData( "title" ) ) { if ( ~newPageTitle.indexOf( "&" ) ) { newPageTitle = $( "<div>" + newPageTitle + "</div>" ).text(); } page.jqmData( "title", newPageTitle ); } //rewrite src and href attrs to use a base url if ( !$.support.dynamicBaseTag ) { var newPath = path.get( fileUrl ); page.find( "[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]" ).each(function() { var thisAttr = $( this ).is( '[href]' ) ? 'href' : $( this ).is( '[src]' ) ? 'src' : 'action', thisUrl = $( this ).attr( thisAttr ); // XXX_jblas: We need to fix this so that it removes the document // base URL, and then prepends with the new page URL. //if full path exists and is same, chop it - helps IE out thisUrl = thisUrl.replace( location.protocol + '//' + location.host + location.pathname, '' ); if ( !/^(\w+:|#|\/)/.test( thisUrl ) ) { $( this ).attr( thisAttr, newPath + thisUrl ); } }); } //append to page and enhance // TODO taging a page with external to make sure that embedded pages aren't removed // by the various page handling code is bad. Having page handling code in many // places is bad. Solutions post 1.0 page .attr( "data-" + $.mobile.ns + "url", path.convertUrlToDataUrl( fileUrl ) ) .attr( "data-" + $.mobile.ns + "external-page", true ) .appendTo( settings.pageContainer ); // wait for page creation to leverage options defined on widget page.one( 'pagecreate', $.mobile._bindPageRemove ); enhancePage( page, settings.role ); // Enhancing the page may result in new dialogs/sub pages being inserted // into the DOM. If the original absUrl refers to a sub-page, that is the // real page we are interested in. if ( absUrl.indexOf( "&" + $.mobile.subPageUrlKey ) > -1 ) { page = settings.pageContainer.children( "[data-" + $.mobile.ns +"url='" + dataUrl + "']" ); } // Remove loading message. if ( settings.showLoadMsg ) { hideMsg(); } // Add the page reference and xhr to our triggerData. triggerData.xhr = xhr; triggerData.textStatus = textStatus; triggerData.page = page; // Let listeners know the page loaded successfully. settings.pageContainer.trigger( "pageload", triggerData ); deferred.resolve( absUrl, options, page, dupCachedPage ); }, error: function( xhr, textStatus, errorThrown ) { //set base back to current path if ( base ) { base.set( path.get() ); } // Add error info to our triggerData. triggerData.xhr = xhr; triggerData.textStatus = textStatus; triggerData.errorThrown = errorThrown; var plfEvent = new $.Event( "pageloadfailed" ); // Let listeners know the page load failed. settings.pageContainer.trigger( plfEvent, triggerData ); // If the default behavior is prevented, stop here! // Note that it is the responsibility of the listener/handler // that called preventDefault(), to resolve/reject the // deferred object within the triggerData. if ( plfEvent.isDefaultPrevented() ) { return; } // Remove loading message. if ( settings.showLoadMsg ) { // Remove loading message. hideMsg(); // show error message $.mobile.showPageLoadingMsg( $.mobile.pageLoadErrorMessageTheme, $.mobile.pageLoadErrorMessage, true ); // hide after delay setTimeout( $.mobile.hidePageLoadingMsg, 1500 ); } deferred.reject( absUrl, options ); } }); } return deferred.promise(); }; $.mobile.loadPage.defaults = { type: "get", data: undefined, reloadPage: false, role: undefined, // By default we rely on the role defined by the @data-role attribute. showLoadMsg: false, pageContainer: undefined, loadMsgDelay: 50 // This delay allows loads that pull from browser cache to occur without showing the loading message. }; // Show a specific page in the page container. $.mobile.changePage = function( toPage, options ) { // If we are in the midst of a transition, queue the current request. // We'll call changePage() once we're done with the current transition to // service the request. if ( isPageTransitioning ) { pageTransitionQueue.unshift( arguments ); return; } var settings = $.extend( {}, $.mobile.changePage.defaults, options ), isToPageString; // Make sure we have a pageContainer to work with. settings.pageContainer = settings.pageContainer || $.mobile.pageContainer; // Make sure we have a fromPage. settings.fromPage = settings.fromPage || $.mobile.activePage; isToPageString = (typeof toPage === "string"); var mpc = settings.pageContainer, pbcEvent = new $.Event( "pagebeforechange" ), triggerData = { toPage: toPage, options: settings }; // NOTE: preserve the original target as the dataUrl value will be simplified // eg, removing ui-state, and removing query params from the hash // this is so that users who want to use query params have access to them // in the event bindings for the page life cycle See issue #5085 if ( isToPageString ) { // if the toPage is a string simply convert it triggerData.absUrl = path.makeUrlAbsolute( toPage, findBaseWithDefault() ); } else { // if the toPage is a jQuery object grab the absolute url stored // in the loadPage callback where it exists triggerData.absUrl = toPage.data( 'absUrl' ); } // Let listeners know we're about to change the current page. mpc.trigger( pbcEvent, triggerData ); // If the default behavior is prevented, stop here! if ( pbcEvent.isDefaultPrevented() ) { return; } // We allow "pagebeforechange" observers to modify the toPage in the trigger // data to allow for redirects. Make sure our toPage is updated. // // We also need to re-evaluate whether it is a string, because an object can // also be replaced by a string toPage = triggerData.toPage; isToPageString = (typeof toPage === "string"); // Set the isPageTransitioning flag to prevent any requests from // entering this method while we are in the midst of loading a page // or transitioning. isPageTransitioning = true; // If the caller passed us a url, call loadPage() // to make sure it is loaded into the DOM. We'll listen // to the promise object it returns so we know when // it is done loading or if an error ocurred. if ( isToPageString ) { // preserve the original target as the dataUrl value will be simplified // eg, removing ui-state, and removing query params from the hash // this is so that users who want to use query params have access to them // in the event bindings for the page life cycle See issue #5085 settings.target = toPage; $.mobile.loadPage( toPage, settings ) .done(function( url, options, newPage, dupCachedPage ) { isPageTransitioning = false; options.duplicateCachedPage = dupCachedPage; // store the original absolute url so that it can be provided // to events in the triggerData of the subsequent changePage call newPage.data( 'absUrl', triggerData.absUrl ); $.mobile.changePage( newPage, options ); }) .fail(function( url, options ) { //clear out the active button state removeActiveLinkClass( true ); //release transition lock so navigation is free again releasePageTransitionLock(); settings.pageContainer.trigger( "pagechangefailed", triggerData ); }); return; } // If we are going to the first-page of the application, we need to make // sure settings.dataUrl is set to the application document url. This allows // us to avoid generating a document url with an id hash in the case where the // first-page of the document has an id attribute specified. if ( toPage[ 0 ] === $.mobile.firstPage[ 0 ] && !settings.dataUrl ) { settings.dataUrl = documentUrl.hrefNoHash; } // The caller passed us a real page DOM element. Update our // internal state and then trigger a transition to the page. var fromPage = settings.fromPage, url = ( settings.dataUrl && path.convertUrlToDataUrl( settings.dataUrl ) ) || toPage.jqmData( "url" ), // The pageUrl var is usually the same as url, except when url is obscured as a dialog url. pageUrl always contains the file path pageUrl = url, fileUrl = path.getFilePath( url ), active = urlHistory.getActive(), activeIsInitialPage = urlHistory.activeIndex === 0, historyDir = 0, pageTitle = document.title, isDialog = settings.role === "dialog" || toPage.jqmData( "role" ) === "dialog"; // By default, we prevent changePage requests when the fromPage and toPage // are the same element, but folks that generate content manually/dynamically // and reuse pages want to be able to transition to the same page. To allow // this, they will need to change the default value of allowSamePageTransition // to true, *OR*, pass it in as an option when they manually call changePage(). // It should be noted that our default transition animations assume that the // formPage and toPage are different elements, so they may behave unexpectedly. // It is up to the developer that turns on the allowSamePageTransitiona option // to either turn off transition animations, or make sure that an appropriate // animation transition is used. if ( fromPage && fromPage[0] === toPage[0] && !settings.allowSamePageTransition ) { isPageTransitioning = false; mpc.trigger( "pagechange", triggerData ); // Even if there is no page change to be done, we should keep the urlHistory in sync with the hash changes if ( settings.fromHashChange ) { urlHistory.direct({ url: url }); } return; } // We need to make sure the page we are given has already been enhanced. enhancePage( toPage, settings.role ); // If the changePage request was sent from a hashChange event, check to see if the // page is already within the urlHistory stack. If so, we'll assume the user hit // the forward/back button and will try to match the transition accordingly. if ( settings.fromHashChange ) { historyDir = options.direction === "back" ? -1 : 1; } // Kill the keyboard. // XXX_jblas: We need to stop crawling the entire document to kill focus. Instead, // we should be tracking focus with a delegate() handler so we already have // the element in hand at this point. // Wrap this in a try/catch block since IE9 throw "Unspecified error" if document.activeElement // is undefined when we are in an IFrame. try { if ( document.activeElement && document.activeElement.nodeName.toLowerCase() !== 'body' ) { $( document.activeElement ).blur(); } else { $( "input:focus, textarea:focus, select:focus" ).blur(); } } catch( e ) {} // Record whether we are at a place in history where a dialog used to be - if so, do not add a new history entry and do not change the hash either var alreadyThere = false; // If we're displaying the page as a dialog, we don't want the url // for the dialog content to be used in the hash. Instead, we want // to append the dialogHashKey to the url of the current page. if ( isDialog && active ) { // on the initial page load active.url is undefined and in that case should // be an empty string. Moving the undefined -> empty string back into // urlHistory.addNew seemed imprudent given undefined better represents // the url state // If we are at a place in history that once belonged to a dialog, reuse // this state without adding to urlHistory and without modifying the hash. // However, if a dialog is already displayed at this point, and we're // about to display another dialog, then we must add another hash and // history entry on top so that one may navigate back to the original dialog if ( active.url && active.url.indexOf( dialogHashKey ) > -1 && $.mobile.activePage && !$.mobile.activePage.hasClass( "ui-dialog" ) && urlHistory.activeIndex > 0 ) { settings.changeHash = false; alreadyThere = true; } // Normally, we tack on a dialog hash key, but if this is the location of a stale dialog, // we reuse the URL from the entry url = ( active.url || "" ); // account for absolute urls instead of just relative urls use as hashes if( !alreadyThere && url.indexOf("#") > -1 ) { url += dialogHashKey; } else { url += "#" + dialogHashKey; } // tack on another dialogHashKey if this is the same as the initial hash // this makes sure that a history entry is created for this dialog if ( urlHistory.activeIndex === 0 && url === urlHistory.initialDst ) { url += dialogHashKey; } } // if title element wasn't found, try the page div data attr too // If this is a deep-link or a reload ( active === undefined ) then just use pageTitle var newPageTitle = ( !active )? pageTitle : toPage.jqmData( "title" ) || toPage.children( ":jqmData(role='header')" ).find( ".ui-title" ).text(); if ( !!newPageTitle && pageTitle === document.title ) { pageTitle = newPageTitle; } if ( !toPage.jqmData( "title" ) ) { toPage.jqmData( "title", pageTitle ); } // Make sure we have a transition defined. settings.transition = settings.transition || ( ( historyDir && !activeIsInitialPage ) ? active.transition : undefined ) || ( isDialog ? $.mobile.defaultDialogTransition : $.mobile.defaultPageTransition ); //add page to history stack if it's not back or forward if ( !historyDir && alreadyThere ) { urlHistory.getActive().pageUrl = pageUrl; } // Set the location hash. if ( url && !settings.fromHashChange ) { var params; // rebuilding the hash here since we loose it earlier on // TODO preserve the originally passed in path if( !path.isPath( url ) && url.indexOf( "#" ) < 0 ) { url = "#" + url; } // TODO the property names here are just silly params = { transition: settings.transition, title: pageTitle, pageUrl: pageUrl, role: settings.role }; if ( settings.changeHash !== false && $.mobile.hashListeningEnabled ) { $.mobile.navigate( url, params, true); } else if ( toPage[ 0 ] !== $.mobile.firstPage[ 0 ] ) { $.mobile.navigate.history.add( url, params ); } } //set page title document.title = pageTitle; //set "toPage" as activePage $.mobile.activePage = toPage; // If we're navigating back in the URL history, set reverse accordingly. settings.reverse = settings.reverse || historyDir < 0; transitionPages( toPage, fromPage, settings.transition, settings.reverse ) .done(function( name, reverse, $to, $from, alreadyFocused ) { removeActiveLinkClass(); //if there's a duplicateCachedPage, remove it from the DOM now that it's hidden if ( settings.duplicateCachedPage ) { settings.duplicateCachedPage.remove(); } // Send focus to the newly shown page. Moved from promise .done binding in transitionPages // itself to avoid ie bug that reports offsetWidth as > 0 (core check for visibility) // despite visibility: hidden addresses issue #2965 // https://github.com/jquery/jquery-mobile/issues/2965 if ( !alreadyFocused ) { $.mobile.focusPage( toPage ); } releasePageTransitionLock(); mpc.trigger( "pagechange", triggerData ); }); }; $.mobile.changePage.defaults = { transition: undefined, reverse: false, changeHash: true, fromHashChange: false, role: undefined, // By default we rely on the role defined by the @data-role attribute. duplicateCachedPage: undefined, pageContainer: undefined, showLoadMsg: true, //loading message shows by default when pages are being fetched during changePage dataUrl: undefined, fromPage: undefined, allowSamePageTransition: false }; /* Event Bindings - hashchange, submit, and click */ function findClosestLink( ele ) { while ( ele ) { // Look for the closest element with a nodeName of "a". // Note that we are checking if we have a valid nodeName // before attempting to access it. This is because the // node we get called with could have originated from within // an embedded SVG document where some symbol instance elements // don't have nodeName defined on them, or strings are of type // SVGAnimatedString. if ( ( typeof ele.nodeName === "string" ) && ele.nodeName.toLowerCase() === "a" ) { break; } ele = ele.parentNode; } return ele; } // The base URL for any given element depends on the page it resides in. function getClosestBaseUrl( ele ) { // Find the closest page and extract out its url. var url = $( ele ).closest( ".ui-page" ).jqmData( "url" ), base = documentBase.hrefNoHash; if ( !url || !path.isPath( url ) ) { url = base; } return path.makeUrlAbsolute( url, base); } //The following event bindings should be bound after mobileinit has been triggered //the following deferred is resolved in the init file $.mobile.navreadyDeferred = $.Deferred(); $.mobile._registerInternalEvents = function() { var getAjaxFormData = function( $form, calculateOnly ) { var url, ret = true, formData, vclickedName, method; if ( !$.mobile.ajaxEnabled || // test that the form is, itself, ajax false $form.is( ":jqmData(ajax='false')" ) || // test that $.mobile.ignoreContentEnabled is set and // the form or one of it's parents is ajax=false !$form.jqmHijackable().length || $form.attr( "target" ) ) { return false; } url = $form.attr( "action" ); method = ( $form.attr( "method" ) || "get" ).toLowerCase(); // If no action is specified, browsers default to using the // URL of the document containing the form. Since we dynamically // pull in pages from external documents, the form should submit // to the URL for the source document of the page containing // the form. if ( !url ) { // Get the @data-url for the page containing the form. url = getClosestBaseUrl( $form ); // NOTE: If the method is "get", we need to strip off the query string // because it will get replaced with the new form data. See issue #5710. if ( method === "get" ) { url = path.parseUrl( url ).hrefNoSearch; } if ( url === documentBase.hrefNoHash ) { // The url we got back matches the document base, // which means the page must be an internal/embedded page, // so default to using the actual document url as a browser // would. url = documentUrl.hrefNoSearch; } } url = path.makeUrlAbsolute( url, getClosestBaseUrl( $form ) ); if ( ( path.isExternal( url ) && !path.isPermittedCrossDomainRequest( documentUrl, url ) ) ) { return false; } if ( !calculateOnly ) { formData = $form.serializeArray(); if ( $lastVClicked && $lastVClicked[ 0 ].form === $form[ 0 ] ) { vclickedName = $lastVClicked.attr( "name" ); if ( vclickedName ) { // Make sure the last clicked element is included in the form $.each( formData, function( key, value ) { if ( value.name === vclickedName ) { // Unset vclickedName - we've found it in the serialized data already vclickedName = ""; return false; } }); if ( vclickedName ) { formData.push( { name: vclickedName, value: $lastVClicked.attr( "value" ) } ); } } } ret = { url: url, options: { type: method, data: $.param( formData ), transition: $form.jqmData( "transition" ), reverse: $form.jqmData( "direction" ) === "reverse", reloadPage: true } }; } return ret; }; //bind to form submit events, handle with Ajax $.mobile.document.delegate( "form", "submit", function( event ) { var formData = getAjaxFormData( $( this ) ); if ( formData ) { $.mobile.changePage( formData.url, formData.options ); event.preventDefault(); } }); //add active state on vclick $.mobile.document.bind( "vclick", function( event ) { var $btn, btnEls, target = event.target, needClosest = false; // if this isn't a left click we don't care. Its important to note // that when the virtual event is generated it will create the which attr if ( event.which > 1 || !$.mobile.linkBindingEnabled ) { return; } // Record that this element was clicked, in case we need it for correct // form submission during the "submit" handler above $lastVClicked = $( target ); // Try to find a target element to which the active class will be applied if ( $.data( target, "mobile-button" ) ) { // If the form will not be submitted via AJAX, do not add active class if ( !getAjaxFormData( $( target ).closest( "form" ), true ) ) { return; } // We will apply the active state to this button widget - the parent // of the input that was clicked will have the associated data if ( target.parentNode ) { target = target.parentNode; } } else { target = findClosestLink( target ); if ( !( target && path.parseUrl( target.getAttribute( "href" ) || "#" ).hash !== "#" ) ) { return; } // TODO teach $.mobile.hijackable to operate on raw dom elements so the // link wrapping can be avoided if ( !$( target ).jqmHijackable().length ) { return; } } // Avoid calling .closest by using the data set during .buttonMarkup() // List items have the button data in the parent of the element clicked if ( !!~target.className.indexOf( "ui-link-inherit" ) ) { if ( target.parentNode ) { btnEls = $.data( target.parentNode, "buttonElements" ); } // Otherwise, look for the data on the target itself } else { btnEls = $.data( target, "buttonElements" ); } // If found, grab the button's outer element if ( btnEls ) { target = btnEls.outer; } else { needClosest = true; } $btn = $( target ); // If the outer element wasn't found by the our heuristics, use .closest() if ( needClosest ) { $btn = $btn.closest( ".ui-btn" ); } if ( $btn.length > 0 && !$btn.hasClass( "ui-disabled" ) ) { removeActiveLinkClass( true ); $activeClickedLink = $btn; $activeClickedLink.addClass( $.mobile.activeBtnClass ); } }); // click routing - direct to HTTP or Ajax, accordingly $.mobile.document.bind( "click", function( event ) { if ( !$.mobile.linkBindingEnabled || event.isDefaultPrevented() ) { return; } var link = findClosestLink( event.target ), $link = $( link ), httpCleanup; // If there is no link associated with the click or its not a left // click we want to ignore the click // TODO teach $.mobile.hijackable to operate on raw dom elements so the link wrapping // can be avoided if ( !link || event.which > 1 || !$link.jqmHijackable().length ) { return; } //remove active link class if external (then it won't be there if you come back) httpCleanup = function() { window.setTimeout(function() { removeActiveLinkClass( true ); }, 200 ); }; //if there's a data-rel=back attr, go back in history if ( $link.is( ":jqmData(rel='back')" ) ) { $.mobile.back(); return false; } var baseUrl = getClosestBaseUrl( $link ), //get href, if defined, otherwise default to empty hash href = path.makeUrlAbsolute( $link.attr( "href" ) || "#", baseUrl ); //if ajax is disabled, exit early if ( !$.mobile.ajaxEnabled && !path.isEmbeddedPage( href ) ) { httpCleanup(); //use default click handling return; } // XXX_jblas: Ideally links to application pages should be specified as // an url to the application document with a hash that is either // the site relative path or id to the page. But some of the // internal code that dynamically generates sub-pages for nested // lists and select dialogs, just write a hash in the link they // create. This means the actual URL path is based on whatever // the current value of the base tag is at the time this code // is called. For now we are just assuming that any url with a // hash in it is an application page reference. if ( href.search( "#" ) !== -1 ) { href = href.replace( /[^#]*#/, "" ); if ( !href ) { //link was an empty hash meant purely //for interaction, so we ignore it. event.preventDefault(); return; } else if ( path.isPath( href ) ) { //we have apath so make it the href we want to load. href = path.makeUrlAbsolute( href, baseUrl ); } else { //we have a simple id so use the documentUrl as its base. href = path.makeUrlAbsolute( "#" + href, documentUrl.hrefNoHash ); } } // Should we handle this link, or let the browser deal with it? var useDefaultUrlHandling = $link.is( "[rel='external']" ) || $link.is( ":jqmData(ajax='false')" ) || $link.is( "[target]" ), // Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR // requests if the document doing the request was loaded via the file:// protocol. // This is usually to allow the application to "phone home" and fetch app specific // data. We normally let the browser handle external/cross-domain urls, but if the // allowCrossDomainPages option is true, we will allow cross-domain http/https // requests to go through our page loading logic. //check for protocol or rel and its not an embedded page //TODO overlap in logic from isExternal, rel=external check should be // moved into more comprehensive isExternalLink isExternal = useDefaultUrlHandling || ( path.isExternal( href ) && !path.isPermittedCrossDomainRequest( documentUrl, href ) ); if ( isExternal ) { httpCleanup(); //use default click handling return; } //use ajax var transition = $link.jqmData( "transition" ), reverse = $link.jqmData( "direction" ) === "reverse" || // deprecated - remove by 1.0 $link.jqmData( "back" ), //this may need to be more specific as we use data-rel more role = $link.attr( "data-" + $.mobile.ns + "rel" ) || undefined; $.mobile.changePage( href, { transition: transition, reverse: reverse, role: role, link: $link } ); event.preventDefault(); }); //prefetch pages when anchors with data-prefetch are encountered $.mobile.document.delegate( ".ui-page", "pageshow.prefetch", function() { var urls = []; $( this ).find( "a:jqmData(prefetch)" ).each(function() { var $link = $( this ), url = $link.attr( "href" ); if ( url && $.inArray( url, urls ) === -1 ) { urls.push( url ); $.mobile.loadPage( url, { role: $link.attr( "data-" + $.mobile.ns + "rel" ),prefetch: true } ); } }); }); $.mobile._handleHashChange = function( url, data ) { //find first page via hash var to = path.stripHash(url), //transition is false if it's the first page, undefined otherwise (and may be overridden by default) transition = $.mobile.urlHistory.stack.length === 0 ? "none" : undefined, // default options for the changPage calls made after examining the current state // of the page and the hash, NOTE that the transition is derived from the previous // history entry changePageOptions = { changeHash: false, fromHashChange: true, reverse: data.direction === "back" }; $.extend( changePageOptions, data, { transition: (urlHistory.getLast() || {}).transition || transition }); // special case for dialogs if ( urlHistory.activeIndex > 0 && to.indexOf( dialogHashKey ) > -1 && urlHistory.initialDst !== to ) { // If current active page is not a dialog skip the dialog and continue // in the same direction if ( $.mobile.activePage && !$.mobile.activePage.hasClass( "ui-dialog" ) ) { //determine if we're heading forward or backward and continue accordingly past //the current dialog if( data.direction === "back" ) { $.mobile.back(); } else { window.history.forward(); } // prevent changePage call return; } else { // if the current active page is a dialog and we're navigating // to a dialog use the dialog objected saved in the stack to = data.pageUrl; var active = $.mobile.urlHistory.getActive(); // make sure to set the role, transition and reversal // as most of this is lost by the domCache cleaning $.extend( changePageOptions, { role: active.role, transition: active.transition, reverse: data.direction === "back" }); } } //if to is defined, load it if ( to ) { // At this point, 'to' can be one of 3 things, a cached page element from // a history stack entry, an id, or site-relative/absolute URL. If 'to' is // an id, we need to resolve it against the documentBase, not the location.href, // since the hashchange could've been the result of a forward/backward navigation // that crosses from an external page/dialog to an internal page/dialog. to = !path.isPath( to ) ? ( path.makeUrlAbsolute( '#' + to, documentBase ) ) : to; // If we're about to go to an initial URL that contains a reference to a non-existent // internal page, go to the first page instead. We know that the initial hash refers to a // non-existent page, because the initial hash did not end up in the initial urlHistory entry if ( to === path.makeUrlAbsolute( '#' + urlHistory.initialDst, documentBase ) && urlHistory.stack.length && urlHistory.stack[0].url !== urlHistory.initialDst.replace( dialogHashKey, "" ) ) { to = $.mobile.firstPage; } $.mobile.changePage( to, changePageOptions ); } else { //there's no hash, go to the first page in the dom $.mobile.changePage( $.mobile.firstPage, changePageOptions ); } }; // TODO roll the logic here into the handleHashChange method $window.bind( "navigate", function( e, data ) { var url; if ( e.originalEvent && e.originalEvent.isDefaultPrevented() ) { return; } url = $.event.special.navigate.originalEventName.indexOf( "hashchange" ) > -1 ? data.state.hash : data.state.url; if( !url ) { url = $.mobile.path.parseLocation().hash; } if( !url || url === "#" || url.indexOf( "#" + $.mobile.path.uiStateKey ) === 0 ){ url = location.href; } $.mobile._handleHashChange( url, data.state ); }); //set page min-heights to be device specific $.mobile.document.bind( "pageshow", $.mobile.resetActivePageHeight ); $.mobile.window.bind( "throttledresize", $.mobile.resetActivePageHeight ); };//navreadyDeferred done callback $( function() { domreadyDeferred.resolve(); } ); $.when( domreadyDeferred, $.mobile.navreadyDeferred ).done( function() { $.mobile._registerInternalEvents(); } ); })( jQuery ); /* * fallback transition for flip in non-3D supporting browsers (which tend to handle complex transitions poorly in general */ (function( $, window, undefined ) { $.mobile.transitionFallbacks.flip = "fade"; })( jQuery, this ); /* * fallback transition for flow in non-3D supporting browsers (which tend to handle complex transitions poorly in general */ (function( $, window, undefined ) { $.mobile.transitionFallbacks.flow = "fade"; })( jQuery, this ); /* * fallback transition for pop in non-3D supporting browsers (which tend to handle complex transitions poorly in general */ (function( $, window, undefined ) { $.mobile.transitionFallbacks.pop = "fade"; })( jQuery, this ); /* * fallback transition for slide in non-3D supporting browsers (which tend to handle complex transitions poorly in general */ (function( $, window, undefined ) { // Use the simultaneous transitions handler for slide transitions $.mobile.transitionHandlers.slide = $.mobile.transitionHandlers.simultaneous; // Set the slide transitions's fallback to "fade" $.mobile.transitionFallbacks.slide = "fade"; })( jQuery, this ); /* * fallback transition for slidedown in non-3D supporting browsers (which tend to handle complex transitions poorly in general */ (function( $, window, undefined ) { $.mobile.transitionFallbacks.slidedown = "fade"; })( jQuery, this ); /* * fallback transition for slidefade in non-3D supporting browsers (which tend to handle complex transitions poorly in general */ (function( $, window, undefined ) { // Set the slide transitions's fallback to "fade" $.mobile.transitionFallbacks.slidefade = "fade"; })( jQuery, this ); /* * fallback transition for slideup in non-3D supporting browsers (which tend to handle complex transitions poorly in general */ (function( $, window, undefined ) { $.mobile.transitionFallbacks.slideup = "fade"; })( jQuery, this ); /* * fallback transition for turn in non-3D supporting browsers (which tend to handle complex transitions poorly in general */ (function( $, window, undefined ) { $.mobile.transitionFallbacks.turn = "fade"; })( jQuery, this ); (function( $, undefined ) { $.mobile.page.prototype.options.degradeInputs = { color: false, date: false, datetime: false, "datetime-local": false, email: false, month: false, number: false, range: "number", search: "text", tel: false, time: false, url: false, week: false }; //auto self-init widgets $.mobile.document.bind( "pagecreate create", function( e ) { var page = $.mobile.closestPageData( $( e.target ) ), options; if ( !page ) { return; } options = page.options; // degrade inputs to avoid poorly implemented native functionality $( e.target ).find( "input" ).not( page.keepNativeSelector() ).each(function() { var $this = $( this ), type = this.getAttribute( "type" ), optType = options.degradeInputs[ type ] || "text"; if ( options.degradeInputs[ type ] ) { var html = $( "<div>" ).html( $this.clone() ).html(), // In IE browsers, the type sometimes doesn't exist in the cloned markup, so we replace the closing tag instead hasType = html.indexOf( " type=" ) > -1, findstr = hasType ? /\s+type=["']?\w+['"]?/ : /\/?>/, repstr = " type=\"" + optType + "\" data-" + $.mobile.ns + "type=\"" + type + "\"" + ( hasType ? "" : ">" ); $this.replaceWith( html.replace( findstr, repstr ) ); } }); }); })( jQuery ); (function( $, window, undefined ) { $.widget( "mobile.dialog", $.mobile.widget, { options: { closeBtn: "left", closeBtnText: "Close", overlayTheme: "a", corners: true, initSelector: ":jqmData(role='dialog')" }, // Override the theme set by the page plugin on pageshow _handlePageBeforeShow: function() { this._isCloseable = true; if ( this.options.overlayTheme ) { this.element .page( "removeContainerBackground" ) .page( "setContainerBackground", this.options.overlayTheme ); } }, _create: function() { var $el = this.element, cornerClass = !!this.options.corners ? " ui-corner-all" : "", dialogWrap = $( "<div/>", { "role" : "dialog", "class" : "ui-dialog-contain ui-overlay-shadow" + cornerClass }); $el.addClass( "ui-dialog ui-overlay-" + this.options.overlayTheme ); // Class the markup for dialog styling // Set aria role $el.wrapInner( dialogWrap ); /* bind events - clicks and submits should use the closing transition that the dialog opened with unless a data-transition is specified on the link/form - if the click was on the close button, or the link has a data-rel="back" it'll go back in history naturally */ $el.bind( "vclick submit", function( event ) { var $target = $( event.target ).closest( event.type === "vclick" ? "a" : "form" ), active; if ( $target.length && !$target.jqmData( "transition" ) ) { active = $.mobile.urlHistory.getActive() || {}; $target.attr( "data-" + $.mobile.ns + "transition", ( active.transition || $.mobile.defaultDialogTransition ) ) .attr( "data-" + $.mobile.ns + "direction", "reverse" ); } }); this._on( $el, { pagebeforeshow: "_handlePageBeforeShow" }); $.extend( this, { _createComplete: false }); this._setCloseBtn( this.options.closeBtn ); }, _setCorners: function( value ) { this.element.children().toggleClass( "ui-corner-all", value ); }, _setOverlayTheme: function( value ) { this.element .removeClass( "ui-overlay-" + this.options.overlayTheme ) .addClass( "ui-overlay-" + value ); if ( $.mobile.activePage[ 0 ] === this.element[ 0 ] ) { this.options.overlayTheme = value; this._handlePageBeforeShow(); } }, _setCloseBtnText: function( value ) { this.options.closeBtnText = value; this._setCloseBtn( this.options.closeBtn ); }, _setCloseBtn: function( value ) { var self = this, btn, location; if ( this._headerCloseButton ) { this._headerCloseButton.remove(); this._headerCloseButton = null; } if ( value !== "none" ) { // Sanitize value location = ( value === "left" ? "left" : "right" ); btn = $( "<a href='#' class='ui-btn-" + location + "' data-" + $.mobile.ns + "icon='delete' data-" + $.mobile.ns + "iconpos='notext'>"+ this.options.closeBtnText + "</a>" ); this.element.children().find( ":jqmData(role='header')" ).first().prepend( btn ); if ( this._createComplete && $.fn.buttonMarkup ) { btn.buttonMarkup(); } this._createComplete = true; // this must be an anonymous function so that select menu dialogs can replace // the close method. This is a change from previously just defining data-rel=back // on the button and letting nav handle it // // Use click rather than vclick in order to prevent the possibility of unintentionally // reopening the dialog if the dialog opening item was directly under the close button. btn.bind( "click", function() { self.close(); }); this._headerCloseButton = btn; } }, _setOption: function( key, value ) { var setter = "_set" + key.charAt( 0 ).toUpperCase() + key.slice( 1 ); if ( this[ setter ] !== undefined ) { this[ setter ]( value ); } this._super( key, value ); }, // Close method goes back in history close: function() { var idx, dst, hist = $.mobile.navigate.history; if ( this._isCloseable ) { this._isCloseable = false; // If the hash listening is enabled and there is at least one preceding history // entry it's ok to go back. Initial pages with the dialog hash state are an example // where the stack check is necessary if ( $.mobile.hashListeningEnabled && hist.activeIndex > 0 ) { $.mobile.back(); } else { idx = Math.max( 0, hist.activeIndex - 1 ); dst = hist.stack[ idx ].pageUrl || hist.stack[ idx ].url; hist.previousIndex = hist.activeIndex; hist.activeIndex = idx; if ( !$.mobile.path.isPath( dst ) ) { dst = $.mobile.path.makeUrlAbsolute( "#" + dst ); } $.mobile.changePage( dst, { direction: "back", changeHash: false, fromHashChange: true } ); } } } }); //auto self-init widgets $.mobile.document.delegate( $.mobile.dialog.prototype.options.initSelector, "pagecreate", function() { $.mobile.dialog.prototype.enhance( this ); }); })( jQuery, this ); (function( $, undefined ) { $.mobile.page.prototype.options.backBtnText = "Back"; $.mobile.page.prototype.options.addBackBtn = false; $.mobile.page.prototype.options.backBtnTheme = null; $.mobile.page.prototype.options.headerTheme = "a"; $.mobile.page.prototype.options.footerTheme = "a"; $.mobile.page.prototype.options.contentTheme = null; // NOTE bind used to force this binding to run before the buttonMarkup binding // which expects .ui-footer top be applied in its gigantic selector // TODO remove the buttonMarkup giant selector and move it to the various modules // on which it depends $.mobile.document.bind( "pagecreate", function( e ) { var $page = $( e.target ), o = $page.data( "mobile-page" ).options, attrPrefix = "data-" + $.mobile.ns, pageRole = $page[ 0 ].getAttribute( attrPrefix + "role" ) || undefined, pageTheme = o.theme; $( ":jqmData(role='header'), :jqmData(role='footer'), :jqmData(role='content')", $page ) .jqmEnhanceable() .each(function() { var $this = $( this ), role = $this[ 0 ].getAttribute( attrPrefix + "role" ) || undefined, theme = $this[ 0 ].getAttribute( attrPrefix + "theme" ) || undefined, contentTheme = theme || o.contentTheme || ( pageRole === "dialog" && pageTheme ), $headeranchors, leftbtn, rightbtn, backBtn; $this.addClass( "ui-" + role ); //apply theming and markup modifications to page,header,content,footer if ( role === "header" || role === "footer" ) { var thisTheme = theme || ( role === "header" ? o.headerTheme : o.footerTheme ) || pageTheme; $this //add theme class .addClass( "ui-bar-" + thisTheme ) // Add ARIA role .attr( "role", role === "header" ? "banner" : "contentinfo" ); if ( role === "header") { // Right,left buttons $headeranchors = $this.children( "a, button" ); leftbtn = $headeranchors.hasClass( "ui-btn-left" ); rightbtn = $headeranchors.hasClass( "ui-btn-right" ); leftbtn = leftbtn || $headeranchors.eq( 0 ).not( ".ui-btn-right" ).addClass( "ui-btn-left" ).length; rightbtn = rightbtn || $headeranchors.eq( 1 ).addClass( "ui-btn-right" ).length; } // Auto-add back btn on pages beyond first view if ( o.addBackBtn && role === "header" && $( ".ui-page" ).length > 1 && $page[ 0 ].getAttribute( attrPrefix + "url" ) !== $.mobile.path.stripHash( location.hash ) && !leftbtn ) { backBtn = $( "<a href='javascript:void(0);' class='ui-btn-left' data-"+ $.mobile.ns +"rel='back' data-"+ $.mobile.ns +"icon='arrow-l'>"+ o.backBtnText +"</a>" ) // If theme is provided, override default inheritance .attr( "data-"+ $.mobile.ns +"theme", o.backBtnTheme || thisTheme ) .prependTo( $this ); } // Page title $this.children( "h1, h2, h3, h4, h5, h6" ) .addClass( "ui-title" ) // Regardless of h element number in src, it becomes h1 for the enhanced page .attr({ "role": "heading", "aria-level": "1" }); } else if ( role === "content" ) { if ( contentTheme ) { $this.addClass( "ui-body-" + ( contentTheme ) ); } // Add ARIA role $this.attr( "role", "main" ); } }); }); })( jQuery ); (function( $, undefined ) { // This function calls getAttribute, which should be safe for data-* attributes var getAttrFixed = function( e, key ) { var value = e.getAttribute( key ); return value === "true" ? true : value === "false" ? false : value === null ? undefined : value; }; $.fn.buttonMarkup = function( options ) { var $workingSet = this, nsKey = "data-" + $.mobile.ns, key; // Enforce options to be of type string options = ( options && ( $.type( options ) === "object" ) )? options : {}; for ( var i = 0; i < $workingSet.length; i++ ) { var el = $workingSet.eq( i ), e = el[ 0 ], o = $.extend( {}, $.fn.buttonMarkup.defaults, { icon: options.icon !== undefined ? options.icon : getAttrFixed( e, nsKey + "icon" ), iconpos: options.iconpos !== undefined ? options.iconpos : getAttrFixed( e, nsKey + "iconpos" ), theme: options.theme !== undefined ? options.theme : getAttrFixed( e, nsKey + "theme" ) || $.mobile.getInheritedTheme( el, "c" ), inline: options.inline !== undefined ? options.inline : getAttrFixed( e, nsKey + "inline" ), shadow: options.shadow !== undefined ? options.shadow : getAttrFixed( e, nsKey + "shadow" ), corners: options.corners !== undefined ? options.corners : getAttrFixed( e, nsKey + "corners" ), iconshadow: options.iconshadow !== undefined ? options.iconshadow : getAttrFixed( e, nsKey + "iconshadow" ), mini: options.mini !== undefined ? options.mini : getAttrFixed( e, nsKey + "mini" ) }, options ), // Classes Defined innerClass = "ui-btn-inner", textClass = "ui-btn-text", buttonClass, iconClass, hover = false, state = "up", // Button inner markup buttonInner, buttonText, buttonIcon, buttonElements; for ( key in o ) { if ( o[ key ] === undefined || o[ key ] === null ) { el.removeAttr( nsKey + key ); } else { e.setAttribute( nsKey + key, o[ key ] ); } } if ( getAttrFixed( e, nsKey + "rel" ) === "popup" && el.attr( "href" ) ) { e.setAttribute( "aria-haspopup", true ); e.setAttribute( "aria-owns", el.attr( "href" ) ); } // Check if this element is already enhanced buttonElements = $.data( ( ( e.tagName === "INPUT" || e.tagName === "BUTTON" ) ? e.parentNode : e ), "buttonElements" ); if ( buttonElements ) { e = buttonElements.outer; el = $( e ); buttonInner = buttonElements.inner; buttonText = buttonElements.text; // We will recreate this icon below $( buttonElements.icon ).remove(); buttonElements.icon = null; hover = buttonElements.hover; state = buttonElements.state; } else { buttonInner = document.createElement( o.wrapperEls ); buttonText = document.createElement( o.wrapperEls ); } buttonIcon = o.icon ? document.createElement( "span" ) : null; if ( attachEvents && !buttonElements ) { attachEvents(); } // if not, try to find closest theme container if ( !o.theme ) { o.theme = $.mobile.getInheritedTheme( el, "c" ); } buttonClass = "ui-btn "; buttonClass += ( hover ? "ui-btn-hover-" + o.theme : "" ); buttonClass += ( state ? " ui-btn-" + state + "-" + o.theme : "" ); buttonClass += o.shadow ? " ui-shadow" : ""; buttonClass += o.corners ? " ui-btn-corner-all" : ""; if ( o.mini !== undefined ) { // Used to control styling in headers/footers, where buttons default to `mini` style. buttonClass += o.mini === true ? " ui-mini" : " ui-fullsize"; } if ( o.inline !== undefined ) { // Used to control styling in headers/footers, where buttons default to `inline` style. buttonClass += o.inline === true ? " ui-btn-inline" : " ui-btn-block"; } if ( o.icon ) { o.icon = "ui-icon-" + o.icon; o.iconpos = o.iconpos || "left"; iconClass = "ui-icon " + o.icon; if ( o.iconshadow ) { iconClass += " ui-icon-shadow"; } } if ( o.iconpos ) { buttonClass += " ui-btn-icon-" + o.iconpos; if ( o.iconpos === "notext" && !el.attr( "title" ) ) { el.attr( "title", el.getEncodedText() ); } } if ( buttonElements ) { el.removeClass( buttonElements.bcls || "" ); } el.removeClass( "ui-link" ).addClass( buttonClass ); buttonInner.className = innerClass; buttonText.className = textClass; if ( !buttonElements ) { buttonInner.appendChild( buttonText ); } if ( buttonIcon ) { buttonIcon.className = iconClass; if ( !( buttonElements && buttonElements.icon ) ) { buttonIcon.innerHTML = " "; buttonInner.appendChild( buttonIcon ); } } while ( e.firstChild && !buttonElements ) { buttonText.appendChild( e.firstChild ); } if ( !buttonElements ) { e.appendChild( buttonInner ); } // Assign a structure containing the elements of this button to the elements of this button. This // will allow us to recognize this as an already-enhanced button in future calls to buttonMarkup(). buttonElements = { hover : hover, state : state, bcls : buttonClass, outer : e, inner : buttonInner, text : buttonText, icon : buttonIcon }; $.data( e, 'buttonElements', buttonElements ); $.data( buttonInner, 'buttonElements', buttonElements ); $.data( buttonText, 'buttonElements', buttonElements ); if ( buttonIcon ) { $.data( buttonIcon, 'buttonElements', buttonElements ); } } return this; }; $.fn.buttonMarkup.defaults = { corners: true, shadow: true, iconshadow: true, wrapperEls: "span" }; function closestEnabledButton( element ) { var cname; while ( element ) { // Note that we check for typeof className below because the element we // handed could be in an SVG DOM where className on SVG elements is defined to // be of a different type (SVGAnimatedString). We only operate on HTML DOM // elements, so we look for plain "string". cname = ( typeof element.className === 'string' ) && ( element.className + ' ' ); if ( cname && cname.indexOf( "ui-btn " ) > -1 && cname.indexOf( "ui-disabled " ) < 0 ) { break; } element = element.parentNode; } return element; } function updateButtonClass( $btn, classToRemove, classToAdd, hover, state ) { var buttonElements = $.data( $btn[ 0 ], "buttonElements" ); $btn.removeClass( classToRemove ).addClass( classToAdd ); if ( buttonElements ) { buttonElements.bcls = $( document.createElement( "div" ) ) .addClass( buttonElements.bcls + " " + classToAdd ) .removeClass( classToRemove ) .attr( "class" ); if ( hover !== undefined ) { buttonElements.hover = hover; } buttonElements.state = state; } } var attachEvents = function() { var hoverDelay = $.mobile.buttonMarkup.hoverDelay, hov, foc; $.mobile.document.bind( { "vmousedown vmousecancel vmouseup vmouseover vmouseout focus blur scrollstart": function( event ) { var theme, $btn = $( closestEnabledButton( event.target ) ), isTouchEvent = event.originalEvent && /^touch/.test( event.originalEvent.type ), evt = event.type; if ( $btn.length ) { theme = $btn.attr( "data-" + $.mobile.ns + "theme" ); if ( evt === "vmousedown" ) { if ( isTouchEvent ) { // Use a short delay to determine if the user is scrolling before highlighting hov = setTimeout( function() { updateButtonClass( $btn, "ui-btn-up-" + theme, "ui-btn-down-" + theme, undefined, "down" ); }, hoverDelay ); } else { updateButtonClass( $btn, "ui-btn-up-" + theme, "ui-btn-down-" + theme, undefined, "down" ); } } else if ( evt === "vmousecancel" || evt === "vmouseup" ) { updateButtonClass( $btn, "ui-btn-down-" + theme, "ui-btn-up-" + theme, undefined, "up" ); } else if ( evt === "vmouseover" || evt === "focus" ) { if ( isTouchEvent ) { // Use a short delay to determine if the user is scrolling before highlighting foc = setTimeout( function() { updateButtonClass( $btn, "ui-btn-up-" + theme, "ui-btn-hover-" + theme, true, "" ); }, hoverDelay ); } else { updateButtonClass( $btn, "ui-btn-up-" + theme, "ui-btn-hover-" + theme, true, "" ); } } else if ( evt === "vmouseout" || evt === "blur" || evt === "scrollstart" ) { updateButtonClass( $btn, "ui-btn-hover-" + theme + " ui-btn-down-" + theme, "ui-btn-up-" + theme, false, "up" ); if ( hov ) { clearTimeout( hov ); } if ( foc ) { clearTimeout( foc ); } } } }, "focusin focus": function( event ) { $( closestEnabledButton( event.target ) ).addClass( $.mobile.focusClass ); }, "focusout blur": function( event ) { $( closestEnabledButton( event.target ) ).removeClass( $.mobile.focusClass ); } }); attachEvents = null; }; //links in bars, or those with data-role become buttons //auto self-init widgets $.mobile.document.bind( "pagecreate create", function( e ) { $( ":jqmData(role='button'), .ui-bar > a, .ui-header > a, .ui-footer > a, .ui-bar > :jqmData(role='controlgroup') > a", e.target ) .jqmEnhanceable() .not( "button, input, .ui-btn, :jqmData(role='none'), :jqmData(role='nojs')" ) .buttonMarkup(); }); })( jQuery ); (function( $, undefined ) { $.widget( "mobile.collapsible", $.mobile.widget, { options: { expandCueText: " click to expand contents", collapseCueText: " click to collapse contents", collapsed: true, heading: "h1,h2,h3,h4,h5,h6,legend", collapsedIcon: "plus", expandedIcon: "minus", iconpos: "left", theme: null, contentTheme: null, inset: true, corners: true, mini: false, initSelector: ":jqmData(role='collapsible')" }, _create: function() { var $el = this.element, o = this.options, collapsible = $el.addClass( "ui-collapsible" ), collapsibleHeading = $el.children( o.heading ).first(), collapsibleContent = collapsible.wrapInner( "<div class='ui-collapsible-content'></div>" ).children( ".ui-collapsible-content" ), collapsibleSet = $el.closest( ":jqmData(role='collapsible-set')" ).addClass( "ui-collapsible-set" ), collapsibleClasses = ""; // Replace collapsibleHeading if it's a legend if ( collapsibleHeading.is( "legend" ) ) { collapsibleHeading = $( "<div role='heading'>"+ collapsibleHeading.html() +"</div>" ).insertBefore( collapsibleHeading ); collapsibleHeading.next().remove(); } // If we are in a collapsible set if ( collapsibleSet.length ) { // Inherit the theme from collapsible-set if ( !o.theme ) { o.theme = collapsibleSet.jqmData( "theme" ) || $.mobile.getInheritedTheme( collapsibleSet, "c" ); } // Inherit the content-theme from collapsible-set if ( !o.contentTheme ) { o.contentTheme = collapsibleSet.jqmData( "content-theme" ); } // Get the preference for collapsed icon in the set, but override with data- attribute on the individual collapsible o.collapsedIcon = $el.jqmData( "collapsed-icon" ) || collapsibleSet.jqmData( "collapsed-icon" ) || o.collapsedIcon; // Get the preference for expanded icon in the set, but override with data- attribute on the individual collapsible o.expandedIcon = $el.jqmData( "expanded-icon" ) || collapsibleSet.jqmData( "expanded-icon" ) || o.expandedIcon; // Gets the preference icon position in the set, but override with data- attribute on the individual collapsible o.iconpos = $el.jqmData( "iconpos" ) || collapsibleSet.jqmData( "iconpos" ) || o.iconpos; // Inherit the preference for inset from collapsible-set or set the default value to ensure equalty within a set if ( collapsibleSet.jqmData( "inset" ) !== undefined ) { o.inset = collapsibleSet.jqmData( "inset" ); } else { o.inset = true; } // Set corners for individual collapsibles to false when in a collapsible-set o.corners = false; // Gets the preference for mini in the set if ( !o.mini ) { o.mini = collapsibleSet.jqmData( "mini" ); } } else { // get inherited theme if not a set and no theme has been set if ( !o.theme ) { o.theme = $.mobile.getInheritedTheme( $el, "c" ); } } if ( !!o.inset ) { collapsibleClasses += " ui-collapsible-inset"; if ( !!o.corners ) { collapsibleClasses += " ui-corner-all" ; } } if ( o.contentTheme ) { collapsibleClasses += " ui-collapsible-themed-content"; collapsibleContent.addClass( "ui-body-" + o.contentTheme ); } if ( collapsibleClasses !== "" ) { collapsible.addClass( collapsibleClasses ); } collapsibleHeading //drop heading in before content .insertBefore( collapsibleContent ) //modify markup & attributes .addClass( "ui-collapsible-heading" ) .append( "<span class='ui-collapsible-heading-status'></span>" ) .wrapInner( "<a href='#' class='ui-collapsible-heading-toggle'></a>" ) .find( "a" ) .first() .buttonMarkup({ shadow: false, corners: false, iconpos: o.iconpos, icon: o.collapsedIcon, mini: o.mini, theme: o.theme }); //events collapsible .bind( "expand collapse", function( event ) { if ( !event.isDefaultPrevented() ) { var $this = $( this ), isCollapse = ( event.type === "collapse" ); event.preventDefault(); collapsibleHeading .toggleClass( "ui-collapsible-heading-collapsed", isCollapse ) .find( ".ui-collapsible-heading-status" ) .text( isCollapse ? o.expandCueText : o.collapseCueText ) .end() .find( ".ui-icon" ) .toggleClass( "ui-icon-" + o.expandedIcon, !isCollapse ) // logic or cause same icon for expanded/collapsed state would remove the ui-icon-class .toggleClass( "ui-icon-" + o.collapsedIcon, ( isCollapse || o.expandedIcon === o.collapsedIcon ) ) .end() .find( "a" ).first().removeClass( $.mobile.activeBtnClass ); $this.toggleClass( "ui-collapsible-collapsed", isCollapse ); collapsibleContent.toggleClass( "ui-collapsible-content-collapsed", isCollapse ).attr( "aria-hidden", isCollapse ); collapsibleContent.trigger( "updatelayout" ); } }) .trigger( o.collapsed ? "collapse" : "expand" ); collapsibleHeading .bind( "tap", function( event ) { collapsibleHeading.find( "a" ).first().addClass( $.mobile.activeBtnClass ); }) .bind( "click", function( event ) { var type = collapsibleHeading.hasClass( "ui-collapsible-heading-collapsed" ) ? "expand" : "collapse"; collapsible.trigger( type ); event.preventDefault(); event.stopPropagation(); }); } }); //auto self-init widgets $.mobile.document.bind( "pagecreate create", function( e ) { $.mobile.collapsible.prototype.enhanceWithin( e.target ); }); })( jQuery ); (function( $, undefined ) { $.mobile.behaviors.addFirstLastClasses = { _getVisibles: function( $els, create ) { var visibles; if ( create ) { visibles = $els.not( ".ui-screen-hidden" ); } else { visibles = $els.filter( ":visible" ); if ( visibles.length === 0 ) { visibles = $els.not( ".ui-screen-hidden" ); } } return visibles; }, _addFirstLastClasses: function( $els, $visibles, create ) { $els.removeClass( "ui-first-child ui-last-child" ); $visibles.eq( 0 ).addClass( "ui-first-child" ).end().last().addClass( "ui-last-child" ); if ( !create ) { this.element.trigger( "updatelayout" ); } } }; })( jQuery ); (function( $, undefined ) { $.widget( "mobile.collapsibleset", $.mobile.widget, $.extend( { options: { initSelector: ":jqmData(role='collapsible-set')" }, _create: function() { var $el = this.element.addClass( "ui-collapsible-set" ), o = this.options; // Inherit the theme from collapsible-set if ( !o.theme ) { o.theme = $.mobile.getInheritedTheme( $el, "c" ); } // Inherit the content-theme from collapsible-set if ( !o.contentTheme ) { o.contentTheme = $el.jqmData( "content-theme" ); } // Inherit the corner styling from collapsible-set if ( !o.corners ) { o.corners = $el.jqmData( "corners" ); } if ( $el.jqmData( "inset" ) !== undefined ) { o.inset = $el.jqmData( "inset" ); } o.inset = o.inset !== undefined ? o.inset : true; o.corners = o.corners !== undefined ? o.corners : true; if ( !!o.corners && !!o.inset ) { $el.addClass( "ui-corner-all" ); } // Initialize the collapsible set if it's not already initialized if ( !$el.jqmData( "collapsiblebound" ) ) { $el .jqmData( "collapsiblebound", true ) .bind( "expand", function( event ) { var closestCollapsible = $( event.target ) .closest( ".ui-collapsible" ); if ( closestCollapsible.parent().is( ":jqmData(role='collapsible-set')" ) ) { closestCollapsible .siblings( ".ui-collapsible" ) .trigger( "collapse" ); } }); } }, _init: function() { var $el = this.element, collapsiblesInSet = $el.children( ":jqmData(role='collapsible')" ), expanded = collapsiblesInSet.filter( ":jqmData(collapsed='false')" ); this._refresh( "true" ); // Because the corners are handled by the collapsible itself and the default state is collapsed // That was causing https://github.com/jquery/jquery-mobile/issues/4116 expanded.trigger( "expand" ); }, _refresh: function( create ) { var collapsiblesInSet = this.element.children( ":jqmData(role='collapsible')" ); $.mobile.collapsible.prototype.enhance( collapsiblesInSet.not( ".ui-collapsible" ) ); this._addFirstLastClasses( collapsiblesInSet, this._getVisibles( collapsiblesInSet, create ), create ); }, refresh: function() { this._refresh( false ); } }, $.mobile.behaviors.addFirstLastClasses ) ); //auto self-init widgets $.mobile.document.bind( "pagecreate create", function( e ) { $.mobile.collapsibleset.prototype.enhanceWithin( e.target ); }); })( jQuery ); (function( $, undefined ) { // filter function removes whitespace between label and form element so we can use inline-block (nodeType 3 = text) $.fn.fieldcontain = function( options ) { return this .addClass( "ui-field-contain ui-body ui-br" ) .contents().filter( function() { return ( this.nodeType === 3 && !/\S/.test( this.nodeValue ) ); }).remove(); }; //auto self-init widgets $( document ).bind( "pagecreate create", function( e ) { $( ":jqmData(role='fieldcontain')", e.target ).jqmEnhanceable().fieldcontain(); }); })( jQuery ); (function( $, undefined ) { $.fn.grid = function( options ) { return this.each(function() { var $this = $( this ), o = $.extend({ grid: null }, options ), $kids = $this.children(), gridCols = { solo:1, a:2, b:3, c:4, d:5 }, grid = o.grid, iterator; if ( !grid ) { if ( $kids.length <= 5 ) { for ( var letter in gridCols ) { if ( gridCols[ letter ] === $kids.length ) { grid = letter; } } } else { grid = "a"; $this.addClass( "ui-grid-duo" ); } } iterator = gridCols[grid]; $this.addClass( "ui-grid-" + grid ); $kids.filter( ":nth-child(" + iterator + "n+1)" ).addClass( "ui-block-a" ); if ( iterator > 1 ) { $kids.filter( ":nth-child(" + iterator + "n+2)" ).addClass( "ui-block-b" ); } if ( iterator > 2 ) { $kids.filter( ":nth-child(" + iterator + "n+3)" ).addClass( "ui-block-c" ); } if ( iterator > 3 ) { $kids.filter( ":nth-child(" + iterator + "n+4)" ).addClass( "ui-block-d" ); } if ( iterator > 4 ) { $kids.filter( ":nth-child(" + iterator + "n+5)" ).addClass( "ui-block-e" ); } }); }; })( jQuery ); (function( $, undefined ) { $.widget( "mobile.navbar", $.mobile.widget, { options: { iconpos: "top", grid: null, initSelector: ":jqmData(role='navbar')" }, _create: function() { var $navbar = this.element, $navbtns = $navbar.find( "a" ), iconpos = $navbtns.filter( ":jqmData(icon)" ).length ? this.options.iconpos : undefined; $navbar.addClass( "ui-navbar ui-mini" ) .attr( "role", "navigation" ) .find( "ul" ) .jqmEnhanceable() .grid({ grid: this.options.grid }); $navbtns.buttonMarkup({ corners: false, shadow: false, inline: true, iconpos: iconpos }); $navbar.delegate( "a", "vclick", function( event ) { // ui-btn-inner is returned as target var target = $( event.target ).is( "a" ) ? $( this ) : $( this ).parent( "a" ); if ( !target.is( ".ui-disabled, .ui-btn-active" ) ) { $navbtns.removeClass( $.mobile.activeBtnClass ); target.addClass( $.mobile.activeBtnClass ); } }); // Buttons in the navbar with ui-state-persist class should regain their active state before page show $navbar.closest( ".ui-page" ).bind( "pagebeforeshow", function() { $navbtns.filter( ".ui-state-persist" ).addClass( $.mobile.activeBtnClass ); }); } }); //auto self-init widgets $.mobile.document.bind( "pagecreate create", function( e ) { $.mobile.navbar.prototype.enhanceWithin( e.target ); }); })( jQuery ); (function( $, undefined ) { //Keeps track of the number of lists per page UID //This allows support for multiple nested list in the same page //https://github.com/jquery/jquery-mobile/issues/1617 var listCountPerPage = {}; $.widget( "mobile.listview", $.mobile.widget, $.extend( { options: { theme: null, countTheme: "c", headerTheme: "b", dividerTheme: "b", icon: "arrow-r", splitIcon: "arrow-r", splitTheme: "b", corners: true, shadow: true, inset: false, initSelector: ":jqmData(role='listview')" }, _create: function() { var t = this, listviewClasses = ""; listviewClasses += t.options.inset ? " ui-listview-inset" : ""; if ( !!t.options.inset ) { listviewClasses += t.options.corners ? " ui-corner-all" : ""; listviewClasses += t.options.shadow ? " ui-shadow" : ""; } // create listview markup t.element.addClass(function( i, orig ) { return orig + " ui-listview" + listviewClasses; }); t.refresh( true ); }, // This is a generic utility method for finding the first // node with a given nodeName. It uses basic DOM traversal // to be fast and is meant to be a substitute for simple // $.fn.closest() and $.fn.children() calls on a single // element. Note that callers must pass both the lowerCase // and upperCase version of the nodeName they are looking for. // The main reason for this is that this function will be // called many times and we want to avoid having to lowercase // the nodeName from the element every time to ensure we have // a match. Note that this function lives here for now, but may // be moved into $.mobile if other components need a similar method. _findFirstElementByTagName: function( ele, nextProp, lcName, ucName ) { var dict = {}; dict[ lcName ] = dict[ ucName ] = true; while ( ele ) { if ( dict[ ele.nodeName ] ) { return ele; } ele = ele[ nextProp ]; } return null; }, _getChildrenByTagName: function( ele, lcName, ucName ) { var results = [], dict = {}; dict[ lcName ] = dict[ ucName ] = true; ele = ele.firstChild; while ( ele ) { if ( dict[ ele.nodeName ] ) { results.push( ele ); } ele = ele.nextSibling; } return $( results ); }, _addThumbClasses: function( containers ) { var i, img, len = containers.length; for ( i = 0; i < len; i++ ) { img = $( this._findFirstElementByTagName( containers[ i ].firstChild, "nextSibling", "img", "IMG" ) ); if ( img.length ) { img.addClass( "ui-li-thumb" ); $( this._findFirstElementByTagName( img[ 0 ].parentNode, "parentNode", "li", "LI" ) ).addClass( img.hasClass( "ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" ); } } }, refresh: function( create ) { this.parentPage = this.element.closest( ".ui-page" ); this._createSubPages(); var o = this.options, $list = this.element, self = this, dividertheme = $list.jqmData( "dividertheme" ) || o.dividerTheme, listsplittheme = $list.jqmData( "splittheme" ), listspliticon = $list.jqmData( "spliticon" ), listicon = $list.jqmData( "icon" ), li = this._getChildrenByTagName( $list[ 0 ], "li", "LI" ), ol = !!$.nodeName( $list[ 0 ], "ol" ), jsCount = !$.support.cssPseudoElement, start = $list.attr( "start" ), itemClassDict = {}, item, itemClass, itemTheme, a, last, splittheme, counter, startCount, newStartCount, countParent, icon, imgParents, img, linkIcon; if ( ol && jsCount ) { $list.find( ".ui-li-dec" ).remove(); } if ( ol ) { // Check if a start attribute has been set while taking a value of 0 into account if ( start || start === 0 ) { if ( !jsCount ) { startCount = parseInt( start , 10 ) - 1; $list.css( "counter-reset", "listnumbering " + startCount ); } else { counter = parseInt( start , 10 ); } } else if ( jsCount ) { counter = 1; } } if ( !o.theme ) { o.theme = $.mobile.getInheritedTheme( this.element, "c" ); } for ( var pos = 0, numli = li.length; pos < numli; pos++ ) { item = li.eq( pos ); itemClass = "ui-li"; // If we're creating the element, we update it regardless if ( create || !item.hasClass( "ui-li" ) ) { itemTheme = item.jqmData( "theme" ) || o.theme; a = this._getChildrenByTagName( item[ 0 ], "a", "A" ); var isDivider = ( item.jqmData( "role" ) === "list-divider" ); if ( a.length && !isDivider ) { icon = item.jqmData( "icon" ); item.buttonMarkup({ wrapperEls: "div", shadow: false, corners: false, iconpos: "right", icon: a.length > 1 || icon === false ? false : icon || listicon || o.icon, theme: itemTheme }); if ( ( icon !== false ) && ( a.length === 1 ) ) { item.addClass( "ui-li-has-arrow" ); } a.first().removeClass( "ui-link" ).addClass( "ui-link-inherit" ); if ( a.length > 1 ) { itemClass += " ui-li-has-alt"; last = a.last(); splittheme = listsplittheme || last.jqmData( "theme" ) || o.splitTheme; linkIcon = last.jqmData( "icon" ); last.appendTo( item ) .attr( "title", $.trim(last.getEncodedText()) ) .addClass( "ui-li-link-alt" ) .empty() .buttonMarkup({ shadow: false, corners: false, theme: itemTheme, icon: false, iconpos: "notext" }) .find( ".ui-btn-inner" ) .append( $( document.createElement( "span" ) ).buttonMarkup({ shadow: true, corners: true, theme: splittheme, iconpos: "notext", // link icon overrides list item icon overrides ul element overrides options icon: linkIcon || icon || listspliticon || o.splitIcon }) ); } } else if ( isDivider ) { itemClass += " ui-li-divider ui-bar-" + ( item.jqmData( "theme" ) || dividertheme ); item.attr( "role", "heading" ); if ( ol ) { //reset counter when a divider heading is encountered if ( start || start === 0 ) { if ( !jsCount ) { newStartCount = parseInt( start , 10 ) - 1; item.css( "counter-reset", "listnumbering " + newStartCount ); } else { counter = parseInt( start , 10 ); } } else if ( jsCount ) { counter = 1; } } } else { itemClass += " ui-li-static ui-btn-up-" + itemTheme; } } if ( ol && jsCount && itemClass.indexOf( "ui-li-divider" ) < 0 ) { countParent = itemClass.indexOf( "ui-li-static" ) > 0 ? item : item.find( ".ui-link-inherit" ); countParent.addClass( "ui-li-jsnumbering" ) .prepend( "<span class='ui-li-dec'>" + ( counter++ ) + ". </span>" ); } // Instead of setting item class directly on the list item and its // btn-inner at this point in time, push the item into a dictionary // that tells us what class to set on it so we can do this after this // processing loop is finished. if ( !itemClassDict[ itemClass ] ) { itemClassDict[ itemClass ] = []; } itemClassDict[ itemClass ].push( item[ 0 ] ); } // Set the appropriate listview item classes on each list item // and their btn-inner elements. The main reason we didn't do this // in the for-loop above is because we can eliminate per-item function overhead // by calling addClass() and children() once or twice afterwards. This // can give us a significant boost on platforms like WP7.5. for ( itemClass in itemClassDict ) { $( itemClassDict[ itemClass ] ).addClass( itemClass ).children( ".ui-btn-inner" ).addClass( itemClass ); } $list.find( "h1, h2, h3, h4, h5, h6" ).addClass( "ui-li-heading" ) .end() .find( "p, dl" ).addClass( "ui-li-desc" ) .end() .find( ".ui-li-aside" ).each(function() { var $this = $( this ); $this.prependTo( $this.parent() ); //shift aside to front for css float }) .end() .find( ".ui-li-count" ).each(function() { $( this ).closest( "li" ).addClass( "ui-li-has-count" ); }).addClass( "ui-btn-up-" + ( $list.jqmData( "counttheme" ) || this.options.countTheme) + " ui-btn-corner-all" ); // The idea here is to look at the first image in the list item // itself, and any .ui-link-inherit element it may contain, so we // can place the appropriate classes on the image and list item. // Note that we used to use something like: // // li.find(">img:eq(0), .ui-link-inherit>img:eq(0)").each( ... ); // // But executing a find() like that on Windows Phone 7.5 took a // really long time. Walking things manually with the code below // allows the 400 listview item page to load in about 3 seconds as // opposed to 30 seconds. this._addThumbClasses( li ); this._addThumbClasses( $list.find( ".ui-link-inherit" ) ); this._addFirstLastClasses( li, this._getVisibles( li, create ), create ); // autodividers binds to this to redraw dividers after the listview refresh this._trigger( "afterrefresh" ); }, //create a string for ID/subpage url creation _idStringEscape: function( str ) { return str.replace(/[^a-zA-Z0-9]/g, '-'); }, _createSubPages: function() { var parentList = this.element, parentPage = parentList.closest( ".ui-page" ), parentUrl = parentPage.jqmData( "url" ), parentId = parentUrl || parentPage[ 0 ][ $.expando ], parentListId = parentList.attr( "id" ), o = this.options, dns = "data-" + $.mobile.ns, self = this, persistentFooterID = parentPage.find( ":jqmData(role='footer')" ).jqmData( "id" ), hasSubPages; if ( typeof listCountPerPage[ parentId ] === "undefined" ) { listCountPerPage[ parentId ] = -1; } parentListId = parentListId || ++listCountPerPage[ parentId ]; $( parentList.find( "li>ul, li>ol" ).toArray().reverse() ).each(function( i ) { var self = this, list = $( this ), listId = list.attr( "id" ) || parentListId + "-" + i, parent = list.parent(), nodeElsFull = $( list.prevAll().toArray().reverse() ), nodeEls = nodeElsFull.length ? nodeElsFull : $( "<span>" + $.trim(parent.contents()[ 0 ].nodeValue) + "</span>" ), title = nodeEls.first().getEncodedText(),//url limits to first 30 chars of text id = ( parentUrl || "" ) + "&" + $.mobile.subPageUrlKey + "=" + listId, theme = list.jqmData( "theme" ) || o.theme, countTheme = list.jqmData( "counttheme" ) || parentList.jqmData( "counttheme" ) || o.countTheme, newPage, anchor; //define hasSubPages for use in later removal hasSubPages = true; newPage = list.detach() .wrap( "<div " + dns + "role='page' " + dns + "url='" + id + "' " + dns + "theme='" + theme + "' " + dns + "count-theme='" + countTheme + "'><div " + dns + "role='content'></div></div>" ) .parent() .before( "<div " + dns + "role='header' " + dns + "theme='" + o.headerTheme + "'><div class='ui-title'>" + title + "</div></div>" ) .after( persistentFooterID ? $( "<div " + dns + "role='footer' " + dns + "id='"+ persistentFooterID +"'>" ) : "" ) .parent() .appendTo( $.mobile.pageContainer ); newPage.page(); anchor = parent.find( 'a:first' ); if ( !anchor.length ) { anchor = $( "<a/>" ).html( nodeEls || title ).prependTo( parent.empty() ); } anchor.attr( "href", "#" + id ); }).listview(); // on pagehide, remove any nested pages along with the parent page, as long as they aren't active // and aren't embedded if ( hasSubPages && parentPage.is( ":jqmData(external-page='true')" ) && parentPage.data( "mobile-page" ).options.domCache === false ) { var newRemove = function( e, ui ) { var nextPage = ui.nextPage, npURL, prEvent = new $.Event( "pageremove" ); if ( ui.nextPage ) { npURL = nextPage.jqmData( "url" ); if ( npURL.indexOf( parentUrl + "&" + $.mobile.subPageUrlKey ) !== 0 ) { self.childPages().remove(); parentPage.trigger( prEvent ); if ( !prEvent.isDefaultPrevented() ) { parentPage.removeWithDependents(); } } } }; // unbind the original page remove and replace with our specialized version parentPage .unbind( "pagehide.remove" ) .bind( "pagehide.remove", newRemove); } }, // TODO sort out a better way to track sub pages of the listview this is brittle childPages: function() { var parentUrl = this.parentPage.jqmData( "url" ); return $( ":jqmData(url^='"+ parentUrl + "&" + $.mobile.subPageUrlKey + "')" ); } }, $.mobile.behaviors.addFirstLastClasses ) ); //auto self-init widgets $.mobile.document.bind( "pagecreate create", function( e ) { $.mobile.listview.prototype.enhanceWithin( e.target ); }); })( jQuery ); (function( $ ) { var meta = $( "meta[name=viewport]" ), initialContent = meta.attr( "content" ), disabledZoom = initialContent + ",maximum-scale=1, user-scalable=no", enabledZoom = initialContent + ",maximum-scale=10, user-scalable=yes", disabledInitially = /(user-scalable[\s]*=[\s]*no)|(maximum-scale[\s]*=[\s]*1)[$,\s]/.test( initialContent ); $.mobile.zoom = $.extend( {}, { enabled: !disabledInitially, locked: false, disable: function( lock ) { if ( !disabledInitially && !$.mobile.zoom.locked ) { meta.attr( "content", disabledZoom ); $.mobile.zoom.enabled = false; $.mobile.zoom.locked = lock || false; } }, enable: function( unlock ) { if ( !disabledInitially && ( !$.mobile.zoom.locked || unlock === true ) ) { meta.attr( "content", enabledZoom ); $.mobile.zoom.enabled = true; $.mobile.zoom.locked = false; } }, restore: function() { if ( !disabledInitially ) { meta.attr( "content", initialContent ); $.mobile.zoom.enabled = true; } } }); }( jQuery )); (function( $, undefined ) { $.widget( "mobile.textinput", $.mobile.widget, { options: { theme: null, mini: false, // This option defaults to true on iOS devices. preventFocusZoom: /iPhone|iPad|iPod/.test( navigator.platform ) && navigator.userAgent.indexOf( "AppleWebKit" ) > -1, initSelector: "input[type='text'], input[type='search'], :jqmData(type='search'), input[type='number'], :jqmData(type='number'), input[type='password'], input[type='email'], input[type='url'], input[type='tel'], textarea, input[type='time'], input[type='date'], input[type='month'], input[type='week'], input[type='datetime'], input[type='datetime-local'], input[type='color'], input:not([type]), input[type='file']", clearBtn: false, clearSearchButtonText: null, //deprecating for 1.3... clearBtnText: "clear text", disabled: false }, _create: function() { var self = this, input = this.element, o = this.options, theme = o.theme || $.mobile.getInheritedTheme( this.element, "c" ), themeclass = " ui-body-" + theme, miniclass = o.mini ? " ui-mini" : "", isSearch = input.is( "[type='search'], :jqmData(type='search')" ), focusedEl, clearbtn, clearBtnText = o.clearSearchButtonText || o.clearBtnText, clearBtnBlacklist = input.is( "textarea, :jqmData(type='range')" ), inputNeedsClearBtn = !!o.clearBtn && !clearBtnBlacklist, inputNeedsWrap = input.is( "input" ) && !input.is( ":jqmData(type='range')" ); function toggleClear() { setTimeout( function() { clearbtn.toggleClass( "ui-input-clear-hidden", !input.val() ); }, 0 ); } $( "label[for='" + input.attr( "id" ) + "']" ).addClass( "ui-input-text" ); focusedEl = input.addClass( "ui-input-text ui-body-"+ theme ); // XXX: Temporary workaround for issue 785 (Apple bug 8910589). // Turn off autocorrect and autocomplete on non-iOS 5 devices // since the popup they use can't be dismissed by the user. Note // that we test for the presence of the feature by looking for // the autocorrect property on the input element. We currently // have no test for iOS 5 or newer so we're temporarily using // the touchOverflow support flag for jQM 1.0. Yes, I feel dirty. - jblas if ( typeof input[0].autocorrect !== "undefined" && !$.support.touchOverflow ) { // Set the attribute instead of the property just in case there // is code that attempts to make modifications via HTML. input[0].setAttribute( "autocorrect", "off" ); input[0].setAttribute( "autocomplete", "off" ); } //"search" and "text" input widgets if ( isSearch ) { focusedEl = input.wrap( "<div class='ui-input-search ui-shadow-inset ui-btn-corner-all ui-btn-shadow ui-icon-searchfield" + themeclass + miniclass + "'></div>" ).parent(); } else if ( inputNeedsWrap ) { focusedEl = input.wrap( "<div class='ui-input-text ui-shadow-inset ui-corner-all ui-btn-shadow" + themeclass + miniclass + "'></div>" ).parent(); } if( inputNeedsClearBtn || isSearch ) { clearbtn = $( "<a href='#' class='ui-input-clear' title='" + clearBtnText + "'>" + clearBtnText + "</a>" ) .bind( "click", function( event ) { input .val( "" ) .focus() .trigger( "change" ); clearbtn.addClass( "ui-input-clear-hidden" ); event.preventDefault(); }) .appendTo( focusedEl ) .buttonMarkup({ icon: "delete", iconpos: "notext", corners: true, shadow: true, mini: o.mini }); if ( !isSearch ) { focusedEl.addClass( "ui-input-has-clear" ); } toggleClear(); input.bind( "paste cut keyup input focus change blur", toggleClear ); } else if ( !inputNeedsWrap && !isSearch ) { input.addClass( "ui-corner-all ui-shadow-inset" + themeclass + miniclass ); } input.focus(function() { // In many situations, iOS will zoom into the input upon tap, this prevents that from happening if ( o.preventFocusZoom ) { $.mobile.zoom.disable( true ); } focusedEl.addClass( $.mobile.focusClass ); }) .blur(function() { focusedEl.removeClass( $.mobile.focusClass ); if ( o.preventFocusZoom ) { $.mobile.zoom.enable( true ); } }); // Autogrow if ( input.is( "textarea" ) ) { var extraLineHeight = 15, keyupTimeoutBuffer = 100, keyupTimeout; this._keyup = function() { var scrollHeight = input[ 0 ].scrollHeight, clientHeight = input[ 0 ].clientHeight; if ( clientHeight < scrollHeight ) { input.height( scrollHeight + extraLineHeight ); } }; input.on( "keyup change input paste", function() { clearTimeout( keyupTimeout ); keyupTimeout = setTimeout( self._keyup, keyupTimeoutBuffer ); }); // binding to pagechange here ensures that for pages loaded via // ajax the height is recalculated without user input this._on( $.mobile.document, { "pagechange": "_keyup" }); // Issue 509: the browser is not providing scrollHeight properly until the styles load if ( $.trim( input.val() ) ) { // bind to the window load to make sure the height is calculated based on BOTH // the DOM and CSS this._on( $.mobile.window, {"load": "_keyup"}); } } if ( input.attr( "disabled" ) ) { this.disable(); } }, disable: function() { var $el, isSearch = this.element.is( "[type='search'], :jqmData(type='search')" ), inputNeedsWrap = this.element.is( "input" ) && !this.element.is( ":jqmData(type='range')" ), parentNeedsDisabled = this.element.attr( "disabled", true ) && ( inputNeedsWrap || isSearch ); if ( parentNeedsDisabled ) { $el = this.element.parent(); } else { $el = this.element; } $el.addClass( "ui-disabled" ); return this._setOption( "disabled", true ); }, enable: function() { var $el, isSearch = this.element.is( "[type='search'], :jqmData(type='search')" ), inputNeedsWrap = this.element.is( "input" ) && !this.element.is( ":jqmData(type='range')" ), parentNeedsEnabled = this.element.attr( "disabled", false ) && ( inputNeedsWrap || isSearch ); if ( parentNeedsEnabled ) { $el = this.element.parent(); } else { $el = this.element; } $el.removeClass( "ui-disabled" ); return this._setOption( "disabled", false ); } }); //auto self-init widgets $.mobile.document.bind( "pagecreate create", function( e ) { $.mobile.textinput.prototype.enhanceWithin( e.target, true ); }); })( jQuery ); (function( $, undefined ) { // TODO rename callback/deprecate and default to the item itself as the first argument var defaultFilterCallback = function( text, searchValue, item ) { return text.toString().toLowerCase().indexOf( searchValue ) === -1; }; $.widget( "mobile.listview", $.mobile.listview, { options: { filter: false, filterPlaceholder: "Filter items...", filterTheme: "c", filterReveal: false, filterCallback: defaultFilterCallback }, _onKeyUp: function( e ) { var search = this._search, o = this.options, list = this.element, val = search[ 0 ].value.toLowerCase(), listItems = null, li = list.children(), lastval = search.jqmData( "lastval" ) + "", childItems = false, itemtext = "", item, // Check if a custom filter callback applies isCustomFilterCallback = o.filterCallback !== defaultFilterCallback; if ( lastval && lastval === val ) { // Execute the handler only once per value change return; } this._trigger( "beforefilter", "beforefilter", { input: search[ 0 ] } ); // Change val as lastval for next execution search.jqmData( "lastval" , val ); if ( isCustomFilterCallback || val.length < lastval.length || val.indexOf( lastval ) !== 0 ) { // Custom filter callback applies or removed chars or pasted something totally different, check all items listItems = list.children(); } else { // Only chars added, not removed, only use visible subset listItems = list.children( ":not(.ui-screen-hidden)" ); if ( !listItems.length && o.filterReveal ) { listItems = list.children( ".ui-screen-hidden" ); } } if ( val ) { // This handles hiding regular rows without the text we search for // and any list dividers without regular rows shown under it for ( var i = listItems.length - 1; i >= 0; i-- ) { item = $( listItems[ i ] ); itemtext = item.jqmData( "filtertext" ) || item.text(); if ( item.is( "li:jqmData(role=list-divider)" ) ) { item.toggleClass( "ui-filter-hidequeue" , !childItems ); // New bucket! childItems = false; } else if ( o.filterCallback( itemtext, val, item ) ) { //mark to be hidden item.toggleClass( "ui-filter-hidequeue" , true ); } else { // There's a shown item in the bucket childItems = true; } } // Show items, not marked to be hidden listItems .filter( ":not(.ui-filter-hidequeue)" ) .toggleClass( "ui-screen-hidden", false ); // Hide items, marked to be hidden listItems .filter( ".ui-filter-hidequeue" ) .toggleClass( "ui-screen-hidden", true ) .toggleClass( "ui-filter-hidequeue", false ); } else { //filtervalue is empty => show all listItems.toggleClass( "ui-screen-hidden", !!o.filterReveal ); } this._addFirstLastClasses( li, this._getVisibles( li, false ), false ); }, _create: function() { var list, wrapper, onKeyUp, search, o = this.options; this._super(); if ( !o.filter ) { return; } list = this.element; if ( o.filterReveal ) { list.children().addClass( "ui-screen-hidden" ); } wrapper = $( "<form>", { "class": "ui-listview-filter ui-bar-" + o.filterTheme, "role": "search" }).submit( function( e ) { search.blur(); return false; }); search = $( "<input>", { placeholder: o.filterPlaceholder }) .attr( "data-" + $.mobile.ns + "type", "search" ) .jqmData( "lastval", "" ) .appendTo( wrapper ) .textinput(); this._on( search, { keyup: "_onKeyUp", change: "_onKeyUp", input: "_onKeyUp" } ); $.extend( this, { _search: search }); if ( o.inset ) { wrapper.addClass( "ui-listview-filter-inset" ); } wrapper.insertBefore( list ); } }); })( jQuery ); (function( $, undefined ) { function defaultAutodividersSelector( elt ) { // look for the text in the given element var text = $.trim( elt.text() ) || null; if ( !text ) { return null; } // create the text for the divider (first uppercased letter) text = text.slice( 0, 1 ).toUpperCase(); return text; } $.widget( "mobile.listview", $.mobile.listview, { options: { autodividers: false, autodividersSelector: defaultAutodividersSelector }, _afterListviewRefresh: function() { var el = this.element; this._off( el, "listviewafterrefresh" ); this._replaceDividers(); this.refresh(); this._on( el, { listviewafterrefresh: "_afterListviewRefresh" } ); }, _replaceDividers: function() { var i, lis, li, dividerText, lastDividerText = null, list = this.element; list.find( "li:jqmData(role='list-divider')" ).remove(); lis = list.find( 'li' ); for ( i = 0; i < lis.length ; i++ ) { li = lis[ i ]; dividerText = this.options.autodividersSelector( $( li ) ); if ( dividerText && lastDividerText !== dividerText ) { var divider = document.createElement( 'li' ); divider.appendChild( document.createTextNode( dividerText ) ); divider.setAttribute( 'data-' + $.mobile.ns + 'role', 'list-divider' ); li.parentNode.insertBefore( divider, li ); } lastDividerText = dividerText; } }, _create: function() { this._super(); if ( !this.options.autodividers ) { return; } this._afterListviewRefresh(); } }); })( jQuery ); (function( $, undefined ) { $( document ).bind( "pagecreate create", function( e ) { $( ":jqmData(role='nojs')", e.target ).addClass( "ui-nojs" ); }); })( jQuery ); (function( $, undefined ) { $.mobile.behaviors.formReset = { _handleFormReset: function() { this._on( this.element.closest( "form" ), { reset: function() { this._delay( "_reset" ); } }); } }; })( jQuery ); /* * "checkboxradio" plugin */ (function( $, undefined ) { $.widget( "mobile.checkboxradio", $.mobile.widget, $.extend( { options: { theme: null, mini: false, initSelector: "input[type='checkbox'],input[type='radio']" }, _create: function() { var input = this.element, o = this.options, inheritAttr = function( input, dataAttr ) { return input.jqmData( dataAttr ) || input.closest( "form, fieldset" ).jqmData( dataAttr ); }, // NOTE: Windows Phone could not find the label through a selector // filter works though. parentLabel = $( input ).closest( "label" ), label = parentLabel.length ? parentLabel : $( input ).closest( "form, fieldset, :jqmData(role='page'), :jqmData(role='dialog')" ).find( "label" ).filter( "[for='" + input[0].id + "']" ).first(), inputtype = input[0].type, mini = inheritAttr( input, "mini" ) || o.mini, checkedState = inputtype + "-on", uncheckedState = inputtype + "-off", iconpos = inheritAttr( input, "iconpos" ), checkedClass = "ui-" + checkedState, uncheckedClass = "ui-" + uncheckedState; if ( inputtype !== "checkbox" && inputtype !== "radio" ) { return; } // If there's no selected theme check the data attr if ( !o.theme ) { o.theme = $.mobile.getInheritedTheme( this.element, "c" ); } // Expose for other methods $.extend( this, { //save buttonMarkup options to use them in refresh buttonMarkupOptions: { theme: o.theme, shadow: false, mini: mini, iconpos: iconpos }, label: label, inputtype: inputtype, checkedClass: checkedClass, uncheckedClass: uncheckedClass, checkedicon: checkedState, uncheckedicon: uncheckedState }); // Wrap the input + label in a div var wrapper = document.createElement('div'); wrapper.className = 'ui-' + inputtype; input.add( label ).wrapAll( wrapper ); this._on( label, { vmouseover: "_handleLabelVMouseOver", vclick: "_handleLabelVClick" }); this._on( input, { vmousedown: "_cacheVals", vclick: "_handleInputVClick", focus: "_handleInputFocus", blur: "_handleInputBlur" }); this._handleFormReset(); this.refresh(); }, _handleInputFocus: function() { this.label.addClass( $.mobile.focusClass ); }, _handleInputBlur: function() { this.label.removeClass( $.mobile.focusClass ); }, _handleInputVClick: function() { var $this = this.element; // Adds checked attribute to checked input when keyboard is used if ( $this.is( ":checked" ) ) { $this.prop( "checked", true); this._getInputSet().not( $this ).prop( "checked", false ); } else { $this.prop( "checked", false ); } this._updateAll(); }, _handleLabelVMouseOver: function( event ) { if ( this.label.parent().hasClass( "ui-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 ).jqmData( "cacheVal", this.checked ); }); }, //returns either a set of radios with the same name attribute, or a single checkbox _getInputSet: function() { if ( this.inputtype === "checkbox" ) { return this.element; } return this.element.closest( "form, :jqmData(role='page'), :jqmData(role='dialog')" ) .find( "input[name='" + this.element[ 0 ].name + "'][type='" + this.inputtype + "']" ); }, _updateAll: function() { var self = this; this._getInputSet().each( function() { var $this = $( this ); if ( this.checked || self.inputtype === "checkbox" ) { $this.trigger( "change" ); } }) .checkboxradio( "refresh" ); }, _reset: function() { this.refresh(); }, refresh: function() { var input = this.element[ 0 ], active = " " + $.mobile.activeBtnClass, checkedClass = this.checkedClass + ( this.element.parents( ".ui-controlgroup-horizontal" ).length ? active : "" ), label = this.label, options = this.buttonMarkupOptions; if ( input.checked ) { options.icon = this.checkedicon; label.removeClass( this.uncheckedClass + active ).addClass( checkedClass ).buttonMarkup( options ); } else { options.icon = this.uncheckedicon; label.removeClass( checkedClass ).addClass( this.uncheckedClass ).buttonMarkup( options ); } this.buttonMarkupOptions = {}; if ( input.disabled ) { this.disable(); } else { this.enable(); } }, disable: function() { this.element.prop( "disabled", true ).parent().addClass( "ui-disabled" ); }, enable: function() { this.element.prop( "disabled", false ).parent().removeClass( "ui-disabled" ); } }, $.mobile.behaviors.formReset ) ); //auto self-init widgets $.mobile.document.bind( "pagecreate create", function( e ) { $.mobile.checkboxradio.prototype.enhanceWithin( e.target, true ); }); })( jQuery ); (function( $, undefined ) { function splitOptions( o ) { var key, ret = { btn: {}, widget: {} }; for ( key in o ) { if ( o[ key ] !== null ) { if ( key === "disabled" ) { ret.widget.disabled = o[ key ]; ret.haveWidget = true; } else if ( key !== "initSelector" ) { ret.btn[ key ] = o[ key ]; ret.haveBtn = true; } } } return ret; } $.widget( "mobile.button", $.mobile.widget, { options: { theme: null, icon: null, iconpos: null, corners: true, shadow: true, iconshadow: true, inline: null, mini: null, initSelector: "button, [type='button'], [type='submit'], [type='reset']" }, _create: function() { var $button, o = this.options, $el = this.element, classes = ""; // if this is a link, check if it's been enhanced and, if not, use the right function if ( $el[ 0 ].tagName === "A" ) { if ( !$el.hasClass( "ui-btn" ) ) { $el.buttonMarkup(); } return; } // get the inherited theme // TODO centralize for all widgets if ( !o.theme ) { o.theme = $.mobile.getInheritedTheme( $el, "c" ); } o.disabled = $el.prop( "disabled" ); o = splitOptions( o ); // TODO: Post 1.1--once we have time to test thoroughly--any classes manually applied to the original element should be carried over to the enhanced element, with an `-enhanced` suffix. See https://github.com/jquery/jquery-mobile/issues/3577 /* if ( $el[0].className.length ) { classes = $el[0].className; } */ if ( !!~$el[ 0 ].className.indexOf( "ui-btn-left" ) ) { classes = "ui-btn-left"; } if ( !!~$el[ 0 ].className.indexOf( "ui-btn-right" ) ) { classes = "ui-btn-right"; } if ( $el.attr( "type" ) === "submit" || $el.attr( "type" ) === "reset" ) { if ( classes ) { classes += " ui-submit"; } else { classes = "ui-submit"; } } $( "label[for='" + $el.attr( "id" ) + "']" ).addClass( "ui-submit" ); // Add ARIA role this.button = $( "<div></div>" ) [ $el.html() ? "html" : "text" ]( $el.html() || $el.val() ) .insertBefore( $el ) .buttonMarkup( o.btn ) .addClass( classes ) .append( $el.addClass( "ui-btn-hidden" ) ); this._setOption( "disabled", o.widget.disabled ); $button = this.button; this._on( $el, { focus: function() { $button.addClass( $.mobile.focusClass ); }, blur: function() { $button.removeClass( $.mobile.focusClass ); } }); }, widget: function() { return this.button; }, _destroy: function() { var b = this.button; this.element.insertBefore( b ); b.remove(); }, _setOptions: function( o ) { o = splitOptions( o ); // Resolve the buttonMarkup options if ( o.haveBtn ) { this.button.buttonMarkup( o.btn ); } // ... and pass the rest up if ( o.haveWidget ) { this._super( o.widget ); } }, _setOption: function( key, value ) { var op = {}; op[ key ] = value; if ( key === "disabled" ) { value = !!value; this.element.prop( "disabled", value ); // FIXME: We should be using ui-state-disabled, so we can get rid of this line this.button.toggleClass( "ui-disabled", value ); } else if ( key !== "initSelector" ) { this.button.buttonMarkup( op ); // Record the option change in the options and in the DOM data-* attributes this.element.attr( "data-" + ( $.mobile.ns || "" ) + ( key.replace( /([A-Z])/, "-$1" ).toLowerCase() ), value ); } this._super( key, value ); }, refresh: function() { var $el = this.element; this._setOption( "disabled", $el.prop( "disabled" ) ); // Grab the button's text element from its implementation-independent data item $( this.button.data( 'buttonElements' ).text )[ $el.html() ? "html" : "text" ]( $el.html() || $el.val() ); } }); //auto self-init widgets $.mobile.document.bind( "pagecreate create", function( e ) { $.mobile.button.prototype.enhanceWithin( e.target, true ); }); })( jQuery ); (function( $, undefined ) { $.widget( "mobile.slider", $.mobile.widget, $.extend( { widgetEventPrefix: "slide", options: { theme: null, trackTheme: null, disabled: false, initSelector: "input[type='range'], :jqmData(type='range'), :jqmData(role='slider')", mini: false, highlight: false }, _create: function() { // TODO: Each of these should have comments explain what they're for var self = this, control = this.element, parentTheme = $.mobile.getInheritedTheme( control, "c" ), theme = this.options.theme || parentTheme, trackTheme = this.options.trackTheme || parentTheme, cType = control[ 0 ].nodeName.toLowerCase(), isSelect = this.isToggleSwitch = cType === "select", isRangeslider = control.parent().is( ":jqmData(role='rangeslider')" ), selectClass = ( this.isToggleSwitch ) ? "ui-slider-switch" : "", controlID = control.attr( "id" ), $label = $( "[for='" + controlID + "']" ), labelID = $label.attr( "id" ) || controlID + "-label", label = $label.attr( "id", labelID ), min = !this.isToggleSwitch ? parseFloat( control.attr( "min" ) ) : 0, max = !this.isToggleSwitch ? parseFloat( control.attr( "max" ) ) : control.find( "option" ).length-1, step = window.parseFloat( control.attr( "step" ) || 1 ), miniClass = ( this.options.mini || control.jqmData( "mini" ) ) ? " ui-mini" : "", domHandle = document.createElement( "a" ), handle = $( domHandle ), domSlider = document.createElement( "div" ), slider = $( domSlider ), valuebg = this.options.highlight && !this.isToggleSwitch ? (function() { var bg = document.createElement( "div" ); bg.className = "ui-slider-bg " + $.mobile.activeBtnClass + " ui-btn-corner-all"; return $( bg ).prependTo( slider ); })() : false, options, wrapper; domHandle.setAttribute( "href", "#" ); domSlider.setAttribute( "role", "application" ); domSlider.className = [this.isToggleSwitch ? "ui-slider " : "ui-slider-track ",selectClass," ui-btn-down-",trackTheme," ui-btn-corner-all", miniClass].join( "" ); domHandle.className = "ui-slider-handle"; domSlider.appendChild( domHandle ); handle.attr({ "role": "slider", "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": this._value(), "aria-valuetext": this._value(), "title": this._value(), "aria-labelledby": labelID }); $.extend( this, { slider: slider, handle: handle, type: cType, step: step, max: max, min: min, valuebg: valuebg, isRangeslider: isRangeslider, dragging: false, beforeStart: null, userModified: false, mouseMoved: false }); if ( this.isToggleSwitch ) { wrapper = document.createElement( "div" ); wrapper.className = "ui-slider-inneroffset"; for ( var j = 0, length = domSlider.childNodes.length; j < length; j++ ) { wrapper.appendChild( domSlider.childNodes[j] ); } domSlider.appendChild( wrapper ); // slider.wrapInner( "<div class='ui-slider-inneroffset'></div>" ); // make the handle move with a smooth transition handle.addClass( "ui-slider-handle-snapping" ); options = control.find( "option" ); for ( var i = 0, optionsCount = options.length; i < optionsCount; i++ ) { var side = !i ? "b" : "a", sliderTheme = !i ? " ui-btn-down-" + trackTheme : ( " " + $.mobile.activeBtnClass ), sliderLabel = document.createElement( "div" ), sliderImg = document.createElement( "span" ); sliderImg.className = ["ui-slider-label ui-slider-label-", side, sliderTheme, " ui-btn-corner-all"].join( "" ); sliderImg.setAttribute( "role", "img" ); sliderImg.appendChild( document.createTextNode( options[i].innerHTML ) ); $( sliderImg ).prependTo( slider ); } self._labels = $( ".ui-slider-label", slider ); } label.addClass( "ui-slider" ); // monitor the input for updated values control.addClass( this.isToggleSwitch ? "ui-slider-switch" : "ui-slider-input" ); this._on( control, { "change": "_controlChange", "keyup": "_controlKeyup", "blur": "_controlBlur", "vmouseup": "_controlVMouseUp" }); slider.bind( "vmousedown", $.proxy( this._sliderVMouseDown, this ) ) .bind( "vclick", false ); // We have to instantiate a new function object for the unbind to work properly // since the method itself is defined in the prototype (causing it to unbind everything) this._on( document, { "vmousemove": "_preventDocumentDrag" }); this._on( slider.add( document ), { "vmouseup": "_sliderVMouseUp" }); slider.insertAfter( control ); // wrap in a div for styling purposes if ( !this.isToggleSwitch && !isRangeslider ) { wrapper = this.options.mini ? "<div class='ui-slider ui-mini'>" : "<div class='ui-slider'>"; control.add( slider ).wrapAll( wrapper ); } // Only add focus class to toggle switch, sliders get it automatically from ui-btn if ( this.isToggleSwitch ) { this.handle.bind({ focus: function() { slider.addClass( $.mobile.focusClass ); }, blur: function() { slider.removeClass( $.mobile.focusClass ); } }); } // bind the handle event callbacks and set the context to the widget instance this._on( this.handle, { "vmousedown": "_handleVMouseDown", "keydown": "_handleKeydown", "keyup": "_handleKeyup" }); this.handle.bind( "vclick", false ); this._handleFormReset(); this.refresh( undefined, undefined, true ); }, _controlChange: function( event ) { // if the user dragged the handle, the "change" event was triggered from inside refresh(); don't call refresh() again if ( this._trigger( "controlchange", event ) === false ) { return false; } if ( !this.mouseMoved ) { this.refresh( this._value(), true ); } }, _controlKeyup: function( event ) { // necessary? this.refresh( this._value(), true, true ); }, _controlBlur: function( event ) { this.refresh( this._value(), true ); }, // it appears the clicking the up and down buttons in chrome on // range/number inputs doesn't trigger a change until the field is // blurred. Here we check thif the value has changed and refresh _controlVMouseUp: function( event ) { this._checkedRefresh(); }, // NOTE force focus on handle _handleVMouseDown: function( event ) { this.handle.focus(); }, _handleKeydown: function( event ) { var index = this._value(); if ( this.options.disabled ) { return; } // In all cases prevent the default and mark the handle as active switch ( event.keyCode ) { case $.mobile.keyCode.HOME: case $.mobile.keyCode.END: case $.mobile.keyCode.PAGE_UP: case $.mobile.keyCode.PAGE_DOWN: case $.mobile.keyCode.UP: case $.mobile.keyCode.RIGHT: case $.mobile.keyCode.DOWN: case $.mobile.keyCode.LEFT: event.preventDefault(); if ( !this._keySliding ) { this._keySliding = true; this.handle.addClass( "ui-state-active" ); } break; } // move the slider according to the keypress switch ( event.keyCode ) { case $.mobile.keyCode.HOME: this.refresh( this.min ); break; case $.mobile.keyCode.END: this.refresh( this.max ); break; case $.mobile.keyCode.PAGE_UP: case $.mobile.keyCode.UP: case $.mobile.keyCode.RIGHT: this.refresh( index + this.step ); break; case $.mobile.keyCode.PAGE_DOWN: case $.mobile.keyCode.DOWN: case $.mobile.keyCode.LEFT: this.refresh( index - this.step ); break; } }, // remove active mark _handleKeyup: function( event ) { if ( this._keySliding ) { this._keySliding = false; this.handle.removeClass( "ui-state-active" ); } }, _sliderVMouseDown: function( event ) { // NOTE: we don't do this in refresh because we still want to // support programmatic alteration of disabled inputs if ( this.options.disabled || !( event.which === 1 || event.which === 0 ) ) { return false; } if ( this._trigger( "beforestart", event ) === false ) { return false; } this.dragging = true; this.userModified = false; this.mouseMoved = false; if ( this.isToggleSwitch ) { this.beforeStart = this.element[0].selectedIndex; } this.refresh( event ); this._trigger( "start" ); return false; }, _sliderVMouseUp: function() { if ( this.dragging ) { this.dragging = false; if ( this.isToggleSwitch ) { // make the handle move with a smooth transition this.handle.addClass( "ui-slider-handle-snapping" ); if ( this.mouseMoved ) { // this is a drag, change the value only if user dragged enough if ( this.userModified ) { this.refresh( this.beforeStart === 0 ? 1 : 0 ); } else { this.refresh( this.beforeStart ); } } else { // this is just a click, change the value this.refresh( this.beforeStart === 0 ? 1 : 0 ); } } this.mouseMoved = false; this._trigger( "stop" ); return false; } }, _preventDocumentDrag: function( event ) { // NOTE: we don't do this in refresh because we still want to // support programmatic alteration of disabled inputs if ( this._trigger( "drag", event ) === false) { return false; } if ( this.dragging && !this.options.disabled ) { // this.mouseMoved must be updated before refresh() because it will be used in the control "change" event this.mouseMoved = true; if ( this.isToggleSwitch ) { // make the handle move in sync with the mouse this.handle.removeClass( "ui-slider-handle-snapping" ); } this.refresh( event ); // only after refresh() you can calculate this.userModified this.userModified = this.beforeStart !== this.element[0].selectedIndex; return false; } }, _checkedRefresh: function() { if ( this.value !== this._value() ) { this.refresh( this._value() ); } }, _value: function() { return this.isToggleSwitch ? this.element[0].selectedIndex : parseFloat( this.element.val() ) ; }, _reset: function() { this.refresh( undefined, false, true ); }, refresh: function( val, isfromControl, preventInputUpdate ) { // NOTE: we don't return here because we want to support programmatic // alteration of the input value, which should still update the slider var self = this, parentTheme = $.mobile.getInheritedTheme( this.element, "c" ), theme = this.options.theme || parentTheme, trackTheme = this.options.trackTheme || parentTheme, left, width, data, tol; self.slider[0].className = [ this.isToggleSwitch ? "ui-slider ui-slider-switch" : "ui-slider-track"," ui-btn-down-" + trackTheme,' ui-btn-corner-all', ( this.options.mini ) ? " ui-mini":""].join( "" ); if ( this.options.disabled || this.element.attr( "disabled" ) ) { this.disable(); } // set the stored value for comparison later this.value = this._value(); if ( this.options.highlight && !this.isToggleSwitch && this.slider.find( ".ui-slider-bg" ).length === 0 ) { this.valuebg = (function() { var bg = document.createElement( "div" ); bg.className = "ui-slider-bg " + $.mobile.activeBtnClass + " ui-btn-corner-all"; return $( bg ).prependTo( self.slider ); })(); } this.handle.buttonMarkup({ corners: true, theme: theme, shadow: true }); var pxStep, percent, control = this.element, isInput = !this.isToggleSwitch, optionElements = isInput ? [] : control.find( "option" ), min = isInput ? parseFloat( control.attr( "min" ) ) : 0, max = isInput ? parseFloat( control.attr( "max" ) ) : optionElements.length - 1, step = ( isInput && parseFloat( control.attr( "step" ) ) > 0 ) ? parseFloat( control.attr( "step" ) ) : 1; if ( typeof val === "object" ) { data = val; // a slight tolerance helped get to the ends of the slider tol = 8; left = this.slider.offset().left; width = this.slider.width(); pxStep = width/((max-min)/step); if ( !this.dragging || data.pageX < left - tol || data.pageX > left + width + tol ) { return; } if ( pxStep > 1 ) { percent = ( ( data.pageX - left ) / width ) * 100; } else { percent = Math.round( ( ( data.pageX - left ) / width ) * 100 ); } } else { if ( val == null ) { val = isInput ? parseFloat( control.val() || 0 ) : control[0].selectedIndex; } percent = ( parseFloat( val ) - min ) / ( max - min ) * 100; } if ( isNaN( percent ) ) { return; } var newval = ( percent / 100 ) * ( max - min ) + min; //from jQuery UI slider, the following source will round to the nearest step var valModStep = ( newval - min ) % step; var alignValue = newval - valModStep; if ( Math.abs( valModStep ) * 2 >= step ) { alignValue += ( valModStep > 0 ) ? step : ( -step ); } var percentPerStep = 100/((max-min)/step); // Since JavaScript has problems with large floats, round // the final value to 5 digits after the decimal point (see jQueryUI: #4124) newval = parseFloat( alignValue.toFixed(5) ); if ( typeof pxStep === "undefined" ) { pxStep = width / ( (max-min) / step ); } if ( pxStep > 1 && isInput ) { percent = ( newval - min ) * percentPerStep * ( 1 / step ); } if ( percent < 0 ) { percent = 0; } if ( percent > 100 ) { percent = 100; } if ( newval < min ) { newval = min; } if ( newval > max ) { newval = max; } this.handle.css( "left", percent + "%" ); this.handle[0].setAttribute( "aria-valuenow", isInput ? newval : optionElements.eq( newval ).attr( "value" ) ); this.handle[0].setAttribute( "aria-valuetext", isInput ? newval : optionElements.eq( newval ).getEncodedText() ); this.handle[0].setAttribute( "title", isInput ? newval : optionElements.eq( newval ).getEncodedText() ); if ( this.valuebg ) { this.valuebg.css( "width", percent + "%" ); } // drag the label widths if ( this._labels ) { var handlePercent = this.handle.width() / this.slider.width() * 100, aPercent = percent && handlePercent + ( 100 - handlePercent ) * percent / 100, bPercent = percent === 100 ? 0 : Math.min( handlePercent + 100 - aPercent, 100 ); this._labels.each(function() { var ab = $( this ).hasClass( "ui-slider-label-a" ); $( this ).width( ( ab ? aPercent : bPercent ) + "%" ); }); } if ( !preventInputUpdate ) { var valueChanged = false; // update control"s value if ( isInput ) { valueChanged = control.val() !== newval; control.val( newval ); } else { valueChanged = control[ 0 ].selectedIndex !== newval; control[ 0 ].selectedIndex = newval; } if ( this._trigger( "beforechange", val ) === false) { return false; } if ( !isfromControl && valueChanged ) { control.trigger( "change" ); } } }, enable: function() { this.element.attr( "disabled", false ); this.slider.removeClass( "ui-disabled" ).attr( "aria-disabled", false ); return this._setOption( "disabled", false ); }, disable: function() { this.element.attr( "disabled", true ); this.slider.addClass( "ui-disabled" ).attr( "aria-disabled", true ); return this._setOption( "disabled", true ); } }, $.mobile.behaviors.formReset ) ); //auto self-init widgets $.mobile.document.bind( "pagecreate create", function( e ) { $.mobile.slider.prototype.enhanceWithin( e.target, true ); }); })( jQuery ); (function( $, undefined ) { $.widget( "mobile.rangeslider", $.mobile.widget, $.extend( { options: { theme: null, trackTheme: null, disabled: false, initSelector: ":jqmData(role='rangeslider')", mini: false, highlight: true }, _create: function() { var secondLabel, $el = this.element, elClass = this.options.mini ? "ui-rangeslider ui-mini" : "ui-rangeslider", _inputFirst = $el.find( "input" ).first(), _inputLast = $el.find( "input" ).last(), label = $el.find( "label" ).first(), _sliderFirst = $.data( _inputFirst.get(0), "mobileSlider" ).slider, _sliderLast = $.data( _inputLast.get(0), "mobileSlider" ).slider, firstHandle = $.data( _inputFirst.get(0), "mobileSlider" ).handle, _sliders = $( "<div class=\"ui-rangeslider-sliders\" />" ).appendTo( $el ); if ( $el.find( "label" ).length > 1 ) { secondLabel = $el.find( "label" ).last().hide(); } _inputFirst.addClass( "ui-rangeslider-first" ); _inputLast.addClass( "ui-rangeslider-last" ); $el.addClass( elClass ); _sliderFirst.appendTo( _sliders ); _sliderLast.appendTo( _sliders ); label.prependTo( $el ); firstHandle.prependTo( _sliderLast ); $.extend( this, { _inputFirst: _inputFirst, _inputLast: _inputLast, _sliderFirst: _sliderFirst, _sliderLast: _sliderLast, _targetVal: null, _sliderTarget: false, _sliders: _sliders, _proxy: false }); this.refresh(); this._on( this.element.find( "input.ui-slider-input" ), { "slidebeforestart": "_slidebeforestart", "slidestop": "_slidestop", "slidedrag": "_slidedrag", "slidebeforechange": "_change", "blur": "_change", "keyup": "_change" }); this._on({ "mousedown":"_change" }); this._on( this.element.closest( "form" ), { "reset":"_handleReset" }); this._on( firstHandle, { "vmousedown": "_dragFirstHandle" }); }, _handleReset: function(){ var self = this; //we must wait for the stack to unwind before updateing other wise sliders will not have updated yet setTimeout( function(){ self._updateHighlight(); },0); }, _dragFirstHandle: function( event ) { //if the first handle is dragged send the event to the first slider $.data( this._inputFirst.get(0), "mobileSlider" ).dragging = true; $.data( this._inputFirst.get(0), "mobileSlider" ).refresh( event ); return false; }, _slidedrag: function( event ) { var first = $( event.target ).is( this._inputFirst ), otherSlider = ( first ) ? this._inputLast : this._inputFirst; this._sliderTarget = false; //if the drag was initiated on an extreme and the other handle is focused send the events to //the closest handle if ( ( this._proxy === "first" && first ) || ( this._proxy === "last" && !first ) ) { $.data( otherSlider.get(0), "mobileSlider" ).dragging = true; $.data( otherSlider.get(0), "mobileSlider" ).refresh( event ); return false; } }, _slidestop: function( event ) { var first = $( event.target ).is( this._inputFirst ); this._proxy = false; //this stops dragging of the handle and brings the active track to the front //this makes clicks on the track go the the last handle used this.element.find( "input" ).trigger( "vmouseup" ); this._sliderFirst.css( "z-index", first ? 1 : "" ); }, _slidebeforestart: function( event ) { this._sliderTarget = false; //if the track is the target remember this and the original value if ( $( event.originalEvent.target ).hasClass( "ui-slider-track" ) ) { this._sliderTarget = true; this._targetVal = $( event.target ).val(); } }, _setOption: function( options ) { this._superApply( options ); this.refresh(); }, refresh: function() { var $el = this.element, o = this.options; $el.find( "input" ).slider({ theme: o.theme, trackTheme: o.trackTheme, disabled: o.disabled, mini: o.mini, highlight: o.highlight }).slider( "refresh" ); this._updateHighlight(); }, _change: function( event ) { if ( event.type === "keyup" ) { this._updateHighlight(); return false; } var self = this, min = parseFloat( this._inputFirst.val(), 10 ), max = parseFloat( this._inputLast.val(), 10 ), first = $( event.target ).hasClass( "ui-rangeslider-first" ), thisSlider = first ? this._inputFirst : this._inputLast, otherSlider = first ? this._inputLast : this._inputFirst; if( ( this._inputFirst.val() > this._inputLast.val() && event.type === "mousedown" && !$(event.target).hasClass("ui-slider-handle")) ){ thisSlider.blur(); } else if( event.type === "mousedown" ){ return; } if ( min > max && !this._sliderTarget ) { //this prevents min from being greater then max thisSlider.val( first ? max: min ).slider( "refresh" ); this._trigger( "normalize" ); } else if ( min > max ) { //this makes it so clicks on the target on either extreme go to the closest handle thisSlider.val( this._targetVal ).slider( "refresh" ); //You must wait for the stack to unwind so first slider is updated before updating second setTimeout( function() { otherSlider.val( first ? min: max ).slider( "refresh" ); $.data( otherSlider.get(0), "mobileSlider" ).handle.focus(); self._sliderFirst.css( "z-index", first ? "" : 1 ); self._trigger( "normalize" ); }, 0 ); this._proxy = ( first ) ? "first" : "last"; } //fixes issue where when both _sliders are at min they cannot be adjusted if ( min === max ) { $.data( thisSlider.get(0), "mobileSlider" ).handle.css( "z-index", 1 ); $.data( otherSlider.get(0), "mobileSlider" ).handle.css( "z-index", 0 ); } else { $.data( otherSlider.get(0), "mobileSlider" ).handle.css( "z-index", "" ); $.data( thisSlider.get(0), "mobileSlider" ).handle.css( "z-index", "" ); } this._updateHighlight(); if ( min >= max ) { return false; } }, _updateHighlight: function() { var min = parseInt( $.data( this._inputFirst.get(0), "mobileSlider" ).handle.get(0).style.left, 10 ), max = parseInt( $.data( this._inputLast.get(0), "mobileSlider" ).handle.get(0).style.left, 10 ), width = (max - min); this.element.find( ".ui-slider-bg" ).css({ "margin-left": min + "%", "width": width + "%" }); }, _destroy: function() { this.element.removeClass( "ui-rangeslider ui-mini" ).find( "label" ).show(); this._inputFirst.after( this._sliderFirst ); this._inputLast.after( this._sliderLast ); this._sliders.remove(); this.element.find( "input" ).removeClass( "ui-rangeslider-first ui-rangeslider-last" ).slider( "destroy" ); } }, $.mobile.behaviors.formReset ) ); //auto self-init widgets $( document ).bind( "pagecreate create", function( e ) { $.mobile.rangeslider.prototype.enhanceWithin( e.target, true ); }); })( jQuery ); (function( $, undefined ) { $.widget( "mobile.selectmenu", $.mobile.widget, $.extend( { options: { theme: null, disabled: false, icon: "arrow-d", iconpos: "right", inline: false, corners: true, shadow: true, iconshadow: true, overlayTheme: "a", dividerTheme: "b", hidePlaceholderMenuItems: true, closeText: "Close", nativeMenu: true, // This option defaults to true on iOS devices. preventFocusZoom: /iPhone|iPad|iPod/.test( navigator.platform ) && navigator.userAgent.indexOf( "AppleWebKit" ) > -1, initSelector: "select:not( :jqmData(role='slider') )", mini: false }, _button: function() { return $( "<div/>" ); }, _setDisabled: function( value ) { this.element.attr( "disabled", value ); this.button.attr( "aria-disabled", value ); return this._setOption( "disabled", value ); }, _focusButton : function() { var self = this; setTimeout( function() { self.button.focus(); }, 40); }, _selectOptions: function() { return this.select.find( "option" ); }, // setup items that are generally necessary for select menu extension _preExtension: function() { var classes = ""; // TODO: Post 1.1--once we have time to test thoroughly--any classes manually applied to the original element should be carried over to the enhanced element, with an `-enhanced` suffix. See https://github.com/jquery/jquery-mobile/issues/3577 /* if ( $el[0].className.length ) { classes = $el[0].className; } */ if ( !!~this.element[0].className.indexOf( "ui-btn-left" ) ) { classes = " ui-btn-left"; } if ( !!~this.element[0].className.indexOf( "ui-btn-right" ) ) { classes = " ui-btn-right"; } this.select = this.element.removeClass( "ui-btn-left ui-btn-right" ).wrap( "<div class='ui-select" + classes + "'>" ); this.selectID = this.select.attr( "id" ); this.label = $( "label[for='"+ this.selectID +"']" ).addClass( "ui-select" ); this.isMultiple = this.select[ 0 ].multiple; if ( !this.options.theme ) { this.options.theme = $.mobile.getInheritedTheme( this.select, "c" ); } }, _destroy: function() { var wrapper = this.element.parents( ".ui-select" ); if ( wrapper.length > 0 ) { if ( wrapper.is( ".ui-btn-left, .ui-btn-right" ) ) { this.element.addClass( wrapper.hasClass( "ui-btn-left" ) ? "ui-btn-left" : "ui-btn-right" ); } this.element.insertAfter( wrapper ); wrapper.remove(); } }, _create: function() { this._preExtension(); // Allows for extension of the native select for custom selects and other plugins // see select.custom for example extension // TODO explore plugin registration this._trigger( "beforeCreate" ); this.button = this._button(); var self = this, options = this.options, inline = options.inline || this.select.jqmData( "inline" ), mini = options.mini || this.select.jqmData( "mini" ), iconpos = options.icon ? ( options.iconpos || this.select.jqmData( "iconpos" ) ) : false, // IE throws an exception at options.item() function when // there is no selected item // select first in this case selectedIndex = this.select[ 0 ].selectedIndex === -1 ? 0 : this.select[ 0 ].selectedIndex, // TODO values buttonId and menuId are undefined here button = this.button .insertBefore( this.select ) .buttonMarkup( { theme: options.theme, icon: options.icon, iconpos: iconpos, inline: inline, corners: options.corners, shadow: options.shadow, iconshadow: options.iconshadow, mini: mini }); this.setButtonText(); // Opera does not properly support opacity on select elements // In Mini, it hides the element, but not its text // On the desktop,it seems to do the opposite // for these reasons, using the nativeMenu option results in a full native select in Opera if ( options.nativeMenu && window.opera && window.opera.version ) { button.addClass( "ui-select-nativeonly" ); } // Add counter for multi selects if ( this.isMultiple ) { this.buttonCount = $( "<span>" ) .addClass( "ui-li-count ui-btn-up-c ui-btn-corner-all" ) .hide() .appendTo( button.addClass('ui-li-has-count') ); } // Disable if specified if ( options.disabled || this.element.attr('disabled')) { this.disable(); } // Events on native select this.select.change(function() { self.refresh(); if ( !!options.nativeMenu ) { this.blur(); } }); this._handleFormReset(); this.build(); }, build: function() { var self = this; this.select .appendTo( self.button ) .bind( "vmousedown", function() { // Add active class to button self.button.addClass( $.mobile.activeBtnClass ); }) .bind( "focus", function() { self.button.addClass( $.mobile.focusClass ); }) .bind( "blur", function() { self.button.removeClass( $.mobile.focusClass ); }) .bind( "focus vmouseover", function() { self.button.trigger( "vmouseover" ); }) .bind( "vmousemove", function() { // Remove active class on scroll/touchmove self.button.removeClass( $.mobile.activeBtnClass ); }) .bind( "change blur vmouseout", function() { self.button.trigger( "vmouseout" ) .removeClass( $.mobile.activeBtnClass ); }) .bind( "change blur", function() { self.button.removeClass( "ui-btn-down-" + self.options.theme ); }); // In many situations, iOS will zoom into the select upon tap, this prevents that from happening self.button.bind( "vmousedown", function() { if ( self.options.preventFocusZoom ) { $.mobile.zoom.disable( true ); } }); self.label.bind( "click focus", function() { if ( self.options.preventFocusZoom ) { $.mobile.zoom.disable( true ); } }); self.select.bind( "focus", function() { if ( self.options.preventFocusZoom ) { $.mobile.zoom.disable( true ); } }); self.button.bind( "mouseup", function() { if ( self.options.preventFocusZoom ) { setTimeout(function() { $.mobile.zoom.enable( true ); }, 0 ); } }); self.select.bind( "blur", function() { if ( self.options.preventFocusZoom ) { $.mobile.zoom.enable( true ); } }); }, selected: function() { return this._selectOptions().filter( ":selected" ); }, selectedIndices: function() { var self = this; return this.selected().map(function() { return self._selectOptions().index( this ); }).get(); }, setButtonText: function() { var self = this, selected = this.selected(), text = this.placeholder, span = $( document.createElement( "span" ) ); this.button.find( ".ui-btn-text" ).html(function() { if ( selected.length ) { text = selected.map(function() { return $( this ).text(); }).get().join( ", " ); } else { text = self.placeholder; } // TODO possibly aggregate multiple select option classes return span.text( text ) .addClass( self.select.attr( "class" ) ) .addClass( selected.attr( "class" ) ); }); }, setButtonCount: function() { var selected = this.selected(); // multiple count inside button if ( this.isMultiple ) { this.buttonCount[ selected.length > 1 ? "show" : "hide" ]().text( selected.length ); } }, _reset: function() { this.refresh(); }, refresh: function() { this.setButtonText(); this.setButtonCount(); }, // open and close preserved in native selects // to simplify users code when looping over selects open: $.noop, close: $.noop, disable: function() { this._setDisabled( true ); this.button.addClass( "ui-disabled" ); }, enable: function() { this._setDisabled( false ); this.button.removeClass( "ui-disabled" ); } }, $.mobile.behaviors.formReset ) ); //auto self-init widgets $.mobile.document.bind( "pagecreate create", function( e ) { $.mobile.selectmenu.prototype.enhanceWithin( e.target, true ); }); })( jQuery ); (function( $, undefined ) { function fitSegmentInsideSegment( winSize, segSize, offset, desired ) { var ret = desired; if ( winSize < segSize ) { // Center segment if it's bigger than the window ret = offset + ( winSize - segSize ) / 2; } else { // Otherwise center it at the desired coordinate while keeping it completely inside the window ret = Math.min( Math.max( offset, desired - segSize / 2 ), offset + winSize - segSize ); } return ret; } function windowCoords() { var $win = $.mobile.window; return { x: $win.scrollLeft(), y: $win.scrollTop(), cx: ( window.innerWidth || $win.width() ), cy: ( window.innerHeight || $win.height() ) }; } $.widget( "mobile.popup", $.mobile.widget, { options: { theme: null, overlayTheme: null, shadow: true, corners: true, transition: "none", positionTo: "origin", tolerance: null, initSelector: ":jqmData(role='popup')", closeLinkSelector: "a:jqmData(rel='back')", closeLinkEvents: "click.popup", navigateEvents: "navigate.popup", closeEvents: "navigate.popup pagebeforechange.popup", dismissible: true, // NOTE Windows Phone 7 has a scroll position caching issue that // requires us to disable popup history management by default // https://github.com/jquery/jquery-mobile/issues/4784 // // NOTE this option is modified in _create! history: !$.mobile.browser.oldIE }, _eatEventAndClose: function( e ) { e.preventDefault(); e.stopImmediatePropagation(); if ( this.options.dismissible ) { this.close(); } return false; }, // Make sure the screen size is increased beyond the page height if the popup's causes the document to increase in height _resizeScreen: function() { var popupHeight = this._ui.container.outerHeight( true ); this._ui.screen.removeAttr( "style" ); if ( popupHeight > this._ui.screen.height() ) { this._ui.screen.height( popupHeight ); } }, _handleWindowKeyUp: function( e ) { if ( this._isOpen && e.keyCode === $.mobile.keyCode.ESCAPE ) { return this._eatEventAndClose( e ); } }, _expectResizeEvent: function() { var winCoords = windowCoords(); if ( this._resizeData ) { if ( winCoords.x === this._resizeData.winCoords.x && winCoords.y === this._resizeData.winCoords.y && winCoords.cx === this._resizeData.winCoords.cx && winCoords.cy === this._resizeData.winCoords.cy ) { // timeout not refreshed return false; } else { // clear existing timeout - it will be refreshed below clearTimeout( this._resizeData.timeoutId ); } } this._resizeData = { timeoutId: setTimeout( $.proxy( this, "_resizeTimeout" ), 200 ), winCoords: winCoords }; return true; }, _resizeTimeout: function() { if ( this._isOpen ) { if ( !this._expectResizeEvent() ) { if ( this._ui.container.hasClass( "ui-popup-hidden" ) ) { // effectively rapid-open the popup while leaving the screen intact this._ui.container.removeClass( "ui-popup-hidden" ); this.reposition( { positionTo: "window" } ); this._ignoreResizeEvents(); } this._resizeScreen(); this._resizeData = null; this._orientationchangeInProgress = false; } } else { this._resizeData = null; this._orientationchangeInProgress = false; } }, _ignoreResizeEvents: function() { var self = this; if ( this._ignoreResizeTo ) { clearTimeout( this._ignoreResizeTo ); } this._ignoreResizeTo = setTimeout( function() { self._ignoreResizeTo = 0; }, 1000 ); }, _handleWindowResize: function( e ) { if ( this._isOpen && this._ignoreResizeTo === 0 ) { if ( ( this._expectResizeEvent() || this._orientationchangeInProgress ) && !this._ui.container.hasClass( "ui-popup-hidden" ) ) { // effectively rapid-close the popup while leaving the screen intact this._ui.container .addClass( "ui-popup-hidden" ) .removeAttr( "style" ); } } }, _handleWindowOrientationchange: function( e ) { if ( !this._orientationchangeInProgress && this._isOpen && this._ignoreResizeTo === 0 ) { this._expectResizeEvent(); this._orientationchangeInProgress = true; } }, // When the popup is open, attempting to focus on an element that is not a // child of the popup will redirect focus to the popup _handleDocumentFocusIn: function( e ) { var tgt = e.target, $tgt, ui = this._ui; if ( !this._isOpen ) { return; } if ( tgt !== ui.container[ 0 ] ) { $tgt = $( e.target ); if ( 0 === $tgt.parents().filter( ui.container[ 0 ] ).length ) { $( document.activeElement ).one( "focus", function( e ) { $tgt.blur(); }); ui.focusElement.focus(); e.preventDefault(); e.stopImmediatePropagation(); return false; } else if ( ui.focusElement[ 0 ] === ui.container[ 0 ] ) { ui.focusElement = $tgt; } } this._ignoreResizeEvents(); }, _create: function() { var ui = { screen: $( "<div class='ui-screen-hidden ui-popup-screen'></div>" ), placeholder: $( "<div style='display: none;'><!-- placeholder --></div>" ), container: $( "<div class='ui-popup-container ui-popup-hidden'></div>" ) }, thisPage = this.element.closest( ".ui-page" ), myId = this.element.attr( "id" ), o = this.options, key, value; // We need to adjust the history option to be false if there's no AJAX nav. // We can't do it in the option declarations because those are run before // it is determined whether there shall be AJAX nav. o.history = o.history && $.mobile.ajaxEnabled && $.mobile.hashListeningEnabled; if ( thisPage.length === 0 ) { thisPage = $( "body" ); } // define the container for navigation event bindings // TODO this would be nice at the the mobile widget level o.container = o.container || $.mobile.pageContainer || thisPage; // Apply the proto thisPage.append( ui.screen ); ui.container.insertAfter( ui.screen ); // Leave a placeholder where the element used to be ui.placeholder.insertAfter( this.element ); if ( myId ) { ui.screen.attr( "id", myId + "-screen" ); ui.container.attr( "id", myId + "-popup" ); ui.placeholder.html( "<!-- placeholder for " + myId + " -->" ); } ui.container.append( this.element ); ui.focusElement = ui.container; // Add class to popup element this.element.addClass( "ui-popup" ); // Define instance variables $.extend( this, { _scrollTop: 0, _page: thisPage, _ui: ui, _fallbackTransition: "", _currentTransition: false, _prereqs: null, _isOpen: false, _tolerance: null, _resizeData: null, _ignoreResizeTo: 0, _orientationchangeInProgress: false }); // This duplicates the code from the various option setters below for // better performance. It must be kept in sync with those setters. this._applyTheme( this.element, o.theme, "body" ); this._applyTheme( this._ui.screen, o.overlayTheme, "overlay" ); this._applyTransition( o.transition ); this.element .toggleClass( "ui-overlay-shadow", o.shadow ) .toggleClass( "ui-corner-all", o.corners ); this._setTolerance( o.tolerance ); ui.screen.bind( "vclick", $.proxy( this, "_eatEventAndClose" ) ); this._on( $.mobile.window, { orientationchange: $.proxy( this, "_handleWindowOrientationchange" ), resize: $.proxy( this, "_handleWindowResize" ), keyup: $.proxy( this, "_handleWindowKeyUp" ) }); this._on( $.mobile.document, { focusin: $.proxy( this, "_handleDocumentFocusIn" ) }); }, _applyTheme: function( dst, theme, prefix ) { var classes = ( dst.attr( "class" ) || "").split( " " ), alreadyAdded = true, currentTheme = null, matches, themeStr = String( theme ); while ( classes.length > 0 ) { currentTheme = classes.pop(); matches = ( new RegExp( "^ui-" + prefix + "-([a-z])$" ) ).exec( currentTheme ); if ( matches && matches.length > 1 ) { currentTheme = matches[ 1 ]; break; } else { currentTheme = null; } } if ( theme !== currentTheme ) { dst.removeClass( "ui-" + prefix + "-" + currentTheme ); if ( ! ( theme === null || theme === "none" ) ) { dst.addClass( "ui-" + prefix + "-" + themeStr ); } } }, _setTheme: function( value ) { this._applyTheme( this.element, value, "body" ); }, _setOverlayTheme: function( value ) { this._applyTheme( this._ui.screen, value, "overlay" ); if ( this._isOpen ) { this._ui.screen.addClass( "in" ); } }, _setShadow: function( value ) { this.element.toggleClass( "ui-overlay-shadow", value ); }, _setCorners: function( value ) { this.element.toggleClass( "ui-corner-all", value ); }, _applyTransition: function( value ) { this._ui.container.removeClass( this._fallbackTransition ); if ( value && value !== "none" ) { this._fallbackTransition = $.mobile._maybeDegradeTransition( value ); if ( this._fallbackTransition === "none" ) { this._fallbackTransition = ""; } this._ui.container.addClass( this._fallbackTransition ); } }, _setTransition: function( value ) { if ( !this._currentTransition ) { this._applyTransition( value ); } }, _setTolerance: function( value ) { var tol = { t: 30, r: 15, b: 30, l: 15 }; if ( value !== undefined ) { var ar = String( value ).split( "," ); $.each( ar, function( idx, val ) { ar[ idx ] = parseInt( val, 10 ); } ); switch( ar.length ) { // All values are to be the same case 1: if ( !isNaN( ar[ 0 ] ) ) { tol.t = tol.r = tol.b = tol.l = ar[ 0 ]; } break; // The first value denotes top/bottom tolerance, and the second value denotes left/right tolerance case 2: if ( !isNaN( ar[ 0 ] ) ) { tol.t = tol.b = ar[ 0 ]; } if ( !isNaN( ar[ 1 ] ) ) { tol.l = tol.r = ar[ 1 ]; } break; // The array contains values in the order top, right, bottom, left case 4: if ( !isNaN( ar[ 0 ] ) ) { tol.t = ar[ 0 ]; } if ( !isNaN( ar[ 1 ] ) ) { tol.r = ar[ 1 ]; } if ( !isNaN( ar[ 2 ] ) ) { tol.b = ar[ 2 ]; } if ( !isNaN( ar[ 3 ] ) ) { tol.l = ar[ 3 ]; } break; default: break; } } this._tolerance = tol; }, _setOption: function( key, value ) { var setter = "_set" + key.charAt( 0 ).toUpperCase() + key.slice( 1 ); if ( this[ setter ] !== undefined ) { this[ setter ]( value ); } this._super( key, value ); }, _clampPopupWidth: function( infoOnly ) { var menuSize, winCoords = windowCoords(), // rectangle within which the popup must fit rc = { x: this._tolerance.l, y: winCoords.y + this._tolerance.t, cx: winCoords.cx - this._tolerance.l - this._tolerance.r, cy: winCoords.cy - this._tolerance.t - this._tolerance.b }, ret; if ( !infoOnly ) { // Clamp the width of the menu before grabbing its size this._ui.container.css( "max-width", rc.cx ); } menuSize = { cx: this._ui.container.outerWidth( true ), cy: this._ui.container.outerHeight( true ) }; return { rc: rc, menuSize: menuSize }; }, _calculateFinalLocation: function( desired, clampInfo ) { var ret, rc = clampInfo.rc, menuSize = clampInfo.menuSize; // Center the menu over the desired coordinates, while not going outside // the window tolerances. This will center wrt. the window if the popup is too large. ret = { x: fitSegmentInsideSegment( rc.cx, menuSize.cx, rc.x, desired.x ), y: fitSegmentInsideSegment( rc.cy, menuSize.cy, rc.y, desired.y ) }; // Make sure the top of the menu is visible ret.y = Math.max( 0, ret.y ); // If the height of the menu is smaller than the height of the document // align the bottom with the bottom of the document // fix for $.mobile.document.height() bug in core 1.7.2. var docEl = document.documentElement, docBody = document.body, docHeight = Math.max( docEl.clientHeight, docBody.scrollHeight, docBody.offsetHeight, docEl.scrollHeight, docEl.offsetHeight ); ret.y -= Math.min( ret.y, Math.max( 0, ret.y + menuSize.cy - docHeight ) ); return { left: ret.x, top: ret.y }; }, // Try and center the overlay over the given coordinates _placementCoords: function( desired ) { return this._calculateFinalLocation( desired, this._clampPopupWidth() ); }, _createPrereqs: function( screenPrereq, containerPrereq, whenDone ) { var self = this, prereqs; // It is important to maintain both the local variable prereqs and self._prereqs. The local variable remains in // the closure of the functions which call the callbacks passed in. The comparison between the local variable and // self._prereqs is necessary, because once a function has been passed to .animationComplete() it will be called // next time an animation completes, even if that's not the animation whose end the function was supposed to catch // (for example, if an abort happens during the opening animation, the .animationComplete handler is not called for // that animation anymore, but the handler remains attached, so it is called the next time the popup is opened // - making it stale. Comparing the local variable prereqs to the widget-level variable self._prereqs ensures that // callbacks triggered by a stale .animationComplete will be ignored. prereqs = { screen: $.Deferred(), container: $.Deferred() }; prereqs.screen.then( function() { if ( prereqs === self._prereqs ) { screenPrereq(); } }); prereqs.container.then( function() { if ( prereqs === self._prereqs ) { containerPrereq(); } }); $.when( prereqs.screen, prereqs.container ).done( function() { if ( prereqs === self._prereqs ) { self._prereqs = null; whenDone(); } }); self._prereqs = prereqs; }, _animate: function( args ) { // NOTE before removing the default animation of the screen // this had an animate callback that would resolve the deferred // now the deferred is resolved immediately // TODO remove the dependency on the screen deferred this._ui.screen .removeClass( args.classToRemove ) .addClass( args.screenClassToAdd ); args.prereqs.screen.resolve(); if ( args.transition && args.transition !== "none" ) { if ( args.applyTransition ) { this._applyTransition( args.transition ); } if ( this._fallbackTransition ) { this._ui.container .animationComplete( $.proxy( args.prereqs.container, "resolve" ) ) .addClass( args.containerClassToAdd ) .removeClass( args.classToRemove ); return; } } this._ui.container.removeClass( args.classToRemove ); args.prereqs.container.resolve(); }, // The desired coordinates passed in will be returned untouched if no reference element can be identified via // desiredPosition.positionTo. Nevertheless, this function ensures that its return value always contains valid // x and y coordinates by specifying the center middle of the window if the coordinates are absent. // options: { x: coordinate, y: coordinate, positionTo: string: "origin", "window", or jQuery selector _desiredCoords: function( o ) { var dst = null, offset, winCoords = windowCoords(), x = o.x, y = o.y, pTo = o.positionTo; // Establish which element will serve as the reference if ( pTo && pTo !== "origin" ) { if ( pTo === "window" ) { x = winCoords.cx / 2 + winCoords.x; y = winCoords.cy / 2 + winCoords.y; } else { try { dst = $( pTo ); } catch( e ) { dst = null; } if ( dst ) { dst.filter( ":visible" ); if ( dst.length === 0 ) { dst = null; } } } } // If an element was found, center over it if ( dst ) { offset = dst.offset(); x = offset.left + dst.outerWidth() / 2; y = offset.top + dst.outerHeight() / 2; } // Make sure x and y are valid numbers - center over the window if ( $.type( x ) !== "number" || isNaN( x ) ) { x = winCoords.cx / 2 + winCoords.x; } if ( $.type( y ) !== "number" || isNaN( y ) ) { y = winCoords.cy / 2 + winCoords.y; } return { x: x, y: y }; }, _reposition: function( o ) { // We only care about position-related parameters for repositioning o = { x: o.x, y: o.y, positionTo: o.positionTo }; this._trigger( "beforeposition", o ); this._ui.container.offset( this._placementCoords( this._desiredCoords( o ) ) ); }, reposition: function( o ) { if ( this._isOpen ) { this._reposition( o ); } }, _openPrereqsComplete: function() { this._ui.container.addClass( "ui-popup-active" ); this._isOpen = true; this._resizeScreen(); this._ui.container.attr( "tabindex", "0" ).focus(); this._ignoreResizeEvents(); this._trigger( "afteropen" ); }, _open: function( options ) { var o = $.extend( {}, this.options, options ), // TODO move blacklist to private method androidBlacklist = ( function() { var w = window, ua = navigator.userAgent, // Rendering engine is Webkit, and capture major version wkmatch = ua.match( /AppleWebKit\/([0-9\.]+)/ ), wkversion = !!wkmatch && wkmatch[ 1 ], androidmatch = ua.match( /Android (\d+(?:\.\d+))/ ), andversion = !!androidmatch && androidmatch[ 1 ], chromematch = ua.indexOf( "Chrome" ) > -1; // Platform is Android, WebKit version is greater than 534.13 ( Android 3.2.1 ) and not Chrome. if( androidmatch !== null && andversion === "4.0" && wkversion && wkversion > 534.13 && !chromematch ) { return true; } return false; }()); // Count down to triggering "popupafteropen" - we have two prerequisites: // 1. The popup window animation completes (container()) // 2. The screen opacity animation completes (screen()) this._createPrereqs( $.noop, $.noop, $.proxy( this, "_openPrereqsComplete" ) ); this._currentTransition = o.transition; this._applyTransition( o.transition ); if ( !this.options.theme ) { this._setTheme( this._page.jqmData( "theme" ) || $.mobile.getInheritedTheme( this._page, "c" ) ); } this._ui.screen.removeClass( "ui-screen-hidden" ); this._ui.container.removeClass( "ui-popup-hidden" ); // Give applications a chance to modify the contents of the container before it appears this._reposition( o ); if ( this.options.overlayTheme && androidBlacklist ) { /* TODO: The native browser on Android 4.0.X ("Ice Cream Sandwich") suffers from an issue where the popup overlay appears to be z-indexed above the popup itself when certain other styles exist on the same page -- namely, any element set to `position: fixed` and certain types of input. These issues are reminiscent of previously uncovered bugs in older versions of Android's native browser: https://github.com/scottjehl/Device-Bugs/issues/3 This fix closes the following bugs ( I use "closes" with reluctance, and stress that this issue should be revisited as soon as possible ): https://github.com/jquery/jquery-mobile/issues/4816 https://github.com/jquery/jquery-mobile/issues/4844 https://github.com/jquery/jquery-mobile/issues/4874 */ // TODO sort out why this._page isn't working this.element.closest( ".ui-page" ).addClass( "ui-popup-open" ); } this._animate({ additionalCondition: true, transition: o.transition, classToRemove: "", screenClassToAdd: "in", containerClassToAdd: "in", applyTransition: false, prereqs: this._prereqs }); }, _closePrereqScreen: function() { this._ui.screen .removeClass( "out" ) .addClass( "ui-screen-hidden" ); }, _closePrereqContainer: function() { this._ui.container .removeClass( "reverse out" ) .addClass( "ui-popup-hidden" ) .removeAttr( "style" ); }, _closePrereqsDone: function() { var opts = this.options; this._ui.container.removeAttr( "tabindex" ); // remove the global mutex for popups $.mobile.popup.active = undefined; // alert users that the popup is closed this._trigger( "afterclose" ); }, _close: function( immediate ) { this._ui.container.removeClass( "ui-popup-active" ); this._page.removeClass( "ui-popup-open" ); this._isOpen = false; // Count down to triggering "popupafterclose" - we have two prerequisites: // 1. The popup window reverse animation completes (container()) // 2. The screen opacity animation completes (screen()) this._createPrereqs( $.proxy( this, "_closePrereqScreen" ), $.proxy( this, "_closePrereqContainer" ), $.proxy( this, "_closePrereqsDone" ) ); this._animate( { additionalCondition: this._ui.screen.hasClass( "in" ), transition: ( immediate ? "none" : ( this._currentTransition ) ), classToRemove: "in", screenClassToAdd: "out", containerClassToAdd: "reverse out", applyTransition: true, prereqs: this._prereqs }); }, _unenhance: function() { // Put the element back to where the placeholder was and remove the "ui-popup" class this._setTheme( "none" ); this.element // Cannot directly insertAfter() - we need to detach() first, because // insertAfter() will do nothing if the payload div was not attached // to the DOM at the time the widget was created, and so the payload // will remain inside the container even after we call insertAfter(). // If that happens and we remove the container a few lines below, we // will cause an infinite recursion - #5244 .detach() .insertAfter( this._ui.placeholder ) .removeClass( "ui-popup ui-overlay-shadow ui-corner-all" ); this._ui.screen.remove(); this._ui.container.remove(); this._ui.placeholder.remove(); }, _destroy: function() { if ( $.mobile.popup.active === this ) { this.element.one( "popupafterclose", $.proxy( this, "_unenhance" ) ); this.close(); } else { this._unenhance(); } }, _closePopup: function( e, data ) { var parsedDst, toUrl, o = this.options, immediate = false; if ( e && e.isDefaultPrevented() ) { return; } // restore location on screen window.scrollTo( 0, this._scrollTop ); if ( e && e.type === "pagebeforechange" && data ) { // Determine whether we need to rapid-close the popup, or whether we can // take the time to run the closing transition if ( typeof data.toPage === "string" ) { parsedDst = data.toPage; } else { parsedDst = data.toPage.jqmData( "url" ); } parsedDst = $.mobile.path.parseUrl( parsedDst ); toUrl = parsedDst.pathname + parsedDst.search + parsedDst.hash; if ( this._myUrl !== $.mobile.path.makeUrlAbsolute( toUrl ) ) { // Going to a different page - close immediately immediate = true; } else { e.preventDefault(); } } // remove nav bindings $.mobile.window.off( o.closeEvents ); // unbind click handlers added when history is disabled this.element.undelegate( o.closeLinkSelector, o.closeLinkEvents ); this._close( immediate ); }, // any navigation event after a popup is opened should close the popup // NOTE the pagebeforechange is bound to catch navigation events that don't // alter the url (eg, dialogs from popups) _bindContainerClose: function() { $.mobile.window .on( this.options.closeEvents, $.proxy( this, "_closePopup" ) ); }, // TODO no clear deliniation of what should be here and // what should be in _open. Seems to be "visual" vs "history" for now open: function( options ) { var self = this, opts = this.options, url, hashkey, activePage, currentIsDialog, hasHash, urlHistory; // make sure open is idempotent if( $.mobile.popup.active ) { return; } // set the global popup mutex $.mobile.popup.active = this; this._scrollTop = $.mobile.window.scrollTop(); // if history alteration is disabled close on navigate events // and leave the url as is if( !( opts.history ) ) { self._open( options ); self._bindContainerClose(); // When histoy is disabled we have to grab the data-rel // back link clicks so we can close the popup instead of // relying on history to do it for us self.element .delegate( opts.closeLinkSelector, opts.closeLinkEvents, function( e ) { self.close(); e.preventDefault(); }); return; } // cache some values for min/readability urlHistory = $.mobile.urlHistory; hashkey = $.mobile.dialogHashKey; activePage = $.mobile.activePage; currentIsDialog = ( activePage ? activePage.hasClass( "ui-dialog" ) : false ); this._myUrl = url = urlHistory.getActive().url; hasHash = ( url.indexOf( hashkey ) > -1 ) && !currentIsDialog && ( urlHistory.activeIndex > 0 ); if ( hasHash ) { self._open( options ); self._bindContainerClose(); return; } // if the current url has no dialog hash key proceed as normal // otherwise, if the page is a dialog simply tack on the hash key if ( url.indexOf( hashkey ) === -1 && !currentIsDialog ){ url = url + (url.indexOf( "#" ) > -1 ? hashkey : "#" + hashkey); } else { url = $.mobile.path.parseLocation().hash + hashkey; } // Tack on an extra hashkey if this is the first page and we've just reconstructed the initial hash if ( urlHistory.activeIndex === 0 && url === urlHistory.initialDst ) { url += hashkey; } // swallow the the initial navigation event, and bind for the next $.mobile.window.one( "beforenavigate", function( e ) { e.preventDefault(); self._open( options ); self._bindContainerClose(); }); this.urlAltered = true; $.mobile.navigate( url, {role: "dialog"} ); }, close: function() { // make sure close is idempotent if( $.mobile.popup.active !== this ) { return; } this._scrollTop = $.mobile.window.scrollTop(); if( this.options.history && this.urlAltered ) { $.mobile.back(); this.urlAltered = false; } else { // simulate the nav bindings having fired this._closePopup(); } } }); // TODO this can be moved inside the widget $.mobile.popup.handleLink = function( $link ) { var closestPage = $link.closest( ":jqmData(role='page')" ), scope = ( ( closestPage.length === 0 ) ? $( "body" ) : closestPage ), // NOTE make sure to get only the hash, ie7 (wp7) return the absolute href // in this case ruining the element selection popup = $( $.mobile.path.parseUrl($link.attr( "href" )).hash, scope[0] ), offset; if ( popup.data( "mobile-popup" ) ) { offset = $link.offset(); popup.popup( "open", { x: offset.left + $link.outerWidth() / 2, y: offset.top + $link.outerHeight() / 2, transition: $link.jqmData( "transition" ), positionTo: $link.jqmData( "position-to" ) }); } //remove after delay setTimeout( function() { // Check if we are in a listview var $parent = $link.parent().parent(); if ($parent.hasClass("ui-li")) { $link = $parent.parent(); } $link.removeClass( $.mobile.activeBtnClass ); }, 300 ); }; // TODO move inside _create $.mobile.document.bind( "pagebeforechange", function( e, data ) { if ( data.options.role === "popup" ) { $.mobile.popup.handleLink( data.options.link ); e.preventDefault(); } }); $.mobile.document.bind( "pagecreate create", function( e ) { $.mobile.popup.prototype.enhanceWithin( e.target, true ); }); })( jQuery ); /* * custom "selectmenu" plugin */ (function( $, undefined ) { var extendSelect = function( widget ) { var select = widget.select, origDestroy = widget._destroy, selectID = widget.selectID, prefix = ( selectID ? selectID : ( ( $.mobile.ns || "" ) + "uuid-" + widget.uuid ) ), popupID = prefix + "-listbox", dialogID = prefix + "-dialog", label = widget.label, thisPage = widget.select.closest( ".ui-page" ), selectOptions = widget._selectOptions(), isMultiple = widget.isMultiple = widget.select[ 0 ].multiple, buttonId = selectID + "-button", menuId = selectID + "-menu", menuPage = $( "<div data-" + $.mobile.ns + "role='dialog' id='" + dialogID + "' data-" +$.mobile.ns + "theme='"+ widget.options.theme +"' data-" +$.mobile.ns + "overlay-theme='"+ widget.options.overlayTheme +"'>" + "<div data-" + $.mobile.ns + "role='header'>" + "<div class='ui-title'>" + label.getEncodedText() + "</div>"+ "</div>"+ "<div data-" + $.mobile.ns + "role='content'></div>"+ "</div>" ), listbox = $( "<div id='" + popupID + "' class='ui-selectmenu'>" ).insertAfter( widget.select ).popup( { theme: widget.options.overlayTheme } ), list = $( "<ul>", { "class": "ui-selectmenu-list", "id": menuId, "role": "listbox", "aria-labelledby": buttonId }).attr( "data-" + $.mobile.ns + "theme", widget.options.theme ) .attr( "data-" + $.mobile.ns + "divider-theme", widget.options.dividerTheme ) .appendTo( listbox ), header = $( "<div>", { "class": "ui-header ui-bar-" + widget.options.theme }).prependTo( listbox ), headerTitle = $( "<h1>", { "class": "ui-title" }).appendTo( header ), menuPageContent, menuPageClose, headerClose; if ( widget.isMultiple ) { headerClose = $( "<a>", { "text": widget.options.closeText, "href": "#", "class": "ui-btn-left" }).attr( "data-" + $.mobile.ns + "iconpos", "notext" ).attr( "data-" + $.mobile.ns + "icon", "delete" ).appendTo( header ).buttonMarkup(); } $.extend( widget, { select: widget.select, selectID: selectID, buttonId: buttonId, menuId: menuId, popupID: popupID, dialogID: dialogID, thisPage: thisPage, menuPage: menuPage, label: label, selectOptions: selectOptions, isMultiple: isMultiple, theme: widget.options.theme, listbox: listbox, list: list, header: header, headerTitle: headerTitle, headerClose: headerClose, menuPageContent: menuPageContent, menuPageClose: menuPageClose, placeholder: "", build: function() { var self = this; // Create list from select, update state self.refresh(); if ( self._origTabIndex === undefined ) { // Map undefined to false, because self._origTabIndex === undefined // indicates that we have not yet checked whether the select has // originally had a tabindex attribute, whereas false indicates that // we have checked the select for such an attribute, and have found // none present. self._origTabIndex = ( self.select[ 0 ].getAttribute( "tabindex" ) === null ) ? false : self.select.attr( "tabindex" ); } self.select.attr( "tabindex", "-1" ).focus(function() { $( this ).blur(); self.button.focus(); }); // Button events self.button.bind( "vclick keydown" , function( event ) { if ( self.options.disabled || self.isOpen ) { return; } if (event.type === "vclick" || event.keyCode && (event.keyCode === $.mobile.keyCode.ENTER || event.keyCode === $.mobile.keyCode.SPACE)) { self._decideFormat(); if ( self.menuType === "overlay" ) { self.button.attr( "href", "#" + self.popupID ).attr( "data-" + ( $.mobile.ns || "" ) + "rel", "popup" ); } else { self.button.attr( "href", "#" + self.dialogID ).attr( "data-" + ( $.mobile.ns || "" ) + "rel", "dialog" ); } self.isOpen = true; // Do not prevent default, so the navigation may have a chance to actually open the chosen format } }); // Events for list items self.list.attr( "role", "listbox" ) .bind( "focusin", function( e ) { $( e.target ) .attr( "tabindex", "0" ) .trigger( "vmouseover" ); }) .bind( "focusout", function( e ) { $( e.target ) .attr( "tabindex", "-1" ) .trigger( "vmouseout" ); }) .delegate( "li:not(.ui-disabled, .ui-li-divider)", "click", function( event ) { // index of option tag to be selected var oldIndex = self.select[ 0 ].selectedIndex, newIndex = self.list.find( "li:not(.ui-li-divider)" ).index( this ), option = self._selectOptions().eq( newIndex )[ 0 ]; // toggle selected status on the tag for multi selects option.selected = self.isMultiple ? !option.selected : true; // toggle checkbox class for multiple selects if ( self.isMultiple ) { $( this ).find( ".ui-icon" ) .toggleClass( "ui-icon-checkbox-on", option.selected ) .toggleClass( "ui-icon-checkbox-off", !option.selected ); } // trigger change if value changed if ( self.isMultiple || oldIndex !== newIndex ) { self.select.trigger( "change" ); } // hide custom select for single selects only - otherwise focus clicked item // We need to grab the clicked item the hard way, because the list may have been rebuilt if ( self.isMultiple ) { self.list.find( "li:not(.ui-li-divider)" ).eq( newIndex ) .addClass( "ui-btn-down-" + widget.options.theme ).find( "a" ).first().focus(); } else { self.close(); } event.preventDefault(); }) .keydown(function( event ) { //keyboard events for menu items var target = $( event.target ), li = target.closest( "li" ), prev, next; // switch logic based on which key was pressed switch ( event.keyCode ) { // up or left arrow keys case 38: prev = li.prev().not( ".ui-selectmenu-placeholder" ); if ( prev.hasClass( "ui-li-divider" ) ) { prev = prev.prev(); } // if there's a previous option, focus it if ( prev.length ) { target .blur() .attr( "tabindex", "-1" ); prev.addClass( "ui-btn-down-" + widget.options.theme ).find( "a" ).first().focus(); } return false; // down or right arrow keys case 40: next = li.next(); if ( next.hasClass( "ui-li-divider" ) ) { next = next.next(); } // if there's a next option, focus it if ( next.length ) { target .blur() .attr( "tabindex", "-1" ); next.addClass( "ui-btn-down-" + widget.options.theme ).find( "a" ).first().focus(); } return false; // If enter or space is pressed, trigger click case 13: case 32: target.trigger( "click" ); return false; } }); // button refocus ensures proper height calculation // by removing the inline style and ensuring page inclusion self.menuPage.bind( "pagehide", function() { // TODO centralize page removal binding / handling in the page plugin. // Suggestion from @jblas to do refcounting // // TODO extremely confusing dependency on the open method where the pagehide.remove // bindings are stripped to prevent the parent page from disappearing. The way // we're keeping pages in the DOM right now sucks // // rebind the page remove that was unbound in the open function // to allow for the parent page removal from actions other than the use // of a dialog sized custom select // // doing this here provides for the back button on the custom select dialog $.mobile._bindPageRemove.call( self.thisPage ); }); // Events on the popup self.listbox.bind( "popupafterclose", function( event ) { self.close(); }); // Close button on small overlays if ( self.isMultiple ) { self.headerClose.click(function() { if ( self.menuType === "overlay" ) { self.close(); return false; } }); } // track this dependency so that when the parent page // is removed on pagehide it will also remove the menupage self.thisPage.addDependents( this.menuPage ); }, _isRebuildRequired: function() { var list = this.list.find( "li" ), options = this._selectOptions(); // TODO exceedingly naive method to determine difference // ignores value changes etc in favor of a forcedRebuild // from the user in the refresh method return options.text() !== list.text(); }, selected: function() { return this._selectOptions().filter( ":selected:not( :jqmData(placeholder='true') )" ); }, refresh: function( forceRebuild , foo ) { var self = this, select = this.element, isMultiple = this.isMultiple, indicies; if ( forceRebuild || this._isRebuildRequired() ) { self._buildList(); } indicies = this.selectedIndices(); self.setButtonText(); self.setButtonCount(); self.list.find( "li:not(.ui-li-divider)" ) .removeClass( $.mobile.activeBtnClass ) .attr( "aria-selected", false ) .each(function( i ) { if ( $.inArray( i, indicies ) > -1 ) { var item = $( this ); // Aria selected attr item.attr( "aria-selected", true ); // Multiple selects: add the "on" checkbox state to the icon if ( self.isMultiple ) { item.find( ".ui-icon" ).removeClass( "ui-icon-checkbox-off" ).addClass( "ui-icon-checkbox-on" ); } else { if ( item.hasClass( "ui-selectmenu-placeholder" ) ) { item.next().addClass( $.mobile.activeBtnClass ); } else { item.addClass( $.mobile.activeBtnClass ); } } } }); }, close: function() { if ( this.options.disabled || !this.isOpen ) { return; } var self = this; if ( self.menuType === "page" ) { self.menuPage.dialog( "close" ); self.list.appendTo( self.listbox ); } else { self.listbox.popup( "close" ); } self._focusButton(); // allow the dialog to be closed again self.isOpen = false; }, open: function() { this.button.click(); }, _decideFormat: function() { var self = this, $window = $.mobile.window, selfListParent = self.list.parent(), menuHeight = selfListParent.outerHeight(), menuWidth = selfListParent.outerWidth(), activePage = $( "." + $.mobile.activePageClass ), scrollTop = $window.scrollTop(), btnOffset = self.button.offset().top, screenHeight = $window.height(), screenWidth = $window.width(); function focusMenuItem() { var selector = self.list.find( "." + $.mobile.activeBtnClass + " a" ); if ( selector.length === 0 ) { selector = self.list.find( "li.ui-btn:not( :jqmData(placeholder='true') ) a" ); } selector.first().focus().closest( "li" ).addClass( "ui-btn-down-" + widget.options.theme ); } if ( menuHeight > screenHeight - 80 || !$.support.scrollTop ) { self.menuPage.appendTo( $.mobile.pageContainer ).page(); self.menuPageContent = menuPage.find( ".ui-content" ); self.menuPageClose = menuPage.find( ".ui-header a" ); // prevent the parent page from being removed from the DOM, // otherwise the results of selecting a list item in the dialog // fall into a black hole self.thisPage.unbind( "pagehide.remove" ); //for WebOS/Opera Mini (set lastscroll using button offset) if ( scrollTop === 0 && btnOffset > screenHeight ) { self.thisPage.one( "pagehide", function() { $( this ).jqmData( "lastScroll", btnOffset ); }); } self.menuPage .one( "pageshow", function() { focusMenuItem(); }) .one( "pagehide", function() { self.close(); }); self.menuType = "page"; self.menuPageContent.append( self.list ); self.menuPage.find("div .ui-title").text(self.label.text()); } else { self.menuType = "overlay"; self.listbox.one( "popupafteropen", focusMenuItem ); } }, _buildList: function() { var self = this, o = this.options, placeholder = this.placeholder, needPlaceholder = true, optgroups = [], lis = [], dataIcon = self.isMultiple ? "checkbox-off" : "false"; self.list.empty().filter( ".ui-listview" ).listview( "destroy" ); var $options = self.select.find( "option" ), numOptions = $options.length, select = this.select[ 0 ], dataPrefix = 'data-' + $.mobile.ns, dataIndexAttr = dataPrefix + 'option-index', dataIconAttr = dataPrefix + 'icon', dataRoleAttr = dataPrefix + 'role', dataPlaceholderAttr = dataPrefix + 'placeholder', fragment = document.createDocumentFragment(), isPlaceholderItem = false, optGroup; for (var i = 0; i < numOptions;i++, isPlaceholderItem = false) { var option = $options[i], $option = $( option ), parent = option.parentNode, text = $option.text(), anchor = document.createElement( 'a' ), classes = []; anchor.setAttribute( 'href', '#' ); anchor.appendChild( document.createTextNode( text ) ); // Are we inside an optgroup? if ( parent !== select && parent.nodeName.toLowerCase() === "optgroup" ) { var optLabel = parent.getAttribute( 'label' ); if ( optLabel !== optGroup ) { var divider = document.createElement( 'li' ); divider.setAttribute( dataRoleAttr, 'list-divider' ); divider.setAttribute( 'role', 'option' ); divider.setAttribute( 'tabindex', '-1' ); divider.appendChild( document.createTextNode( optLabel ) ); fragment.appendChild( divider ); optGroup = optLabel; } } if ( needPlaceholder && ( !option.getAttribute( "value" ) || text.length === 0 || $option.jqmData( "placeholder" ) ) ) { needPlaceholder = false; isPlaceholderItem = true; // If we have identified a placeholder, record the fact that it was // us who have added the placeholder to the option and mark it // retroactively in the select as well if ( null === option.getAttribute( dataPlaceholderAttr ) ) { this._removePlaceholderAttr = true; } option.setAttribute( dataPlaceholderAttr, true ); if ( o.hidePlaceholderMenuItems ) { classes.push( "ui-selectmenu-placeholder" ); } if ( placeholder !== text ) { placeholder = self.placeholder = text; } } var item = document.createElement('li'); if ( option.disabled ) { classes.push( "ui-disabled" ); item.setAttribute('aria-disabled',true); } item.setAttribute( dataIndexAttr,i ); item.setAttribute( dataIconAttr, dataIcon ); if ( isPlaceholderItem ) { item.setAttribute( dataPlaceholderAttr, true ); } item.className = classes.join( " " ); item.setAttribute( 'role', 'option' ); anchor.setAttribute( 'tabindex', '-1' ); item.appendChild( anchor ); fragment.appendChild( item ); } self.list[0].appendChild( fragment ); // Hide header if it's not a multiselect and there's no placeholder if ( !this.isMultiple && !placeholder.length ) { this.header.hide(); } else { this.headerTitle.text( this.placeholder ); } // Now populated, create listview self.list.listview(); }, _button: function() { return $( "<a>", { "href": "#", "role": "button", // TODO value is undefined at creation "id": this.buttonId, "aria-haspopup": "true", // TODO value is undefined at creation "aria-owns": this.menuId }); }, _destroy: function() { this.close(); // Restore the tabindex attribute to its original value if ( this._origTabIndex !== undefined ) { if ( this._origTabIndex !== false ) { this.select.attr( "tabindex", this._origTabIndex ); } else { this.select.removeAttr( "tabindex" ); } } // Remove the placeholder attribute if we were the ones to add it if ( this._removePlaceholderAttr ) { this._selectOptions().removeAttr( "data-" + $.mobile.ns + "placeholder" ); } // Remove the popup this.listbox.remove(); // Chain up origDestroy.apply( this, arguments ); } }); }; // issue #3894 - core doesn't trigger events on disabled delegates $.mobile.document.bind( "selectmenubeforecreate", function( event ) { var selectmenuWidget = $( event.target ).data( "mobile-selectmenu" ); if ( !selectmenuWidget.options.nativeMenu && selectmenuWidget.element.parents( ":jqmData(role='popup')" ).length === 0 ) { extendSelect( selectmenuWidget ); } }); })( jQuery ); (function( $, undefined ) { $.widget( "mobile.controlgroup", $.mobile.widget, $.extend( { options: { shadow: false, corners: true, excludeInvisible: true, type: "vertical", mini: false, initSelector: ":jqmData(role='controlgroup')" }, _create: function() { var $el = this.element, inner = $( "<div class='ui-controlgroup-controls'></div>" ), grouplegend = $el.children( "legend" ), o = this.options; // Apply the proto $el.wrapInner( inner ); if ( grouplegend.length ) { $( "<div role='heading' class='ui-controlgroup-label'></div>" ).append( grouplegend ).insertBefore( $el.children( 0 ) ); } $el.addClass( "ui-controlgroup" ); $.extend( this, { _initialRefresh: true }); // This duplicates the code from the various option setters below for // better performance. It must be kept in sync with those setters. $el .addClass( "ui-controlgroup-" + o.type ) .toggleClass( "ui-corner-all", o.corners ) .toggleClass( "ui-shadow", o.shadow ) .toggleClass( "ui-mini", o.mini ); }, _init: function() { this.refresh(); }, _setOption: function( key, value ) { var setter = "_set" + key.charAt( 0 ).toUpperCase() + key.slice( 1 ); if ( this[ setter ] !== undefined ) { this[ setter ]( value ); } this._super( key, value ); this.element.attr( "data-" + ( $.mobile.ns || "" ) + ( key.replace( /([A-Z])/, "-$1" ).toLowerCase() ), value ); }, _setType: function( value ) { this.element .removeClass( "ui-controlgroup-horizontal ui-controlgroup-vertical" ) .addClass( "ui-controlgroup-" + value ); this.refresh(); }, _setCorners: function( value ) { this.element.toggleClass( "ui-corner-all", value ); }, _setShadow: function( value ) { this.element.toggleClass( "ui-shadow", value ); }, _setMini: function( value ) { this.element.toggleClass( "ui-mini", value ); }, container: function() { return this.element.children( ".ui-controlgroup-controls" ); }, refresh: function() { var els = this.element.find( ".ui-btn" ).not( ".ui-slider-handle" ), create = this._initialRefresh; if ( $.mobile.checkboxradio ) { this.element.find( ":mobile-checkboxradio" ).checkboxradio( "refresh" ); } this._addFirstLastClasses( els, this.options.excludeInvisible ? this._getVisibles( els, create ) : els, create ); this._initialRefresh = false; } }, $.mobile.behaviors.addFirstLastClasses ) ); // TODO: Implement a mechanism to allow widgets to become enhanced in the // correct order when their correct enhancement depends on other widgets in // the page being correctly enhanced already. // // Dom ready was insufficient for preventing timing issues, use the page init // event to run this widget after all else. Widget registry incoming. $.mobile.document.bind( "pageinit create", function( e ) { $.mobile.controlgroup.prototype.enhanceWithin( e.target, true ); }); })(jQuery); (function( $, undefined ) { $( document ).bind( "pagecreate create", function( e ) { //links within content areas, tests included with page $( e.target ) .find( "a" ) .jqmEnhanceable() .not( ".ui-btn, .ui-link-inherit, :jqmData(role='none'), :jqmData(role='nojs')" ) .addClass( "ui-link" ); }); })( jQuery ); (function( $, undefined ) { $.widget( "mobile.fixedtoolbar", $.mobile.widget, { options: { visibleOnPageShow: true, disablePageZoom: true, transition: "slide", //can be none, fade, slide (slide maps to slideup or slidedown) fullscreen: false, tapToggle: true, tapToggleBlacklist: "a, button, input, select, textarea, .ui-header-fixed, .ui-footer-fixed, .ui-popup, .ui-panel, .ui-panel-dismiss-open", hideDuringFocus: "input, textarea, select", updatePagePadding: true, trackPersistentToolbars: true, // Browser detection! Weeee, here we go... // Unfortunately, position:fixed is costly, not to mention probably impossible, to feature-detect accurately. // Some tests exist, but they currently return false results in critical devices and browsers, which could lead to a broken experience. // Testing fixed positioning is also pretty obtrusive to page load, requiring injected elements and scrolling the window // The following function serves to rule out some popular browsers with known fixed-positioning issues // This is a plugin option like any other, so feel free to improve or overwrite it supportBlacklist: function() { return !$.support.fixedPosition; }, initSelector: ":jqmData(position='fixed')" }, _create: function() { var self = this, o = self.options, $el = self.element, tbtype = $el.is( ":jqmData(role='header')" ) ? "header" : "footer", $page = $el.closest( ".ui-page" ); // Feature detecting support for if ( o.supportBlacklist() ) { self.destroy(); return; } $el.addClass( "ui-"+ tbtype +"-fixed" ); // "fullscreen" overlay positioning if ( o.fullscreen ) { $el.addClass( "ui-"+ tbtype +"-fullscreen" ); $page.addClass( "ui-page-" + tbtype + "-fullscreen" ); } // If not fullscreen, add class to page to set top or bottom padding else{ $page.addClass( "ui-page-" + tbtype + "-fixed" ); } $.extend( this, { _thisPage: null }); self._addTransitionClass(); self._bindPageEvents(); self._bindToggleHandlers(); }, _addTransitionClass: function() { var tclass = this.options.transition; if ( tclass && tclass !== "none" ) { // use appropriate slide for header or footer if ( tclass === "slide" ) { tclass = this.element.hasClass( "ui-header" ) ? "slidedown" : "slideup"; } this.element.addClass( tclass ); } }, _bindPageEvents: function() { this._thisPage = this.element.closest( ".ui-page" ); //page event bindings // Fixed toolbars require page zoom to be disabled, otherwise usability issues crop up // This method is meant to disable zoom while a fixed-positioned toolbar page is visible this._on( this._thisPage, { "pagebeforeshow": "_handlePageBeforeShow", "webkitAnimationStart":"_handleAnimationStart", "animationstart":"_handleAnimationStart", "updatelayout": "_handleAnimationStart", "pageshow": "_handlePageShow", "pagebeforehide": "_handlePageBeforeHide" }); }, _handlePageBeforeShow: function() { var o = this.options; if ( o.disablePageZoom ) { $.mobile.zoom.disable( true ); } if ( !o.visibleOnPageShow ) { this.hide( true ); } }, _handleAnimationStart: function() { if ( this.options.updatePagePadding ) { this.updatePagePadding( this._thisPage ); } }, _handlePageShow: function() { this.updatePagePadding( this._thisPage ); if ( this.options.updatePagePadding ) { this._on( $.mobile.window, { "throttledresize": "updatePagePadding" } ); } }, _handlePageBeforeHide: function( e, ui ) { var o = this.options; if ( o.disablePageZoom ) { $.mobile.zoom.enable( true ); } if ( o.updatePagePadding ) { this._off( $.mobile.window, "throttledresize" ); } if ( o.trackPersistentToolbars ) { var thisFooter = $( ".ui-footer-fixed:jqmData(id)", this._thisPage ), thisHeader = $( ".ui-header-fixed:jqmData(id)", this._thisPage ), nextFooter = thisFooter.length && ui.nextPage && $( ".ui-footer-fixed:jqmData(id='" + thisFooter.jqmData( "id" ) + "')", ui.nextPage ) || $(), nextHeader = thisHeader.length && ui.nextPage && $( ".ui-header-fixed:jqmData(id='" + thisHeader.jqmData( "id" ) + "')", ui.nextPage ) || $(); if ( nextFooter.length || nextHeader.length ) { nextFooter.add( nextHeader ).appendTo( $.mobile.pageContainer ); ui.nextPage.one( "pageshow", function() { nextHeader.prependTo( this ); nextFooter.appendTo( this ); }); } } }, _visible: true, // This will set the content element's top or bottom padding equal to the toolbar's height updatePagePadding: function( tbPage ) { var $el = this.element, header = $el.hasClass( "ui-header" ), pos = parseFloat( $el.css( header ? "top" : "bottom" ) ); // This behavior only applies to "fixed", not "fullscreen" if ( this.options.fullscreen ) { return; } // tbPage argument can be a Page object or an event, if coming from throttled resize. tbPage = ( tbPage && tbPage.type === undefined && tbPage ) || this._thisPage || $el.closest( ".ui-page" ); $( tbPage ).css( "padding-" + ( header ? "top" : "bottom" ), $el.outerHeight() + pos ); }, _useTransition: function( notransition ) { var $win = $.mobile.window, $el = this.element, scroll = $win.scrollTop(), elHeight = $el.height(), pHeight = $el.closest( ".ui-page" ).height(), viewportHeight = $.mobile.getScreenHeight(), tbtype = $el.is( ":jqmData(role='header')" ) ? "header" : "footer"; return !notransition && ( this.options.transition && this.options.transition !== "none" && ( ( tbtype === "header" && !this.options.fullscreen && scroll > elHeight ) || ( tbtype === "footer" && !this.options.fullscreen && scroll + viewportHeight < pHeight - elHeight ) ) || this.options.fullscreen ); }, show: function( notransition ) { var hideClass = "ui-fixed-hidden", $el = this.element; if ( this._useTransition( notransition ) ) { $el .removeClass( "out " + hideClass ) .addClass( "in" ) .animationComplete(function () { $el.removeClass( "in" ); }); } else { $el.removeClass( hideClass ); } this._visible = true; }, hide: function( notransition ) { var hideClass = "ui-fixed-hidden", $el = this.element, // if it's a slide transition, our new transitions need the reverse class as well to slide outward outclass = "out" + ( this.options.transition === "slide" ? " reverse" : "" ); if ( this._useTransition( notransition ) ) { $el .addClass( outclass ) .removeClass( "in" ) .animationComplete(function() { $el.addClass( hideClass ).removeClass( outclass ); }); } else { $el.addClass( hideClass ).removeClass( outclass ); } this._visible = false; }, toggle: function() { this[ this._visible ? "hide" : "show" ](); }, _bindToggleHandlers: function() { var self = this, o = self.options, $el = self.element, delayShow, delayHide, isVisible = true; // tap toggle $el.closest( ".ui-page" ) .bind( "vclick", function( e ) { if ( o.tapToggle && !$( e.target ).closest( o.tapToggleBlacklist ).length ) { self.toggle(); } }) .bind( "focusin focusout", function( e ) { //this hides the toolbars on a keyboard pop to give more screen room and prevent ios bug which //positions fixed toolbars in the middle of the screen on pop if the input is near the top or //bottom of the screen addresses issues #4410 Footer navbar moves up when clicking on a textbox in an Android environment //and issue #4113 Header and footer change their position after keyboard popup - iOS //and issue #4410 Footer navbar moves up when clicking on a textbox in an Android environment if ( screen.width < 1025 && $( e.target ).is( o.hideDuringFocus ) && !$( e.target ).closest( ".ui-header-fixed, .ui-footer-fixed" ).length ) { //Fix for issue #4724 Moving through form in Mobile Safari with "Next" and "Previous" system //controls causes fixed position, tap-toggle false Header to reveal itself // isVisible instead of self._visible because the focusin and focusout events fire twice at the same time // Also use a delay for hiding the toolbars because on Android native browser focusin is direclty followed // by a focusout when a native selects opens and the other way around when it closes. if ( e.type === "focusout" && !isVisible ) { isVisible = true; //wait for the stack to unwind and see if we have jumped to another input clearTimeout( delayHide ); delayShow = setTimeout( function() { self.show(); }, 0 ); } else if ( e.type === "focusin" && !!isVisible ) { //if we have jumped to another input clear the time out to cancel the show. clearTimeout( delayShow ); isVisible = false; delayHide = setTimeout( function() { self.hide(); }, 0 ); } } }); }, _destroy: function() { var $el = this.element, header = $el.hasClass( "ui-header" ); $el.closest( ".ui-page" ).css( "padding-" + ( header ? "top" : "bottom" ), "" ); $el.removeClass( "ui-header-fixed ui-footer-fixed ui-header-fullscreen ui-footer-fullscreen in out fade slidedown slideup ui-fixed-hidden" ); $el.closest( ".ui-page" ).removeClass( "ui-page-header-fixed ui-page-footer-fixed ui-page-header-fullscreen ui-page-footer-fullscreen" ); } }); //auto self-init widgets $.mobile.document .bind( "pagecreate create", function( e ) { // DEPRECATED in 1.1: support for data-fullscreen=true|false on the page element. // This line ensures it still works, but we recommend moving the attribute to the toolbars themselves. if ( $( e.target ).jqmData( "fullscreen" ) ) { $( $.mobile.fixedtoolbar.prototype.options.initSelector, e.target ).not( ":jqmData(fullscreen)" ).jqmData( "fullscreen", true ); } $.mobile.fixedtoolbar.prototype.enhanceWithin( e.target ); }); })( jQuery ); (function( $, undefined ) { $.widget( "mobile.fixedtoolbar", $.mobile.fixedtoolbar, { _create: function() { this._super(); this._workarounds(); }, //check the browser and version and run needed workarounds _workarounds: function() { var ua = navigator.userAgent, platform = navigator.platform, // Rendering engine is Webkit, and capture major version wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ ), wkversion = !!wkmatch && wkmatch[ 1 ], os = null, self = this; //set the os we are working in if it dosent match one with workarounds return if( platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1 || platform.indexOf( "iPod" ) > -1 ) { os = "ios"; } else if ( ua.indexOf( "Android" ) > -1 ) { os = "android"; } else { return; } //check os version if it dosent match one with workarounds return if( os === "ios" ) { //iOS workarounds self._bindScrollWorkaround(); } else if ( os === "android" && wkversion && wkversion < 534 ) { //Android 2.3 run all Android 2.3 workaround self._bindScrollWorkaround(); self._bindListThumbWorkaround(); } else { return; } }, //Utility class for checking header and footer positions relative to viewport _viewportOffset: function() { var $el = this.element, header = $el.hasClass( "ui-header" ), offset = Math.abs( $el.offset().top - $.mobile.window.scrollTop() ); if( !header ) { offset = Math.round( offset - $.mobile.window.height() + $el.outerHeight() ) - 60; } return offset; }, //bind events for _triggerRedraw() function _bindScrollWorkaround: function() { var self = this; //bind to scrollstop and check if the toolbars are correctly positioned this._on( $.mobile.window, { scrollstop: function() { var viewportOffset = self._viewportOffset(); //check if the header is visible and if its in the right place if( viewportOffset > 2 && self._visible ) { self._triggerRedraw(); } }}); }, //this addresses issue #4250 Persistent footer instability in v1.1 with long select lists in Android 2.3.3 //and issue #3748 Android 2.x: Page transitions broken when fixed toolbars used //the absolutely positioned thumbnail in a list view causes problems with fixed position buttons above in a nav bar //setting the li's to -webkit-transform:translate3d(0,0,0); solves this problem to avoide potential issues in other //platforms we scope this with the class ui-android-2x-fix _bindListThumbWorkaround: function() { this.element.closest( ".ui-page" ).addClass( "ui-android-2x-fixed" ); }, //this addresses issues #4337 Fixed header problem after scrolling content on iOS and Android //and device bugs project issue #1 Form elements can lose click hit area in position: fixed containers. //this also addresses not on fixed toolbars page in docs //adding 1px of padding to the bottom then removing it causes a "redraw" //which positions the toolbars correctly (they will always be visually correct) _triggerRedraw: function() { var paddingBottom = parseFloat( $( ".ui-page-active" ).css( "padding-bottom" ) ); //trigger page redraw to fix incorrectly positioned fixed elements $( ".ui-page-active" ).css( "padding-bottom", ( paddingBottom + 1 ) + "px" ); //if the padding is reset with out a timeout the reposition will not occure. //this is independant of JQM the browser seems to need the time to react. setTimeout( function() { $( ".ui-page-active" ).css( "padding-bottom", paddingBottom + "px" ); }, 0 ); }, destroy: function() { this._super(); //Remove the class we added to the page previously in android 2.x this.element.closest( ".ui-page-active" ).removeClass( "ui-android-2x-fix" ); } }); })( jQuery ); (function( $, undefined ) { $.widget( "mobile.table", $.mobile.widget, { options: { classes: { table: "ui-table" }, initSelector: ":jqmData(role='table')" }, _create: function() { var $el = this.element, trs = this.element.find( "thead tr" ), headers = this.element.find( "tr:eq(0)" ).children(), allHeaders = headers.add( trs.children() ); this.element.addClass( this.options.classes.table ); $.extend( this, { // Expose headers and allHeaders properties on the widget // headers references the THs within the first TR in the table headers: headers, // allHeaders references headers, plus all THs in the thead, which may include several rows, or not allHeaders: allHeaders }); trs.each( function() { var coltally = 0, $this = $( this ); $this.children().each( function( i ) { var $this = $( this ), span = parseInt( $this.attr( "colspan" ), 10 ), sel = ":nth-child(" + ( coltally + 1 ) + ")"; $this.jqmData( "colstart", coltally + 1 ); if( span ) { for( var j = 0; j < span - 1; j++ ) { coltally++; sel += ", :nth-child(" + ( coltally + 1 ) + ")"; } } // Store "cells" data on header as a reference to all cells in the same column as this TH $this .jqmData( "cells", $el.find( "tr" ).not( trs.eq( 0 ) ).not( this ).children( sel ) ); coltally++; }); }); } }); //auto self-init widgets $.mobile.document.bind( "pagecreate create", function( e ) { $.mobile.table.prototype.enhanceWithin( e.target ); }); })( jQuery ); (function( $, undefined ) { $.widget( "mobile.table", $.mobile.table, { options: { mode: "columntoggle", columnBtnTheme: null, columnPopupTheme: null, columnBtnText: "Columns...", classes: $.extend( $.mobile.table.prototype.options.classes, { popup: "ui-table-columntoggle-popup", columnBtn: "ui-table-columntoggle-btn", priorityPrefix: "ui-table-priority-", columnToggleTable: "ui-table-columntoggle" }) }, _create: function() { var id, $menuButton, $popup, $menu, $table = this.element, o = this.options, ns = $.mobile.ns, menuInputChange = function( e ) { var checked = this.checked; $( this ).jqmData( "cells" ) .toggleClass( "ui-table-cell-hidden", !checked ) .toggleClass( "ui-table-cell-visible", checked ); }; this._super(); if( o.mode !== "columntoggle" ) { return; } this.element.addClass( o.classes.columnToggleTable ); id = ( $table.attr( "id" ) || o.classes.popup ) + "-popup"; //TODO BETTER FALLBACK ID HERE $menuButton = $( "<a href='#" + id + "' class='" + o.classes.columnBtn + "' data-" + ns + "rel='popup' data-" + ns + "mini='true'>" + o.columnBtnText + "</a>" ); $popup = $( "<div data-" + ns + "role='popup' data-" + ns + "role='fieldcontain' class='" + o.classes.popup + "' id='" + id + "'></div>" ); $menu = $( "<fieldset data-" + ns + "role='controlgroup'></fieldset>" ); // create the hide/show toggles this.headers.not( "td" ).each( function() { var $this = $( this ), priority = $this.jqmData( "priority" ), $cells = $this.add( $this.jqmData( "cells" ) ); if( priority ) { $cells.addClass( o.classes.priorityPrefix + priority ); $("<label><input type='checkbox' checked />" + $this.text() + "</label>" ) .appendTo( $menu ) .children( 0 ) .jqmData( "cells", $cells ) .checkboxradio( { theme: o.columnPopupTheme }); } }); $menu.appendTo( $popup ); // bind change event listeners to inputs - TODO: move to a private method? $menu.on( "change", "input", menuInputChange ); $menuButton .insertBefore( $table ) .buttonMarkup( { theme: o.columnBtnTheme }); $popup .insertBefore( $table ) .popup(); // refresh method this._on( $.mobile.window, { "throttledresize": "refresh" } ); $.extend( this, { _menu: $menu, _menuInputChange: menuInputChange }); this.refresh(); }, _destroy: function() { this._menu.off( "change", "input", this._menuInputChange ); this._super(); }, refresh: function() { this._menu.find( "input" ).each( function() { var $this = $( this ); this.checked = $this.jqmData( "cells" ).eq( 0 ).css( "display" ) === "table-cell"; $this.checkboxradio( "refresh" ); }); } }); })( jQuery ); (function( $, undefined ) { $.widget( "mobile.table", $.mobile.table, { options: { mode: "reflow", classes: $.extend( $.mobile.table.prototype.options.classes, { reflowTable: "ui-table-reflow", cellLabels: "ui-table-cell-label" }) }, _create: function() { var o = this.options; this._super(); // If it's not reflow mode, return here. if( o.mode !== "reflow" ) { return; } this.element.addClass( o.classes.reflowTable ); // get headers in reverse order so that top-level headers are appended last $( this.allHeaders.get().reverse() ).each( function( i ) { // create the hide/show toggles var $cells = $( this ).jqmData( "cells" ), colstart = $( this ).jqmData( "colstart" ), hierarchyClass = $cells.not( this ).filter( "thead th" ).length && " ui-table-cell-label-top", text = $(this).text(); if( text !== "" ) { if( hierarchyClass ) { var iteration = parseInt( $( this ).attr( "colspan" ), 10 ), filter = ""; if( iteration ){ filter = "td:nth-child("+ iteration +"n + " + ( colstart ) +")"; } $cells.filter( filter ).prepend( "<b class='" + o.classes.cellLabels + hierarchyClass + "'>" + text + "</b>" ); } else { $cells.prepend( "<b class='" + o.classes.cellLabels + "'>" + text + "</b>" ); } } }); } }); })( jQuery ); (function( $, window ) { $.mobile.iosorientationfixEnabled = true; // This fix addresses an iOS bug, so return early if the UA claims it's something else. var ua = navigator.userAgent; if( !( /iPhone|iPad|iPod/.test( navigator.platform ) && /OS [1-5]_[0-9_]* like Mac OS X/i.test( ua ) && ua.indexOf( "AppleWebKit" ) > -1 ) ){ $.mobile.iosorientationfixEnabled = false; return; } var zoom = $.mobile.zoom, evt, x, y, z, aig; function checkTilt( e ) { evt = e.originalEvent; aig = evt.accelerationIncludingGravity; x = Math.abs( aig.x ); y = Math.abs( aig.y ); z = Math.abs( aig.z ); // If portrait orientation and in one of the danger zones if ( !window.orientation && ( x > 7 || ( ( z > 6 && y < 8 || z < 8 && y > 6 ) && x > 5 ) ) ) { if ( zoom.enabled ) { zoom.disable(); } } else if ( !zoom.enabled ) { zoom.enable(); } } $.mobile.document.on( "mobileinit", function(){ if( $.mobile.iosorientationfixEnabled ){ $.mobile.window .bind( "orientationchange.iosorientationfix", zoom.enable ) .bind( "devicemotion.iosorientationfix", checkTilt ); } }); }( jQuery, this )); (function( $, window, undefined ) { var $html = $( "html" ), $head = $( "head" ), $window = $.mobile.window; //remove initial build class (only present on first pageshow) function hideRenderingClass() { $html.removeClass( "ui-mobile-rendering" ); } // trigger mobileinit event - useful hook for configuring $.mobile settings before they're used $( window.document ).trigger( "mobileinit" ); // support conditions // if device support condition(s) aren't met, leave things as they are -> a basic, usable experience, // otherwise, proceed with the enhancements if ( !$.mobile.gradeA() ) { return; } // override ajaxEnabled on platforms that have known conflicts with hash history updates // or generally work better browsing in regular http for full page refreshes (BB5, Opera Mini) if ( $.mobile.ajaxBlacklist ) { $.mobile.ajaxEnabled = false; } // Add mobile, initial load "rendering" classes to docEl $html.addClass( "ui-mobile ui-mobile-rendering" ); // This is a fallback. If anything goes wrong (JS errors, etc), or events don't fire, // this ensures the rendering class is removed after 5 seconds, so content is visible and accessible setTimeout( hideRenderingClass, 5000 ); $.extend( $.mobile, { // find and enhance the pages in the dom and transition to the first page. initializePage: function() { // find present pages var path = $.mobile.path, $pages = $( ":jqmData(role='page'), :jqmData(role='dialog')" ), hash = path.stripHash( path.stripQueryParams(path.parseLocation().hash) ), hashPage = document.getElementById( hash ); // if no pages are found, create one with body's inner html if ( !$pages.length ) { $pages = $( "body" ).wrapInner( "<div data-" + $.mobile.ns + "role='page'></div>" ).children( 0 ); } // add dialogs, set data-url attrs $pages.each(function() { var $this = $( this ); // unless the data url is already set set it to the pathname if ( !$this[ 0 ].getAttribute( "data-" + $.mobile.ns + "url" ) ) { $this.attr( "data-" + $.mobile.ns + "url", $this.attr( "id" ) || location.pathname + location.search ); } }); // define first page in dom case one backs out to the directory root (not always the first page visited, but defined as fallback) $.mobile.firstPage = $pages.first(); // define page container $.mobile.pageContainer = $.mobile.firstPage.parent().addClass( "ui-mobile-viewport" ); // alert listeners that the pagecontainer has been determined for binding // to events triggered on it $window.trigger( "pagecontainercreate" ); // cue page loading message $.mobile.showPageLoadingMsg(); //remove initial build class (only present on first pageshow) hideRenderingClass(); // if hashchange listening is disabled, there's no hash deeplink, // the hash is not valid (contains more than one # or does not start with #) // or there is no page with that hash, change to the first page in the DOM // Remember, however, that the hash can also be a path! if ( ! ( $.mobile.hashListeningEnabled && $.mobile.path.isHashValid( location.hash ) && ( $( hashPage ).is( ':jqmData(role="page")' ) || $.mobile.path.isPath( hash ) || hash === $.mobile.dialogHashKey ) ) ) { // Store the initial destination if ( $.mobile.path.isHashValid( location.hash ) ) { $.mobile.urlHistory.initialDst = hash.replace( "#", "" ); } // make sure to set initial popstate state if it exists // so that navigation back to the initial page works properly if( $.event.special.navigate.isPushStateEnabled() ) { $.mobile.navigate.navigator.squash( path.parseLocation().href ); } $.mobile.changePage( $.mobile.firstPage, { transition: "none", reverse: true, changeHash: false, fromHashChange: true }); } else { // trigger hashchange or navigate to squash and record the correct // history entry for an initial hash path if( !$.event.special.navigate.isPushStateEnabled() ) { $window.trigger( "hashchange", [true] ); } else { // TODO figure out how to simplify this interaction with the initial history entry // at the bottom js/navigate/navigate.js $.mobile.navigate.history.stack = []; $.mobile.navigate( $.mobile.path.isPath( location.hash ) ? location.hash : location.href ); } } } }); // initialize events now, after mobileinit has occurred $.mobile.navreadyDeferred.resolve(); // check which scrollTop value should be used by scrolling to 1 immediately at domready // then check what the scroll top is. Android will report 0... others 1 // note that this initial scroll won't hide the address bar. It's just for the check. $(function() { window.scrollTo( 0, 1 ); // if defaultHomeScroll hasn't been set yet, see if scrollTop is 1 // it should be 1 in most browsers, but android treats 1 as 0 (for hiding addr bar) // so if it's 1, use 0 from now on $.mobile.defaultHomeScroll = ( !$.support.scrollTop || $.mobile.window.scrollTop() === 1 ) ? 0 : 1; //dom-ready inits if ( $.mobile.autoInitializePage ) { $.mobile.initializePage(); } // window load event // hide iOS browser chrome on load $window.load( $.mobile.silentScroll ); if ( !$.support.cssPointerEvents ) { // IE and Opera don't support CSS pointer-events: none that we use to disable link-based buttons // by adding the 'ui-disabled' class to them. Using a JavaScript workaround for those browser. // https://github.com/jquery/jquery-mobile/issues/3558 $.mobile.document.delegate( ".ui-disabled", "vclick", function( e ) { e.preventDefault(); e.stopImmediatePropagation(); } ); } }); }( jQuery, this )); })); //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); //>>description: Responsive presentation and behavior for HTML data panels //>>label: Panel //>>group: Widgets //>>css.structure: ../css/structure/jquery.mobile.panel.css //>>css.theme: ../css/themes/default/jquery.mobile.theme.css //define( [ "jquery", "../jquery.mobile.widget", "./page", "./page.sections" ], function( $ ) { //>>excludeEnd("jqmBuildExclude"); (function( $, undefined ) { $.widget( "mobile.panel", $.mobile.widget, { options: { classes: { panel: "ui-panel", panelOpen: "ui-panel-open", panelClosed: "ui-panel-closed", panelFixed: "ui-panel-fixed", panelInner: "ui-panel-inner", modal: "ui-panel-dismiss", modalOpen: "ui-panel-dismiss-open", pagePanel: "ui-page-panel", pagePanelOpen: "ui-page-panel-open", contentWrap: "ui-panel-content-wrap", contentWrapOpen: "ui-panel-content-wrap-open", contentWrapClosed: "ui-panel-content-wrap-closed", contentFixedToolbar: "ui-panel-content-fixed-toolbar", contentFixedToolbarOpen: "ui-panel-content-fixed-toolbar-open", contentFixedToolbarClosed: "ui-panel-content-fixed-toolbar-closed", animate: "ui-panel-animate", modalOpaque: "background-opaque", modalBright: "background-bright", panelFixedButtonsTop: "ui-panel-fixed-buttons-scrollable-top", panelFixedButtonsBottom: "ui-panel-fixed-buttons-bottom", panelFixedButton: "panel-fixed-button" }, animate: true, theme: null, position: "left", dismissible: true, display: "reveal", //accepts reveal, push, overlay initSelector: ":jqmData(role='panel')", swipeClose: true, positionFixed: false, darkModal: false }, _panelID: null, _closeLink: null, _page: null, _modal: null, _panelInner: null, _wrapper: null, _fixedToolbar: null, _fixedPanelBtn: null, _create: function() { var self = this, $el = self.element, page = $el.closest( ":jqmData(role='page')" ), _getPageTheme = function() { var $theme = $.data( page[0], "mobilePage" ).options.theme, $pageThemeClass = "ui-body-" + $theme; return $pageThemeClass; }, _getPanelInner = function() { var $panelInner = $el.find( "." + self.options.classes.panelInner ); if ( $panelInner.length === 0 ) { $panelInner = $el.children().not(":jqmData(role='fixedpanelbutton')").wrapAll( '<div class="' + self.options.classes.panelInner + '" />' ).parent(); } return $panelInner; }, _getWrapper = function() { var $wrapper = page.find( "." + self.options.classes.contentWrap ); if ( $wrapper.length === 0 ) { $wrapper = page.children( ".ui-header:not(:jqmData(position='fixed')), .ui-content:not(:jqmData(role='popup')), .ui-footer:not(:jqmData(position='fixed'))" ).wrapAll( '<div class="' + self.options.classes.contentWrap + ' ' + _getPageTheme() + '" />' ).parent(); if ( $.support.cssTransform3d && !!self.options.animate ) { $wrapper.addClass( self.options.classes.animate ); } } return $wrapper; }, _getFixedToolbar = function() { var $fixedToolbar = page.find( "." + self.options.classes.contentFixedToolbar ); if ( $fixedToolbar.length === 0 ) { $fixedToolbar = page.find( ".ui-header:jqmData(position='fixed'), .ui-footer:jqmData(position='fixed')" ).addClass( self.options.classes.contentFixedToolbar ); if ( $.support.cssTransform3d && !!self.options.animate ) { $fixedToolbar.addClass( self.options.classes.animate ); } } return $fixedToolbar; }; // expose some private props to other methods $.extend( this, { _panelID: $el.attr( "id" ), _closeLink: $el.find( ":jqmData(rel='close')" ), _page: $el.closest( ":jqmData(role='page')" ), _pageTheme: _getPageTheme(), _panelInner: _getPanelInner(), _wrapper: _getWrapper(), _fixedToolbar: _getFixedToolbar() }); // if there is a fixed panel button wrap the content with two separate divs var $fixedPanelBtn = $el.find(":jqmData(role='fixedpanelbutton')"); if($fixedPanelBtn.length != 0){ self._panelInner.wrapAll( '<div class="' + self.options.classes.panelFixedButtonsTop + '" />'); $fixedPanelBtn.addClass( self.options.classes.panelFixedButtonsBottom ) .children().wrapAll('<div class="' + self.options.classes.panelInner + '" />'); self._fixedPanelBtn = $fixedPanelBtn; self.element.addClass( self.options.classes.panelFixedButton ); } self._addPanelClasses(); self._wrapper.addClass( this.options.classes.contentWrapClosed ); self._fixedToolbar.addClass( this.options.classes.contentFixedToolbarClosed ); // add class to page so we can set "overflow-x: hidden;" for it to fix Android zoom issue self._page.addClass( self.options.classes.pagePanel ); // if animating, add the class to do so if ( $.support.cssTransform3d && !!self.options.animate ) { this.element.addClass( self.options.classes.animate ); } //set fixed position if panel is on top or bottom if ( self.options.position == 'top' || self.options.position == 'bottom' ){ self.options.positionFixed = 'true'; } self._bindUpdateLayout(); self._bindCloseEvents(); self._bindLinkListeners(); self._bindPageEvents(); if ( !!self.options.dismissible ) { self._createModal(); } self._bindSwipeEvents(); }, _createModal: function( options ) { var self = this; self._modal = $( "<div class='" + self.options.classes.modal + "' data-panelid='" + self._panelID + "'></div>" ) .on( "mousedown", function() { self.close(); }) .appendTo( this._page ); }, _getPosDisplayClasses: function( prefix ) { return prefix + "-position-" + this.options.position + " " + prefix + "-display-" + this.options.display; }, _getPanelClasses: function() { var panelClasses = this.options.classes.panel + " " + this._getPosDisplayClasses( this.options.classes.panel ) + " " + this.options.classes.panelClosed; if ( this.options.theme ) { panelClasses += " ui-body-" + this.options.theme; } if ( !!this.options.positionFixed ) { panelClasses += " " + this.options.classes.panelFixed; } return panelClasses; }, _addPanelClasses: function() { this.element.addClass( this._getPanelClasses() ); }, _bindCloseEvents: function() { var self = this; self._closeLink.on( "click.panel" , function( e ) { e.preventDefault(); self.close(); return false; }); self.element.on( "click.panel" , "a:jqmData(ajax='false')", function( e ) { self.close(); }); }, _positionPanel: function() { var self = this, panelHeight = self.element.height(); //adjust the height of the scrollable area self._setPanelAreaInnerHeight( panelHeight ); var panelInnerHeight = self._panelInner.outerHeight(), expand = panelInnerHeight > $.mobile.getScreenHeight(); if( self.options.position === "top" ){ var panelHeight = self.element.height(); self._setVerticalCssTransform( self._wrapper, panelHeight ); } if ( expand || !self.options.positionFixed ) { if ( expand ) { self._unfixPanel(); $.mobile.resetActivePageHeight( panelInnerHeight ); } self._scrollIntoView( panelInnerHeight ); } else { self._fixPanel(); } }, _scrollIntoView: function( panelInnerHeight ) { if ( panelInnerHeight < $( window ).scrollTop() ) { window.scrollTo( 0, 0 ); } }, _bindFixListener: function() { this._on( $( window ), { "throttledresize": "_positionPanel" }); }, _unbindFixListener: function() { this._off( $( window ), "throttledresize" ); }, _unfixPanel: function() { if ( !!this.options.positionFixed && $.support.fixedPosition ) { this.element.removeClass( this.options.classes.panelFixed ); } }, _fixPanel: function() { if ( !!this.options.positionFixed && $.support.fixedPosition ) { this.element.addClass( this.options.classes.panelFixed ); } }, _bindUpdateLayout: function() { var self = this; self.element.on( "updatelayout", function( e ) { if ( self._open ) { self._positionPanel(); } }); }, _bindLinkListeners: function() { var self = this; self._page.on( "click.panel" , "a", function( e ) { if ( this.href.split( "#" )[ 1 ] === self._panelID && self._panelID !== undefined ) { e.preventDefault(); var $link = $( this ); if ( ! $link.hasClass( "ui-link" ) ) { $link.addClass( $.mobile.activeBtnClass ); self.element.one( "panelopen panelclose", function() { $link.removeClass( $.mobile.activeBtnClass ); }); } self.toggle(); return false; } }); }, _bindSwipeEvents: function() { var self = this, area = self._modal ? self.element.add( self._modal ) : self.element; if( self._fixedPanelBtn != null ){ self._fixedPanelBtn.on( "scrollstart", function( e ){ e.preventDefault(); }); } // on swipe, close the panel if( !!self.options.swipeClose ) { self._modal.on( "scrollstart.panel", function( e ){ e.preventDefault(); //prevent scrolling self.close(); }); if ( self.options.position === "left" ) { area.on( "swipeleft.panel", function( e ) { self.close(); }); } else if( self.options.position === "top" || self.options.position === 'bottom' ){ self.element.on( "scrollstart.panel", function( e ){ e.preventDefault(); }); } else { area.on( "swiperight.panel", function( e ) { self.close(); }); } } }, _bindPageEvents: function() { var self = this; self._page // Close the panel if another panel on the page opens .on( "panelbeforeopen", function( e ) { if ( self._open && e.target !== self.element[ 0 ] ) { self.close(); } }) // clean up open panels after page hide .on( "pagehide", function( e ) { if ( self._open ) { self.close( true ); } }) // on escape, close? might need to have a target check too... .on( "keyup.panel", function( e ) { if ( e.keyCode === 27 && self._open ) { self.close(); } }); }, // state storage of open or closed _open: false, _contentWrapOpenClasses: null, _fixedToolbarOpenClasses: null, _modalOpenClasses: null, _setVerticalCssTransform: function( element, distance ) { element.css( { '-webkit-transform': 'translate3d(0,' + distance +'px,0)', '-moz-transform':'translate3d(0,' + distance + 'px,0)', 'transform':'translate3d(0,' + distance + 'px,0)' } ); }, _resetCssTransform: function( element ){ element.css( { '-webkit-transform': '', '-moz-transform':'', 'transform':'' } ); }, _setPanelAreaInnerHeight: function( panelHeight ){ var self = this; //adjust the scrollable area height only if there are fixed buttons if( self._fixedPanelBtn != null ){ var areaHeight = panelHeight - self._fixedPanelBtn.height(); self._panelInner.parent().css( 'height',areaHeight ); } }, open: function( immediate ) { if ( !this._open ) { var self = this, o = self.options, _openPanel = function() { self._page.off( "panelclose" ); self._page.jqmData( "panel", "open" ); self.element.removeClass( o.classes.panelClosed ).addClass( o.classes.panelOpen ); var panelHeight = self.element.height(); //adjust the height of the scrollable area self._setPanelAreaInnerHeight( panelHeight ); if ( !immediate && $.support.cssTransform3d && !!o.animate ) { self.element.add( self._wrapper ).on( self._transitionEndEvents, complete ); //css transform to open top and bottom panels if( self.options.position === 'top' || self.options.position === 'bottom'){ self._setVerticalCssTransform( self.element, 0); } //css transform for page content when top panel opens in push mode if( self.options.position === 'top' && self.options.display ==="push" ){ self._setVerticalCssTransform( self._wrapper , panelHeight ); self._setVerticalCssTransform( self._fixedToolbar , panelHeight ); }else if( self.options.position === 'bottom' && self.options.display === "push" ){ self._setVerticalCssTransform( self._wrapper , -panelHeight ); self._setVerticalCssTransform( self._fixedToolbar , -panelHeight ); } } else { setTimeout( complete, 0 ); //move the main content up or down when display mode is push if( self.options.display === 'push'){ if( self.options.position === 'top' ){ self._wrapper.css('top', panelHeight+"px"); }else if( self.options.position === 'bottom' ){ self._wrapper.css('bottom', panelHeight+"px"); } } } if ( self.options.theme && self.options.display !== "overlay" ) { self._page .removeClass( self._pageTheme ) .addClass( "ui-body-" + self.options.theme ); } self._contentWrapOpenClasses = self._getPosDisplayClasses( o.classes.contentWrap ); self._wrapper .removeClass( o.classes.contentWrapClosed ) .addClass( self._contentWrapOpenClasses + " " + o.classes.contentWrapOpen ); self._fixedToolbarOpenClasses = self._getPosDisplayClasses( o.classes.contentFixedToolbar ); self._fixedToolbar .removeClass( o.classes.contentFixedToolbarClosed ) .addClass( self._fixedToolbarOpenClasses + " " + o.classes.contentFixedToolbarOpen ); self._modalOpenClasses = self._getPosDisplayClasses( o.classes.modal ) + " " + o.classes.modalOpen; if ( self._modal ) { self._modal.addClass( self._modalOpenClasses ); if( self.options.darkModal ){ //add an opacity transition self._modal.removeClass( o.classes.modalBright ).addClass( o.classes.modalOpaque ); } } }, complete = function() { self.element.add( self._wrapper ).off( self._transitionEndEvents, complete ); self._page.addClass( o.classes.pagePanelOpen ); self._positionPanel(); self._bindFixListener(); self._trigger( "open" ); }; if ( this.element.closest( ".ui-page-active" ).length < 0 ) { immediate = true; } self._trigger( "beforeopen" ); if ( self._page.jqmData('panel') === "open" ) { self._page.on( "panelclose", function() { _openPanel(); }); } else { _openPanel(); } self._open = true; } }, close: function( immediate ) { if ( this._open ) { var o = this.options, self = this, _closePanel = function() { self.element.removeClass( o.classes.panelOpen ); var panelHeight = self.element.height(); if ( !immediate && $.support.cssTransform3d && !!o.animate ) { self.element.add( self._wrapper ).on( self._transitionEndEvents, complete ); //css transform for top and bottom panels when closed if( self.options.position === 'top' ){ self._setVerticalCssTransform( self.element , -panelHeight ); }else if ( self.options.position === 'bottom' ){ self._setVerticalCssTransform( self.element , panelHeight ); } self._wrapper.removeClass( o.classes.contentWrapOpen ); //css transform for page content when top or bottom panel close in push mode if( self.options.position === 'top' || self.options.position === 'bottom' ){ if( self.options.display === "push" ){ self._resetCssTransform( self._wrapper ); self._resetCssTransform( self._fixedToolbar ); } } } else { setTimeout( complete, 0 ); if( self.options.position === 'top' ){ self._wrapper.css('top', ""); }else if( self.options.position === 'bottom' ){ self._wrapper.css('bottom', ""); } } self._page.removeClass( o.classes.pagePanelOpen ); self._fixedToolbar.removeClass( o.classes.contentFixedToolbarOpen ); if ( self._modal ) { if( self.options.darkModal ){ //remove opacity transition self._modal.removeClass( o.classes.modalOpaque ); } self._modal.removeClass( self._modalOpenClasses ).addClass( o.classes.modalBright ); } }, complete = function() { if ( self.options.theme && self.options.display !== "overlay" ) { self._page.removeClass( "ui-body-" + self.options.theme ).addClass( self._pageTheme ); } self.element.add( self._wrapper ).off( self._transitionEndEvents, complete ); self.element.addClass( o.classes.panelClosed ); self._wrapper .removeClass( self._contentWrapOpenClasses ) .addClass( o.classes.contentWrapClosed ); self._fixedToolbar .removeClass( self._fixedToolbarOpenClasses ) .addClass( o.classes.contentFixedToolbarClosed ); self._fixPanel(); self._unbindFixListener(); $.mobile.resetActivePageHeight(); self._page.jqmRemoveData( "panel" ); self._trigger( "close" ); }; if ( this.element.closest( ".ui-page-active" ).length < 0 ) { immediate = true; } self._trigger( "beforeclose" ); _closePanel(); self._open = false; } }, toggle: function( options ) { this[ this._open ? "close" : "open" ](); }, _transitionEndEvents: "webkitTransitionEnd oTransitionEnd otransitionend transitionend msTransitionEnd", _destroy: function() { var classes = this.options.classes, theme = this.options.theme, hasOtherSiblingPanels = this.element.siblings( "." + classes.panel ).length; // create if ( !hasOtherSiblingPanels ) { this._wrapper.children().unwrap(); this._page.find( "a" ).unbind( "panelopen panelclose" ); this._page.removeClass( classes.pagePanel ); if ( this._open ) { this._page.jqmRemoveData( "panel" ); this._page.removeClass( classes.pagePanelOpen ); if ( theme ) { this._page.removeClass( "ui-body-" + theme ).addClass( this._pageTheme ); } $.mobile.resetActivePageHeight(); } } else if ( this._open ) { this._wrapper.removeClass( classes.contentWrapOpen ); this._fixedToolbar.removeClass( classes.contentFixedToolbarOpen ); this._page.jqmRemoveData( "panel" ); this._page.removeClass( classes.pagePanelOpen ); if ( theme ) { this._page.removeClass( "ui-body-" + theme ).addClass( this._pageTheme ); } } this._panelInner.children().unwrap(); this.element.removeClass( [ this._getPanelClasses(), classes.panelAnimate ].join( " " ) ) .off( "swipeleft.panel swiperight.panel scrollstart.panel" ) .off( "panelbeforeopen" ) .off( "panelhide" ) .off( "keyup.panel" ) .off( "updatelayout" ); this._closeLink.off( "click.panel" ); if ( this._modal ) { this._modal.remove(); } // open and close this.element.off( this._transitionEndEvents ) .removeClass( [ classes.panelUnfixed, classes.panelClosed, classes.panelOpen ].join( " " ) ); } }); //auto self-init widgets $( document ).bind( "pagecreate create", function( e ) { $.mobile.panel.prototype.enhanceWithin( e.target ); }); })( jQuery ); //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); //}); //>>excludeEnd("jqmBuildExclude"); (function($) { $.widget("mobile.actionbar", $.mobile.widget, { options: { initSelector: ":jqmData(role='actionbar')", backButtonText: $.mobile.page.prototype.options.backBtnText, disableTapToggle: true }, _init: function() { }, _create: function() { this.actionBarArea = $( this.element ); this.actionOverflow = this.tabOverflow = 0; var overflow, self = this, o = self.options, bar = $( document.createElement( 'div' ) ), actions = $( document.createElement( 'div' ) ), itemList = this.actionBarArea.children( ":jqmData(role='action'), :jqmData(role='tab')" ), actionList = itemList.filter( ":jqmData(role='action')" ), tabList = itemList.filter( ":jqmData(role='tab')" ), back = this.actionBarArea.children( ":jqmData(role='back')").first(); this.tabOverflow = this.actionBarArea.children(":jqmData(role='tab-overflow')"); this.actionOverflow = this.actionBarArea.children(":jqmData(role='action-overflow')"); $("[data-role=footer]").fixedtoolbar({ tapToggle: !o.disableTapToggle }); this.actionBarArea.addClass('action-bar-area action-bar'); actions.addClass('action-bar-actions'); //If text field is selected, hide the actionbar $(".ui-input-text").focus(function(){ $(".ui-footer").addClass('bb10-landscape-hidden'); }).blur(function(){ $(".ui-footer").removeClass('bb10-landscape-hidden'); }); //If we have tabs we can not have a back button if (back.length && !tabList.length) { this._createBackButton(back, this.actionBarArea); } else { back.remove(); actions.addClass('action-bar-actions-full-width'); } for (var i = 0; i < tabList.length; i++) { actions.append(this._createTabItem(tabList.eq(i))); } for (i = 0; i < actionList.length; i++) { actions.append(this._createActionItem(actionList.eq(i))); } if( this.actionOverflow.length > 0 ) { this.actionOverflow.addClass('action-bar-overflow actions') .appendTo(actions) .bind('vclick', function(){ $(self.element).trigger("actionOverflowPressed"); }); } if( this.tabOverflow.length > 0 ) { var hasContent = (this.tabOverflow.children("p, img").length > 0) ? "" : "noContent"; this.tabOverflow.addClass('action-bar-overflow tabs action-bar-tab-item ' + hasContent) .prependTo(actions) .bind('vclick', function(event) { $(self.element).trigger("tabOverflowPressed"); }); } this.actionBarArea.append(actions); var itemsLen, actionBarItems, itemsize; if ( tabList.not('[data-overflow]').length > 0) { actionBarItems = this.actionBarArea.find(".action-bar-tab-item").not(".tabs"); itemsize = "action-bar-grid-" + ( (actionList.length || this.actionOverflow.length) > 0 ? "tabAction" : "tabs"); } else { actionBarItems = this.actionBarArea.find(".action-bar-action-item"); itemsize = "action-bar-grid-actions"; } itemsLen = actionBarItems.length; itemsize += "-" + ( ( itemsLen > 5 ) ? "e" : String.fromCharCode( 96 + itemsLen ) ); actionBarItems.addClass(itemsize); $( '.action-bar-action-item, .action-bar-tab-item' ) .bind( 'vmousedown', function( event ) { $(event.delegateTarget).addClass( 'pressed' ); }) .bind ('vmouseup', function(event) { $(event.delegateTarget).removeClass( 'pressed' ); }); $( document ) .bind( 'actionbarIn', function() { self._animateIn( back, actions ); }) .bind( 'actionbarOut', function() { self._animateOut( back, actions ); }); }, refresh: function() { }, destroy: function() { $(this.overflowActionMenu).remove(); $(this.overflowTabMenu).remove(); }, _createOverFlowItem: function(item) { item.addClass('menuItem light-text-fade'); var content = item.children("p").first().html(); item.children("p").remove(); item.append(content); return item; }, _createActionItem: function(item) { item = this._createBarItem(item); item.addClass("action-bar-action-item"); return item; }, _createTabItem: function(item) { item = this._createBarItem(item); item.addClass("action-bar-tab-item"); return item; }, _createBarItem: function(item) { var label = item.find('p').first(), itemImage = item.find('img').first() .addClass('action-bar-action-item-image') .appendTo(item); if (label) { var actionItemText = $(document.createElement('div')) .addClass('action-bar-action-item-text') .html(label) .appendTo(item); } return item; }, _createBackButton: function(back, appendto) { var backText = $(document.createElement('div')); backText .addClass('action-bar-back-text') .html("<p>" + this.options.backButtonText + "</p>"); back.addClass( 'action-bar-back' ) .append(backText) .bind( 'vmousedown', function(event) { $(event.delegateTarget).addClass('pressed'); window.history.back(); return false; }) .bind( 'vmouseup', function(event) { $(event.delegateTarget).removeClass('pressed'); }) .appendTo(appendto); }, _createOverflowButton: function(menu) { return $(document.createElement('div')) .addClass('action-bar-overflow'); }, _animateIn: function(back, actions) { if (back) { back.removeClass('action-bar-back-offset-right action-bar-back-offset-left'); } actions.removeClass('action-bar-actions-offset-right action-bar-actions-offset-left'); }, _animateOut: function(back, actions, bar, callback) { if (back) { back.addClass('action-bar-back-offset-right'); } actions.addClass('action-bar-actions-offset-right'); if (callback) { actions.on('webkitTransitionEnd', function caller() {actions.off('webkitTransitionEnd', caller); callback(); }); } }, _animateBelow: function(back, actions) { if (back) { back.addClass('action-bar-back-offset-left'); } actions.addClass('action-bar-actions-offset-left'); } }); $( document ).bind( "pagecreate create", function( e ){ $.mobile.actionbar.prototype.enhanceWithin( e.target ); }); })(jQuery); (function($) { $.widget( "mobile.activityindicator", $.mobile.widget, { options: { initSelector: ":jqmData(role='bb-activity-indicator')", size: "medium", theme: 'c', speed: '2s' }, _create: function() { //Get attribute data and store for readibility var themeAttr = this.element.attr('data-theme'); var sizeAttr = this.element.attr('data-size'); //Set Options this.options.size = sizeAttr !== undefined ? sizeAttr : this.options.size; this.options.theme = themeAttr !== undefined ? themeAttr : this.options.theme; //Create widget this.swirl = $(document.createElement('div')) .addClass('bb-activity-swirl' + ' bb-activity-' + this.options.size); this.element .append(this.swirl) .addClass( 'bb-activity ' + 'bb-activity-bg-' + this.options.theme + ' bb-activity-' + this.options.size ); }, display: function(isVisible) { if (!isVisible) { this.element.addClass('bb-activity-hidden'); } else { this.element.removeClass('bb-activity-hidden'); } }, speed: function (seconds) { this.swirl.css('-webkit-animation-duration', seconds); } }); $( document ).bind( "pagecreate create", function( e ){ $.mobile.activityindicator.prototype.enhanceWithin( e.target ); }); })(jQuery); (function($) { $.widget("mobile.applicationmenu", $.mobile.widget, { options: { initSelector: ":jqmData(role='applicationmenu')", maxItems: 5 }, _init: function() { }, _create: function() { this.actionsArea = this.element; this.actionsArea.parent('.ui-panel-inner').addClass('ui-panel-inner-application-menu'); var self = this, o = self.options, actions = $( document.createElement( 'div' ) ), itemList = this.actionsArea.children( ":jqmData(role='action'), :jqmData(role='SettingsActionItem')" ), barItemsTotal = (self.options.maxItems < itemList.length) ? self.options.maxItems : itemList.length, isSettingsActionFound = false; for (var i = 0; i < barItemsTotal; i++) { this.actionsArea.append(this._createActionItem(itemList.eq(i))); } //display SettingsActionItem on the right side when it is the only item if( barItemsTotal === 1 ){ this.actionsArea.children(":jqmData(role='SettingsActionItem')").addClass('application-menu-item-settings'); } this.actionsArea.addClass('application-menu'); $( '.application-menu-item' ) .bind( 'vmousedown', function( event ) { $(event.delegateTarget) .children('.application-menu-item-pressed-indicator') .addClass( 'pressed' ); }) .bind ('vmouseup', function(event) { $(event.delegateTarget) .children('.application-menu-item-pressed-indicator') .removeClass( 'pressed' ); }); }, refresh: function() { }, destroy: function() { }, _createActionItem: function(item) { var label = item.find('p').first(), itemImage = item.find('img').first() .addClass('application-menu-item-image') .appendTo(item); if (label) { var actionItemText = $(document.createElement('div')) .addClass('application-menu-item-text') .html(label) .appendTo(item); } var pressedIndicator = $(document.createElement('div')) .addClass('application-menu-item-pressed-indicator') .prependTo(item); item.addClass('application-menu-item visible'); return item; } }); $( document ).bind( "pagecreate create", function( e ){ $.mobile.applicationmenu.prototype.enhanceWithin( e.target ); }); })(jQuery); (function ($) { $.widget("mobile.buttonGroup", $.mobile.widget, { options: { initSelector: ':jqmData(role="controlgroup"):jqmData(type="horizontal")' }, _create: function () { this.element.addClass('bb-buttongroup'); this.controlWrapper = this.element.children() .addClass('bb-buttongroup-controls'); var buttons = this.controlWrapper.children(), numButtons = buttons.length, divisor = numButtons % 3; // If button group has multiple rows add stacked class to apply margin correction offset if (numButtons >= 3) { this.controlWrapper.addClass('bb-buttongroup-stacked'); } // Arrange Buttons into a grid layout // If button group is multiple of three, wrap in 3 column grid if (divisor === 0) { this.controlWrapper.addClass('ui-grid-b'); } else { // Single line - 1 Button if (numButtons === 1) { this.controlWrapper.addClass('ui-grid-solo'); } // 2 or 4 Buttons else if (numButtons === 2 || numButtons === 4) { this.controlWrapper.addClass('ui-grid-a'); } // Multi Line else { // Arrange buttons into a 3 column grid this.controlWrapper.addClass('ui-grid-b'); // Move the remaining buttons into a separate container with an appropriate grid layout this.controlWrapperExtra = $(document.createElement('div')) .addClass('bb-buttongroup-controls-extra ui-controlgroup-controls'); this.element.append(this.controlWrapperExtra); var extra = buttons.slice(numButtons - divisor, buttons.length); this.controlWrapperExtra.append(extra); // If 2 buttons remain if (extra.length === 2) { this.controlWrapperExtra.addClass('ui-grid-a'); } // If 1 button remains else if (extra.length === 1) { this.controlWrapperExtra.addClass('ui-grid-solo'); } } } // Add Corner rounding classes to the appropriate buttons // If the button group is stacked if (numButtons > 3) { //Top left Button buttons.eq(0) .removeClass('ui-corner-left') .addClass('ui-corner-tl') .children().removeClass('ui-corner-left') .addClass('ui-corner-tl'); //Bottom Right Button buttons.eq(numButtons-1) .removeClass('ui-corner-right') .addClass('ui-corner-br') .children().removeClass('ui-corner-right') .addClass('ui-corner-br'); // 4 Button layout requires a special case as it breaks the pattern if (numButtons === 4) { //Top Right Button buttons.eq(1) .addClass('ui-corner-tr') .children().addClass('ui-corner-tr'); //Bottom Left Button buttons.eq(2) .addClass('ui-corner-') .css('margin-top', '-2px') .children().addClass('ui-corner-bl'); } else { //Top Right Button buttons.eq(2) .addClass('ui-corner-tr') .children().addClass('ui-corner-tr'); //Bottom Left Button if (divisor === 0) { divisor = 3; } buttons.eq(numButtons - divisor) .addClass('ui-corner-bl') .children().addClass('ui-corner-bl'); } } } }); //TODO: enhance upon controlgroup creation event instead of pageinit $( document ).bind( "pageinit create", function( e ){ $.mobile.buttonGroup.prototype.enhanceWithin( e.target ); }); })(jQuery); (function($) { $.widget("mobile.dropdown", $.mobile.widget, { options: { initSelector: ""//":jqmData(role='dropdown')" }, _init: function() { }, _create: function() { var self = this, parent = $(this.element).parent(), collapse = $('<div data-role="collapsible" data-content-theme=' + $.mobile.getInheritedTheme( parent, "c" ) + ' data-collapsed="true" data-iconpos="right">'), select = $(this.element), list = $('<ul data-role="listview"></ul>').appendTo(collapse); this.header = $('<h1></h1>').appendTo(collapse); select.find('option').each( function (i,el){ var item = $(document.createElement('li')).text($(el).text()); if($(el).attr("selected") === "selected") { self._setHeader($(el).text()); item.addClass("checked"); } list.append(item); }); select.css("display", "none"); $("<div>").attr("data-enhance", "false").wrap(select); parent.after(collapse); collapse.parent().trigger("create"); parent.remove(); select.remove(); setTimeout(function(){ select.appendTo(collapse); }, 0); list.bind("vclick", function(event){ var item = $(event.target); self._setHeader(item.text()); $(".checked").removeClass("checked"); item.addClass("checked"); select.prop('value', select.find('option:contains("' + item.text() + '")').attr('value')); select.trigger("change"); collapse.trigger('collapse'); }); }, _setHeader: function(text) { var headerText = this.header.find('.ui-btn-text'), headerStatusText = headerText.find('.ui-collapsible-heading-status'); //Check if the content has been enhanced yet if(headerText.length === 0){ this.header.text(text); } else { headerText.text(text).append(headerStatusText); } }, refresh: function() { } }); $( document ).unbind( "selectmenubeforecreate" ); $( document ).bind( "selectmenubeforecreate", function(event){ $( event.target ).dropdown(); } ); })(jQuery); (function($) { $.widget("mobile.gridview", $.mobile.widget, { options: { initSelector: ":jqmData(role='gridview')", margin: 0.78125 }, _create: function() { var self = this; self.element.addClass(function( i, orig ) { return orig + " ui-gridview "; }); self.refresh(true); }, _getChildrenByTagName: function( ele, lcName, ucName ) { var results = [], dict = {}; dict[ lcName ] = dict[ ucName ] = true; ele = ele.firstChild; while ( ele ) { if ( dict[ ele.nodeName ] ) { results.push( ele ); } ele = ele.nextSibling; } return $( results ); }, refresh: function() { this.parentPage = this.element.closest( ".ui-page" ); var self = this, o = self.options, $list = this.element, div = this._getChildrenByTagName( $list[ 0 ], "div", "DIV" ), ww = window.innerWidth || $( window ).width(), innerDivWidth = 0, itemClassDict = {}, item, itemClass; function fixLeft (i) { return ( innerDivWidth * i + o.margin * ( i + 1 ) ) + "%"; } for ( var pos = 0, numdiv = div.length; pos < numdiv; pos++ ) { item = div.eq( pos ); itemClass = "ui-grid-li"; if ( item.jqmData( "role" ) === "list-divider" ) { itemClass += " ui-li ui-li-divider ui-bar-" + "b"; item.attr( "role", "heading" ); } else { var innerDiv = this._getChildrenByTagName( item[ 0 ], "div", "DIV" ); innerDivWidth = 100 / innerDiv.length - o.margin - o.margin / innerDiv.length; var img = innerDiv .find( "p" ) .addClass( "ui-grid-item-details" ) .css("width", ( innerDivWidth )+ "%" ) .css( "left", fixLeft ) .end() .find( "img" ).css( "width", ( innerDivWidth ) + "%" ); } if ( !itemClassDict[ itemClass ] ) { itemClassDict[ itemClass ] = []; } itemClassDict[ itemClass ].push( item[ 0 ] ); } for ( itemClass in itemClassDict ) { $( itemClassDict[ itemClass ] ).addClass( itemClass ).children( ".ui-btn-inner" ).addClass( itemClass ); } $list.find( "h1, h2, h3, h4, h5, h6" ).addClass( "ui-li-heading" ); $list.find("a").removeClass( "ui-link" ).addClass( "ui-link-inherit" ); } }); $( document ).bind( "pagecreate create", function( e ){ $.mobile.gridview.prototype.enhanceWithin( e.target ); }); })(jQuery); (function($) { $.widget("mobile.inputValidator", $.mobile.widget, { options: { initSelector: 'input[type="text"]:jqmData(role="bb-inputvalidator"), input[type="search"]:jqmData(role="bb-inputvalidator")' }, _create: function() { var self = this; this.element .addClass("bb-text-validated"); this.wrapper = $(document.createElement("div")) .addClass("bb-text-validator"); // Check whether the target is a search input by inspecting its parent. var parent = $(this.element).parent('div.ui-input-search, div.ui-input-text'); if (parent.length !== 0) { //Bubble the wrapper target to the input's parent parent.addClass("bb-text-validated"); this.wrapper .insertBefore(parent) .append(parent); } else { this.wrapper .insertBefore(this.element) .append(this.element); } this.icon = $(document.createElement("div")) .addClass("bb-validation-icon") .appendTo(this.wrapper); this._validate($(this.element)); // Watch the input and attach valid/invalid classes depending on the state $('.bb-text-validated').bind('focus keyup blur', function(event) { self._validate($(event.target)); }); }, //@desc Adds valid/invalid classes depending on the validity of the input //@param input A jQuery object representing the input to validate _validate: function(input) { var pattern = new RegExp(input.attr("pattern")), parent = input.parent('div.ui-input-search, div.ui-input-text'), watched = input.add(parent); if (input.attr('required') === undefined) { //If not required, empty and out of focus; set as neutral if (input.val() === "" && !input.is(':focus')) { watched.addClass('bb-text-empty') .removeClass('bb-text-valid') .removeClass('bb-text-invalid'); return; } //If in focus and has bb-text-empty class, remove to allow validation else if (input.hasClass('bb-text-empty') && !input.val() && input.is(':focus')) { watched.removeClass('bb-text-empty'); } } // Add / Remove validation classes if ( pattern.test(input.val())) { watched.addClass('bb-text-valid') .removeClass('bb-text-invalid'); } else { watched.removeClass("bb-text-valid") .addClass("bb-text-invalid"); } } }); $(document).bind("pagecreate create", function(e) { $.mobile.inputValidator.prototype.enhanceWithin(e.target); }); })(jQuery); (function($) { $.widget( "mobile.progressbar", $.mobile.widget, { options: { initSelector: "progress", color: "dark", highlightColor: '#00BC9F', accentColor: 'rgb(-120, 68, 39)' }, _create: function() { this.progress = this.element .css( 'display', 'none' ); this.outerElement = $( document.createElement( 'div' ) ) .attr( 'class', 'bb-progress' ) .insertBefore( this.progress ) .append( this.progress ); this.outer = $( document.createElement( 'div' ) ) .attr( 'class', 'outer bb-progress-outer-' + this.options.color + ' bb-progress-outer-idle-background-' + this.options.color ) .appendTo( this.outerElement ); this.fill = $( document.createElement( 'div' ) ) .attr( 'class', 'bb-progress-fill bb10Highlight' ) .appendTo( this.outer ); this.inner = $( document.createElement( 'div' ) ) .attr( 'class', 'inner' ) .appendTo( this.outer ); // Get our values this.outerElement.maxValue = this.progress.attr( 'max' ) ? parseInt( this.progress.attr( 'max' ), 10 ) : 0; this.outerElement.value = this.progress.attr( 'value' ) ? parseInt( this.progress.attr( 'value' ), 10 ) : 0; this.setValue( this.outerElement.value ); }, pause: function( isPaused ) { var pause = ( isPaused !== undefined ) ? isPaused : true; if ( pause ) { this.fill.addClass( 'paused' ); } else { this.fill.removeClass( 'paused' ); } }, setError: function( hasError ) { var error = ( hasError !== undefined ) ? hasError : true; if ( error ) { this.fill.addClass( 'error' ); } else { this.fill.removeClass( 'error' ); } }, setValue: function( value ) { var percent = 0; if ( value === undefined || value < 0 || value > this.outerElement.maxValue ) { return; } else { this.outerElement.value = value; this.value = value; } // Calculate our percentage if ( value >= this.outerElement.maxValue ) { percent = 100; this.fill.addClass( 'completed' ) .removeClass( 'active paused error' ); } else if ( value === 0 ) { this.outer.attr( 'class', 'outer bb-progress-outer-' + this.options.color + ' bb-progress-outer-idle-background-' + this.options.color ); } else { this.outer.attr( 'class', 'outer bb-progress-outer-' + this.options.color ); this.fill.attr( 'class', 'bb-progress-fill bb10Highlight' ) .css( 'background', '' ); this.fill.addClass( 'active' ) .removeClass( 'completed paused error' ); percent = this.outerElement.value/parseInt( this.outerElement.maxValue, 10 ) * 100; } // Set width by percentage this.fill.css( 'width', percent + '%' ); } }); $( document ).bind( "pagecreate create", function( e ){ $.mobile.progressbar.prototype.enhanceWithin( e.target ); }); })(jQuery);