From 473ef7e26fc8d2c6b26e66b80d50e49c18fa24f8 Mon Sep 17 00:00:00 2001 From: Thibaut Horel Date: Thu, 27 Feb 2014 11:24:22 -0500 Subject: Basic tornado app displaying a page image and associated text side by side --- web/static/js/jquery.imagemapster.js | 4668 +++++++++++++++++ web/static/js/jquery.js | 9111 ++++++++++++++++++++++++++++++++++ web/static/js/main.js | 22 + 3 files changed, 13801 insertions(+) create mode 100644 web/static/js/jquery.imagemapster.js create mode 100644 web/static/js/jquery.js create mode 100644 web/static/js/main.js (limited to 'web/static/js') diff --git a/web/static/js/jquery.imagemapster.js b/web/static/js/jquery.imagemapster.js new file mode 100644 index 0000000..107bfcc --- /dev/null +++ b/web/static/js/jquery.imagemapster.js @@ -0,0 +1,4668 @@ +/* ImageMapster + Version: 1.2.10 (2/25/2013) + +Copyright 2011-2012 James Treworgy + +http://www.outsharked.com/imagemapster +https://github.com/jamietre/ImageMapster + +A jQuery plugin to enhance image maps. + +*/ + +; + +/// LICENSE (MIT License) +/// +/// Permission is hereby granted, free of charge, to any person obtaining +/// a copy of this software and associated documentation files (the +/// "Software"), to deal in the Software without restriction, including +/// without limitation the rights to use, copy, modify, merge, publish, +/// distribute, sublicense, and/or sell copies of the Software, and to +/// permit persons to whom the Software is furnished to do so, subject to +/// the following conditions: +/// +/// The above copyright notice and this permission notice shall be +/// included in all copies or substantial portions of the Software. +/// +/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +/// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +/// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +/// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +/// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +/// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +/// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +/// +/// January 19, 2011 + +/** @license MIT License (c) copyright B Cavalier & J Hann */ + +/** +* when +* A lightweight CommonJS Promises/A and when() implementation +* +* when is part of the cujo.js family of libraries (http://cujojs.com/) +* +* Licensed under the MIT License at: +* http://www.opensource.org/licenses/mit-license.php +* +* @version 1.2.0 +*/ + +/*lint-ignore-start*/ + +(function (define) { + define(function () { + var freeze, reduceArray, slice, undef; + + // + // Public API + // + + when.defer = defer; + when.reject = reject; + when.isPromise = isPromise; + + when.all = all; + when.some = some; + when.any = any; + + when.map = map; + when.reduce = reduce; + + when.chain = chain; + + /** Object.freeze */ + freeze = Object.freeze || function (o) { return o; }; + + /** + * Trusted Promise constructor. A Promise created from this constructor is + * a trusted when.js promise. Any other duck-typed promise is considered + * untrusted. + * + * @constructor + */ + function Promise() { } + + Promise.prototype = freeze({ + always: function (alwaysback, progback) { + return this.then(alwaysback, alwaysback, progback); + }, + + otherwise: function (errback) { + return this.then(undef, errback); + } + }); + + /** + * Create an already-resolved promise for the supplied value + * @private + * + * @param value anything + * @return {Promise} + */ + function resolved(value) { + + var p = new Promise(); + + p.then = function (callback) { + var nextValue; + try { + if (callback) nextValue = callback(value); + return promise(nextValue === undef ? value : nextValue); + } catch (e) { + return rejected(e); + } + }; + + return freeze(p); + } + + /** + * Create an already-rejected {@link Promise} with the supplied + * rejection reason. + * @private + * + * @param reason rejection reason + * @return {Promise} + */ + function rejected(reason) { + + var p = new Promise(); + + p.then = function (callback, errback) { + var nextValue; + try { + if (errback) { + nextValue = errback(reason); + return promise(nextValue === undef ? reason : nextValue) + } + + return rejected(reason); + + } catch (e) { + return rejected(e); + } + }; + + return freeze(p); + } + + /** + * Returns a rejected promise for the supplied promiseOrValue. If + * promiseOrValue is a value, it will be the rejection value of the + * returned promise. If promiseOrValue is a promise, its + * completion value will be the rejected value of the returned promise + * + * @param promiseOrValue {*} the rejected value of the returned {@link Promise} + * + * @return {Promise} rejected {@link Promise} + */ + function reject(promiseOrValue) { + return when(promiseOrValue, function (value) { + return rejected(value); + }); + } + + /** + * Creates a new, CommonJS compliant, Deferred with fully isolated + * resolver and promise parts, either or both of which may be given out + * safely to consumers. + * The Deferred itself has the full API: resolve, reject, progress, and + * then. The resolver has resolve, reject, and progress. The promise + * only has then. + * + * @memberOf when + * @function + * + * @returns {Deferred} + */ + function defer() { + var deferred, promise, listeners, progressHandlers, _then, _progress, complete; + + listeners = []; + progressHandlers = []; + + /** + * Pre-resolution then() that adds the supplied callback, errback, and progback + * functions to the registered listeners + * + * @private + * + * @param [callback] {Function} resolution handler + * @param [errback] {Function} rejection handler + * @param [progback] {Function} progress handler + * + * @throws {Error} if any argument is not null, undefined, or a Function + */ + _then = function unresolvedThen(callback, errback, progback) { + var deferred = defer(); + + listeners.push(function (promise) { + promise.then(callback, errback) + .then(deferred.resolve, deferred.reject, deferred.progress); + }); + + progback && progressHandlers.push(progback); + + return deferred.promise; + }; + + /** + * Registers a handler for this {@link Deferred}'s {@link Promise}. Even though all arguments + * are optional, each argument that *is* supplied must be null, undefined, or a Function. + * Any other value will cause an Error to be thrown. + * + * @memberOf Promise + * + * @param [callback] {Function} resolution handler + * @param [errback] {Function} rejection handler + * @param [progback] {Function} progress handler + * + * @throws {Error} if any argument is not null, undefined, or a Function + */ + function then(callback, errback, progback) { + return _then(callback, errback, progback); + } + + /** + * Resolves this {@link Deferred}'s {@link Promise} with val as the + * resolution value. + * + * @memberOf Resolver + * + * @param val anything + */ + function resolve(val) { + complete(resolved(val)); + } + + /** + * Rejects this {@link Deferred}'s {@link Promise} with err as the + * reason. + * + * @memberOf Resolver + * + * @param err anything + */ + function reject(err) { + complete(rejected(err)); + } + + /** + * @private + * @param update + */ + _progress = function (update) { + var progress, i = 0; + while (progress = progressHandlers[i++]) progress(update); + }; + + /** + * Emits a progress update to all progress observers registered with + * this {@link Deferred}'s {@link Promise} + * + * @memberOf Resolver + * + * @param update anything + */ + function progress(update) { + _progress(update); + } + + /** + * Transition from pre-resolution state to post-resolution state, notifying + * all listeners of the resolution or rejection + * + * @private + * + * @param completed {Promise} the completed value of this deferred + */ + complete = function (completed) { + var listener, i = 0; + + // Replace _then with one that directly notifies with the result. + _then = completed.then; + + // Replace complete so that this Deferred can only be completed + // once. Also Replace _progress, so that subsequent attempts to issue + // progress throw. + complete = _progress = function alreadyCompleted() { + // TODO: Consider silently returning here so that parties who + // have a reference to the resolver cannot tell that the promise + // has been resolved using try/catch + throw new Error("already completed"); + }; + + // Free progressHandlers array since we'll never issue progress events + // for this promise again now that it's completed + progressHandlers = undef; + + // Notify listeners + // Traverse all listeners registered directly with this Deferred + + while (listener = listeners[i++]) { + listener(completed); + } + + listeners = []; + }; + + /** + * The full Deferred object, with both {@link Promise} and {@link Resolver} + * parts + * @class Deferred + * @name Deferred + */ + deferred = {}; + + // Promise and Resolver parts + // Freeze Promise and Resolver APIs + + promise = new Promise(); + promise.then = deferred.then = then; + + /** + * The {@link Promise} for this {@link Deferred} + * @memberOf Deferred + * @name promise + * @type {Promise} + */ + deferred.promise = freeze(promise); + + /** + * The {@link Resolver} for this {@link Deferred} + * @memberOf Deferred + * @name resolver + * @class Resolver + */ + deferred.resolver = freeze({ + resolve: (deferred.resolve = resolve), + reject: (deferred.reject = reject), + progress: (deferred.progress = progress) + }); + + return deferred; + } + + /** + * Determines if promiseOrValue is a promise or not. Uses the feature + * test from http://wiki.commonjs.org/wiki/Promises/A to determine if + * promiseOrValue is a promise. + * + * @param promiseOrValue anything + * + * @returns {Boolean} true if promiseOrValue is a {@link Promise} + */ + function isPromise(promiseOrValue) { + return promiseOrValue && typeof promiseOrValue.then === 'function'; + } + + /** + * Register an observer for a promise or immediate value. + * + * @function + * @name when + * @namespace + * + * @param promiseOrValue anything + * @param {Function} [callback] callback to be called when promiseOrValue is + * successfully resolved. If promiseOrValue is an immediate value, callback + * will be invoked immediately. + * @param {Function} [errback] callback to be called when promiseOrValue is + * rejected. + * @param {Function} [progressHandler] callback to be called when progress updates + * are issued for promiseOrValue. + * + * @returns {Promise} a new {@link Promise} that will complete with the return + * value of callback or errback or the completion value of promiseOrValue if + * callback and/or errback is not supplied. + */ + function when(promiseOrValue, callback, errback, progressHandler) { + // Get a promise for the input promiseOrValue + // See promise() + var trustedPromise = promise(promiseOrValue); + + // Register promise handlers + return trustedPromise.then(callback, errback, progressHandler); + } + + /** + * Returns promiseOrValue if promiseOrValue is a {@link Promise}, a new Promise if + * promiseOrValue is a foreign promise, or a new, already-resolved {@link Promise} + * whose resolution value is promiseOrValue if promiseOrValue is an immediate value. + * + * Note that this function is not safe to export since it will return its + * input when promiseOrValue is a {@link Promise} + * + * @private + * + * @param promiseOrValue anything + * + * @returns Guaranteed to return a trusted Promise. If promiseOrValue is a when.js {@link Promise} + * returns promiseOrValue, otherwise, returns a new, already-resolved, when.js {@link Promise} + * whose resolution value is: + * * the resolution value of promiseOrValue if it's a foreign promise, or + * * promiseOrValue if it's a value + */ + function promise(promiseOrValue) { + var promise, deferred; + + if (promiseOrValue instanceof Promise) { + // It's a when.js promise, so we trust it + promise = promiseOrValue; + + } else { + // It's not a when.js promise. Check to see if it's a foreign promise + // or a value. + + deferred = defer(); + if (isPromise(promiseOrValue)) { + // It's a compliant promise, but we don't know where it came from, + // so we don't trust its implementation entirely. Introduce a trusted + // middleman when.js promise + + // IMPORTANT: This is the only place when.js should ever call .then() on + // an untrusted promise. + promiseOrValue.then(deferred.resolve, deferred.reject, deferred.progress); + promise = deferred.promise; + + } else { + // It's a value, not a promise. Create an already-resolved promise + // for it. + deferred.resolve(promiseOrValue); + promise = deferred.promise; + } + } + + return promise; + } + + /** + * Return a promise that will resolve when howMany of the supplied promisesOrValues + * have resolved. The resolution value of the returned promise will be an array of + * length howMany containing the resolutions values of the triggering promisesOrValues. + * + * @memberOf when + * + * @param promisesOrValues {Array} array of anything, may contain a mix + * of {@link Promise}s and values + * @param howMany + * @param [callback] + * @param [errback] + * @param [progressHandler] + * + * @returns {Promise} + */ + function some(promisesOrValues, howMany, callback, errback, progressHandler) { + + checkCallbacks(2, arguments); + + return when(promisesOrValues, function (promisesOrValues) { + + var toResolve, results, ret, deferred, resolver, rejecter, handleProgress, len, i; + + len = promisesOrValues.length >>> 0; + + toResolve = Math.max(0, Math.min(howMany, len)); + results = []; + deferred = defer(); + ret = when(deferred, callback, errback, progressHandler); + + // Wrapper so that resolver can be replaced + function resolve(val) { + resolver(val); + } + + // Wrapper so that rejecter can be replaced + function reject(err) { + rejecter(err); + } + + // Wrapper so that progress can be replaced + function progress(update) { + handleProgress(update); + } + + function complete() { + resolver = rejecter = handleProgress = noop; + } + + // No items in the input, resolve immediately + if (!toResolve) { + deferred.resolve(results); + + } else { + // Resolver for promises. Captures the value and resolves + // the returned promise when toResolve reaches zero. + // Overwrites resolver var with a noop once promise has + // be resolved to cover case where n < promises.length + resolver = function (val) { + // This orders the values based on promise resolution order + // Another strategy would be to use the original position of + // the corresponding promise. + results.push(val); + + if (! --toResolve) { + complete(); + deferred.resolve(results); + } + }; + + // Rejecter for promises. Rejects returned promise + // immediately, and overwrites rejecter var with a noop + // once promise to cover case where n < promises.length. + // TODO: Consider rejecting only when N (or promises.length - N?) + // promises have been rejected instead of only one? + rejecter = function (err) { + complete(); + deferred.reject(err); + }; + + handleProgress = deferred.progress; + + // TODO: Replace while with forEach + for (i = 0; i < len; ++i) { + if (i in promisesOrValues) { + when(promisesOrValues[i], resolve, reject, progress); + } + } + } + + return ret; + }); + } + + /** + * Return a promise that will resolve only once all the supplied promisesOrValues + * have resolved. The resolution value of the returned promise will be an array + * containing the resolution values of each of the promisesOrValues. + * + * @memberOf when + * + * @param promisesOrValues {Array|Promise} array of anything, may contain a mix + * of {@link Promise}s and values + * @param [callback] {Function} + * @param [errback] {Function} + * @param [progressHandler] {Function} + * + * @returns {Promise} + */ + function all(promisesOrValues, callback, errback, progressHandler) { + + checkCallbacks(1, arguments); + + return when(promisesOrValues, function (promisesOrValues) { + return _reduce(promisesOrValues, reduceIntoArray, []); + }).then(callback, errback, progressHandler); + } + + function reduceIntoArray(current, val, i) { + current[i] = val; + return current; + } + + /** + * Return a promise that will resolve when any one of the supplied promisesOrValues + * has resolved. The resolution value of the returned promise will be the resolution + * value of the triggering promiseOrValue. + * + * @memberOf when + * + * @param promisesOrValues {Array|Promise} array of anything, may contain a mix + * of {@link Promise}s and values + * @param [callback] {Function} + * @param [errback] {Function} + * @param [progressHandler] {Function} + * + * @returns {Promise} + */ + function any(promisesOrValues, callback, errback, progressHandler) { + + function unwrapSingleResult(val) { + return callback ? callback(val[0]) : val[0]; + } + + return some(promisesOrValues, 1, unwrapSingleResult, errback, progressHandler); + } + + /** + * Traditional map function, similar to `Array.prototype.map()`, but allows + * input to contain {@link Promise}s and/or values, and mapFunc may return + * either a value or a {@link Promise} + * + * @memberOf when + * + * @param promise {Array|Promise} array of anything, may contain a mix + * of {@link Promise}s and values + * @param mapFunc {Function} mapping function mapFunc(value) which may return + * either a {@link Promise} or value + * + * @returns {Promise} a {@link Promise} that will resolve to an array containing + * the mapped output values. + */ + function map(promise, mapFunc) { + return when(promise, function (array) { + return _map(array, mapFunc); + }); + } + + /** + * Private map helper to map an array of promises + * @private + * + * @param promisesOrValues {Array} + * @param mapFunc {Function} + * @return {Promise} + */ + function _map(promisesOrValues, mapFunc) { + + var results, len, i; + + // Since we know the resulting length, we can preallocate the results + // array to avoid array expansions. + len = promisesOrValues.length >>> 0; + results = new Array(len); + + // Since mapFunc may be async, get all invocations of it into flight + // asap, and then use reduce() to collect all the results + for (i = 0; i < len; i++) { + if (i in promisesOrValues) + results[i] = when(promisesOrValues[i], mapFunc); + } + + // Could use all() here, but that would result in another array + // being allocated, i.e. map() would end up allocating 2 arrays + // of size len instead of just 1. Since all() uses reduce() + // anyway, avoid the additional allocation by calling reduce + // directly. + return _reduce(results, reduceIntoArray, results); + } + + /** + * Traditional reduce function, similar to `Array.prototype.reduce()`, but + * input may contain {@link Promise}s and/or values, and reduceFunc + * may return either a value or a {@link Promise}, *and* initialValue may + * be a {@link Promise} for the starting value. + * + * @memberOf when + * + * @param promise {Array|Promise} array of anything, may contain a mix + * of {@link Promise}s and values. May also be a {@link Promise} for + * an array. + * @param reduceFunc {Function} reduce function reduce(currentValue, nextValue, index, total), + * where total is the total number of items being reduced, and will be the same + * in each call to reduceFunc. + * @param initialValue starting value, or a {@link Promise} for the starting value + * + * @returns {Promise} that will resolve to the final reduced value + */ + function reduce(promise, reduceFunc, initialValue) { + var args = slice.call(arguments, 1); + return when(promise, function (array) { + return _reduce.apply(undef, [array].concat(args)); + }); + } + + /** + * Private reduce to reduce an array of promises + * @private + * + * @param promisesOrValues {Array} + * @param reduceFunc {Function} + * @param initialValue {*} + * @return {Promise} + */ + function _reduce(promisesOrValues, reduceFunc, initialValue) { + + var total, args; + + total = promisesOrValues.length; + + // Skip promisesOrValues, since it will be used as 'this' in the call + // to the actual reduce engine below. + + // Wrap the supplied reduceFunc with one that handles promises and then + // delegates to the supplied. + + args = [ + function (current, val, i) { + return when(current, function (c) { + return when(val, function (value) { + return reduceFunc(c, value, i, total); + }); + }); + } + ]; + + if (arguments.length > 2) args.push(initialValue); + + return reduceArray.apply(promisesOrValues, args); + } + + /** + * Ensure that resolution of promiseOrValue will complete resolver with the completion + * value of promiseOrValue, or instead with resolveValue if it is provided. + * + * @memberOf when + * + * @param promiseOrValue + * @param resolver {Resolver} + * @param [resolveValue] anything + * + * @returns {Promise} + */ + function chain(promiseOrValue, resolver, resolveValue) { + var useResolveValue = arguments.length > 2; + + return when(promiseOrValue, + function (val) { + if (useResolveValue) val = resolveValue; + resolver.resolve(val); + return val; + }, + function (e) { + resolver.reject(e); + return rejected(e); + }, + resolver.progress + ); + } + + // + // Utility functions + // + + /** + * Helper that checks arrayOfCallbacks to ensure that each element is either + * a function, or null or undefined. + * + * @private + * + * @param arrayOfCallbacks {Array} array to check + * @throws {Error} if any element of arrayOfCallbacks is something other than + * a Functions, null, or undefined. + */ + function checkCallbacks(start, arrayOfCallbacks) { + var arg, i = arrayOfCallbacks.length; + while (i > start) { + arg = arrayOfCallbacks[--i]; + if (arg != null && typeof arg != 'function') throw new Error('callback is not a function'); + } + } + + /** + * No-Op function used in method replacement + * @private + */ + function noop() { } + + slice = [].slice; + + // ES5 reduce implementation if native not available + // See: http://es5.github.com/#x15.4.4.21 as there are many + // specifics and edge cases. + reduceArray = [].reduce || + function (reduceFunc /*, initialValue */) { + // ES5 dictates that reduce.length === 1 + + // This implementation deviates from ES5 spec in the following ways: + // 1. It does not check if reduceFunc is a Callable + + var arr, args, reduced, len, i; + + i = 0; + arr = Object(this); + len = arr.length >>> 0; + args = arguments; + + // If no initialValue, use first item of array (we know length !== 0 here) + // and adjust i to start at second item + if (args.length <= 1) { + // Skip to the first real element in the array + for (; ; ) { + if (i in arr) { + reduced = arr[i++]; + break; + } + + // If we reached the end of the array without finding any real + // elements, it's a TypeError + if (++i >= len) { + throw new TypeError(); + } + } + } else { + // If initialValue provided, use it + reduced = args[1]; + } + + // Do the actual reduce + for (; i < len; ++i) { + // Skip holes + if (i in arr) + reduced = reduceFunc(reduced, arr[i], i, arr); + } + + return reduced; + }; + + return when; + }); +})(typeof define == 'function' + ? define + : function (factory) { + typeof module != 'undefined' + ? (module.exports = factory()) + : (jQuery.mapster_when = factory()); + } +// Boilerplate for AMD, Node, and browser global +); +/*lint-ignore-end*/ +/* ImageMapster core */ + +/*jslint laxbreak: true, evil: true, unparam: true */ + +/*global jQuery: true, Zepto: true */ + + +(function ($) { + // all public functions in $.mapster.impl are methods + $.fn.mapster = function (method) { + var m = $.mapster.impl; + if ($.isFunction(m[method])) { + return m[method].apply(this, Array.prototype.slice.call(arguments, 1)); + } else if (typeof method === 'object' || !method) { + return m.bind.apply(this, arguments); + } else { + $.error('Method ' + method + ' does not exist on jQuery.mapster'); + } + }; + + $.mapster = { + version: "1.2.10", + render_defaults: { + isSelectable: true, + isDeselectable: true, + fade: false, + fadeDuration: 150, + fill: true, + fillColor: '000000', + fillColorMask: 'FFFFFF', + fillOpacity: 0.7, + highlight: true, + stroke: false, + strokeColor: 'ff0000', + strokeOpacity: 1, + strokeWidth: 1, + includeKeys: '', + altImage: null, + altImageId: null, // used internally + altImages: {} + }, + defaults: { + clickNavigate: false, + wrapClass: null, + wrapCss: null, + onGetList: null, + sortList: false, + listenToList: false, + mapKey: '', + mapValue: '', + singleSelect: false, + listKey: 'value', + listSelectedAttribute: 'selected', + listSelectedClass: null, + onClick: null, + onMouseover: null, + onMouseout: null, + mouseoutDelay: 0, + onStateChange: null, + boundList: null, + onConfigured: null, + configTimeout: 30000, + noHrefIsMask: true, + scaleMap: true, + safeLoad: false, + areas: [] + }, + shared_defaults: { + render_highlight: { fade: true }, + render_select: { fade: false }, + staticState: null, + selected: null + }, + area_defaults: + { + includeKeys: '', + isMask: false + }, + canvas_style: { + position: 'absolute', + left: 0, + top: 0, + padding: 0, + border: 0 + }, + hasCanvas: null, + isTouch: null, + map_cache: [], + hooks: {}, + addHook: function(name,callback) { + this.hooks[name]=(this.hooks[name]||[]).push(callback); + }, + callHooks: function(name,context) { + $.each(this.hooks[name]||[],function(i,e) { + e.apply(context); + }); + }, + utils: { + when: $.mapster_when, + defer: $.mapster_when.defer, + + // extends the constructor, returns a new object prototype. Does not refer to the + // original constructor so is protected if the original object is altered. This way you + // can "extend" an object by replacing it with its subclass. + subclass: function(BaseClass, constr) { + var Subclass=function() { + var me=this, + args=Array.prototype.slice.call(arguments,0); + me.base = BaseClass.prototype; + me.base.init = function() { + BaseClass.prototype.constructor.apply(me,args); + }; + constr.apply(me,args); + }; + Subclass.prototype = new BaseClass(); + Subclass.prototype.constructor=Subclass; + return Subclass; + }, + asArray: function (obj) { + return obj.constructor === Array ? + obj : this.split(obj); + }, + // clean split: no padding or empty elements + split: function (text,cb) { + var i,el, arr = text.split(','); + for (i = 0; i < arr.length; i++) { + el = $.trim(arr[i]); + if (el==='') { + arr.splice(i,1); + } else { + arr[i] = cb ? cb(el):el; + } + } + return arr; + }, + // similar to $.extend but does not add properties (only updates), unless the + // first argument is an empty object, then all properties will be copied + updateProps: function (_target, _template) { + var onlyProps, + target = _target || {}, + template = $.isEmptyObject(target) ? _template : _target; + + //if (template) { + onlyProps = []; + $.each(template, function (prop) { + onlyProps.push(prop); + }); + //} + + $.each(Array.prototype.slice.call(arguments, 1), function (i, src) { + $.each(src || {}, function (prop) { + if (!onlyProps || $.inArray(prop, onlyProps) >= 0) { + var p = src[prop]; + + if ($.isPlainObject(p)) { + // not recursive - only copies 1 level of subobjects, and always merges + target[prop] = $.extend(target[prop] || {}, p); + } else if (p && p.constructor === Array) { + target[prop] = p.slice(0); + } else if (typeof p !== 'undefined') { + target[prop] = src[prop]; + } + + } + }); + }); + return target; + }, + isElement: function (o) { + return (typeof HTMLElement === "object" ? o instanceof HTMLElement : + o && typeof o === "object" && o.nodeType === 1 && typeof o.nodeName === "string"); + }, + // finds element of array or object with a property "prop" having value "val" + // if prop is not defined, then just looks for property with value "val" + indexOfProp: function (obj, prop, val) { + var result = obj.constructor === Array ? -1 : null; + $.each(obj, function (i, e) { + if (e && (prop ? e[prop] : e) === val) { + result = i; + return false; + } + }); + return result; + }, + // returns "obj" if true or false, or "def" if not true/false + boolOrDefault: function (obj, def) { + return this.isBool(obj) ? + obj : def || false; + }, + isBool: function (obj) { + return typeof obj === "boolean"; + }, + isUndef: function(obj) { + return typeof obj === "undefined"; + }, + // evaluates "obj", if function, calls it with args + // (todo - update this to handle variable lenght/more than one arg) + ifFunction: function (obj, that, args) { + if ($.isFunction(obj)) { + obj.call(that, args); + } + }, + size: function(image, raw) { + var u=$.mapster.utils; + return { + width: raw ? (image.width || image.naturalWidth) : u.imgWidth(image,true) , + height: raw ? (image.height || image.naturalHeight) : u.imgHeight(image,true), + complete: function() { return !!this.height && !!this.width;} + }; + }, + + + /** + * Set the opacity of the element. This is an IE<8 specific function for handling VML. + * When using VML we must override the "setOpacity" utility function (monkey patch ourselves). + * jQuery does not deal with opacity correctly for VML elements. This deals with that. + * + * @param {Element} el The DOM element + * @param {double} opacity A value between 0 and 1 inclusive. + */ + + setOpacity: function (el, opacity) { + if ($.mapster.hasCanvas()) { + el.style.opacity = opacity; + } else { + $(el).each(function(i,e) { + if (typeof e.opacity !=='undefined') { + e.opacity=opacity; + } else { + $(e).css("opacity",opacity); + } + }); + } + }, + + + // fade "el" from opacity "op" to "endOp" over a period of time "duration" + + fader: (function () { + var elements = {}, + lastKey = 0, + fade_func = function (el, op, endOp, duration) { + var index, + cbIntervals = duration/15, + obj, u = $.mapster.utils; + + if (typeof el === 'number') { + obj = elements[el]; + if (!obj) { + return; + } + } else { + index = u.indexOfProp(elements, null, el); + if (index) { + delete elements[index]; + } + elements[++lastKey] = obj = el; + el = lastKey; + } + + endOp = endOp || 1; + + op = (op + (endOp / cbIntervals) > endOp - 0.01) ? endOp : op + (endOp / cbIntervals); + + u.setOpacity(obj, op); + if (op < endOp) { + setTimeout(function () { + fade_func(el, op, endOp, duration); + }, 15); + } + }; + return fade_func; + } ()) + }, + getBoundList: function (opts, key_list) { + if (!opts.boundList) { + return null; + } + var index, key, result = $(), list = $.mapster.utils.split(key_list); + opts.boundList.each(function (i,e) { + for (index = 0; index < list.length; index++) { + key = list[index]; + if ($(e).is('[' + opts.listKey + '="' + key + '"]')) { + result = result.add(e); + } + } + }); + return result; + }, + // Causes changes to the bound list based on the user action (select or deselect) + // area: the jQuery area object + // returns the matching elements from the bound list for the first area passed (normally only one should be passed, but + // a list can be passed + setBoundListProperties: function (opts, target, selected) { + target.each(function (i,e) { + if (opts.listSelectedClass) { + if (selected) { + $(e).addClass(opts.listSelectedClass); + } else { + $(e).removeClass(opts.listSelectedClass); + } + } + if (opts.listSelectedAttribute) { + $(e).attr(opts.listSelectedAttribute, selected); + } + }); + }, + getMapDataIndex: function (obj) { + var img, id; + switch (obj.tagName && obj.tagName.toLowerCase()) { + case 'area': + id = $(obj).parent().attr('name'); + img = $("img[usemap='#" + id + "']")[0]; + break; + case 'img': + img = obj; + break; + } + return img ? + this.utils.indexOfProp(this.map_cache, 'image', img) : -1; + }, + getMapData: function (obj) { + var index = this.getMapDataIndex(obj.length ? obj[0]:obj); + if (index >= 0) { + return index >= 0 ? this.map_cache[index] : null; + } + }, + /** + * Queue a command to be run after the active async operation has finished + * @param {MapData} map_data The target MapData object + * @param {jQuery} that jQuery object on which the command was invoked + * @param {string} command the ImageMapster method name + * @param {object[]} args arguments passed to the method + * @return {bool} true if the command was queued, false if not (e.g. there was no need to) + */ + queueCommand: function (map_data, that, command, args) { + if (!map_data) { + return false; + } + if (!map_data.complete || map_data.currentAction) { + map_data.commands.push( + { + that: that, + command: command, + args: args + }); + return true; + } + return false; + }, + unload: function () { + this.impl.unload(); + this.utils = null; + this.impl = null; + $.fn.mapster = null; + $.mapster = null; + $('*').unbind(); + } + }; + + // Config for object prototypes + // first: use only first object (for things that should not apply to lists) + /// calls back one of two fuinctions, depending on whether an area was obtained. + // opts: { + // name: 'method name', + // key: 'key, + // args: 'args' + // + //} + // name: name of method (required) + // args: arguments to re-call with + // Iterates through all the objects passed, and determines whether it's an area or an image, and calls the appropriate + // callback for each. If anything is returned from that callback, the process is stopped and that data return. Otherwise, + // the object itself is returned. + + var m = $.mapster, + u = m.utils, + ap = Array.prototype; + + + // jQuery's width() and height() are broken on IE9 in some situations. This tries everything. + $.each(["width","height"],function(i,e) { + var capProp = e.substr(0,1).toUpperCase() + e.substr(1); + // when jqwidth parm is passed, it also checks the jQuery width()/height() property + // the issue is that jQUery width() can report a valid size before the image is loaded in some browsers + // without it, we can read zero even when image is loaded in other browsers if its not visible + // we must still check because stuff like adblock can temporarily block it + // what a goddamn headache + u["img"+capProp]=function(img,jqwidth) { + return (jqwidth ? $(img)[e]() : 0) || + img[e] || img["natural"+capProp] || img["client"+capProp] || img["offset"+capProp]; + }; + + }); + + /** + * The Method object encapsulates the process of testing an ImageMapster method to see if it's being + * invoked on an image, or an area; then queues the command if the MapData is in an active state. + * + * @param {[jQuery]} that The target of the invocation + * @param {[function]} func_map The callback if the target is an imagemap + * @param {[function]} func_area The callback if the target is an area + * @param {[object]} opt Options: { key: a map key if passed explicitly + * name: the command name, if it can be queued, + * args: arguments to the method + * } + */ + + m.Method = function (that, func_map, func_area, opts) { + var me = this; + me.name = opts.name; + me.output = that; + me.input = that; + me.first = opts.first || false; + me.args = opts.args ? ap.slice.call(opts.args, 0) : []; + me.key = opts.key; + me.func_map = func_map; + me.func_area = func_area; + //$.extend(me, opts); + me.name = opts.name; + me.allowAsync = opts.allowAsync || false; + }; + m.Method.prototype = { + constructor: m.Method, + go: function () { + var i, data, ar, len, result, src = this.input, + area_list = [], + me = this; + + len = src.length; + for (i = 0; i < len; i++) { + data = $.mapster.getMapData(src[i]); + if (data) { + if (!me.allowAsync && m.queueCommand(data, me.input, me.name, me.args)) { + if (this.first) { + result = ''; + } + continue; + } + + ar = data.getData(src[i].nodeName === 'AREA' ? src[i] : this.key); + if (ar) { + if ($.inArray(ar, area_list) < 0) { + area_list.push(ar); + } + } else { + result = this.func_map.apply(data, me.args); + } + if (this.first || typeof result !== 'undefined') { + break; + } + } + } + // if there were areas, call the area function for each unique group + $(area_list).each(function (i,e) { + result = me.func_area.apply(e, me.args); + }); + + if (typeof result !== 'undefined') { + return result; + } else { + return this.output; + } + } + }; + + $.mapster.impl = (function () { + var me = {}, + addMap= function (map_data) { + return m.map_cache.push(map_data) - 1; + }, + removeMap = function (map_data) { + m.map_cache.splice(map_data.index, 1); + for (var i = m.map_cache.length - 1; i >= this.index; i--) { + m.map_cache[i].index--; + } + }; + + + /** + * Test whether the browser supports VML. Credit: google. + * http://stackoverflow.com/questions/654112/how-do-you-detect-support-for-vml-or-svg-in-a-browser + * + * @return {bool} true if vml is supported, false if not + */ + + function hasVml() { + var a = $('
').appendTo('body'); + a.html(''); + + var b = a[0].firstChild; + b.style.behavior = "url(#default#VML)"; + var has = b ? typeof b.adj === "object" : true; + a.remove(); + return has; + } + + /** + * Return a reference to the IE namespaces object, if available, or an empty object otherwise + * @return {obkect} The document.namespaces object. + */ + function namespaces() { + return typeof(document.namespaces)==='object' ? + document.namespaces : + null; + } + + /** + * Test for the presence of HTML5 Canvas support. This also checks to see if excanvas.js has been + * loaded and is faking it; if so, we assume that canvas is not supported. + * + * @return {bool} true if HTML5 canvas support, false if not + */ + + function hasCanvas() { + var d = namespaces(); + // when g_vml_ is present, then we can be sure excanvas is active, meaning there's not a real canvas. + + return d && d.g_vml_ ? + false : + $('')[0].getContext ? + true : + false; + } + + /** + * Merge new area data into existing area options on a MapData object. Used for rebinding. + * + * @param {[MapData]} map_data The MapData object + * @param {[object[]]} areas areas array to merge + */ + + function merge_areas(map_data, areas) { + var ar, index, + map_areas = map_data.options.areas; + + if (areas) { + $.each(areas, function (i, e) { + + // Issue #68 - ignore invalid data in areas array + + if (!e || !e.key) { + return; + } + + index = u.indexOfProp(map_areas, "key", e.key); + + if (index >= 0) { + $.extend(map_areas[index], e); + } + else { + map_areas.push(e); + } + ar = map_data.getDataForKey(e.key); + if (ar) { + $.extend(ar.options, e); + } + }); + } + } + function merge_options(map_data, options) { + var temp_opts = u.updateProps({}, options); + delete temp_opts.areas; + + u.updateProps(map_data.options, temp_opts); + + merge_areas(map_data, options.areas); + // refresh the area_option template + u.updateProps(map_data.area_options, map_data.options); + } + + // Most methods use the "Method" object which handles figuring out whether it's an image or area called and + // parsing key parameters. The constructor wants: + // this, the jQuery object + // a function that is called when an image was passed (with a this context of the MapData) + // a function that is called when an area was passed (with a this context of the AreaData) + // options: first = true means only the first member of a jQuery object is handled + // key = the key parameters passed + // defaultReturn: a value to return other than the jQuery object (if its not chainable) + // args: the arguments + // Returns a comma-separated list of user-selected areas. "staticState" areas are not considered selected for the purposes of this method. + + me.get = function (key) { + var md = m.getMapData(this); + if (!(md && md.complete)) { + throw("Can't access data until binding complete."); + } + + return (new m.Method(this, + function () { + // map_data return + return this.getSelected(); + }, + function () { + return this.isSelected(); + }, + { name: 'get', + args: arguments, + key: key, + first: true, + allowAsync: true, + defaultReturn: '' + } + )).go(); + }; + me.data = function (key) { + return (new m.Method(this, + null, + function () { + return this; + }, + { name: 'data', + args: arguments, + key: key + } + )).go(); + }; + + + // Set or return highlight state. + // $(img).mapster('highlight') -- return highlighted area key, or null if none + // $(area).mapster('highlight') -- highlight an area + // $(img).mapster('highlight','area_key') -- highlight an area + // $(img).mapster('highlight',false) -- remove highlight + me.highlight = function (key) { + return (new m.Method(this, + function () { + if (key === false) { + this.ensureNoHighlight(); + } else { + var id = this.highlightId; + return id >= 0 ? this.data[id].key : null; + } + }, + function () { + this.highlight(); + }, + { name: 'highlight', + args: arguments, + key: key, + first: true + } + )).go(); + }; + // Return the primary keys for an area or group key. + // $(area).mapster('key') + // includes all keys (not just primary keys) + // $(area).mapster('key',true) + // $(img).mapster('key','group-key') + + // $(img).mapster('key','group-key', true) + me.keys = function(key,all) { + var keyList=[], + md = m.getMapData(this); + + if (!(md && md.complete)) { + throw("Can't access data until binding complete."); + } + + + function addUniqueKeys(ad) { + var areas,keys=[]; + if (!all) { + keys.push(ad.key); + } else { + areas=ad.areas(); + $.each(areas,function(i,e) { + keys=keys.concat(e.keys); + }); + } + $.each(keys,function(i,e) { + if ($.inArray(e,keyList)<0) { + keyList.push(e); + } + }); + } + + if (!(md && md.complete)) { + return ''; + } + if (typeof key === 'string') { + if (all) { + addUniqueKeys(md.getDataForKey(key)); + } else { + keyList=[md.getKeysForGroup(key)]; + } + } else { + all = key; + this.each(function(i,e) { + if (e.nodeName==='AREA') { + addUniqueKeys(md.getDataForArea(e)); + } + }); + } + return keyList.join(','); + + + }; + me.select = function () { + me.set.call(this, true); + }; + me.deselect = function () { + me.set.call(this, false); + }; + + /** + * Select or unselect areas. Areas can be identified by a single string key, a comma-separated list of keys, + * or an array of strings. + * + * + * @param {boolean} selected Determines whether areas are selected or deselected + * @param {string|string[]} key A string, comma-separated string, or array of strings indicating + * the areas to select or deselect + * @param {object} options Rendering options to apply when selecting an area + */ + + me.set = function (selected, key, options) { + var lastMap, map_data, opts=options, + key_list, area_list; // array of unique areas passed + + function setSelection(ar) { + if (ar) { + switch (selected) { + case true: + ar.select(opts); break; + case false: + ar.deselect(true); break; + default: + ar.toggle(opts); break; + } + } + } + function addArea(ar) { + if (ar && $.inArray(ar, area_list) < 0) { + area_list.push(ar); + key_list+=(key_list===''?'':',')+ar.key; + } + } + // Clean up after a group that applied to the same map + function finishSetForMap(map_data) { + $.each(area_list, function (i, el) { + setSelection(el); + }); + if (!selected) { + map_data.removeSelectionFinish(); + } + if (map_data.options.boundList) { + m.setBoundListProperties(map_data.options, m.getBoundList(map_data.options, key_list), selected); + } + } + + this.filter('img,area').each(function (i,e) { + var keys; + map_data = m.getMapData(e); + + if (map_data !== lastMap) { + if (lastMap) { + finishSetForMap(lastMap); + } + + area_list = []; + key_list=''; + } + + if (map_data) { + + keys = ''; + if (e.nodeName.toUpperCase()==='IMG') { + if (!m.queueCommand(map_data, $(e), 'set', [selected, key, opts])) { + if (key instanceof Array) { + if (key.length) { + keys = key.join(","); + } + } + else { + keys = key; + } + + if (keys) { + $.each(u.split(keys), function (i,key) { + addArea(map_data.getDataForKey(key.toString())); + lastMap = map_data; + }); + } + } + } else { + opts=key; + if (!m.queueCommand(map_data, $(e), 'set', [selected, opts])) { + addArea(map_data.getDataForArea(e)); + lastMap = map_data; + } + + } + } + }); + + if (map_data) { + finishSetForMap(map_data); + } + + + return this; + }; + me.unbind = function (preserveState) { + return (new m.Method(this, + function () { + this.clearEvents(); + this.clearMapData(preserveState); + removeMap(this); + }, + null, + { name: 'unbind', + args: arguments + } + )).go(); + }; + + + // refresh options and update selection information. + me.rebind = function (options) { + return (new m.Method(this, + function () { + var me=this; + + me.complete=false; + me.configureOptions(options); + me.bindImages().then(function() { + me.buildDataset(true); + me.complete=true; + }); + //this.redrawSelections(); + }, + null, + { + name: 'rebind', + args: arguments + } + )).go(); + }; + // get options. nothing or false to get, or "true" to get effective options (versus passed options) + me.get_options = function (key, effective) { + var eff = u.isBool(key) ? key : effective; // allow 2nd parm as "effective" when no key + return (new m.Method(this, + function () { + var opts = $.extend({}, this.options); + if (eff) { + opts.render_select = u.updateProps( + {}, + m.render_defaults, + opts, + opts.render_select); + + opts.render_highlight = u.updateProps( + {}, + m.render_defaults, + opts, + opts.render_highlight); + } + return opts; + }, + function () { + return eff ? this.effectiveOptions() : this.options; + }, + { + name: 'get_options', + args: arguments, + first: true, + allowAsync: true, + key: key + } + )).go(); + }; + + // set options - pass an object with options to set, + me.set_options = function (options) { + return (new m.Method(this, + function () { + merge_options(this, options); + }, + null, + { + name: 'set_options', + args: arguments + } + )).go(); + }; + me.unload = function () { + var i; + for (i = m.map_cache.length - 1; i >= 0; i--) { + if (m.map_cache[i]) { + me.unbind.call($(m.map_cache[i].image)); + } + } + me.graphics = null; + }; + + me.snapshot = function () { + return (new m.Method(this, + function () { + $.each(this.data, function (i, e) { + e.selected = false; + }); + + this.base_canvas = this.graphics.createVisibleCanvas(this); + $(this.image).before(this.base_canvas); + }, + null, + { name: 'snapshot' } + )).go(); + }; + + // do not queue this function + + me.state = function () { + var md, result = null; + $(this).each(function (i,e) { + if (e.nodeName === 'IMG') { + md = m.getMapData(e); + if (md) { + result = md.state(); + } + return false; + } + }); + return result; + }; + + me.bind = function (options) { + + return this.each(function (i,e) { + var img, map, usemap, md; + + // save ref to this image even if we can't access it yet. commands will be queued + img = $(e); + + md = m.getMapData(e); + + // if already bound completely, do a total rebind + + if (md) { + me.unbind.apply(img); + if (!md.complete) { + // will be queued + img.bind(); + return true; + } + md = null; + } + + // ensure it's a valid image + // jQuery bug with Opera, results in full-url#usemap being returned from jQuery's attr. + // So use raw getAttribute instead. + + usemap = this.getAttribute('usemap'); + map = usemap && $('map[name="' + usemap.substr(1) + '"]'); + if (!(img.is('img') && usemap && map.size() > 0)) { + return true; + } + + // sorry - your image must have border:0, things are too unpredictable otherwise. + img.css('border', 0); + + if (!md) { + md = new m.MapData(this, options); + + md.index = addMap(md); + md.map = map; + md.bindImages().then(function() { + md.initialize(); + }); + } + }); + }; + + me.init = function (useCanvas) { + var style, shapes; + + // for testing/debugging, use of canvas can be forced by initializing + // manually with "true" or "false". But generally we test for it. + + m.hasCanvas = function() { + if (!u.isBool(m.hasCanvas.value)) { + m.hasCanvas.value = u.isBool(useCanvas) ? + useCanvas : + hasCanvas(); + } + return m.hasCanvas.value; + }; + m.hasVml = function() { + if (!u.isBool(m.hasVml.value)) { + // initialize VML the first time we detect its presence. + var d = namespaces(); + + if (d && !d.v) { + d.add("v", "urn:schemas-microsoft-com:vml"); + style = document.createStyleSheet(); + shapes = ['shape', 'rect', 'oval', 'circ', 'fill', 'stroke', 'imagedata', 'group', 'textbox']; + $.each(shapes, + function (i, el) { + style.addRule('v\\:' + el, "behavior: url(#default#VML); antialias:true"); + }); + } + m.hasVml.value = hasVml(); + } + + return m.hasVml.value; + }; + + m.isTouch = !!document.documentElement.ontouchstart; + + $.extend(m.defaults, m.render_defaults,m.shared_defaults); + $.extend(m.area_defaults, m.render_defaults,m.shared_defaults); + + }; + me.test = function (obj) { + return eval(obj); + }; + return me; + } ()); + + $.mapster.impl.init(); + + +} (jQuery)); +/* graphics.js + Graphics object handles all rendering. +*/ +(function ($) { + var p, m=$.mapster, + u=m.utils, + canvasMethods, + vmlMethods; + + /** + * Implemenation to add each area in an AreaData object to the canvas + * @param {Graphics} graphics The target graphics object + * @param {AreaData} areaData The AreaData object (a collection of area elements and metadata) + * @param {object} options Rendering options to apply when rendering this group of areas + */ + function addShapeGroupImpl(graphics, areaData, options) { + var me = graphics, + md = me.map_data, + isMask = options.isMask; + + // first get area options. Then override fade for selecting, and finally merge in the + // "select" effect options. + + $.each(areaData.areas(), function (i,e) { + options.isMask = isMask || (e.nohref && md.options.noHrefIsMask); + me.addShape(e, options); + }); + + // it's faster just to manipulate the passed options isMask property and restore it, than to + // copy the object each time + + options.isMask=isMask; + + } + + /** + * Convert a hex value to decimal + * @param {string} hex A hexadecimal toString + * @return {int} Integer represenation of the hex string + */ + + function hex_to_decimal(hex) { + return Math.max(0, Math.min(parseInt(hex, 16), 255)); + } + function css3color(color, opacity) { + return 'rgba(' + hex_to_decimal(color.substr(0, 2)) + ',' + + hex_to_decimal(color.substr(2, 2)) + ',' + + hex_to_decimal(color.substr(4, 2)) + ',' + opacity + ')'; + } + /** + * An object associated with a particular map_data instance to manage renderin. + * @param {MapData} map_data The MapData object bound to this instance + */ + + m.Graphics = function (map_data) { + //$(window).unload($.mapster.unload); + // create graphics functions for canvas and vml browsers. usage: + // 1) init with map_data, 2) call begin with canvas to be used (these are separate b/c may not require canvas to be specified + // 3) call add_shape_to for each shape or mask, 4) call render() to finish + + var me = this; + me.active = false; + me.canvas = null; + me.width = 0; + me.height = 0; + me.shapes = []; + me.masks = []; + me.map_data = map_data; + }; + + p = m.Graphics.prototype= { + constructor: m.Graphics, + + /** + * Initiate a graphics request for a canvas + * @param {Element} canvas The canvas element that is the target of this operation + * @param {string} [elementName] The name to assign to the element (VML only) + */ + + begin: function(canvas, elementName) { + var c = $(canvas); + + this.elementName = elementName; + this.canvas = canvas; + + this.width = c.width(); + this.height = c.height(); + this.shapes = []; + this.masks = []; + this.active = true; + + }, + + /** + * Add an area to be rendered to this canvas. + * @param {MapArea} mapArea The MapArea object to render + * @param {object} options An object containing any rendering options that should override the + * defaults for the area + */ + + addShape: function(mapArea, options) { + var addto = options.isMask ? this.masks : this.shapes; + addto.push({ mapArea: mapArea, options: options }); + }, + + /** + * Create a canvas that is sized and styled for the MapData object + * @param {MapData} mapData The MapData object that will receive this new canvas + * @return {Element} A canvas element + */ + + createVisibleCanvas: function (mapData) { + return $(this.createCanvasFor(mapData)) + .addClass('mapster_el') + .css(m.canvas_style)[0]; + }, + + /** + * Add a group of shapes from an AreaData object to the canvas + * + * @param {AreaData} areaData An AreaData object (a set of area elements) + * @param {string} mode The rendering mode, "select" or "highlight". This determines the target + * canvas and which default options to use. + * @param {striong} options Rendering options + */ + + addShapeGroup: function (areaData, mode,options) { + // render includeKeys first - because they could be masks + var me = this, + list, name, canvas, + map_data = this.map_data, + opts = areaData.effectiveRenderOptions(mode); + + if (options) { + $.extend(opts,options); + } + + if (mode === 'select') { + name = "static_" + areaData.areaId.toString(); + canvas = map_data.base_canvas; + } else { + canvas = map_data.overlay_canvas; + } + + me.begin(canvas, name); + + if (opts.includeKeys) { + list = u.split(opts.includeKeys); + $.each(list, function (i,e) { + var areaData = map_data.getDataForKey(e.toString()); + addShapeGroupImpl(me,areaData, areaData.effectiveRenderOptions(mode)); + }); + } + + addShapeGroupImpl(me,areaData, opts); + me.render(); + if (opts.fade) { + + // fading requires special handling for IE. We must access the fill elements directly. The fader also has to deal with + // the "opacity" attribute (not css) + + u.fader(m.hasCanvas() ? + canvas : + $(canvas).find('._fill').not('.mapster_mask'), + 0, + m.hasCanvas() ? + 1 : + opts.fillOpacity, + opts.fadeDuration); + + } + + } + + // These prototype methods are implementation dependent + }; + + function noop() {} + + + // configure remaining prototype methods for ie or canvas-supporting browser + + canvasMethods = { + renderShape: function (context, mapArea, offset) { + var i, + c = mapArea.coords(null,offset); + + switch (mapArea.shape) { + case 'rect': + context.rect(c[0], c[1], c[2] - c[0], c[3] - c[1]); + break; + case 'poly': + context.moveTo(c[0], c[1]); + + for (i = 2; i < mapArea.length; i += 2) { + context.lineTo(c[i], c[i + 1]); + } + context.lineTo(c[0], c[1]); + break; + case 'circ': + case 'circle': + context.arc(c[0], c[1], c[2], 0, Math.PI * 2, false); + break; + } + }, + addAltImage: function (context, image, mapArea, options) { + context.beginPath(); + + this.renderShape(context, mapArea); + context.closePath(); + context.clip(); + + context.globalAlpha = options.altImageOpacity || options.fillOpacity; + + context.drawImage(image, 0, 0, mapArea.owner.scaleInfo.width, mapArea.owner.scaleInfo.height); + }, + render: function () { + // firefox 6.0 context.save() seems to be broken. to work around, we have to draw the contents on one temp canvas, + // the mask on another, and merge everything. ugh. fixed in 1.2.2. unfortunately this is a lot more code for masks, + // but no other way around it that i can see. + + var maskCanvas, maskContext, + me = this, + md = me.map_data, + hasMasks = me.masks.length, + shapeCanvas = me.createCanvasFor(md), + shapeContext = shapeCanvas.getContext('2d'), + context = me.canvas.getContext('2d'); + + if (hasMasks) { + maskCanvas = me.createCanvasFor(md); + maskContext = maskCanvas.getContext('2d'); + maskContext.clearRect(0, 0, maskCanvas.width, maskCanvas.height); + + $.each(me.masks, function (i,e) { + maskContext.save(); + maskContext.beginPath(); + me.renderShape(maskContext, e.mapArea); + maskContext.closePath(); + maskContext.clip(); + maskContext.lineWidth = 0; + maskContext.fillStyle = '#000'; + maskContext.fill(); + maskContext.restore(); + }); + + } + + $.each(me.shapes, function (i,s) { + shapeContext.save(); + if (s.options.fill) { + if (s.options.altImageId) { + me.addAltImage(shapeContext, md.images[s.options.altImageId], s.mapArea, s.options); + } else { + shapeContext.beginPath(); + me.renderShape(shapeContext, s.mapArea); + shapeContext.closePath(); + //shapeContext.clip(); + shapeContext.fillStyle = css3color(s.options.fillColor, s.options.fillOpacity); + shapeContext.fill(); + } + } + shapeContext.restore(); + }); + + + // render strokes at end since masks get stroked too + + $.each(me.shapes.concat(me.masks), function (i,s) { + var offset = s.options.strokeWidth === 1 ? 0.5 : 0; + // offset applies only when stroke width is 1 and stroke would render between pixels. + + if (s.options.stroke) { + shapeContext.save(); + shapeContext.strokeStyle = css3color(s.options.strokeColor, s.options.strokeOpacity); + shapeContext.lineWidth = s.options.strokeWidth; + + shapeContext.beginPath(); + + me.renderShape(shapeContext, s.mapArea, offset); + shapeContext.closePath(); + shapeContext.stroke(); + shapeContext.restore(); + } + }); + + if (hasMasks) { + // render the new shapes against the mask + + maskContext.globalCompositeOperation = "source-out"; + maskContext.drawImage(shapeCanvas, 0, 0); + + // flatten into the main canvas + context.drawImage(maskCanvas, 0, 0); + } else { + context.drawImage(shapeCanvas, 0, 0); + } + + me.active = false; + return me.canvas; + }, + + // create a canvas mimicing dimensions of an existing element + createCanvasFor: function (md) { + return $('')[0]; + }, + clearHighlight: function () { + var c = this.map_data.overlay_canvas; + c.getContext('2d').clearRect(0, 0, c.width, c.height); + }, + // Draw all items from selected_list to a new canvas, then swap with the old one. This is used to delete items when using canvases. + refreshSelections: function () { + var canvas_temp, map_data = this.map_data; + // draw new base canvas, then swap with the old one to avoid flickering + canvas_temp = map_data.base_canvas; + + map_data.base_canvas = this.createVisibleCanvas(map_data); + $(map_data.base_canvas).hide(); + $(canvas_temp).before(map_data.base_canvas); + + map_data.redrawSelections(); + + $(map_data.base_canvas).show(); + $(canvas_temp).remove(); + } + }; + + vmlMethods = { + + renderShape: function (mapArea, options, cssclass) { + var me = this, fill,stroke, e, t_fill, el_name, el_class, template, c = mapArea.coords(); + el_name = me.elementName ? 'name="' + me.elementName + '" ' : ''; + el_class = cssclass ? 'class="' + cssclass + '" ' : ''; + + t_fill = ''; + + + stroke = options.stroke ? + ' strokeweight=' + options.strokeWidth + ' stroked="t" strokecolor="#' + + options.strokeColor + '"' : + ' stroked="f"'; + + fill = options.fill ? + ' filled="t"' : + ' filled="f"'; + + switch (mapArea.shape) { + case 'rect': + template = '' + t_fill + ''; + break; + case 'poly': + template = '' + t_fill + ''; + break; + case 'circ': + case 'circle': + template = '' + t_fill + ''; + break; + } + e = $(template); + $(me.canvas).append(e); + + return e; + }, + render: function () { + var opts, me = this; + + $.each(this.shapes, function (i,e) { + me.renderShape(e.mapArea, e.options); + }); + + if (this.masks.length) { + $.each(this.masks, function (i,e) { + opts = u.updateProps({}, + e.options, { + fillOpacity: 1, + fillColor: e.options.fillColorMask + }); + me.renderShape(e.mapArea, opts, 'mapster_mask'); + }); + } + + this.active = false; + return this.canvas; + }, + + createCanvasFor: function (md) { + var w = md.scaleInfo.width, + h = md.scaleInfo.height; + return $('')[0]; + }, + + clearHighlight: function () { + $(this.map_data.overlay_canvas).children().remove(); + }, + // remove single or all selections + removeSelections: function (area_id) { + if (area_id >= 0) { + $(this.map_data.base_canvas).find('[name="static_' + area_id.toString() + '"]').remove(); + } + else { + $(this.map_data.base_canvas).children().remove(); + } + } + + }; + + // for all methods with two implemenatations, add a function that will automatically replace itself with the correct + // method on first invocation + + $.each(['renderShape', + 'addAltImage', + 'render', + 'createCanvasFor', + 'clearHighlight', + 'removeSelections', + 'refreshSelections'], + function(i,e) { + p[e]=(function(method) { + return function() { + p[method] = (m.hasCanvas() ? + canvasMethods[method] : + vmlMethods[method]) || noop; + + return p[method].apply(this,arguments); + }; + }(e)); + }); + + +} (jQuery)); +/* mapimage.js + the MapImage object, repesents an instance of a single bound imagemap +*/ + +(function ($) { + + var m = $.mapster, + u = m.utils, + ap=[]; + /** + * An object encapsulating all the images used by a MapData. + */ + + m.MapImages = function(owner) { + this.owner = owner; + this.clear(); + }; + + + m.MapImages.prototype = { + constructor: m.MapImages, + + /* interface to make this array-like */ + + slice: function() { + return ap.slice.apply(this,arguments); + }, + splice: function() { + ap.slice.apply(this.status,arguments); + var result= ap.slice.apply(this,arguments); + return result; + }, + + /** + * a boolean value indicates whether all images are done loading + * @return {bool} true when all are done + */ + complete: function() { + return $.inArray(false, this.status) < 0; + }, + + /** + * Save an image in the images array and return its index + * @param {Image} image An Image object + * @return {int} the index of the image + */ + + _add: function(image) { + var index = ap.push.call(this,image)-1; + this.status[index] = false; + return index; + }, + + /** + * Return the index of an Image within the images array + * @param {Image} img An Image + * @return {int} the index within the array, or -1 if it was not found + */ + + indexOf: function(image) { + return $.inArray(image, this); + }, + + /** + * Clear this object and reset it to its initial state after binding. + */ + + clear: function() { + var me=this; + + if (me.ids && me.ids.length>0) { + $.each(me.ids,function(i,e) { + delete me[e]; + }); + } + + /** + * A list of the cross-reference IDs bound to this object + * @type {string[]} + */ + + me.ids=[]; + + /** + * Length property for array-like behavior, set to zero when initializing. Array prototype + * methods will update it after that. + * + * @type {int} + */ + + me.length=0; + + /** + * the loaded status of the corresponding image + * @type {boolean[]} + */ + + me.status=[]; + + + // actually erase the images + + me.splice(0); + + }, + + /** + * Bind an image to the map and add it to the queue to be loaded; return an ID that + * can be used to reference the + * + * @param {Image|string} image An Image object or a URL to an image + * @param {string} [id] An id to refer to this image + * @returns {int} an ID referencing the index of the image object in + * map_data.images + */ + + add: function(image,id) { + var index,src,me = this; + + if (!image) { return; } + + if (typeof image === 'string') { + src = image; + image = me[src]; + if (typeof image==='object') { + return me.indexOf(image); + } + + image = $('') + .addClass('mapster_el') + .hide(); + + index=me._add(image[0]); + + image + .bind('load',function(e) { + me.imageLoaded.call(me,e); + }) + .bind('error',function(e) { + me.imageLoadError.call(me,e); + }); + + image.attr('src', src); + } else { + + // use attr because we want the actual source, not the resolved path the browser will return directly calling image.src + + index=me._add($(image)[0]); + } + if (id) { + if (this[id]) { + throw(id+" is already used or is not available as an altImage alias."); + } + me.ids.push(id); + me[id]=me[index]; + } + return index; + }, + + /** + * Bind the images in this object, + * @param {boolean} retry when true, indicates that the function is calling itself after failure + * @return {Promise} a promise that resolves when the images have finished loading + */ + + bind: function(retry) { + var me = this, + promise, + triesLeft = me.owner.options.configTimeout / 200, + + /* A recursive function to continue checking that the images have been + loaded until a timeout has elapsed */ + + check=function() { + var i; + + // refresh status of images + + i=me.length; + + while (i-->0) { + if (!me.isLoaded(i)) { + break; + } + } + + // check to see if every image has already been loaded + + if (me.complete()) { + me.resolve(); + } else { + // to account for failure of onLoad to fire in rare situations + if (triesLeft-- > 0) { + me.imgTimeout=window.setTimeout(function() { + check.call(me,true); + }, 50); + } else { + me.imageLoadError.call(me); + } + } + + }; + + promise = me.deferred=u.defer(); + + check(); + return promise; + }, + + resolve: function() { + var me=this, + resolver=me.deferred; + + if (resolver) { + // Make a copy of the resolver before calling & removing it to ensure + // it is not called twice + me.deferred=null; + resolver.resolve(); + } + }, + + /** + * Event handler for image onload + * @param {object} e jQuery event data + */ + + imageLoaded: function(e) { + var me=this, + index = me.indexOf(e.target); + + if (index>=0) { + + me.status[index] = true; + if ($.inArray(false, me.status) < 0) { + me.resolve(); + } + } + }, + + /** + * Event handler for onload error + * @param {object} e jQuery event data + */ + + imageLoadError: function(e) { + clearTimeout(this.imgTimeout); + this.triesLeft=0; + var err = e ? 'The image ' + e.target.src + ' failed to load.' : + 'The images never seemed to finish loading. You may just need to increase the configTimeout if images could take a long time to load.'; + throw err; + }, + /** + * Test if the image at specificed index has finished loading + * @param {int} index The image index + * @return {boolean} true if loaded, false if not + */ + + isLoaded: function(index) { + var img, + me=this, + status=me.status; + + if (status[index]) { return true; } + img = me[index]; + + if (typeof img.complete !== 'undefined') { + status[index]=img.complete; + } else { + status[index]=!!u.imgWidth(img); + } + // if complete passes, the image is loaded, but may STILL not be available because of stuff like adblock. + // make sure it is. + + return status[index]; + } + }; + } (jQuery)); +/* mapdata.js + the MapData object, repesents an instance of a single bound imagemap +*/ + + +(function ($) { + + var m = $.mapster, + u = m.utils; + + /** + * Set default values for MapData object properties + * @param {MapData} me The MapData object + */ + + function initializeDefaults(me) { + $.extend(me,{ + complete: false, // (bool) when configuration is complete + map: null, // ($) the image map + base_canvas: null, // (canvas|var) where selections are rendered + overlay_canvas: null, // (canvas|var) where highlights are rendered + commands: [], // {} commands that were run before configuration was completed (b/c images weren't loaded) + data: [], // MapData[] area groups + mapAreas: [], // MapArea[] list. AreaData entities contain refs to this array, so options are stored with each. + _xref: {}, // (int) xref of mapKeys to data[] + highlightId: -1, // (int) the currently highlighted element. + currentAreaId: -1, + _tooltip_events: [], // {} info on events we bound to a tooltip container, so we can properly unbind them + scaleInfo: null, // {} info about the image size, scaling, defaults + index: -1, // index of this in map_cache - so we have an ID to use for wraper div + activeAreaEvent: null + }); + } + + /** + * Return an array of all image-containing options from an options object; + * that is, containers that may have an "altImage" property + * + * @param {object} obj An options object + * @return {object[]} An array of objects + */ + function getOptionImages(obj) { + return [obj, obj.render_highlight, obj.render_select]; + } + + /** + * Parse all the altImage references, adding them to the library so they can be preloaded + * and aliased. + * + * @param {MapData} me The MapData object on which to operate + */ + function configureAltImages(me) + { + var opts = me.options, + mi = me.images; + + // add alt images + + if (m.hasCanvas()) { + // map altImage library first + + $.each(opts.altImages || {}, function(i,e) { + mi.add(e,i); + }); + + // now find everything else + + $.each([opts].concat(opts.areas),function(i,e) { + $.each(getOptionImages(e),function(i2,e2) { + if (e2 && e2.altImage) { + e2.altImageId=mi.add(e2.altImage); + } + }); + }); + } + + // set area_options + me.area_options = u.updateProps({}, // default options for any MapArea + m.area_defaults, + opts); + } + + /** + * Queue a mouse move action based on current delay settings + * (helper for mouseover/mouseout handlers) + * + * @param {MapData} me The MapData context + * @param {number} delay The number of milliseconds to delay the action + * @param {AreaData} area AreaData affected + * @param {Deferred} deferred A deferred object to return (instead of a new one) + * @return {Promise} A promise that resolves when the action is completed + */ + function queueMouseEvent(me,delay,area, deferred) { + + deferred = deferred || u.when.defer(); + + function cbFinal(areaId) { + if (me.currentAreaId!==areaId && me.highlightId>=0) { + deferred.resolve(); + } + } + if (me.activeAreaEvent) { + window.clearTimeout(me.activeAreaEvent); + me.activeAreaEvent=0; + } + if (delay<0) { + return; + } + + if (area.owner.currentAction || delay) { + me.activeAreaEvent = window.setTimeout((function() { + return function() { + queueMouseEvent(me,0,area,deferred); + }; + }(area)), + delay || 100); + } else { + cbFinal(area.areaId); + } + return deferred; + } + + /** + * Mousedown event. This is captured only to prevent browser from drawing an outline around an + * area when it's clicked. + * + * @param {EventData} e jQuery event data + */ + + function mousedown(e) { + if (!m.hasCanvas()) { + this.blur(); + } + e.preventDefault(); + } + + /** + * Mouseover event. Handle highlight rendering and client callback on mouseover + * + * @param {MapData} me The MapData context + * @param {EventData} e jQuery event data + * @return {[type]} [description] + */ + + function mouseover(me,e) { + var arData = me.getAllDataForArea(this), + ar=arData.length ? arData[0] : null; + + // mouseover events are ignored entirely while resizing, though we do care about mouseout events + // and must queue the action to keep things clean. + + if (!ar || ar.isNotRendered() || ar.owner.currentAction) { + return; + } + + if (me.currentAreaId === ar.areaId) { + return; + } + if (me.highlightId !== ar.areaId) { + me.clearEffects(); + + ar.highlight(); + + if (me.options.showToolTip) { + $.each(arData,function(i,e) { + if (e.effectiveOptions().toolTip) { + e.showToolTip(); + } + }); + } + } + + me.currentAreaId = ar.areaId; + + if ($.isFunction(me.options.onMouseover)) { + me.options.onMouseover.call(this, + { + e: e, + options:ar.effectiveOptions(), + key: ar.key, + selected: ar.isSelected() + }); + } + } + + /** + * Mouseout event. + * + * @param {MapData} me The MapData context + * @param {EventData} e jQuery event data + * @return {[type]} [description] + */ + + function mouseout(me,e) { + var newArea, + ar = me.getDataForArea(this), + opts = me.options; + + + if (me.currentAreaId<0 || !ar) { + return; + } + + newArea=me.getDataForArea(e.relatedTarget); + + if (newArea === ar) { + return; + } + + me.currentAreaId = -1; + ar.area=null; + + queueMouseEvent(me,opts.mouseoutDelay,ar) + .then(me.clearEffects); + + if ($.isFunction(opts.onMouseout)) { + opts.onMouseout.call(this, + { + e: e, + options: opts, + key: ar.key, + selected: ar.isSelected() + }); + } + + } + + /** + * Clear any active tooltip or highlight + * + * @param {MapData} me The MapData context + * @param {EventData} e jQuery event data + * @return {[type]} [description] + */ + + function clearEffects(me) { + var opts = me.options; + + me.ensureNoHighlight(); + + if (opts.toolTipClose + && $.inArray('area-mouseout', opts.toolTipClose) >= 0 + && me.activeToolTip) + { + me.clearToolTip(); + } + } + + /** + * Mouse click event handler + * + * @param {MapData} me The MapData context + * @param {EventData} e jQuery event data + * @return {[type]} [description] + */ + + function click(me,e) { + var selected, list, list_target, newSelectionState, canChangeState, cbResult, + that = this, + ar = me.getDataForArea(this), + opts = me.options; + + function clickArea(ar) { + var areaOpts,target; + canChangeState = (ar.isSelectable() && + (ar.isDeselectable() || !ar.isSelected())); + + if (canChangeState) { + newSelectionState = !ar.isSelected(); + } else { + newSelectionState = ar.isSelected(); + } + + list_target = m.getBoundList(opts, ar.key); + + if ($.isFunction(opts.onClick)) + { + cbResult= opts.onClick.call(that, + { + e: e, + listTarget: list_target, + key: ar.key, + selected: newSelectionState + }); + + if (u.isBool(cbResult)) { + if (!cbResult) { + return false; + } + target = $(ar.area).attr('href'); + if (target!=='#') { + window.location.href=target; + return false; + } + } + } + + if (canChangeState) { + selected = ar.toggle(); + } + + if (opts.boundList && opts.boundList.length > 0) { + m.setBoundListProperties(opts, list_target, ar.isSelected()); + } + + areaOpts = ar.effectiveOptions(); + if (areaOpts.includeKeys) { + list = u.split(areaOpts.includeKeys); + $.each(list, function (i, e) { + var ar = me.getDataForKey(e.toString()); + if (!ar.options.isMask) { + clickArea(ar); + } + }); + } + } + + mousedown.call(this,e); + + if (opts.clickNavigate && ar.href) { + window.location.href=ar.href; + return; + } + + if (ar && !ar.owner.currentAction) { + opts = me.options; + clickArea(ar); + } + } + + /** + * Prototype for a MapData object, representing an ImageMapster bound object + * @param {Element} image an IMG element + * @param {object} options ImageMapster binding options + */ + m.MapData = function (image, options) + { + var me = this; + + // (Image) main map image + + me.image = image; + + me.images = new m.MapImages(me); + me.graphics = new m.Graphics(me); + + // save the initial style of the image for unbinding. This is problematic, chrome + // duplicates styles when assigning, and cssText is apparently not universally supported. + // Need to do something more robust to make unbinding work universally. + + me.imgCssText = image.style.cssText || null; + + initializeDefaults(me); + + me.configureOptions(options); + + // create context-bound event handlers from our private functions + + me.mouseover = function(e) { mouseover.call(this,me,e); }; + me.mouseout = function(e) { mouseout.call(this,me,e); }; + me.click = function(e) { click.call(this,me,e); }; + me.clearEffects = function(e) { clearEffects.call(this,me,e); }; + }; + + m.MapData.prototype = { + constructor: m.MapData, + + /** + * Set target.options from defaults + options + * @param {[type]} target The target + * @param {[type]} options The options to merge + */ + + configureOptions: function(options) { + this.options= u.updateProps({}, m.defaults, options); + }, + + /** + * Ensure all images are loaded + * @return {Promise} A promise that resolves when the images have finished loading (or fail) + */ + + bindImages: function() { + var me=this, + mi = me.images; + + // reset the images if this is a rebind + + if (mi.length>2) { + mi.splice(2); + } else if (mi.length===0) { + + // add the actual main image + mi.add(me.image); + // will create a duplicate of the main image, we need this to get raw size info + mi.add(me.image.src); + } + + configureAltImages(me); + + return me.images.bind(); + }, + + /** + * Test whether an async action is currently in progress + * @return {Boolean} true or false indicating state + */ + + isActive: function() { + return !this.complete || this.currentAction; + }, + + /** + * Return an object indicating the various states. This isn't really used by + * production code. + * + * @return {object} An object with properties for various states + */ + + state: function () { + return { + complete: this.complete, + resizing: this.currentAction==='resizing', + zoomed: this.zoomed, + zoomedArea: this.zoomedArea, + scaleInfo: this.scaleInfo + }; + }, + + /** + * Get a unique ID for the wrapper of this imagemapster + * @return {string} A string that is unique to this image + */ + + wrapId: function () { + return 'mapster_wrap_' + this.index; + }, + _idFromKey: function (key) { + return typeof key === "string" && this._xref.hasOwnProperty(key) ? + this._xref[key] : -1; + }, + + /** + * Return a comma-separated string of all selected keys + * @return {string} CSV of all keys that are currently selected + */ + + getSelected: function () { + var result = ''; + $.each(this.data, function (i,e) { + if (e.isSelected()) { + result += (result ? ',' : '') + this.key; + } + }); + return result; + }, + + /** + * Get an array of MapAreas associated with a specific AREA based on the keys for that area + * @param {Element} area An HTML AREA + * @param {number} atMost A number limiting the number of areas to be returned (typically 1 or 0 for no limit) + * @return {MapArea[]} Array of MapArea objects + */ + + getAllDataForArea:function (area,atMost) { + var i,ar, result, + me=this, + key = $(area).filter('area').attr(me.options.mapKey); + + if (key) { + result=[]; + key = u.split(key); + + for (i=0;i<(atMost || key.length);i++) { + ar = me.data[me._idFromKey(key[i])]; + ar.area=area.length ? area[0]:area; + // set the actual area moused over/selected + // TODO: this is a brittle model for capturing which specific area - if this method was not used, + // ar.area could have old data. fix this. + result.push(ar); + } + } + + return result; + }, + getDataForArea: function(area) { + var ar=this.getAllDataForArea(area,1); + return ar ? ar[0] || null : null; + }, + getDataForKey: function (key) { + return this.data[this._idFromKey(key)]; + }, + + /** + * Get the primary keys associated with an area group. + * If this is a primary key, it will be returned. + * + * @param {string key An area key + * @return {string} A CSV of area keys + */ + + getKeysForGroup: function(key) { + var ar=this.getDataForKey(key); + + return !ar ? '': + ar.isPrimary ? + ar.key : + this.getPrimaryKeysForMapAreas(ar.areas()).join(','); + }, + + /** + * given an array of MapArea object, return an array of its unique primary keys + * @param {MapArea[]} areas The areas to analyze + * @return {string[]} An array of unique primary keys + */ + + getPrimaryKeysForMapAreas: function(areas) + { + var keys=[]; + $.each(areas,function(i,e) { + if ($.inArray(e.keys[0],keys)<0) { + keys.push(e.keys[0]); + } + }); + return keys; + }, + getData: function (obj) { + if (typeof obj === 'string') { + return this.getDataForKey(obj); + } else if (obj && obj.mapster || u.isElement(obj)) { + return this.getDataForArea(obj); + } else { + return null; + } + }, + // remove highlight if present, raise event + ensureNoHighlight: function () { + var ar; + if (this.highlightId >= 0) { + this.graphics.clearHighlight(); + ar = this.data[this.highlightId]; + ar.changeState('highlight', false); + this.setHighlightId(-1); + } + }, + setHighlightId: function(id) { + this.highlightId = id; + }, + + /** + * Clear all active selections on this map + */ + + clearSelections: function () { + $.each(this.data, function (i,e) { + if (e.selected) { + e.deselect(true); + } + }); + this.removeSelectionFinish(); + + }, + + /** + * Set area options from an array of option data. + * + * @param {object[]} areas An array of objects containing area-specific options + */ + + setAreaOptions: function (areas) { + var i, area_options, ar; + areas = areas || []; + + // refer by: map_data.options[map_data.data[x].area_option_id] + + for (i = areas.length - 1; i >= 0; i--) { + area_options = areas[i]; + if (area_options) { + ar = this.getDataForKey(area_options.key); + if (ar) { + u.updateProps(ar.options, area_options); + + // TODO: will not deselect areas that were previously selected, so this only works + // for an initial bind. + + if (u.isBool(area_options.selected)) { + ar.selected = area_options.selected; + } + } + } + } + }, + // keys: a comma-separated list + drawSelections: function (keys) { + var i, key_arr = u.asArray(keys); + + for (i = key_arr.length - 1; i >= 0; i--) { + this.data[key_arr[i]].drawSelection(); + } + }, + redrawSelections: function () { + $.each(this.data, function (i, e) { + if (e.isSelectedOrStatic()) { + e.drawSelection(); + } + }); + + }, + ///called when images are done loading + initialize: function () { + var imgCopy, base_canvas, overlay_canvas, wrap, parentId, css, i,size, + img,sort_func, sorted_list, scale, + me = this, + opts = me.options; + + if (me.complete) { + return; + } + + img = $(me.image); + + parentId = img.parent().attr('id'); + + // create a div wrapper only if there's not already a wrapper, otherwise, own it + + if (parentId && parentId.length >= 12 && parentId.substring(0, 12) === "mapster_wrap") { + wrap = img.parent(); + wrap.attr('id', me.wrapId()); + } else { + wrap = $('
'); + + if (opts.wrapClass) { + if (opts.wrapClass === true) { + wrap.addClass(img[0].className); + } + else { + wrap.addClass(opts.wrapClass); + } + } + } + me.wrapper = wrap; + + // me.images[1] is the copy of the original image. It should be loaded & at its native size now so we can obtain the true + // width & height. This is needed to scale the imagemap if not being shown at its native size. It is also needed purely + // to finish binding in case the original image was not visible. It can be impossible in some browsers to obtain the + // native size of a hidden image. + + me.scaleInfo = scale = u.scaleMap(me.images[0],me.images[1], opts.scaleMap); + + me.base_canvas = base_canvas = me.graphics.createVisibleCanvas(me); + me.overlay_canvas = overlay_canvas = me.graphics.createVisibleCanvas(me); + + // Now we got what we needed from the copy -clone from the original image again to make sure any other attributes are copied + imgCopy = $(me.images[1]) + .addClass('mapster_el '+ me.images[0].className) + .attr({id:null, usemap: null}); + + size=u.size(me.images[0]); + + if (size.complete) { + imgCopy.css({ + width: size.width, + height: size.height + }); + } + + me.buildDataset(); + + // now that we have processed all the areas, set css for wrapper, scale map if needed + + css = { + display: 'block', + position: 'relative', + padding: 0, + width: scale.width, + height: scale.height + }; + + if (opts.wrapCss) { + $.extend(css, opts.wrapCss); + } + // if we were rebinding with an existing wrapper, the image will aready be in it + if (img.parent()[0] !== me.wrapper[0]) { + + img.before(me.wrapper); + } + + wrap.css(css); + + // move all generated images into the wrapper for easy removal later + + $(me.images.slice(2)).hide(); + for (i = 1; i < me.images.length; i++) { + wrap.append(me.images[i]); + } + + //me.images[1].style.cssText = me.image.style.cssText; + + wrap.append(base_canvas) + .append(overlay_canvas) + .append(img.css(m.canvas_style)); + + // images[0] is the original image with map, images[1] is the copy/background that is visible + + u.setOpacity(me.images[0], 0); + $(me.images[1]).show(); + + u.setOpacity(me.images[1],1); + + if (opts.isSelectable && opts.onGetList) { + sorted_list = me.data.slice(0); + if (opts.sortList) { + if (opts.sortList === "desc") { + sort_func = function (a, b) { + return a === b ? 0 : (a > b ? -1 : 1); + }; + } + else { + sort_func = function (a, b) { + return a === b ? 0 : (a < b ? -1 : 1); + }; + } + + sorted_list.sort(function (a, b) { + a = a.value; + b = b.value; + return sort_func(a, b); + }); + } + + me.options.boundList = opts.onGetList.call(me.image, sorted_list); + } + + me.complete=true; + me.processCommandQueue(); + + if (opts.onConfigured && typeof opts.onConfigured === 'function') { + opts.onConfigured.call(img, true); + } + }, + + // when rebind is true, the MapArea data will not be rebuilt. + buildDataset: function(rebind) { + var sel,areas,j,area_id,$area,area,curKey,mapArea,key,keys,mapAreaId,group_value,dataItem,href, + me=this, + opts=me.options, + default_group; + + function addAreaData(key, value) { + var dataItem = new m.AreaData(me, key, value); + dataItem.areaId = me._xref[key] = me.data.push(dataItem) - 1; + return dataItem.areaId; + } + + me._xref = {}; + me.data = []; + if (!rebind) { + me.mapAreas=[]; + } + + default_group = !opts.mapKey; + if (default_group) { + opts.mapKey = 'data-mapster-key'; + } + + // the [attribute] selector is broken on old IE with jQuery. hasVml() is a quick and dirty + // way to test for that + + sel = m.hasVml() ? 'area' : + (default_group ? + 'area[coords]' : + 'area[' + opts.mapKey + ']'); + + areas = $(me.map).find(sel).unbind('.mapster'); + + for (mapAreaId = 0;mapAreaId= 0; j--) { + key = keys[j]; + + if (opts.mapValue) { + group_value = $area.attr(opts.mapValue); + } + if (default_group) { + // set an attribute so we can refer to the area by index from the DOM object if no key + area_id = addAreaData(me.data.length, group_value); + dataItem = me.data[area_id]; + dataItem.key = key = area_id.toString(); + } + else { + area_id = me._xref[key]; + if (area_id >= 0) { + dataItem = me.data[area_id]; + if (group_value && !me.data[area_id].value) { + dataItem.value = group_value; + } + } + else { + area_id = addAreaData(key, group_value); + dataItem = me.data[area_id]; + dataItem.isPrimary=j===0; + } + } + mapArea.areaDataXref.push(area_id); + dataItem.areasXref.push(mapAreaId); + } + + href=$area.attr('href'); + if (href && href!=='#' && !dataItem.href) + { + dataItem.href=href; + } + + if (!mapArea.nohref) { + $area.bind('click.mapster', me.click); + + if (!m.isTouch) { + $area.bind('mouseover.mapster', me.mouseover) + .bind('mouseout.mapster', me.mouseout) + .bind('mousedown.mapster', me.mousedown); + + } + + } + + // store an ID with each area. + $area.data("mapster", mapAreaId+1); + } + + // TODO listenToList + // if (opts.listenToList && opts.nitG) { + // opts.nitG.bind('click.mapster', event_hooks[map_data.hooks_index].listclick_hook); + // } + + // populate areas from config options + me.setAreaOptions(opts.areas); + me.redrawSelections(); + + }, + processCommandQueue: function() { + + var cur,me=this; + while (!me.currentAction && me.commands.length) { + cur = me.commands[0]; + me.commands.splice(0,1); + m.impl[cur.command].apply(cur.that, cur.args); + } + }, + clearEvents: function () { + $(this.map).find('area') + .unbind('.mapster'); + $(this.images) + .unbind('.mapster'); + }, + _clearCanvases: function (preserveState) { + // remove the canvas elements created + if (!preserveState) { + $(this.base_canvas).remove(); + } + $(this.overlay_canvas).remove(); + }, + clearMapData: function (preserveState) { + var me = this; + this._clearCanvases(preserveState); + + // release refs to DOM elements + $.each(this.data, function (i, e) { + e.reset(); + }); + this.data = null; + if (!preserveState) { + // get rid of everything except the original image + this.image.style.cssText = this.imgCssText; + $(this.wrapper).before(this.image).remove(); + } + + me.images.clear(); + + this.image = null; + u.ifFunction(this.clearTooltip, this); + }, + + // Compelete cleanup process for deslecting items. Called after a batch operation, or by AreaData for single + // operations not flagged as "partial" + + removeSelectionFinish: function () { + var g = this.graphics; + + g.refreshSelections(); + // do not call ensure_no_highlight- we don't really want to unhilight it, just remove the effect + g.clearHighlight(); + } + }; +} (jQuery)); +/* areadata.js + AreaData and MapArea protoypes +*/ + +(function ($) { + var m = $.mapster, u = m.utils; + + /** + * Select this area + * + * @param {AreaData} me AreaData context + * @param {object} options Options for rendering the selection + */ + function select(options) { + // need to add the new one first so that the double-opacity effect leaves the current one highlighted for singleSelect + + var me=this, o = me.owner; + if (o.options.singleSelect) { + o.clearSelections(); + } + + // because areas can overlap - we can't depend on the selection state to tell us anything about the inner areas. + // don't check if it's already selected + if (!me.isSelected()) { + if (options) { + + // cache the current options, and map the altImageId if an altimage + // was passed + + me.optsCache = $.extend(me.effectiveRenderOptions('select'), + options, + { + altImageId: o.images.add(options.altImage) + }); + } + + me.drawSelection(); + + me.selected = true; + me.changeState('select', true); + } + + if (o.options.singleSelect) { + o.graphics.refreshSelections(); + } + } + + /** + * Deselect this area, optionally deferring finalization so additional areas can be deselected + * in a single operation + * + * @param {boolean} partial when true, the caller must invoke "finishRemoveSelection" to render + */ + + function deselect(partial) { + var me=this; + me.selected = false; + me.changeState('select', false); + + // release information about last area options when deselecting. + + me.optsCache=null; + me.owner.graphics.removeSelections(me.areaId); + + // Complete selection removal process. This is separated because it's very inefficient to perform the whole + // process for multiple removals, as the canvas must be totally redrawn at the end of the process.ar.remove + + if (!partial) { + me.owner.removeSelectionFinish(); + } + } + + /** + * Toggle the selection state of this area + * @param {object} options Rendering options, if toggling on + * @return {bool} The new selection state + */ + function toggle(options) { + var me=this; + if (!me.isSelected()) { + me.select(options); + } + else { + me.deselect(); + } + return me.isSelected(); + } + + /** + * An AreaData object; represents a conceptual area that can be composed of + * one or more MapArea objects + * + * @param {MapData} owner The MapData object to which this belongs + * @param {string} key The key for this area + * @param {string} value The mapValue string for this area + */ + + m.AreaData = function (owner, key, value) { + $.extend(this,{ + owner: owner, + key: key || '', + // means this represents the first key in a list of keys (it's the area group that gets highlighted on mouseover) + isPrimary: true, + areaId: -1, + href: '', + value: value || '', + options:{}, + // "null" means unchanged. Use "isSelected" method to just test true/false + selected: null, + // xref to MapArea objects + areasXref: [], + // (temporary storage) - the actual area moused over + area: null, + // the last options used to render this. Cache so when re-drawing after a remove, changes in options won't + // break already selected things. + optsCache: null + }); + }; + + /** + * The public API for AreaData object + */ + + m.AreaData.prototype = { + constuctor: m.AreaData, + select: select, + deselect: deselect, + toggle: toggle, + areas: function() { + var i,result=[]; + for (i=0;i= 0; j -= 2) { + curX = coords[j]; + curY = coords[j + 1]; + + if (curX < minX) { + minX = curX; + bestMaxY = curY; + } + if (curX > maxX) { + maxX = curX; + bestMinY = curY; + } + if (curY < minY) { + minY = curY; + bestMaxX = curX; + } + if (curY > maxY) { + maxY = curY; + bestMinX = curX; + } + + } + + // try to figure out the best place for the tooltip + + if (width && height) { + found=false; + $.each([[bestMaxX - width, minY - height], [bestMinX, minY - height], + [minX - width, bestMaxY - height], [minX - width, bestMinY], + [maxX,bestMaxY - height], [ maxX,bestMinY], + [bestMaxX - width, maxY], [bestMinX, maxY] + ],function (i, e) { + if (!found && (e[0] > rootx && e[1] > rooty)) { + nest = e; + found=true; + return false; + } + }); + + // default to lower-right corner if nothing fit inside the boundaries of the image + + if (!found) { + nest=[maxX,maxY]; + } + } + return nest; + }; +} (jQuery)); +/* scale.js: resize and zoom functionality + requires areacorners.js, when.js +*/ + + +(function ($) { + var m = $.mapster, u = m.utils, p = m.MapArea.prototype; + + m.utils.getScaleInfo = function (eff, actual) { + var pct; + if (!actual) { + pct = 1; + actual=eff; + } else { + pct = eff.width / actual.width || eff.height / actual.height; + // make sure a float error doesn't muck us up + if (pct > 0.98 && pct < 1.02) { pct = 1; } + } + return { + scale: (pct !== 1), + scalePct: pct, + realWidth: actual.width, + realHeight: actual.height, + width: eff.width, + height: eff.height, + ratio: eff.width / eff.height + }; + }; + // Scale a set of AREAs, return old data as an array of objects + m.utils.scaleMap = function (image, imageRaw, scale) { + + // stunningly, jQuery width can return zero even as width does not, seems to happen only + // with adBlock or maybe other plugins. These must interfere with onload events somehow. + + + var vis=u.size(image), + raw=u.size(imageRaw,true); + + if (!raw.complete()) { + throw("Another script, such as an extension, appears to be interfering with image loading. Please let us know about this."); + } + if (!vis.complete()) { + vis=raw; + } + return this.getScaleInfo(vis, scale ? raw : null); + }; + + /** + * Resize the image map. Only one of newWidth and newHeight should be passed to preserve scale + * + * @param {int} width The new width OR an object containing named parameters matching this function sig + * @param {int} height The new height + * @param {int} effectDuration Time in ms for the resize animation, or zero for no animation + * @param {function} callback A function to invoke when the operation finishes + * @return {promise} NOT YET IMPLEMENTED + */ + + m.MapData.prototype.resize = function (width, height, duration, callback) { + var p,promises,newsize,els, highlightId, ratio, + me = this; + + // allow omitting duration + callback = callback || duration; + + function sizeCanvas(canvas, w, h) { + if (m.hasCanvas()) { + canvas.width = w; + canvas.height = h; + } else { + $(canvas).width(w); + $(canvas).height(h); + } + } + + // Finalize resize action, do callback, pass control to command queue + + function cleanupAndNotify() { + + me.currentAction = ''; + + if ($.isFunction(callback)) { + callback(); + } + + me.processCommandQueue(); + } + + // handle cleanup after the inner elements are resized + + function finishResize() { + sizeCanvas(me.overlay_canvas, width, height); + + // restore highlight state if it was highlighted before + if (highlightId >= 0) { + var areaData = me.data[highlightId]; + areaData.tempOptions = { fade: false }; + me.getDataForKey(areaData.key).highlight(); + areaData.tempOptions = null; + } + sizeCanvas(me.base_canvas, width, height); + me.redrawSelections(); + cleanupAndNotify(); + } + + function resizeMapData() { + $(me.image).css(newsize); + // start calculation at the same time as effect + me.scaleInfo = u.getScaleInfo({ + width: width, + height: height + }, + { + width: me.scaleInfo.realWidth, + height: me.scaleInfo.realHeight + }); + $.each(me.data, function (i, e) { + $.each(e.areas(), function (i, e) { + e.resize(); + }); + }); + } + + if (me.scaleInfo.width === width && me.scaleInfo.height === height) { + return; + } + + highlightId = me.highlightId; + + + if (!width) { + ratio = height / me.scaleInfo.realHeight; + width = Math.round(me.scaleInfo.realWidth * ratio); + } + if (!height) { + ratio = width / me.scaleInfo.realWidth; + height = Math.round(me.scaleInfo.realHeight * ratio); + } + + newsize = { 'width': String(width) + 'px', 'height': String(height) + 'px' }; + if (!m.hasCanvas()) { + $(me.base_canvas).children().remove(); + } + + // resize all the elements that are part of the map except the image itself (which is not visible) + // but including the div wrapper + els = $(me.wrapper).find('.mapster_el').add(me.wrapper); + + if (duration) { + promises = []; + me.currentAction = 'resizing'; + els.each(function (i, e) { + p = u.defer(); + promises.push(p); + + $(e).animate(newsize, { + duration: duration, + complete: p.resolve, + easing: "linear" + }); + }); + + p = u.defer(); + promises.push(p); + + // though resizeMapData is not async, it needs to be finished just the same as the animations, + // so add it to the "to do" list. + + u.when.all(promises).then(finishResize); + resizeMapData(); + p.resolve(); + } else { + els.css(newsize); + resizeMapData(); + finishResize(); + + } + }; + + + m.MapArea = u.subclass(m.MapArea, function () { + //change the area tag data if needed + this.base.init(); + if (this.owner.scaleInfo.scale) { + this.resize(); + } + }); + + p.coords = function (percent, coordOffset) { + var j, newCoords = [], + pct = percent || this.owner.scaleInfo.scalePct, + offset = coordOffset || 0; + + if (pct === 1 && coordOffset === 0) { + return this.originalCoords; + } + + for (j = 0; j < this.length; j++) { + //amount = j % 2 === 0 ? xPct : yPct; + newCoords.push(Math.round(this.originalCoords[j] * pct) + offset); + } + return newCoords; + }; + p.resize = function () { + this.area.coords = this.coords().join(','); + }; + + p.reset = function () { + this.area.coords = this.coords(1).join(','); + }; + + m.impl.resize = function (width, height, duration, callback) { + if (!width && !height) { + return false; + } + var x= (new m.Method(this, + function () { + this.resize(width, height, duration, callback); + }, + null, + { + name: 'resize', + args: arguments + } + )).go(); + return x; + }; + +/* + m.impl.zoom = function (key, opts) { + var options = opts || {}; + + function zoom(areaData) { + // this will be MapData object returned by Method + + var scroll, corners, height, width, ratio, + diffX, diffY, ratioX, ratioY, offsetX, offsetY, newWidth, newHeight, scrollLeft, scrollTop, + padding = options.padding || 0, + scrollBarSize = areaData ? 20 : 0, + me = this, + zoomOut = false; + + if (areaData) { + // save original state on first zoom operation + if (!me.zoomed) { + me.zoomed = true; + me.preZoomWidth = me.scaleInfo.width; + me.preZoomHeight = me.scaleInfo.height; + me.zoomedArea = areaData; + if (options.scroll) { + me.wrapper.css({ overflow: 'auto' }); + } + } + corners = $.mapster.utils.areaCorners(areaData.coords(1, 0)); + width = me.wrapper.innerWidth() - scrollBarSize - padding * 2; + height = me.wrapper.innerHeight() - scrollBarSize - padding * 2; + diffX = corners.maxX - corners.minX; + diffY = corners.maxY - corners.minY; + ratioX = width / diffX; + ratioY = height / diffY; + ratio = Math.min(ratioX, ratioY); + offsetX = (width - diffX * ratio) / 2; + offsetY = (height - diffY * ratio) / 2; + + newWidth = me.scaleInfo.realWidth * ratio; + newHeight = me.scaleInfo.realHeight * ratio; + scrollLeft = (corners.minX) * ratio - padding - offsetX; + scrollTop = (corners.minY) * ratio - padding - offsetY; + } else { + if (!me.zoomed) { + return; + } + zoomOut = true; + newWidth = me.preZoomWidth; + newHeight = me.preZoomHeight; + scrollLeft = null; + scrollTop = null; + } + + this.resize({ + width: newWidth, + height: newHeight, + duration: options.duration, + scroll: scroll, + scrollLeft: scrollLeft, + scrollTop: scrollTop, + // closure so we can be sure values are correct + callback: (function () { + var isZoomOut = zoomOut, + scroll = options.scroll, + areaD = areaData; + return function () { + if (isZoomOut) { + me.preZoomWidth = null; + me.preZoomHeight = null; + me.zoomed = false; + me.zoomedArea = false; + if (scroll) { + me.wrapper.css({ overflow: 'inherit' }); + } + } else { + // just to be sure it wasn't canceled & restarted + me.zoomedArea = areaD; + } + }; + } ()) + }); + } + return (new m.Method(this, + function (opts) { + zoom.call(this); + }, + function () { + zoom.call(this.owner, this); + }, + { + name: 'zoom', + args: arguments, + first: true, + key: key + } + )).go(); + + + }; + */ +} (jQuery)); +/* tooltip.js - tooltip functionality + requires areacorners.js +*/ + +(function ($) { + + var m = $.mapster, u = m.utils; + + $.extend(m.defaults, { + toolTipContainer: '
', + showToolTip: false, + toolTipFade: true, + toolTipClose: ['area-mouseout','image-mouseout'], + onShowToolTip: null, + onHideToolTip: null + }); + + $.extend(m.area_defaults, { + toolTip: null, + toolTipClose: null + }); + + + /** + * Show a tooltip positioned near this area. + * + * @param {string|jquery} html A string of html or a jQuery object containing the tooltip content. + * @param {string|jquery} [template] The html template in which to wrap the content + * @param {string|object} [css] CSS to apply to the outermost element of the tooltip + * @return {jquery} The tooltip that was created + */ + + function createToolTip(html, template, css) { + var tooltip; + + // wrap the template in a jQuery object, or clone the template if it's already one. + // This assumes that anything other than a string is a jQuery object; if it's not jQuery will + // probably throw an error. + + if (template) { + tooltip = typeof template === 'string' ? + $(template) : + $(template).clone(); + + tooltip.append(html); + } else { + tooltip=$(html); + } + + // always set display to block, or the positioning css won't work if the end user happened to + // use a non-block type element. + + tooltip.css($.extend((css || {}),{ + display:"block", + position:"absolute" + })).hide(); + + $('body').append(tooltip); + + // we must actually add the tooltip to the DOM and "show" it in order to figure out how much space it + // consumes, and then reposition it with that knowledge. + // We also cache the actual opacity setting to restore finally. + + tooltip.attr("data-opacity",tooltip.css("opacity")) + .css("opacity",0); + + // doesn't really show it because opacity=0 + + return tooltip.show(); + } + + + /** + * Show a tooltip positioned near this area. + * + * @param {jquery} tooltip The tooltip + * @param {object} [options] options for displaying the tooltip. + * @config {int} [left] The 0-based absolute x position for the tooltip + * @config {int} [top] The 0-based absolute y position for the tooltip + * @config {string|object} [css] CSS to apply to the outermost element of the tooltip + * @config {bool} [fadeDuration] When non-zero, the duration in milliseconds of a fade-in effect for the tooltip. + */ + + function showToolTipImpl(tooltip,options) + { + var tooltipCss = { + "left": options.left + "px", + "top": options.top + "px" + }, + actalOpacity=tooltip.attr("data-opacity") || 0, + zindex = tooltip.css("z-index"); + + if (parseInt(zindex,10)===0 + || zindex === "auto") { + tooltipCss["z-index"] = 9999; + } + + tooltip.css(tooltipCss) + .addClass('mapster_tooltip'); + + + if (options.fadeDuration && options.fadeDuration>0) { + u.fader(tooltip[0], 0, actalOpacity, options.fadeDuration); + } else { + u.setOpacity(tooltip[0], actalOpacity); + } + } + + /** + * Hide and remove active tooltips + * + * @param {MapData} this The mapdata object to which the tooltips belong + */ + + m.MapData.prototype.clearToolTip = function() { + if (this.activeToolTip) { + this.activeToolTip.stop().remove(); + this.activeToolTip = null; + this.activeToolTipID = null; + u.ifFunction(this.options.onHideToolTip, this); + } + }; + + /** + * Configure the binding between a named tooltip closing option, and a mouse event. + * + * If a callback is passed, it will be called when the activating event occurs, and the tooltip will + * only closed if it returns true. + * + * @param {MapData} [this] The MapData object to which this tooltip belongs. + * @param {String} option The name of the tooltip closing option + * @param {String} event UI event to bind to this option + * @param {Element} target The DOM element that is the target of the event + * @param {Function} [beforeClose] Callback when the tooltip is closed + * @param {Function} [onClose] Callback when the tooltip is closed + */ + function bindToolTipClose(options, bindOption, event, target, beforeClose, onClose) { + var event_name = event + '.mapster-tooltip'; + + if ($.inArray(bindOption, options) >= 0) { + target.unbind(event_name) + .bind(event_name, function (e) { + if (!beforeClose || beforeClose.call(this,e)) { + target.unbind('.mapster-tooltip'); + if (onClose) { + onClose.call(this); + } + } + }); + + return { + object: target, + event: event_name + }; + } + } + + /** + * Show a tooltip. + * + * @param {string|jquery} [tooltip] A string of html or a jQuery object containing the tooltip content. + * + * @param {string|jquery} [target] The target of the tooltip, to be used to determine positioning. If null, + * absolute position values must be passed with left and top. + * + * @param {string|jquery} [image] If target is an [area] the image that owns it + * + * @param {string|jquery} [container] An element within which the tooltip must be bounded + * + * + * + * @param {object|string|jQuery} [options] options to apply when creating this tooltip - OR - + * The markup, or a jquery object, containing the data for the tooltip + * + * @config {string} [closeEvents] A string with one or more comma-separated values that determine when the tooltip + * closes: 'area-click','tooltip-click','image-mouseout' are valid values + * then no template will be used. + * @config {int} [offsetx] the horizontal amount to offset the tooltip + * @config {int} [offsety] the vertical amount to offset the tooltip + * @config {string|object} [css] CSS to apply to the outermost element of the tooltip + */ + + function showToolTip(tooltip,target,image,container,options) { + var corners, + ttopts = {}; + + options = options || {}; + + + if (target) { + + corners = u.areaCorners(target,image,container, + tooltip.outerWidth(true), + tooltip.outerHeight(true)); + + // Try to upper-left align it first, if that doesn't work, change the parameters + + ttopts.left = corners[0]; + ttopts.top = corners[1]; + + } else { + + ttopts.left = options.left; + ttopts.top = options.top; + } + + ttopts.left += (options.offsetx || 0); + ttopts.top +=(options.offsety || 0); + + ttopts.css= options.css; + ttopts.fadeDuration = options.fadeDuration; + + showToolTipImpl(tooltip,ttopts); + + return tooltip; + } + + /** + * Show a tooltip positioned near this area. + * + * @param {string|jquery} [content] A string of html or a jQuery object containing the tooltip content. + + * @param {object|string|jQuery} [options] options to apply when creating this tooltip - OR - + * The markup, or a jquery object, containing the data for the tooltip + * @config {string|jquery} [container] An element within which the tooltip must be bounded + * @config {bool} [template] a template to use instead of the default. If this property exists and is null, + * then no template will be used. + * @config {string} [closeEvents] A string with one or more comma-separated values that determine when the tooltip + * closes: 'area-click','tooltip-click','image-mouseout' are valid values + * then no template will be used. + * @config {int} [offsetx] the horizontal amount to offset the tooltip + * @config {int} [offsety] the vertical amount to offset the tooltip + * @config {string|object} [css] CSS to apply to the outermost element of the tooltip + */ + m.AreaData.prototype.showToolTip= function(content,options) { + var tooltip, closeOpts, target, tipClosed, template, + ttopts = {}, + ad=this, + md=ad.owner, + areaOpts = ad.effectiveOptions(); + + // copy the options object so we can update it + options = options ? $.extend({},options) : {}; + + content = content || areaOpts.toolTip; + closeOpts = options.closeEvents || areaOpts.toolTipClose || md.options.toolTipClose || 'tooltip-click'; + + template = typeof options.template !== 'undefined' ? + options.template : + md.options.toolTipContainer; + + options.closeEvents = typeof closeOpts === 'string' ? + closeOpts = u.split(closeOpts) : + closeOpts; + + options.fadeDuration = options.fadeDuration || + (md.options.toolTipFade ? + (md.options.fadeDuration || areaOpts.fadeDuration) : 0); + + target = ad.area ? + ad.area : + $.map(ad.areas(), + function(e) { + return e.area; + }); + + if (md.activeToolTipID===ad.areaId) { + return; + } + + md.clearToolTip(); + + md.activeToolTip = tooltip = createToolTip(content, + template, + options.css); + + md.activeToolTipID = ad.areaId; + + tipClosed = function() { + md.clearToolTip(); + }; + + bindToolTipClose(closeOpts,'area-click', 'click', $(md.map), null, tipClosed); + bindToolTipClose(closeOpts,'tooltip-click', 'click', tooltip,null, tipClosed); + bindToolTipClose(closeOpts,'image-mouseout', 'mouseout', $(md.image), function(e) { + return (e.relatedTarget && e.relatedTarget.nodeName!=='AREA' && e.relatedTarget!==ad.area); + }, tipClosed); + + + showToolTip(tooltip, + target, + md.image, + options.container, + template, + options); + + u.ifFunction(md.options.onShowToolTip, ad.area, + { + toolTip: tooltip, + options: ttopts, + areaOptions: areaOpts, + key: ad.key, + selected: ad.isSelected() + }); + + return tooltip; + }; + + + /** + * Parse an object that could be a string, a jquery object, or an object with a "contents" property + * containing html or a jQuery object. + * + * @param {object|string|jQuery} options The parameter to parse + * @return {string|jquery} A string or jquery object + */ + function getHtmlFromOptions(options) { + + // see if any html was passed as either the options object itself, or the content property + + return (options ? + ((typeof options === 'string' || options.jquery) ? + options : + options.content) : + null); + } + + /** + * Activate or remove a tooltip for an area. When this method is called on an area, the + * key parameter doesn't apply and "options" is the first parameter. + * + * When called with no parameters, or "key" is a falsy value, any active tooltip is cleared. + * + * When only a key is provided, the default tooltip for the area is used. + * + * When html is provided, this is used instead of the default tooltip. + * + * When "noTemplate" is true, the default tooltip template will not be used either, meaning only + * the actual html passed will be used. + * + * @param {string|AreaElement} key The area for which to activate a tooltip, or a DOM element. + * + * @param {object|string|jquery} [options] options to apply when creating this tooltip - OR - + * The markup, or a jquery object, containing the data for the tooltip + * @config {string|jQuery} [content] the inner content of the tooltip; the tooltip text or HTML + * @config {Element|jQuery} [container] the inner content of the tooltip; the tooltip text or HTML + * @config {bool} [template] a template to use instead of the default. If this property exists and is null, + * then no template will be used. + * @config {int} [offsetx] the horizontal amount to offset the tooltip. + * @config {int} [offsety] the vertical amount to offset the tooltip. + * @config {string|object} [css] CSS to apply to the outermost element of the tooltip + * @config {string|object} [css] CSS to apply to the outermost element of the tooltip + * @config {bool} [fadeDuration] When non-zero, the duration in milliseconds of a fade-in effect for the tooltip. + * @return {jQuery} The jQuery object + */ + + m.impl.tooltip = function (key,options) { + return (new m.Method(this, + function mapData() { + var tooltip, target, md=this; + if (!key) { + md.clearToolTip(); + } else { + target=$(key); + if (md.activeToolTipID ===target[0]) { + return; + } + md.clearToolTip(); + + md.activeToolTip = tooltip = createToolTip(getHtmlFromOptions(options), + options.template || md.options.toolTipContainer, + options.css); + md.activeToolTipID = target[0]; + + bindToolTipClose(['tooltip-click'],'tooltip-click', 'click', tooltip, null, function() { + md.clearToolTip(); + }); + + md.activeToolTip = tooltip = showToolTip(tooltip, + target, + md.image, + options.container, + options); + } + }, + function areaData() { + if ($.isPlainObject(key) && !options) { + options = key; + } + + this.showToolTip(getHtmlFromOptions(options),options); + }, + { + name: 'tooltip', + args: arguments, + key: key + } + )).go(); + }; +} (jQuery)); diff --git a/web/static/js/jquery.js b/web/static/js/jquery.js new file mode 100644 index 0000000..f7f4227 --- /dev/null +++ b/web/static/js/jquery.js @@ -0,0 +1,9111 @@ +/*! + * jQuery JavaScript Library v2.1.0 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-01-23T21:10Z + */ + +(function( global, factory ) { + + if ( typeof module === "object" && typeof module.exports === "object" ) { + // For CommonJS and CommonJS-like environments where a proper window is present, + // execute the factory and get jQuery + // For environments that do not inherently posses a window with a document + // (such as Node.js), expose a jQuery-making factory as module.exports + // This accentuates the need for the creation of a real window + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Can't do this because several apps including ASP.NET trace +// the stack via arguments.caller.callee and Firefox dies if +// you try to trace through "use strict" call chains. (#13335) +// Support: Firefox 18+ +// + +var arr = []; + +var slice = arr.slice; + +var concat = arr.concat; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var trim = "".trim; + +var support = {}; + + + +var + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + + version = "2.1.0", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // 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 + ( num < 0 ? this[ num + this.length ] : this[ num ] ) : + + // Return just the object + slice.call( this ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // 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 ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); + }, + + 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: arr.sort, + splice: arr.splice +}; + +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; + + // skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // 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 ( i === length ) { + 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({ + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + // 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, + + isWindow: function( obj ) { + return obj != null && obj === obj.window; + }, + + isNumeric: function( obj ) { + // parseFloat NaNs numeric-cast false positives (null|true|false|"") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + return obj - parseFloat( obj ) >= 0; + }, + + isPlainObject: function( obj ) { + // Not plain objects: + // - Any object or value whose internal [[Class]] property is not "[object Object]" + // - DOM nodes + // - window + if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + // Support: Firefox <20 + // The try/catch suppresses exceptions thrown when attempting to access + // the "constructor" property of certain host objects, ie. |window.location| + // https://bugzilla.mozilla.org/show_bug.cgi?id=814622 + try { + if ( obj.constructor && + !hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) { + return false; + } + } catch ( e ) { + return false; + } + + // If the function hasn't returned already, we're confident that + // |obj| is a plain object, created by {} or constructed with new Object + return true; + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + type: function( obj ) { + if ( obj == null ) { + return obj + ""; + } + // Support: Android < 4.0, iOS < 6 (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call(obj) ] || "object" : + typeof obj; + }, + + // Evaluates a script in a global context + globalEval: function( code ) { + var script, + indirect = eval; + + code = jQuery.trim( code ); + + if ( code ) { + // If the code includes a valid, prologue position + // strict mode pragma, execute code by injecting a + // script tag into the document. + if ( code.indexOf("use strict") === 1 ) { + script = document.createElement("script"); + script.text = code; + document.head.appendChild( script ).parentNode.removeChild( script ); + } else { + // Otherwise, avoid the DOM node creation, insertion + // and removal by using an indirect global eval + indirect( code ); + } + } + }, + + // 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.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + trim: function( text ) { + return text == null ? "" : trim.call( text ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return 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 ) { + var tmp, args, proxy; + + if ( typeof context === "string" ) { + 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 + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, 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 || jQuery.guid++; + + return proxy; + }, + + now: Date.now, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +function isArraylike( obj ) { + var length = obj.length, + type = jQuery.type( obj ); + + if ( type === "function" || jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v1.10.16 + * http://sizzlejs.com/ + * + * Copyright 2013 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-01-13 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + compile, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + -(new Date()), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // General-purpose constants + strundefined = typeof undefined, + MAX_NEGATIVE = 1 << 31, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf if we can't use a native one + indexOf = arr.indexOf || function( elem ) { + var i = 0, + len = this.length; + for ( ; i < len; i++ ) { + if ( this[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + + "*(?:([*^$|!~]?=)" + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", + + // Prefer arguments quoted, + // then not containing pseudos/brackets, + // then attribute selectors/non-parenthetical expressions, + // then anything else + // These preferences are here to reduce the number of selectors + // needing tokenize in the PSEUDO preFilter + pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + rescape = /'|\\/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }; + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { + return []; + } + + if ( documentIsHTML && !seed ) { + + // Shortcuts + if ( (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document (jQuery #6963) + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // QSA path + if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + nid = old = expando; + newContext = context; + newSelector = nodeType === 9 && selector; + + // 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 + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return !!fn( div ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( div.parentNode ) { + div.parentNode.removeChild( div ); + } + // release memory in IE + div = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = attrs.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + ( ~b.sourceIndex || MAX_NEGATIVE ) - + ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== strundefined && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = 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).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, + doc = node ? node.ownerDocument || node : preferredDoc, + parent = doc.defaultView; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + + // Support tests + documentIsHTML = !isXML( doc ); + + // Support: IE>8 + // If iframe document is assigned to "document" variable and if iframe has been reloaded, + // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936 + // IE6-8 do not support the defaultView property so parent will be undefined + if ( parent && parent !== parent.top ) { + // IE11 does not have attachEvent, so all must suffer + if ( parent.addEventListener ) { + parent.addEventListener( "unload", function() { + setDocument(); + }, false ); + } else if ( parent.attachEvent ) { + parent.attachEvent( "onunload", function() { + setDocument(); + }); + } + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans) + support.attributes = assert(function( div ) { + div.className = "i"; + return !div.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Check if getElementsByClassName can be trusted + support.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) { + div.innerHTML = "
"; + + // Support: Safari<4 + // Catch class over-caching + div.firstChild.className = "i"; + // Support: Opera<10 + // Catch gEBCN failure to find non-leading classes + return div.getElementsByClassName("i").length === 2; + }); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( div ) { + docElem.appendChild( div ).id = expando; + return !doc.getElementsByName || !doc.getElementsByName( expando ).length; + }); + + // ID find and filter + if ( support.getById ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && documentIsHTML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + // Support: IE6/7 + // getElementById is not reliable as a find shortcut + delete Expr.find["ID"]; + + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== strundefined ) { + return context.getElementsByTagName( tag ); + } + } : + function( tag, context ) { + var elem, + tmp = [], + i = 0, + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See http://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + div.innerHTML = ""; + + // Support: IE8, Opera 10-12 + // Nothing should be selected when empty strings follow ^= or $= or *= + if ( div.querySelectorAll("[t^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + }); + + assert(function( div ) { + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = doc.createElement("input"); + input.setAttribute( "type", "hidden" ); + div.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( div.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return doc; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch(e) {} + } + + return Sizzle( expr, document, null, [elem] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[5] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] && match[4] !== undefined ) { + match[2] = match[4]; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "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; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +} + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (oldCache = outerCache[ dir ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + outerCache[ dir ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context !== document && context; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !group ) { + group = tokenize( selector ); + } + i = group.length; + while ( i-- ) { + cached = matcherFromTokens( group[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + } + return cached; +}; + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function select( selector, context, results, seed ) { + var i, tokens, token, type, find, + match = tokenize( selector ); + + if ( !seed ) { + // Try to minimize operations if there is only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + support.getById && context.nodeType === 9 && documentIsHTML && + Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + } + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + } + + // Compile and execute a filtering function + // Provide `match` to avoid retokenization if we modified the selector above + compile( selector, match )( + seed, + context, + !documentIsHTML, + results, + rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +} + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome<14 +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( div1 ) { + // Should return 1, but returns 4 (following) + return div1.compareDocumentPosition( document.createElement("div") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( div ) { + div.innerHTML = ""; + return div.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( div ) { + div.innerHTML = ""; + div.firstChild.setAttribute( "value", "" ); + return div.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( div ) { + return div.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + + +var rneedsContext = jQuery.expr.match.needsContext; + +var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/); + + + +var risSimple = /^.[^:#\[\.,]*$/; + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + /* jshint -W018 */ + return !!qualifier.call( elem, i, elem ) !== not; + }); + + } + + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + }); + + } + + if ( typeof qualifier === "string" ) { + if ( risSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + qualifier = jQuery.filter( qualifier, elements ); + } + + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) >= 0 ) !== not; + }); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 && elem.nodeType === 1 ? + jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : + jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + })); +}; + +jQuery.fn.extend({ + find: function( selector ) { + var i, + len = this.length, + ret = [], + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = this.selector ? this.selector + " " + selector : selector; + return ret; + }, + filter: function( selector ) { + return this.pushStack( winnow(this, selector || [], false) ); + }, + not: function( selector ) { + return this.pushStack( winnow(this, selector || [], true) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +}); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + init = jQuery.fn.init = function( selector, context ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[0] === "<" && selector[ 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 = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // 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 ) { + // 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: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return typeof rootjQuery.ready !== "undefined" ? + rootjQuery.ready( selector ) : + // Execute immediately if ready is not present + selector( jQuery ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.extend({ + dir: function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; + }, + + sibling: function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; + } +}); + +jQuery.fn.extend({ + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter(function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { + // Always skip document fragments + if ( cur.nodeType < 11 && (pos ? + pos.index(cur) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) ) { + + matched.push( cur ); + break; + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched ); + }, + + // 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.first().prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.unique( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +function sibling( cur, dir ) { + while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {} + return cur; +} + +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 sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "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 elem.contentDocument || jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.unique( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +}); +var rnotwhite = (/\S+/g); + + + +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * 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( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // 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, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( 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 + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + firingLength = 0; + 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 ) { + 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 ( list && ( !fired || stack ) ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( firing ) { + stack.push( args ); + } else { + fire( 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 !!fired; + } + }; + + return self; +}; + + +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); + + +// The deferred used on DOM ready +var readyList; + +jQuery.fn.ready = function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; +}; + +jQuery.extend({ + // 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 ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // 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.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger("ready").off("ready"); + } + } +}); + +/** + * The ready event handler and self cleanup method + */ +function completed() { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + jQuery.ready(); +} + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + } else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed, false ); + } + } + return readyList.promise( obj ); +}; + +// Kick off the DOM ready check even if the user does not +jQuery.ready.promise(); + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + len ? fn( elems[0], key ) : emptyGet; +}; + + +/** + * Determines whether an object can have data + */ +jQuery.acceptData = function( owner ) { + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + /* jshint -W018 */ + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + +function Data() { + // Support: Android < 4, + // Old WebKit does not have Object.preventExtensions/freeze method, + // return new empty object instead with no [[set]] accessor + Object.defineProperty( this.cache = {}, 0, { + get: function() { + return {}; + } + }); + + this.expando = jQuery.expando + Math.random(); +} + +Data.uid = 1; +Data.accepts = jQuery.acceptData; + +Data.prototype = { + key: function( owner ) { + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return the key for a frozen object. + if ( !Data.accepts( owner ) ) { + return 0; + } + + var descriptor = {}, + // Check if the owner object already has a cache key + unlock = owner[ this.expando ]; + + // If not, create one + if ( !unlock ) { + unlock = Data.uid++; + + // Secure it in a non-enumerable, non-writable property + try { + descriptor[ this.expando ] = { value: unlock }; + Object.defineProperties( owner, descriptor ); + + // Support: Android < 4 + // Fallback to a less secure definition + } catch ( e ) { + descriptor[ this.expando ] = unlock; + jQuery.extend( owner, descriptor ); + } + } + + // Ensure the cache object + if ( !this.cache[ unlock ] ) { + this.cache[ unlock ] = {}; + } + + return unlock; + }, + set: function( owner, data, value ) { + var prop, + // There may be an unlock assigned to this node, + // if there is no entry for this "owner", create one inline + // and set the unlock as though an owner entry had always existed + unlock = this.key( owner ), + cache = this.cache[ unlock ]; + + // Handle: [ owner, key, value ] args + if ( typeof data === "string" ) { + cache[ data ] = value; + + // Handle: [ owner, { properties } ] args + } else { + // Fresh assignments by object are shallow copied + if ( jQuery.isEmptyObject( cache ) ) { + jQuery.extend( this.cache[ unlock ], data ); + // Otherwise, copy the properties one-by-one to the cache object + } else { + for ( prop in data ) { + cache[ prop ] = data[ prop ]; + } + } + } + return cache; + }, + get: function( owner, key ) { + // Either a valid cache is found, or will be created. + // New caches will be created and the unlock returned, + // allowing direct access to the newly created + // empty data object. A valid owner object must be provided. + var cache = this.cache[ this.key( owner ) ]; + + return key === undefined ? + cache : cache[ key ]; + }, + access: function( owner, key, value ) { + var stored; + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ((key && typeof key === "string") && value === undefined) ) { + + stored = this.get( owner, key ); + + return stored !== undefined ? + stored : this.get( owner, jQuery.camelCase(key) ); + } + + // [*]When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, name, camel, + unlock = this.key( owner ), + cache = this.cache[ unlock ]; + + if ( key === undefined ) { + this.cache[ unlock ] = {}; + + } else { + // Support array or space separated string of keys + if ( jQuery.isArray( key ) ) { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = key.concat( key.map( jQuery.camelCase ) ); + } else { + camel = jQuery.camelCase( key ); + // Try the string as a key before any manipulation + if ( key in cache ) { + name = [ key, camel ]; + } else { + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + name = camel; + name = name in cache ? + [ name ] : ( name.match( rnotwhite ) || [] ); + } + } + + i = name.length; + while ( i-- ) { + delete cache[ name[ i ] ]; + } + } + }, + hasData: function( owner ) { + return !jQuery.isEmptyObject( + this.cache[ owner[ this.expando ] ] || {} + ); + }, + discard: function( owner ) { + if ( owner[ this.expando ] ) { + delete this.cache[ owner[ this.expando ] ]; + } + } +}; +var data_priv = new Data(); + +var data_user = new Data(); + + + +/* + Implementation Summary + + 1. Enforce API surface and semantic compatibility with 1.9.x branch + 2. Improve the module's maintainability by reducing the storage + paths to a single mechanism. + 3. Use the same single mechanism to support "private" and "user" data. + 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) + 5. Avoid exposing implementation details on user objects (eg. expando properties) + 6. Provide a clear path for implementation upgrade to WeakMap in 2014 +*/ +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /([A-Z])/g; + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + 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 : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + data_user.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend({ + hasData: function( elem ) { + return data_user.hasData( elem ) || data_priv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return data_user.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + data_user.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to data_priv methods, these can be deprecated. + _data: function( elem, name, data ) { + return data_priv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + data_priv.remove( elem, name ); + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = data_user.get( elem ); + + if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + name = attrs[ i ].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.slice(5) ); + dataAttr( elem, name, data[ name ] ); + } + } + data_priv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + data_user.set( this, key ); + }); + } + + return access( this, function( value ) { + var data, + camelKey = jQuery.camelCase( key ); + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + // Attempt to get data from the cache + // with the key as-is + data = data_user.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to get data from the cache + // with the key camelized + data = data_user.get( elem, camelKey ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, camelKey, undefined ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each(function() { + // First, attempt to store a copy or reference of any + // data that might've been store with a camelCased key. + var data = data_user.get( this, camelKey ); + + // For HTML5 data-* attribute interop, we have to + // store property names with dashes in a camelCase form. + // This might not apply to all properties...* + data_user.set( this, camelKey, value ); + + // *... In the case of properties that might _actually_ + // have dashes, we need to also store a copy of that + // unchanged property. + if ( key.indexOf("-") !== -1 && data !== undefined ) { + data_user.set( this, key, value ); + } + }); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each(function() { + data_user.remove( this, key ); + }); + } +}); + + +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = data_priv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray( data ) ) { + queue = data_priv.access( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return data_priv.get( elem, key ) || data_priv.access( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + data_priv.remove( elem, [ type + "queue", key ] ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + 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, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = data_priv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source; + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var isHidden = function( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); + }; + +var rcheckableType = (/^(?:checkbox|radio)$/i); + + + +(function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ); + + // #11217 - WebKit loses check when the name is after the checked attribute + div.innerHTML = ""; + + // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3 + // old WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Make sure textarea (and checkbox) defaultValue is properly cloned + // Support: IE9-IE11+ + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; +})(); +var strundefined = typeof undefined; + + + +support.focusinBubbles = "onfocusin" in window; + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = data_priv.get( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // 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 + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = 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 !== strundefined && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // 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: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + } + } + } + + 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; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = data_priv.hasData( elem ) && data_priv.get( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // 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; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.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 ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + data_priv.remove( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // 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 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // 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 ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && 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) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && jQuery.acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === 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( eventPath.pop(), data ) === false) && + jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + 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 ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, j, ret, matched, handleObj, + handlerQueue = [], + args = slice.call( arguments ), + handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, matches, sel, handleObj, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.disabled !== true || event.type !== "click" ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "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 offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button; + + // 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 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, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: Cordova 2.5 (WebKit) (#13255) + // All events should have a target; Cordova deviceready doesn't + if ( !event.target ) { + event.target = document; + } + + // Support: Safari 6.0+, Chrome < 28 + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + this.focus(); + return false; + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + 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(); + } + } +}; + +jQuery.removeEvent = function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } +}; + +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 || + // Support: Android < 4.0 + src.defaultPrevented === undefined && + 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; +}; + +// 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 = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && e.preventDefault ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && e.stopPropagation ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +// Support: Chrome 15+ +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // 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; + } + }; +}); + +// Create "bubbling" focus and blur events +// Support: Firefox, Chrome, Safari +if ( !support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = data_priv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + data_priv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = data_priv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + data_priv.remove( doc, fix ); + + } else { + data_priv.access( doc, fix, attaches ); + } + } + }; + }); +} + +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 = 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( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( 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 ); + }); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); + + +var + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rhtml = /<|&#?\w+;/, + rnoInnerhtml = /<(?:script|style|link)/i, + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptType = /^$|\/(?:java|ecma)script/i, + rscriptTypeMasked = /^true\/(.*)/, + rcleanScript = /^\s*\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + + // Support: IE 9 + option: [ 1, "" ], + + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] + }; + +// Support: IE 9 +wrapMap.optgroup = wrapMap.option; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: 1.x compatibility +// Manipulating tables requires a tbody +function manipulationTarget( elem, content ) { + return jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ? + + elem.getElementsByTagName("tbody")[0] || + elem.appendChild( elem.ownerDocument.createElement("tbody") ) : + elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + + if ( match ) { + elem.type = match[ 1 ]; + } else { + elem.removeAttribute("type"); + } + + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + data_priv.set( + elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" ) + ); + } +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( data_priv.hasData( src ) ) { + pdataOld = data_priv.access( src ); + pdataCur = data_priv.set( dest, pdataOld ); + events = pdataOld.events; + + if ( events ) { + delete pdataCur.handle; + pdataCur.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( data_user.hasData( src ) ) { + udataOld = data_user.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + data_user.set( dest, udataCur ); + } +} + +function getAll( context, tag ) { + var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) : + context.querySelectorAll ? context.querySelectorAll( tag || "*" ) : + []; + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], ret ) : + ret; +} + +// Support: IE >= 9 +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = jQuery.contains( elem.ownerDocument, elem ); + + // Support: IE >= 9 + // Fix Cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var elem, tmp, tag, wrap, contains, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + // Support: QtWebKit + // jQuery.merge because push.apply(_, arraylike) throws + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: QtWebKit + // jQuery.merge because push.apply(_, arraylike) throws + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Fixes #12346 + // Support: Webkit, IE + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; + }, + + cleanData: function( elems ) { + var data, elem, events, type, key, j, + special = jQuery.event.special, + i = 0; + + for ( ; (elem = elems[ i ]) !== undefined; i++ ) { + if ( jQuery.acceptData( elem ) ) { + key = elem[ data_priv.expando ]; + + if ( key && (data = data_priv.cache[ key ]) ) { + events = Object.keys( data.events || {} ); + if ( events.length ) { + for ( j = 0; (type = events[j]) !== undefined; j++ ) { + 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 ); + } + } + } + if ( data_priv.cache[ key ] ) { + // Discard any remaining `private` data + delete data_priv.cache[ key ]; + } + } + } + // Discard any remaining `user` data + delete data_user.cache[ elem[ data_user.expando ] ]; + } + } +}); + +jQuery.fn.extend({ + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each(function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + }); + }, null, value, arguments.length ); + }, + + append: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + remove: function( selector, keepData /* Internal Use Only */ ) { + var elem, + elems = selector ? jQuery.filter( selector, this ) : this, + i = 0; + + for ( ; (elem = elems[i]) != null; i++ ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + 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 ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var arg = arguments[ 0 ]; + + // Make the changes, replacing each context element with the new content + this.domManip( arguments, function( elem ) { + arg = this.parentNode; + + jQuery.cleanData( getAll( this ) ); + + if ( arg ) { + arg.replaceChild( elem, this ); + } + }); + + // Force removal if there was no new content (e.g., from empty arguments) + return arg && (arg.length || arg.nodeType) ? this : this.remove(); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, callback ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[ 0 ], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + self.domManip( args, callback ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + // Support: QtWebKit + // jQuery.merge because push.apply(_, arraylike) throws + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( this[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl ) { + jQuery._evalUrl( node.src ); + } + } else { + jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) ); + } + } + } + } + } + } + + return this; + } +}); + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: QtWebKit + // .get() because push.apply(_, arraylike) throws + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + + +var iframe, + elemdisplay = {}; + +/** + * Retrieve the actual display of a element + * @param {String} name nodeName of the element + * @param {Object} doc Document object + */ +// Called only from within defaultDisplay +function actualDisplay( name, doc ) { + var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), + + // getDefaultComputedStyle might be reliably used only on attached element + display = window.getDefaultComputedStyle ? + + // Use of this method is a temporary fix (more like optmization) until something better comes along, + // since it was removed from specification and supported only in FF + window.getDefaultComputedStyle( elem[ 0 ] ).display : jQuery.css( elem[ 0 ], "display" ); + + // We don't have any data stored on the element, + // so use "detach" method as fast way to get rid of the element + elem.detach(); + + return display; +} + +/** + * Try to determine the default display value of an element + * @param {String} nodeName + */ +function defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + + // Use the already-created iframe if possible + iframe = (iframe || jQuery( "