text: /*!
   * jQuery JavaScript Library v3.2.1
   * https://jquery.com/
   *
   * Includes Sizzle.js
   * https://sizzlejs.com/
   *
   * Copyright JS Foundation and other contributors
   * Released under the MIT license
   * https://jquery.org/license
   *
   * Date: 2017-03-20T18:59Z
   */
  
  ( function( global, factory ) {
  
  	"use strict";
  
  	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 have a `window` with a `document`
  		// (such as Node.js), expose a 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 ) {
  
  // Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
  // throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
  // arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
  // enough that all such attempts are guarded in a try block.
  "use strict";
  
  var arr = [];
  
  var document = window.document;
  
  var getProto = Object.getPrototypeOf;
  
  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 fnToString = hasOwn.toString;
  
  var ObjectFunctionString = fnToString.call( Object );
  
  var support = {};
  
  
  
  	function DOMEval( code, doc ) {
  		doc = doc || document;
  
  		var script = doc.createElement( "script" );
  
  		script.text = code;
  		doc.head.appendChild( script ).parentNode.removeChild( script );
  	}
  /* global Symbol */
  // Defining this global in .eslintrc.json would create a danger of using the global
  // unguarded in another place, it seems safer to define global only for this module
  
  
  
  var
  	version = "3.2.1",
  
  	// 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 );
  	},
  
  	// Support: Android <=4.0 only
  	// Make sure we trim BOM and NBSP
  	rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
  
  	// Matches dashed string for camelizing
  	rmsPrefix = /^-ms-/,
  	rdashAlpha = /-([a-z])/g,
  
  	// 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,
  
  	// 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 all the elements in a clean array
  		if ( num == null ) {
  			return slice.call( this );
  		}
  
  		// Return just the one element from the set
  		return num < 0 ? this[ num + this.length ] : this[ num ];
  	},
  
  	// 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;
  
  		// Return the newly-formed element set
  		return ret;
  	},
  
  	// Execute a callback for every element in the matched set.
  	each: function( callback ) {
  		return jQuery.each( this, callback );
  	},
  
  	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();
  	},
  
  	// 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 = Array.isArray( copy ) ) ) ) {
  
  					if ( copyIsArray ) {
  						copyIsArray = false;
  						clone = src && Array.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() {},
  
  	isFunction: function( obj ) {
  		return jQuery.type( obj ) === "function";
  	},
  
  	isWindow: function( obj ) {
  		return obj != null && obj === obj.window;
  	},
  
  	isNumeric: function( obj ) {
  
  		// As of jQuery 3.0, isNumeric is limited to
  		// strings and numbers (primitives or objects)
  		// that can be coerced to finite numbers (gh-2662)
  		var type = jQuery.type( obj );
  		return ( type === "number" || type === "string" ) &&
  
  			// parseFloat NaNs numeric-cast false positives ("")
  			// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
  			// subtraction forces infinities to NaN
  			!isNaN( obj - parseFloat( obj ) );
  	},
  
  	isPlainObject: function( obj ) {
  		var proto, Ctor;
  
  		// Detect obvious negatives
  		// Use toString instead of jQuery.type to catch host objects
  		if ( !obj || toString.call( obj ) !== "[object Object]" ) {
  			return false;
  		}
  
  		proto = getProto( obj );
  
  		// Objects with no prototype (e.g., `Object.create( null )`) are plain
  		if ( !proto ) {
  			return true;
  		}
  
  		// Objects with prototype are plain iff they were constructed by a global Object function
  		Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
  		return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
  	},
  
  	isEmptyObject: function( obj ) {
  
  		/* eslint-disable no-unused-vars */
  		// See https://github.com/eslint/eslint/issues/6125
  		var name;
  
  		for ( name in obj ) {
  			return false;
  		}
  		return true;
  	},
  
  	type: function( obj ) {
  		if ( obj == null ) {
  			return obj + "";
  		}
  
  		// Support: Android <=2.3 only (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 ) {
  		DOMEval( code );
  	},
  
  	// Convert dashed to camelCase; used by the css and data modules
  	// Support: IE <=9 - 11, Edge 12 - 13
  	// Microsoft forgot to hump their vendor prefix (#9572)
  	camelCase: function( string ) {
  		return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
  	},
  
  	each: function( obj, callback ) {
  		var length, i = 0;
  
  		if ( isArrayLike( obj ) ) {
  			length = obj.length;
  			for ( ; i < length; i++ ) {
  				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
  					break;
  				}
  			}
  		} else {
  			for ( i in obj ) {
  				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
  					break;
  				}
  			}
  		}
  
  		return obj;
  	},
  
  	// Support: Android <=4.0 only
  	trim: function( text ) {
  		return text == null ?
  			"" :
  			( text + "" ).replace( rtrim, "" );
  	},
  
  	// 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 );
  	},
  
  	// Support: Android <=4.0 only, PhantomJS 1 only
  	// push.apply(_, arraylike) throws on ancient WebKit
  	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 length, value,
  			i = 0,
  			ret = [];
  
  		// Go through the array, translating each of the items to their new values
  		if ( isArrayLike( elems ) ) {
  			length = elems.length;
  			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
  } );
  
  if ( typeof Symbol === "function" ) {
  	jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];
  }
  
  // Populate the class2type map
  jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
  function( i, name ) {
  	class2type[ "[object " + name + "]" ] = name.toLowerCase();
  } );
  
  function isArrayLike( obj ) {
  
  	// Support: real iOS 8.2 only (not reproducible in simulator)
  	// `in` check used to prevent JIT error (gh-2145)
  	// hasOwn isn't used here due to false negatives
  	// regarding Nodelist length in IE
  	var length = !!obj && "length" in obj && obj.length,
  		type = jQuery.type( obj );
  
  	if ( type === "function" || jQuery.isWindow( obj ) ) {
  		return false;
  	}
  
  	return type === "array" || length === 0 ||
  		typeof length === "number" && length > 0 && ( length - 1 ) in obj;
  }
  var Sizzle =
  /*!
   * Sizzle CSS Selector Engine v2.3.3
   * https://sizzlejs.com/
   *
   * Copyright jQuery Foundation and other contributors
   * Released under the MIT license
   * http://jquery.org/license
   *
   * Date: 2016-08-08
   */
  (function( window ) {
  
  var i,
  	support,
  	Expr,
  	getText,
  	isXML,
  	tokenize,
  	compile,
  	select,
  	outermostContext,
  	sortInput,
  	hasDuplicate,
  
  	// Local document vars
  	setDocument,
  	document,
  	docElem,
  	documentIsHTML,
  	rbuggyQSA,
  	rbuggyMatches,
  	matches,
  	contains,
  
  	// Instance-specific data
  	expando = "sizzle" + 1 * 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;
  	},
  
  	// Instance methods
  	hasOwn = ({}).hasOwnProperty,
  	arr = [],
  	pop = arr.pop,
  	push_native = arr.push,
  	push = arr.push,
  	slice = arr.slice,
  	// Use a stripped-down indexOf as it's faster than native
  	// https://jsperf.com/thor-indexof-vs-for/5
  	indexOf = function( list, elem ) {
  		var i = 0,
  			len = list.length;
  		for ( ; i < len; i++ ) {
  			if ( list[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
  
  	// http://www.w3.org/TR/css3-selectors/#whitespace
  	whitespace = "[\\x20\\t\\r\\n\\f]",
  
  	// http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
  	identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+",
  
  	// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
  	attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
  		// Operator (capture 2)
  		"*([*^$|!~]?=)" + whitespace +
  		// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
  		"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
  		"*\\]",
  
  	pseudos = ":(" + identifier + ")(?:\\((" +
  		// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
  		// 1. quoted (capture 3; capture 4 or capture 5)
  		"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
  		// 2. simple (capture 6)
  		"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
  		// 3. anything else (capture 2)
  		".*" +
  		")\\)|)",
  
  	// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
  	rwhitespace = new RegExp( whitespace + "+", "g" ),
  	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( "^#(" + identifier + ")" ),
  		"CLASS": new RegExp( "^\\.(" + identifier + ")" ),
  		"TAG": new RegExp( "^(" + identifier + "|[*])" ),
  		"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 = /[+~]/,
  
  	// 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<24
  		// 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 );
  	},
  
  	// CSS string/identifier serialization
  	// https://drafts.csswg.org/cssom/#common-serializing-idioms
  	rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,
  	fcssescape = function( ch, asCodePoint ) {
  		if ( asCodePoint ) {
  
  			// U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
  			if ( ch === "\0" ) {
  				return "\uFFFD";
  			}
  
  			// Control characters and (dependent upon position) numbers get escaped as code points
  			return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
  		}
  
  		// Other potentially-special ASCII characters get backslash-escaped
  		return "\\" + ch;
  	},
  
  	// Used for iframes
  	// See setDocument()
  	// Removing the function wrapper causes a "Permission Denied"
  	// error in IE
  	unloadHandler = function() {
  		setDocument();
  	},
  
  	disabledAncestor = addCombinator(
  		function( elem ) {
  			return elem.disabled === true && ("form" in elem || "label" in elem);
  		},
  		{ dir: "parentNode", next: "legend" }
  	);
  
  // 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 m, i, elem, nid, match, groups, newSelector,
  		newContext = context && context.ownerDocument,
  
  		// nodeType defaults to 9, since context defaults to document
  		nodeType = context ? context.nodeType : 9;
  
  	results = results || [];
  
  	// Return early from calls with invalid selector or context
  	if ( typeof selector !== "string" || !selector ||
  		nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
  
  		return results;
  	}
  
  	// Try to shortcut find operations (as opposed to filters) in HTML documents
  	if ( !seed ) {
  
  		if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
  			setDocument( context );
  		}
  		context = context || document;
  
  		if ( documentIsHTML ) {
  
  			// If the selector is sufficiently simple, try using a "get*By*" DOM method
  			// (excepting DocumentFragment context, where the methods don't exist)
  			if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
  
  				// ID selector
  				if ( (m = match[1]) ) {
  
  					// Document context
  					if ( nodeType === 9 ) {
  						if ( (elem = context.getElementById( m )) ) {
  
  							// Support: IE, Opera, Webkit
  							// TODO: identify versions
  							// getElementById can match elements by name instead of ID
  							if ( elem.id === m ) {
  								results.push( elem );
  								return results;
  							}
  						} else {
  							return results;
  						}
  
  					// Element context
  					} else {
  
  						// Support: IE, Opera, Webkit
  						// TODO: identify versions
  						// getElementById can match elements by name instead of ID
  						if ( newContext && (elem = newContext.getElementById( m )) &&
  							contains( context, elem ) &&
  							elem.id === m ) {
  
  							results.push( elem );
  							return results;
  						}
  					}
  
  				// Type selector
  				} else if ( match[2] ) {
  					push.apply( results, context.getElementsByTagName( selector ) );
  					return results;
  
  				// Class selector
  				} else if ( (m = match[3]) && support.getElementsByClassName &&
  					context.getElementsByClassName ) {
  
  					push.apply( results, context.getElementsByClassName( m ) );
  					return results;
  				}
  			}
  
  			// Take advantage of querySelectorAll
  			if ( support.qsa &&
  				!compilerCache[ selector + " " ] &&
  				(!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
  
  				if ( nodeType !== 1 ) {
  					newContext = context;
  					newSelector = selector;
  
  				// qSA looks outside Element context, which is not what we want
  				// Thanks to Andrew Dupont for this workaround technique
  				// Support: IE <=8
  				// Exclude object elements
  				} else if ( context.nodeName.toLowerCase() !== "object" ) {
  
  					// Capture the context ID, setting it first if necessary
  					if ( (nid = context.getAttribute( "id" )) ) {
  						nid = nid.replace( rcssescape, fcssescape );
  					} else {
  						context.setAttribute( "id", (nid = expando) );
  					}
  
  					// Prefix every selector in the list
  					groups = tokenize( selector );
  					i = groups.length;
  					while ( i-- ) {
  						groups[i] = "#" + nid + " " + toSelector( groups[i] );
  					}
  					newSelector = groups.join( "," );
  
  					// Expand context for sibling selectors
  					newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
  						context;
  				}
  
  				if ( newSelector ) {
  					try {
  						push.apply( results,
  							newContext.querySelectorAll( newSelector )
  						);
  						return results;
  					} catch ( qsaError ) {
  					} finally {
  						if ( nid === expando ) {
  							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 element and returns a boolean result
   */
  function assert( fn ) {
  	var el = document.createElement("fieldset");
  
  	try {
  		return !!fn( el );
  	} catch (e) {
  		return false;
  	} finally {
  		// Remove from its parent by default
  		if ( el.parentNode ) {
  			el.parentNode.removeChild( el );
  		}
  		// release memory in IE
  		el = 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 = arr.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 &&
  			a.sourceIndex - b.sourceIndex;
  
  	// 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 :enabled/:disabled
   * @param {Boolean} disabled true for :disabled; false for :enabled
   */
  function createDisabledPseudo( disabled ) {
  
  	// Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable
  	return function( elem ) {
  
  		// Only certain elements can match :enabled or :disabled
  		// https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
  		// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
  		if ( "form" in elem ) {
  
  			// Check for inherited disabledness on relevant non-disabled elements:
  			// * listed form-associated elements in a disabled fieldset
  			//   https://html.spec.whatwg.org/multipage/forms.html#category-listed
  			//   https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
  			// * option elements in a disabled optgroup
  			//   https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
  			// All such elements have a "form" property.
  			if ( elem.parentNode && elem.disabled === false ) {
  
  				// Option elements defer to a parent optgroup if present
  				if ( "label" in elem ) {
  					if ( "label" in elem.parentNode ) {
  						return elem.parentNode.disabled === disabled;
  					} else {
  						return elem.disabled === disabled;
  					}
  				}
  
  				// Support: IE 6 - 11
  				// Use the isDisabled shortcut property to check for disabled fieldset ancestors
  				return elem.isDisabled === disabled ||
  
  					// Where there is no isDisabled, check manually
  					/* jshint -W018 */
  					elem.isDisabled !== !disabled &&
  						disabledAncestor( elem ) === disabled;
  			}
  
  			return elem.disabled === disabled;
  
  		// Try to winnow out elements that can't be disabled before trusting the disabled property.
  		// Some victims get caught in our net (label, legend, menu, track), but it shouldn't
  		// even exist on them, let alone have a boolean value.
  		} else if ( "label" in elem ) {
  			return elem.disabled === disabled;
  		}
  
  		// Remaining elements are neither :enabled nor :disabled
  		return false;
  	};
  }
  
  /**
   * 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 !== "undefined" && 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, subWindow,
  		doc = node ? node.ownerDocument || node : preferredDoc;
  
  	// Return early if doc is invalid or already selected
  	if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
  		return document;
  	}
  
  	// Update global variables
  	document = doc;
  	docElem = document.documentElement;
  	documentIsHTML = !isXML( document );
  
  	// Support: IE 9-11, Edge
  	// Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
  	if ( preferredDoc !== document &&
  		(subWindow = document.defaultView) && subWindow.top !== subWindow ) {
  
  		// Support: IE 11, Edge
  		if ( subWindow.addEventListener ) {
  			subWindow.addEventListener( "unload", unloadHandler, false );
  
  		// Support: IE 9 - 10 only
  		} else if ( subWindow.attachEvent ) {
  			subWindow.attachEvent( "onunload", unloadHandler );
  		}
  	}
  
  	/* Attributes
  	---------------------------------------------------------------------- */
  
  	// Support: IE<8
  	// Verify that getAttribute really returns attributes and not properties
  	// (excepting IE8 booleans)
  	support.attributes = assert(function( el ) {
  		el.className = "i";
  		return !el.getAttribute("className");
  	});
  
  	/* getElement(s)By*
  	---------------------------------------------------------------------- */
  
  	// Check if getElementsByTagName("*") returns only elements
  	support.getElementsByTagName = assert(function( el ) {
  		el.appendChild( document.createComment("") );
  		return !el.getElementsByTagName("*").length;
  	});
  
  	// Support: IE<9
  	support.getElementsByClassName = rnative.test( document.getElementsByClassName );
  
  	// Support: IE<10
  	// Check if getElementById returns elements by name
  	// The broken getElementById methods don't pick up programmatically-set names,
  	// so use a roundabout getElementsByName test
  	support.getById = assert(function( el ) {
  		docElem.appendChild( el ).id = expando;
  		return !document.getElementsByName || !document.getElementsByName( expando ).length;
  	});
  
  	// ID filter and find
  	if ( support.getById ) {
  		Expr.filter["ID"] = function( id ) {
  			var attrId = id.replace( runescape, funescape );
  			return function( elem ) {
  				return elem.getAttribute("id") === attrId;
  			};
  		};
  		Expr.find["ID"] = function( id, context ) {
  			if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
  				var elem = context.getElementById( id );
  				return elem ? [ elem ] : [];
  			}
  		};
  	} else {
  		Expr.filter["ID"] =  function( id ) {
  			var attrId = id.replace( runescape, funescape );
  			return function( elem ) {
  				var node = typeof elem.getAttributeNode !== "undefined" &&
  					elem.getAttributeNode("id");
  				return node && node.value === attrId;
  			};
  		};
  
  		// Support: IE 6 - 7 only
  		// getElementById is not reliable as a find shortcut
  		Expr.find["ID"] = function( id, context ) {
  			if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
  				var node, i, elems,
  					elem = context.getElementById( id );
  
  				if ( elem ) {
  
  					// Verify the id attribute
  					node = elem.getAttributeNode("id");
  					if ( node && node.value === id ) {
  						return [ elem ];
  					}
  
  					// Fall back on getElementsByName
  					elems = context.getElementsByName( id );
  					i = 0;
  					while ( (elem = elems[i++]) ) {
  						node = elem.getAttributeNode("id");
  						if ( node && node.value === id ) {
  							return [ elem ];
  						}
  					}
  				}
  
  				return [];
  			}
  		};
  	}
  
  	// Tag
  	Expr.find["TAG"] = support.getElementsByTagName ?
  		function( tag, context ) {
  			if ( typeof context.getElementsByTagName !== "undefined" ) {
  				return context.getElementsByTagName( tag );
  
  			// DocumentFragment nodes don't have gEBTN
  			} else if ( support.qsa ) {
  				return context.querySelectorAll( tag );
  			}
  		} :
  
  		function( tag, context ) {
  			var elem,
  				tmp = [],
  				i = 0,
  				// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
  				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 !== "undefined" && 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 https://bugs.jquery.com/ticket/13378
  	rbuggyQSA = [];
  
  	if ( (support.qsa = rnative.test( document.querySelectorAll )) ) {
  		// Build QSA regex
  		// Regex strategy adopted from Diego Perini
  		assert(function( el ) {
  			// 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
  			// https://bugs.jquery.com/ticket/12359
  			docElem.appendChild( el ).innerHTML = "<a id='" + expando + "'></a>" +
  				"<select id='" + expando + "-\r\\' msallowcapture=''>" +
  				"<option selected=''></option></select>";
  
  			// Support: IE8, Opera 11-12.16
  			// Nothing should be selected when empty strings follow ^= or $= or *=
  			// The test attribute must be unknown in Opera but "safe" for WinRT
  			// https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
  			if ( el.querySelectorAll("[msallowcapture^='']").length ) {
  				rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
  			}
  
  			// Support: IE8
  			// Boolean attributes and "value" are not treated correctly
  			if ( !el.querySelectorAll("[selected]").length ) {
  				rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
  			}
  
  			// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
  			if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
  				rbuggyQSA.push("~=");
  			}
  
  			// 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 ( !el.querySelectorAll(":checked").length ) {
  				rbuggyQSA.push(":checked");
  			}
  
  			// Support: Safari 8+, iOS 8+
  			// https://bugs.webkit.org/show_bug.cgi?id=136851
  			// In-page `selector#id sibling-combinator selector` fails
  			if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
  				rbuggyQSA.push(".#.+[+~]");
  			}
  		});
  
  		assert(function( el ) {
  			el.innerHTML = "<a href='' disabled='disabled'></a>" +
  				"<select disabled='disabled'><option/></select>";
  
  			// Support: Windows 8 Native Apps
  			// The type and name attributes are restricted during .innerHTML assignment
  			var input = document.createElement("input");
  			input.setAttribute( "type", "hidden" );
  			el.appendChild( input ).setAttribute( "name", "D" );
  
  			// Support: IE8
  			// Enforce case-sensitivity of name attribute
  			if ( el.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 ( el.querySelectorAll(":enabled").length !== 2 ) {
  				rbuggyQSA.push( ":enabled", ":disabled" );
  			}
  
  			// Support: IE9-11+
  			// IE's :disabled selector does not pick up the children of disabled fieldsets
  			docElem.appendChild( el ).disabled = true;
  			if ( el.querySelectorAll(":disabled").length !== 2 ) {
  				rbuggyQSA.push( ":enabled", ":disabled" );
  			}
  
  			// Opera 10-11 does not throw on post-comma invalid pseudos
  			el.querySelectorAll("*,:x");
  			rbuggyQSA.push(",.*:");
  		});
  	}
  
  	if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
  		docElem.webkitMatchesSelector ||
  		docElem.mozMatchesSelector ||
  		docElem.oMatchesSelector ||
  		docElem.msMatchesSelector) )) ) {
  
  		assert(function( el ) {
  			// Check to see if it's possible to do matchesSelector
  			// on a disconnected node (IE 9)
  			support.disconnectedMatch = matches.call( el, "*" );
  
  			// This should fail with an exception
  			// Gecko does not error, returns false instead
  			matches.call( el, "[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 self-exclusive
  	// 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 === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
  				return -1;
  			}
  			if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
  				return 1;
  			}
  
  			// Maintain original order
  			return sortInput ?
  				( indexOf( sortInput, a ) - indexOf( 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 === document ? -1 :
  				b === document ? 1 :
  				aup ? -1 :
  				bup ? 1 :
  				sortInput ?
  				( indexOf( sortInput, a ) - indexOf( 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 document;
  };
  
  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 &&
  		!compilerCache[ expr + " " ] &&
  		( !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.escape = function( sel ) {
  	return (sel + "").replace( rcssescape, fcssescape );
  };
  
  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[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[6] && match[2];
  
  			if ( matchExpr["CHILD"].test( match[0] ) ) {
  				return null;
  			}
  
  			// Accept quoted arguments as-is
  			if ( match[3] ) {
  				match[2] = match[4] || match[5] || "";
  
  			// 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 !== "undefined" && 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.replace( rwhitespace, " " ) + " " ).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, uniqueCache, outerCache, node, nodeIndex, start,
  						dir = simple !== forward ? "nextSibling" : "previousSibling",
  						parent = elem.parentNode,
  						name = ofType && elem.nodeName.toLowerCase(),
  						useCache = !xml && !ofType,
  						diff = false;
  
  					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
  
  							// ...in a gzip-friendly way
  							node = parent;
  							outerCache = node[ expando ] || (node[ expando ] = {});
  
  							// Support: IE <9 only
  							// Defend against cloned attroperties (jQuery gh-1709)
  							uniqueCache = outerCache[ node.uniqueID ] ||
  								(outerCache[ node.uniqueID ] = {});
  
  							cache = uniqueCache[ type ] || [];
  							nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
  							diff = nodeIndex && 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 ) {
  									uniqueCache[ type ] = [ dirruns, nodeIndex, diff ];
  									break;
  								}
  							}
  
  						} else {
  							// Use previously-cached element index if available
  							if ( useCache ) {
  								// ...in a gzip-friendly way
  								node = elem;
  								outerCache = node[ expando ] || (node[ expando ] = {});
  
  								// Support: IE <9 only
  								// Defend against cloned attroperties (jQuery gh-1709)
  								uniqueCache = outerCache[ node.uniqueID ] ||
  									(outerCache[ node.uniqueID ] = {});
  
  								cache = uniqueCache[ type ] || [];
  								nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
  								diff = nodeIndex;
  							}
  
  							// xml :nth-child(...)
  							// or :nth-last-child(...) or :nth(-last)?-of-type(...)
  							if ( diff === false ) {
  								// 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 ) {
  											outerCache = node[ expando ] || (node[ expando ] = {});
  
  											// Support: IE <9 only
  											// Defend against cloned attroperties (jQuery gh-1709)
  											uniqueCache = outerCache[ node.uniqueID ] ||
  												(outerCache[ node.uniqueID ] = {});
  
  											uniqueCache[ 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( 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 );
  					// Don't keep the element (issue #299)
  					input[0] = null;
  					return !results.pop();
  				};
  		}),
  
  		"has": markFunction(function( selector ) {
  			return function( elem ) {
  				return Sizzle( selector, elem ).length > 0;
  			};
  		}),
  
  		"contains": markFunction(function( text ) {
  			text = text.replace( runescape, funescape );
  			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": createDisabledPseudo( false ),
  		"disabled": createDisabledPseudo( 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();
  
  tokenize = Sizzle.tokenize = function( 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,
  		skip = combinator.next,
  		key = skip || dir,
  		checkNonElements = base && key === "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 );
  				}
  			}
  			return false;
  		} :
  
  		// Check against all ancestor/preceding elements
  		function( elem, context, xml ) {
  			var oldCache, uniqueCache, outerCache,
  				newCache = [ dirruns, doneName ];
  
  			// We can't set arbitrary data on XML nodes, so they don't benefit from combinator 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 ] = {});
  
  						// Support: IE <9 only
  						// Defend against cloned attroperties (jQuery gh-1709)
  						uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {});
  
  						if ( skip && skip === elem.nodeName.toLowerCase() ) {
  							elem = elem[ dir ] || elem;
  						} else if ( (oldCache = uniqueCache[ key ]) &&
  							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
  							uniqueCache[ key ] = newCache;
  
  							// A match means we're done; a fail means we have to keep checking
  							if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
  								return true;
  							}
  						}
  					}
  				}
  			}
  			return false;
  		};
  }
  
  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 multipleContexts( selector, contexts, results ) {
  	var i = 0,
  		len = contexts.length;
  	for ( ; i < len; i++ ) {
  		Sizzle( selector, contexts[i], results );
  	}
  	return results;
  }
  
  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( 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( checkContext, elem ) > -1;
  		}, implicitRelative, true ),
  		matchers = [ function( elem, context, xml ) {
  			var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
  				(checkContext = context).nodeType ?
  					matchContext( elem, context, xml ) :
  					matchAnyContext( elem, context, xml ) );
  			// Avoid hanging onto element (issue #299)
  			checkContext = null;
  			return ret;
  		} ];
  
  	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 || outermost;
  			}
  
  			// Add elements passing elementMatchers directly to results
  			// Support: IE<9, Safari
  			// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
  			for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
  				if ( byElement && elem ) {
  					j = 0;
  					if ( !context && elem.ownerDocument !== document ) {
  						setDocument( elem );
  						xml = !documentIsHTML;
  					}
  					while ( (matcher = elementMatchers[j++]) ) {
  						if ( matcher( elem, context || document, 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 );
  					}
  				}
  			}
  
  			// `i` is now the count of elements visited above, and adding it to `matchedCount`
  			// makes the latter nonnegative.
  			matchedCount += i;
  
  			// Apply set filters to unmatched elements
  			// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
  			// equals `i`), unless we didn't visit _any_ elements in the above loop because we have
  			// no element matchers and no seed.
  			// Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
  			// case, which will result in a "00" `matchedCount` that differs from `i` but is also
  			// numerically zero.
  			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, match /* 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 ( !match ) {
  			match = tokenize( selector );
  		}
  		i = match.length;
  		while ( i-- ) {
  			cached = matcherFromTokens( match[i] );
  			if ( cached[ expando ] ) {
  				setMatchers.push( cached );
  			} else {
  				elementMatchers.push( cached );
  			}
  		}
  
  		// Cache the compiled function
  		cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
  
  		// Save selector and tokenization
  		cached.selector = selector;
  	}
  	return cached;
  };
  
  /**
   * A low-level selection function that works with Sizzle's compiled
   *  selector functions
   * @param {String|Function} selector A selector or a pre-compiled
   *  selector function built with Sizzle.compile
   * @param {Element} context
   * @param {Array} [results]
   * @param {Array} [seed] A set of elements to match against
   */
  select = Sizzle.select = function( selector, context, results, seed ) {
  	var i, tokens, token, type, find,
  		compiled = typeof selector === "function" && selector,
  		match = !seed && tokenize( (selector = compiled.selector || selector) );
  
  	results = results || [];
  
  	// Try to minimize operations if there is only one selector in the list and no seed
  	// (the latter of which guarantees us context)
  	if ( match.length === 1 ) {
  
  		// Reduce context if the leading compound selector is an ID
  		tokens = match[0] = match[0].slice( 0 );
  		if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
  				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;
  
  			// Precompiled matchers will still verify ancestry, so step up a level
  			} else if ( compiled ) {
  				context = context.parentNode;
  			}
  
  			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 if one is not provided
  	// Provide `match` to avoid retokenization if we modified the selector above
  	( compiled || compile( selector, match ) )(
  		seed,
  		context,
  		!documentIsHTML,
  		results,
  		!context || 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-35+
  // 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( el ) {
  	// Should return 1, but returns 4 (following)
  	return el.compareDocumentPosition( document.createElement("fieldset") ) & 1;
  });
  
  // Support: IE<8
  // Prevent attribute/property "interpolation"
  // https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
  if ( !assert(function( el ) {
  	el.innerHTML = "<a href='#'></a>";
  	return el.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( el ) {
  	el.innerHTML = "<input/>";
  	el.firstChild.setAttribute( "value", "" );
  	return el.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( el ) {
  	return el.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;
  
  // Deprecated
  jQuery.expr[ ":" ] = jQuery.expr.pseudos;
  jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;
  jQuery.text = Sizzle.getText;
  jQuery.isXMLDoc = Sizzle.isXML;
  jQuery.contains = Sizzle.contains;
  jQuery.escapeSelector = Sizzle.escape;
  
  
  
  
  var 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;
  };
  
  
  var siblings = function( n, elem ) {
  	var matched = [];
  
  	for ( ; n; n = n.nextSibling ) {
  		if ( n.nodeType === 1 && n !== elem ) {
  			matched.push( n );
  		}
  	}
  
  	return matched;
  };
  
  
  var rneedsContext = jQuery.expr.match.needsContext;
  
  
  
  function nodeName( elem, name ) {
  
    return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
  
  };
  var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
  
  
  
  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 ) {
  			return !!qualifier.call( elem, i, elem ) !== not;
  		} );
  	}
  
  	// Single element
  	if ( qualifier.nodeType ) {
  		return jQuery.grep( elements, function( elem ) {
  			return ( elem === qualifier ) !== not;
  		} );
  	}
  
  	// Arraylike of elements (jQuery, arguments, Array)
  	if ( typeof qualifier !== "string" ) {
  		return jQuery.grep( elements, function( elem ) {
  			return ( indexOf.call( qualifier, elem ) > -1 ) !== not;
  		} );
  	}
  
  	// Simple selector that can be filtered directly, removing non-Elements
  	if ( risSimple.test( qualifier ) ) {
  		return jQuery.filter( qualifier, elements, not );
  	}
  
  	// Complex selector, compare the two sets, removing non-Elements
  	qualifier = jQuery.filter( qualifier, elements );
  	return jQuery.grep( elements, function( elem ) {
  		return ( indexOf.call( qualifier, elem ) > -1 ) !== not && elem.nodeType === 1;
  	} );
  }
  
  jQuery.filter = function( expr, elems, not ) {
  	var elem = elems[ 0 ];
  
  	if ( not ) {
  		expr = ":not(" + expr + ")";
  	}
  
  	if ( elems.length === 1 && elem.nodeType === 1 ) {
  		return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [];
  	}
  
  	return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
  		return elem.nodeType === 1;
  	} ) );
  };
  
  jQuery.fn.extend( {
  	find: function( selector ) {
  		var i, ret,
  			len = this.length,
  			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;
  					}
  				}
  			} ) );
  		}
  
  		ret = this.pushStack( [] );
  
  		for ( i = 0; i < len; i++ ) {
  			jQuery.find( selector, self[ i ], ret );
  		}
  
  		return len > 1 ? jQuery.uniqueSort( ret ) : 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 <tag> to avoid XSS via location.hash (#9521)
  	// Strict HTML recognition (#11290: must start with <)
  	// Shortcut simple #id case for speed
  	rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
  
  	init = jQuery.fn.init = function( selector, context, root ) {
  		var match, elem;
  
  		// HANDLE: $(""), $(null), $(undefined), $(false)
  		if ( !selector ) {
  			return this;
  		}
  
  		// Method init() accepts an alternate rootjQuery
  		// so migrate can support jQuery.sub (gh-2101)
  		root = root || rootjQuery;
  
  		// 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;
  
  					// Option to run 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 ] );
  
  					if ( elem ) {
  
  						// Inject the element directly into the jQuery object
  						this[ 0 ] = elem;
  						this.length = 1;
  					}
  					return this;
  				}
  
  			// HANDLE: $(expr, $(...))
  			} else if ( !context || context.jquery ) {
  				return ( context || root ).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[ 0 ] = selector;
  			this.length = 1;
  			return this;
  
  		// HANDLE: $(function)
  		// Shortcut for document ready
  		} else if ( jQuery.isFunction( selector ) ) {
  			return root.ready !== undefined ?
  				root.ready( selector ) :
  
  				// Execute immediately if ready is not present
  				selector( jQuery );
  		}
  
  		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.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 = [],
  			targets = typeof selectors !== "string" && jQuery( selectors );
  
  		// Positional selectors never match, since there's no _selection_ context
  		if ( !rneedsContext.test( selectors ) ) {
  			for ( ; i < l; i++ ) {
  				for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {
  
  					// Always skip document fragments
  					if ( cur.nodeType < 11 && ( targets ?
  						targets.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.uniqueSort( matched ) : matched );
  	},
  
  	// Determine the position of an element within the set
  	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.uniqueSort(
  				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 dir( elem, "parentNode" );
  	},
  	parentsUntil: function( elem, i, until ) {
  		return dir( elem, "parentNode", until );
  	},
  	next: function( elem ) {
  		return sibling( elem, "nextSibling" );
  	},
  	prev: function( elem ) {
  		return sibling( elem, "previousSibling" );
  	},
  	nextAll: function( elem ) {
  		return dir( elem, "nextSibling" );
  	},
  	prevAll: function( elem ) {
  		return dir( elem, "previousSibling" );
  	},
  	nextUntil: function( elem, i, until ) {
  		return dir( elem, "nextSibling", until );
  	},
  	prevUntil: function( elem, i, until ) {
  		return dir( elem, "previousSibling", until );
  	},
  	siblings: function( elem ) {
  		return siblings( ( elem.parentNode || {} ).firstChild, elem );
  	},
  	children: function( elem ) {
  		return siblings( elem.firstChild );
  	},
  	contents: function( elem ) {
          if ( nodeName( elem, "iframe" ) ) {
              return elem.contentDocument;
          }
  
          // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only
          // Treat the template element as a regular one in browsers that
          // don't support it.
          if ( nodeName( elem, "template" ) ) {
              elem = elem.content || elem;
          }
  
          return 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.uniqueSort( matched );
  			}
  
  			// Reverse order for parents* and prev-derivatives
  			if ( rparentsprev.test( name ) ) {
  				matched.reverse();
  			}
  		}
  
  		return this.pushStack( matched );
  	};
  } );
  var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g );
  
  
  
  // Convert String-formatted options into Object-formatted ones
  function createOptions( options ) {
  	var object = {};
  	jQuery.each( options.match( rnothtmlwhite ) || [], 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" ?
  		createOptions( options ) :
  		jQuery.extend( {}, options );
  
  	var // Flag to know if list is currently firing
  		firing,
  
  		// Last fire value for non-forgettable lists
  		memory,
  
  		// Flag to know if list was already fired
  		fired,
  
  		// Flag to prevent firing
  		locked,
  
  		// Actual callback list
  		list = [],
  
  		// Queue of execution data for repeatable lists
  		queue = [],
  
  		// Index of currently firing callback (modified by add/remove as needed)
  		firingIndex = -1,
  
  		// Fire callbacks
  		fire = function() {
  
  			// Enforce single-firing
  			locked = locked || options.once;
  
  			// Execute callbacks for all pending executions,
  			// respecting firingIndex overrides and runtime changes
  			fired = firing = true;
  			for ( ; queue.length; firingIndex = -1 ) {
  				memory = queue.shift();
  				while ( ++firingIndex < list.length ) {
  
  					// Run callback and check for early termination
  					if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
  						options.stopOnFalse ) {
  
  						// Jump to end and forget the data so .add doesn't re-fire
  						firingIndex = list.length;
  						memory = false;
  					}
  				}
  			}
  
  			// Forget the data if we're done with it
  			if ( !options.memory ) {
  				memory = false;
  			}
  
  			firing = false;
  
  			// Clean up if we're done firing for good
  			if ( locked ) {
  
  				// Keep an empty list if we have data for future add calls
  				if ( memory ) {
  					list = [];
  
  				// Otherwise, this object is spent
  				} else {
  					list = "";
  				}
  			}
  		},
  
  		// Actual Callbacks object
  		self = {
  
  			// Add a callback or a collection of callbacks to the list
  			add: function() {
  				if ( list ) {
  
  					// If we have memory from a past run, we should fire after adding
  					if ( memory && !firing ) {
  						firingIndex = list.length - 1;
  						queue.push( memory );
  					}
  
  					( function add( args ) {
  						jQuery.each( args, function( _, arg ) {
  							if ( jQuery.isFunction( arg ) ) {
  								if ( !options.unique || !self.has( arg ) ) {
  									list.push( arg );
  								}
  							} else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {
  
  								// Inspect recursively
  								add( arg );
  							}
  						} );
  					} )( arguments );
  
  					if ( memory && !firing ) {
  						fire();
  					}
  				}
  				return this;
  			},
  
  			// Remove a callback from the list
  			remove: function() {
  				jQuery.each( arguments, function( _, arg ) {
  					var index;
  					while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
  						list.splice( index, 1 );
  
  						// Handle firing indexes
  						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.length > 0;
  			},
  
  			// Remove all callbacks from the list
  			empty: function() {
  				if ( list ) {
  					list = [];
  				}
  				return this;
  			},
  
  			// Disable .fire and .add
  			// Abort any current/pending executions
  			// Clear all callbacks and values
  			disable: function() {
  				locked = queue = [];
  				list = memory = "";
  				return this;
  			},
  			disabled: function() {
  				return !list;
  			},
  
  			// Disable .fire
  			// Also disable .add unless we have memory (since it would have no effect)
  			// Abort any pending executions
  			lock: function() {
  				locked = queue = [];
  				if ( !memory && !firing ) {
  					list = memory = "";
  				}
  				return this;
  			},
  			locked: function() {
  				return !!locked;
  			},
  
  			// Call all callbacks with the given context and arguments
  			fireWith: function( context, args ) {
  				if ( !locked ) {
  					args = args || [];
  					args = [ context, args.slice ? args.slice() : args ];
  					queue.push( args );
  					if ( !firing ) {
  						fire();
  					}
  				}
  				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;
  };
  
  
  function Identity( v ) {
  	return v;
  }
  function Thrower( ex ) {
  	throw ex;
  }
  
  function adoptValue( value, resolve, reject, noValue ) {
  	var method;
  
  	try {
  
  		// Check for promise aspect first to privilege synchronous behavior
  		if ( value && jQuery.isFunction( ( method = value.promise ) ) ) {
  			method.call( value ).done( resolve ).fail( reject );
  
  		// Other thenables
  		} else if ( value && jQuery.isFunction( ( method = value.then ) ) ) {
  			method.call( value, resolve, reject );
  
  		// Other non-thenables
  		} else {
  
  			// Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:
  			// * false: [ value ].slice( 0 ) => resolve( value )
  			// * true: [ value ].slice( 1 ) => resolve()
  			resolve.apply( undefined, [ value ].slice( noValue ) );
  		}
  
  	// For Promises/A+, convert exceptions into rejections
  	// Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in
  	// Deferred#then to conditionally suppress rejection.
  	} catch ( value ) {
  
  		// Support: Android 4.0 only
  		// Strict mode functions invoked without .call/.apply get global-object context
  		reject.apply( undefined, [ value ] );
  	}
  }
  
  jQuery.extend( {
  
  	Deferred: function( func ) {
  		var tuples = [
  
  				// action, add listener, callbacks,
  				// ... .then handlers, argument index, [final state]
  				[ "notify", "progress", jQuery.Callbacks( "memory" ),
  					jQuery.Callbacks( "memory" ), 2 ],
  				[ "resolve", "done", jQuery.Callbacks( "once memory" ),
  					jQuery.Callbacks( "once memory" ), 0, "resolved" ],
  				[ "reject", "fail", jQuery.Callbacks( "once memory" ),
  					jQuery.Callbacks( "once memory" ), 1, "rejected" ]
  			],
  			state = "pending",
  			promise = {
  				state: function() {
  					return state;
  				},
  				always: function() {
  					deferred.done( arguments ).fail( arguments );
  					return this;
  				},
  				"catch": function( fn ) {
  					return promise.then( null, fn );
  				},
  
  				// Keep pipe for back-compat
  				pipe: function( /* fnDone, fnFail, fnProgress */ ) {
  					var fns = arguments;
  
  					return jQuery.Deferred( function( newDefer ) {
  						jQuery.each( tuples, function( i, tuple ) {
  
  							// Map tuples (progress, done, fail) to arguments (done, fail, progress)
  							var fn = jQuery.isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];
  
  							// deferred.progress(function() { bind to newDefer or newDefer.notify })
  							// deferred.done(function() { bind to newDefer or newDefer.resolve })
  							// deferred.fail(function() { bind to newDefer or newDefer.reject })
  							deferred[ tuple[ 1 ] ]( function() {
  								var returned = fn && fn.apply( this, arguments );
  								if ( returned && jQuery.isFunction( returned.promise ) ) {
  									returned.promise()
  										.progress( newDefer.notify )
  										.done( newDefer.resolve )
  										.fail( newDefer.reject );
  								} else {
  									newDefer[ tuple[ 0 ] + "With" ](
  										this,
  										fn ? [ returned ] : arguments
  									);
  								}
  							} );
  						} );
  						fns = null;
  					} ).promise();
  				},
  				then: function( onFulfilled, onRejected, onProgress ) {
  					var maxDepth = 0;
  					function resolve( depth, deferred, handler, special ) {
  						return function() {
  							var that = this,
  								args = arguments,
  								mightThrow = function() {
  									var returned, then;
  
  									// Support: Promises/A+ section 2.3.3.3.3
  									// https://promisesaplus.com/#point-59
  									// Ignore double-resolution attempts
  									if ( depth < maxDepth ) {
  										return;
  									}
  
  									returned = handler.apply( that, args );
  
  									// Support: Promises/A+ section 2.3.1
  									// https://promisesaplus.com/#point-48
  									if ( returned === deferred.promise() ) {
  										throw new TypeError( "Thenable self-resolution" );
  									}
  
  									// Support: Promises/A+ sections 2.3.3.1, 3.5
  									// https://promisesaplus.com/#point-54
  									// https://promisesaplus.com/#point-75
  									// Retrieve `then` only once
  									then = returned &&
  
  										// Support: Promises/A+ section 2.3.4
  										// https://promisesaplus.com/#point-64
  										// Only check objects and functions for thenability
  										( typeof returned === "object" ||
  											typeof returned === "function" ) &&
  										returned.then;
  
  									// Handle a returned thenable
  									if ( jQuery.isFunction( then ) ) {
  
  										// Special processors (notify) just wait for resolution
  										if ( special ) {
  											then.call(
  												returned,
  												resolve( maxDepth, deferred, Identity, special ),
  												resolve( maxDepth, deferred, Thrower, special )
  											);
  
  										// Normal processors (resolve) also hook into progress
  										} else {
  
  											// ...and disregard older resolution values
  											maxDepth++;
  
  											then.call(
  												returned,
  												resolve( maxDepth, deferred, Identity, special ),
  												resolve( maxDepth, deferred, Thrower, special ),
  												resolve( maxDepth, deferred, Identity,
  													deferred.notifyWith )
  											);
  										}
  
  									// Handle all other returned values
  									} else {
  
  										// Only substitute handlers pass on context
  										// and multiple values (non-spec behavior)
  										if ( handler !== Identity ) {
  											that = undefined;
  											args = [ returned ];
  										}
  
  										// Process the value(s)
  										// Default process is resolve
  										( special || deferred.resolveWith )( that, args );
  									}
  								},
  
  								// Only normal processors (resolve) catch and reject exceptions
  								process = special ?
  									mightThrow :
  									function() {
  										try {
  											mightThrow();
  										} catch ( e ) {
  
  											if ( jQuery.Deferred.exceptionHook ) {
  												jQuery.Deferred.exceptionHook( e,
  													process.stackTrace );
  											}
  
  											// Support: Promises/A+ section 2.3.3.3.4.1
  											// https://promisesaplus.com/#point-61
  											// Ignore post-resolution exceptions
  											if ( depth + 1 >= maxDepth ) {
  
  												// Only substitute handlers pass on context
  												// and multiple values (non-spec behavior)
  												if ( handler !== Thrower ) {
  													that = undefined;
  													args = [ e ];
  												}
  
  												deferred.rejectWith( that, args );
  											}
  										}
  									};
  
  							// Support: Promises/A+ section 2.3.3.3.1
  							// https://promisesaplus.com/#point-57
  							// Re-resolve promises immediately to dodge false rejection from
  							// subsequent errors
  							if ( depth ) {
  								process();
  							} else {
  
  								// Call an optional hook to record the stack, in case of exception
  								// since it's otherwise lost when execution goes async
  								if ( jQuery.Deferred.getStackHook ) {
  									process.stackTrace = jQuery.Deferred.getStackHook();
  								}
  								window.setTimeout( process );
  							}
  						};
  					}
  
  					return jQuery.Deferred( function( newDefer ) {
  
  						// progress_handlers.add( ... )
  						tuples[ 0 ][ 3 ].add(
  							resolve(
  								0,
  								newDefer,
  								jQuery.isFunction( onProgress ) ?
  									onProgress :
  									Identity,
  								newDefer.notifyWith
  							)
  						);
  
  						// fulfilled_handlers.add( ... )
  						tuples[ 1 ][ 3 ].add(
  							resolve(
  								0,
  								newDefer,
  								jQuery.isFunction( onFulfilled ) ?
  									onFulfilled :
  									Identity
  							)
  						);
  
  						// rejected_handlers.add( ... )
  						tuples[ 2 ][ 3 ].add(
  							resolve(
  								0,
  								newDefer,
  								jQuery.isFunction( onRejected ) ?
  									onRejected :
  									Thrower
  							)
  						);
  					} ).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 = {};
  
  		// Add list-specific methods
  		jQuery.each( tuples, function( i, tuple ) {
  			var list = tuple[ 2 ],
  				stateString = tuple[ 5 ];
  
  			// promise.progress = list.add
  			// promise.done = list.add
  			// promise.fail = list.add
  			promise[ tuple[ 1 ] ] = list.add;
  
  			// Handle state
  			if ( stateString ) {
  				list.add(
  					function() {
  
  						// state = "resolved" (i.e., fulfilled)
  						// state = "rejected"
  						state = stateString;
  					},
  
  					// rejected_callbacks.disable
  					// fulfilled_callbacks.disable
  					tuples[ 3 - i ][ 2 ].disable,
  
  					// progress_callbacks.lock
  					tuples[ 0 ][ 2 ].lock
  				);
  			}
  
  			// progress_handlers.fire
  			// fulfilled_handlers.fire
  			// rejected_handlers.fire
  			list.add( tuple[ 3 ].fire );
  
  			// deferred.notify = function() { deferred.notifyWith(...) }
  			// deferred.resolve = function() { deferred.resolveWith(...) }
  			// deferred.reject = function() { deferred.rejectWith(...) }
  			deferred[ tuple[ 0 ] ] = function() {
  				deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
  				return this;
  			};
  
  			// deferred.notifyWith = list.fireWith
  			// deferred.resolveWith = list.fireWith
  			// deferred.rejectWith = list.fireWith
  			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( singleValue ) {
  		var
  
  			// count of uncompleted subordinates
  			remaining = arguments.length,
  
  			// count of unprocessed arguments
  			i = remaining,
  
  			// subordinate fulfillment data
  			resolveContexts = Array( i ),
  			resolveValues = slice.call( arguments ),
  
  			// the master Deferred
  			master = jQuery.Deferred(),
  
  			// subordinate callback factory
  			updateFunc = function( i ) {
  				return function( value ) {
  					resolveContexts[ i ] = this;
  					resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
  					if ( !( --remaining ) ) {
  						master.resolveWith( resolveContexts, resolveValues );
  					}
  				};
  			};
  
  		// Single- and empty arguments are adopted like Promise.resolve
  		if ( remaining <= 1 ) {
  			adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject,
  				!remaining );
  
  			// Use .then() to unwrap secondary thenables (cf. gh-3000)
  			if ( master.state() === "pending" ||
  				jQuery.isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {
  
  				return master.then();
  			}
  		}
  
  		// Multiple arguments are aggregated like Promise.all array elements
  		while ( i-- ) {
  			adoptValue( resolveValues[ i ], updateFunc( i ), master.reject );
  		}
  
  		return master.promise();
  	}
  } );
  
  
  // These usually indicate a programmer mistake during development,
  // warn about them ASAP rather than swallowing them by default.
  var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;
  
  jQuery.Deferred.exceptionHook = function( error, stack ) {
  
  	// Support: IE 8 - 9 only
  	// Console exists when dev tools are open, which can happen at any time
  	if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
  		window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack );
  	}
  };
  
  
  
  
  jQuery.readyException = function( error ) {
  	window.setTimeout( function() {
  		throw error;
  	} );
  };
  
  
  
  
  // The deferred used on DOM ready
  var readyList = jQuery.Deferred();
  
  jQuery.fn.ready = function( fn ) {
  
  	readyList
  		.then( fn )
  
  		// Wrap jQuery.readyException in a function so that the lookup
  		// happens at the time of error handling instead of callback
  		// registration.
  		.catch( function( error ) {
  			jQuery.readyException( error );
  		} );
  
  	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,
  
  	// 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 ] );
  	}
  } );
  
  jQuery.ready.then = readyList.then;
  
  // The ready event handler and self cleanup method
  function completed() {
  	document.removeEventListener( "DOMContentLoaded", completed );
  	window.removeEventListener( "load", completed );
  	jQuery.ready();
  }
  
  // Catch cases where $(document).ready() is called
  // after the browser event has already occurred.
  // Support: IE <=9 - 10 only
  // Older IE sometimes signals "interactive" too soon
  if ( document.readyState === "complete" ||
  	( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
  
  	// Handle it asynchronously to allow scripts the opportunity to delay ready
  	window.setTimeout( jQuery.ready );
  
  } else {
  
  	// Use the handy event callback
  	document.addEventListener( "DOMContentLoaded", completed );
  
  	// A fallback to window.onload, that will always work
  	window.addEventListener( "load", completed );
  }
  
  
  
  
  // Multifunctional method to get and set values of a collection
  // The value/s can optionally be executed if it's a function
  var 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 ) {
  			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 ) )
  				);
  			}
  		}
  	}
  
  	if ( chainable ) {
  		return elems;
  	}
  
  	// Gets
  	if ( bulk ) {
  		return fn.call( elems );
  	}
  
  	return len ? fn( elems[ 0 ], key ) : emptyGet;
  };
  var acceptData = function( owner ) {
  
  	// Accepts only:
  	//  - Node
  	//    - Node.ELEMENT_NODE
  	//    - Node.DOCUMENT_NODE
  	//  - Object
  	//    - Any
  	return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
  };
  
  
  
  
  function Data() {
  	this.expando = jQuery.expando + Data.uid++;
  }
  
  Data.uid = 1;
  
  Data.prototype = {
  
  	cache: function( owner ) {
  
  		// Check if the owner object already has a cache
  		var value = owner[ this.expando ];
  
  		// If not, create one
  		if ( !value ) {
  			value = {};
  
  			// We can accept data for non-element nodes in modern browsers,
  			// but we should not, see #8335.
  			// Always return an empty object.
  			if ( acceptData( owner ) ) {
  
  				// If it is a node unlikely to be stringify-ed or looped over
  				// use plain assignment
  				if ( owner.nodeType ) {
  					owner[ this.expando ] = value;
  
  				// Otherwise secure it in a non-enumerable property
  				// configurable must be true to allow the property to be
  				// deleted when data is removed
  				} else {
  					Object.defineProperty( owner, this.expando, {
  						value: value,
  						configurable: true
  					} );
  				}
  			}
  		}
  
  		return value;
  	},
  	set: function( owner, data, value ) {
  		var prop,
  			cache = this.cache( owner );
  
  		// Handle: [ owner, key, value ] args
  		// Always use camelCase key (gh-2257)
  		if ( typeof data === "string" ) {
  			cache[ jQuery.camelCase( data ) ] = value;
  
  		// Handle: [ owner, { properties } ] args
  		} else {
  
  			// Copy the properties one-by-one to the cache object
  			for ( prop in data ) {
  				cache[ jQuery.camelCase( prop ) ] = data[ prop ];
  			}
  		}
  		return cache;
  	},
  	get: function( owner, key ) {
  		return key === undefined ?
  			this.cache( owner ) :
  
  			// Always use camelCase key (gh-2257)
  			owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ];
  	},
  	access: function( owner, key, value ) {
  
  		// 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 ) ) {
  
  			return this.get( owner, 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,
  			cache = owner[ this.expando ];
  
  		if ( cache === undefined ) {
  			return;
  		}
  
  		if ( key !== undefined ) {
  
  			// Support array or space separated string of keys
  			if ( Array.isArray( key ) ) {
  
  				// If key is an array of keys...
  				// We always set camelCase keys, so remove that.
  				key = key.map( jQuery.camelCase );
  			} else {
  				key = jQuery.camelCase( key );
  
  				// If a key with the spaces exists, use it.
  				// Otherwise, create an array by matching non-whitespace
  				key = key in cache ?
  					[ key ] :
  					( key.match( rnothtmlwhite ) || [] );
  			}
  
  			i = key.length;
  
  			while ( i-- ) {
  				delete cache[ key[ i ] ];
  			}
  		}
  
  		// Remove the expando if there's no more data
  		if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
  
  			// Support: Chrome <=35 - 45
  			// Webkit & Blink performance suffers when deleting properties
  			// from DOM nodes, so set to undefined instead
  			// https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
  			if ( owner.nodeType ) {
  				owner[ this.expando ] = undefined;
  			} else {
  				delete owner[ this.expando ];
  			}
  		}
  	},
  	hasData: function( owner ) {
  		var cache = owner[ this.expando ];
  		return cache !== undefined && !jQuery.isEmptyObject( cache );
  	}
  };
  var dataPriv = new Data();
  
  var dataUser = 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 getData( data ) {
  	if ( data === "true" ) {
  		return true;
  	}
  
  	if ( data === "false" ) {
  		return false;
  	}
  
  	if ( data === "null" ) {
  		return null;
  	}
  
  	// Only convert to a number if it doesn't change the string
  	if ( data === +data + "" ) {
  		return +data;
  	}
  
  	if ( rbrace.test( data ) ) {
  		return JSON.parse( data );
  	}
  
  	return data;
  }
  
  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, "-$&" ).toLowerCase();
  		data = elem.getAttribute( name );
  
  		if ( typeof data === "string" ) {
  			try {
  				data = getData( data );
  			} catch ( e ) {}
  
  			// Make sure we set the data so it isn't changed later
  			dataUser.set( elem, key, data );
  		} else {
  			data = undefined;
  		}
  	}
  	return data;
  }
  
  jQuery.extend( {
  	hasData: function( elem ) {
  		return dataUser.hasData( elem ) || dataPriv.hasData( elem );
  	},
  
  	data: function( elem, name, data ) {
  		return dataUser.access( elem, name, data );
  	},
  
  	removeData: function( elem, name ) {
  		dataUser.remove( elem, name );
  	},
  
  	// TODO: Now that all calls to _data and _removeData have been replaced
  	// with direct calls to dataPriv methods, these can be deprecated.
  	_data: function( elem, name, data ) {
  		return dataPriv.access( elem, name, data );
  	},
  
  	_removeData: function( elem, name ) {
  		dataPriv.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 = dataUser.get( elem );
  
  				if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
  					i = attrs.length;
  					while ( i-- ) {
  
  						// Support: IE 11 only
  						// The attrs elements can be null (#14894)
  						if ( attrs[ i ] ) {
  							name = attrs[ i ].name;
  							if ( name.indexOf( "data-" ) === 0 ) {
  								name = jQuery.camelCase( name.slice( 5 ) );
  								dataAttr( elem, name, data[ name ] );
  							}
  						}
  					}
  					dataPriv.set( elem, "hasDataAttrs", true );
  				}
  			}
  
  			return data;
  		}
  
  		// Sets multiple values
  		if ( typeof key === "object" ) {
  			return this.each( function() {
  				dataUser.set( this, key );
  			} );
  		}
  
  		return access( this, function( value ) {
  			var data;
  
  			// 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
  				// The key will always be camelCased in Data
  				data = dataUser.get( elem, key );
  				if ( data !== undefined ) {
  					return data;
  				}
  
  				// Attempt to "discover" the data in
  				// HTML5 custom data-* attrs
  				data = dataAttr( elem, key );
  				if ( data !== undefined ) {
  					return data;
  				}
  
  				// We tried really hard, but the data doesn't exist.
  				return;
  			}
  
  			// Set the data...
  			this.each( function() {
  
  				// We always store the camelCased key
  				dataUser.set( this, key, value );
  			} );
  		}, null, value, arguments.length > 1, null, true );
  	},
  
  	removeData: function( key ) {
  		return this.each( function() {
  			dataUser.remove( this, key );
  		} );
  	}
  } );
  
  
  jQuery.extend( {
  	queue: function( elem, type, data ) {
  		var queue;
  
  		if ( elem ) {
  			type = ( type || "fx" ) + "queue";
  			queue = dataPriv.get( elem, type );
  
  			// Speed up dequeue by getting out quickly if this is just a lookup
  			if ( data ) {
  				if ( !queue || Array.isArray( data ) ) {
  					queue = dataPriv.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 public - generate a queueHooks object, or return the current one
  	_queueHooks: function( elem, type ) {
  		var key = type + "queueHooks";
  		return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
  			empty: jQuery.Callbacks( "once memory" ).add( function() {
  				dataPriv.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 = dataPriv.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 rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );
  
  
  var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
  
  var isHiddenWithinTree = function( elem, el ) {
  
  		// isHiddenWithinTree might be called from jQuery#filter function;
  		// in that case, element will be second argument
  		elem = el || elem;
  
  		// Inline style trumps all
  		return elem.style.display === "none" ||
  			elem.style.display === "" &&
  
  			// Otherwise, check computed style
  			// Support: Firefox <=43 - 45
  			// Disconnected elements can have computed display: none, so first confirm that elem is
  			// in the document.
  			jQuery.contains( elem.ownerDocument, elem ) &&
  
  			jQuery.css( elem, "display" ) === "none";
  	};
  
  var swap = function( elem, options, callback, args ) {
  	var ret, name,
  		old = {};
  
  	// Remember the old values, and insert the new ones
  	for ( name in options ) {
  		old[ name ] = elem.style[ name ];
  		elem.style[ name ] = options[ name ];
  	}
  
  	ret = callback.apply( elem, args || [] );
  
  	// Revert the old values
  	for ( name in options ) {
  		elem.style[ name ] = old[ name ];
  	}
  
  	return ret;
  };
  
  
  
  
  function adjustCSS( elem, prop, valueParts, tween ) {
  	var adjusted,
  		scale = 1,
  		maxIterations = 20,
  		currentValue = tween ?
  			function() {
  				return tween.cur();
  			} :
  			function() {
  				return jQuery.css( elem, prop, "" );
  			},
  		initial = currentValue(),
  		unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
  
  		// Starting value computation is required for potential unit mismatches
  		initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
  			rcssNum.exec( jQuery.css( elem, prop ) );
  
  	if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {
  
  		// Trust units reported by jQuery.css
  		unit = unit || initialInUnit[ 3 ];
  
  		// Make sure we update the tween properties later on
  		valueParts = valueParts || [];
  
  		// Iteratively approximate from a nonzero starting point
  		initialInUnit = +initial || 1;
  
  		do {
  
  			// If previous iteration zeroed out, double until we get *something*.
  			// Use string for doubling so we don't accidentally see scale as unchanged below
  			scale = scale || ".5";
  
  			// Adjust and apply
  			initialInUnit = initialInUnit / scale;
  			jQuery.style( elem, prop, initialInUnit + unit );
  
  		// Update scale, tolerating zero or NaN from tween.cur()
  		// Break the loop if scale is unchanged or perfect, or if we've just had enough.
  		} while (
  			scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations
  		);
  	}
  
  	if ( valueParts ) {
  		initialInUnit = +initialInUnit || +initial || 0;
  
  		// Apply relative offset (+=/-=) if specified
  		adjusted = valueParts[ 1 ] ?
  			initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
  			+valueParts[ 2 ];
  		if ( tween ) {
  			tween.unit = unit;
  			tween.start = initialInUnit;
  			tween.end = adjusted;
  		}
  	}
  	return adjusted;
  }
  
  
  var defaultDisplayMap = {};
  
  function getDefaultDisplay( elem ) {
  	var temp,
  		doc = elem.ownerDocument,
  		nodeName = elem.nodeName,
  		display = defaultDisplayMap[ nodeName ];
  
  	if ( display ) {
  		return display;
  	}
  
  	temp = doc.body.appendChild( doc.createElement( nodeName ) );
  	display = jQuery.css( temp, "display" );
  
  	temp.parentNode.removeChild( temp );
  
  	if ( display === "none" ) {
  		display = "block";
  	}
  	defaultDisplayMap[ nodeName ] = display;
  
  	return display;
  }
  
  function showHide( elements, show ) {
  	var display, elem,
  		values = [],
  		index = 0,
  		length = elements.length;
  
  	// Determine new display value for elements that need to change
  	for ( ; index < length; index++ ) {
  		elem = elements[ index ];
  		if ( !elem.style ) {
  			continue;
  		}
  
  		display = elem.style.display;
  		if ( show ) {
  
  			// Since we force visibility upon cascade-hidden elements, an immediate (and slow)
  			// check is required in this first loop unless we have a nonempty display value (either
  			// inline or about-to-be-restored)
  			if ( display === "none" ) {
  				values[ index ] = dataPriv.get( elem, "display" ) || null;
  				if ( !values[ index ] ) {
  					elem.style.display = "";
  				}
  			}
  			if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) {
  				values[ index ] = getDefaultDisplay( elem );
  			}
  		} else {
  			if ( display !== "none" ) {
  				values[ index ] = "none";
  
  				// Remember what we're overwriting
  				dataPriv.set( elem, "display", display );
  			}
  		}
  	}
  
  	// Set the display of the elements in a second loop to avoid constant reflow
  	for ( index = 0; index < length; index++ ) {
  		if ( values[ index ] != null ) {
  			elements[ index ].style.display = values[ index ];
  		}
  	}
  
  	return elements;
  }
  
  jQuery.fn.extend( {
  	show: function() {
  		return showHide( this, true );
  	},
  	hide: function() {
  		return showHide( this );
  	},
  	toggle: function( state ) {
  		if ( typeof state === "boolean" ) {
  			return state ? this.show() : this.hide();
  		}
  
  		return this.each( function() {
  			if ( isHiddenWithinTree( this ) ) {
  				jQuery( this ).show();
  			} else {
  				jQuery( this ).hide();
  			}
  		} );
  	}
  } );
  var rcheckableType = ( /^(?:checkbox|radio)$/i );
  
  var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i );
  
  var rscriptType = ( /^$|\/(?:java|ecma)script/i );
  
  
  
  // We have to close these tags to support XHTML (#13200)
  var wrapMap = {
  
  	// Support: IE <=9 only
  	option: [ 1, "<select multiple='multiple'>", "</select>" ],
  
  	// XHTML parsers do not magically insert elements in the
  	// same way that tag soup parsers do. So we cannot shorten
  	// this by omitting <tbody> or other required elements.
  	thead: [ 1, "<table>", "</table>" ],
  	col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
  	tr: [ 2, "<table><tbody>", "</tbody></table>" ],
  	td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
  
  	_default: [ 0, "", "" ]
  };
  
  // Support: IE <=9 only
  wrapMap.optgroup = wrapMap.option;
  
  wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
  wrapMap.th = wrapMap.td;
  
  
  function getAll( context, tag ) {
  
  	// Support: IE <=9 - 11 only
  	// Use typeof to avoid zero-argument method invocation on host objects (#15151)
  	var ret;
  
  	if ( typeof context.getElementsByTagName !== "undefined" ) {
  		ret = context.getElementsByTagName( tag || "*" );
  
  	} else if ( typeof context.querySelectorAll !== "undefined" ) {
  		ret = context.querySelectorAll( tag || "*" );
  
  	} else {
  		ret = [];
  	}
  
  	if ( tag === undefined || tag && nodeName( context, tag ) ) {
  		return jQuery.merge( [ context ], ret );
  	}
  
  	return ret;
  }
  
  
  // Mark scripts as having already been evaluated
  function setGlobalEval( elems, refElements ) {
  	var i = 0,
  		l = elems.length;
  
  	for ( ; i < l; i++ ) {
  		dataPriv.set(
  			elems[ i ],
  			"globalEval",
  			!refElements || dataPriv.get( refElements[ i ], "globalEval" )
  		);
  	}
  }
  
  
  var rhtml = /<|&#?\w+;/;
  
  function buildFragment( elems, context, scripts, selection, ignored ) {
  	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: Android <=4.0 only, PhantomJS 1 only
  				// push.apply(_, arraylike) throws on ancient WebKit
  				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 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];
  
  				// Descend through wrappers to the right content
  				j = wrap[ 0 ];
  				while ( j-- ) {
  					tmp = tmp.lastChild;
  				}
  
  				// Support: Android <=4.0 only, PhantomJS 1 only
  				// push.apply(_, arraylike) throws on ancient WebKit
  				jQuery.merge( nodes, tmp.childNodes );
  
  				// Remember the top-level container
  				tmp = fragment.firstChild;
  
  				// Ensure the created nodes are orphaned (#12392)
  				tmp.textContent = "";
  			}
  		}
  	}
  
  	// Remove wrapper from fragment
  	fragment.textContent = "";
  
  	i = 0;
  	while ( ( elem = nodes[ i++ ] ) ) {
  
  		// Skip elements already in the context collection (trac-4087)
  		if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
  			if ( ignored ) {
  				ignored.push( elem );
  			}
  			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;
  }
  
  
  ( function() {
  	var fragment = document.createDocumentFragment(),
  		div = fragment.appendChild( document.createElement( "div" ) ),
  		input = document.createElement( "input" );
  
  	// Support: Android 4.0 - 4.3 only
  	// Check state lost if the name is set (#11217)
  	// Support: Windows Web Apps (WWA)
  	// `name` and `type` must use .setAttribute for WWA (#14901)
  	input.setAttribute( "type", "radio" );
  	input.setAttribute( "checked", "checked" );
  	input.setAttribute( "name", "t" );
  
  	div.appendChild( input );
  
  	// Support: Android <=4.1 only
  	// Older WebKit doesn't clone checked state correctly in fragments
  	support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
  
  	// Support: IE <=11 only
  	// Make sure textarea (and checkbox) defaultValue is properly cloned
  	div.innerHTML = "<textarea>x</textarea>";
  	support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
  } )();
  var documentElement = document.documentElement;
  
  
  
  var
  	rkeyEvent = /^key/,
  	rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
  	rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
  
  function returnTrue() {
  	return true;
  }
  
  function returnFalse() {
  	return false;
  }
  
  // Support: IE <=9 only
  // See #13393 for more info
  function safeActiveElement() {
  	try {
  		return document.activeElement;
  	} catch ( err ) { }
  }
  
  function on( elem, types, selector, data, fn, 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 ) {
  			on( elem, type, selector, data, types[ type ], one );
  		}
  		return elem;
  	}
  
  	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 elem;
  	}
  
  	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 elem.each( function() {
  		jQuery.event.add( this, types, fn, data, selector );
  	} );
  }
  
  /*
   * 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 = dataPriv.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;
  		}
  
  		// Ensure that invalid selectors throw exceptions at attach time
  		// Evaluate against documentElement in case elem is a non-element node (e.g., document)
  		if ( selector ) {
  			jQuery.find.matchesSelector( documentElement, 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 !== "undefined" && jQuery.event.triggered !== e.type ?
  					jQuery.event.dispatch.apply( elem, arguments ) : undefined;
  			};
  		}
  
  		// Handle multiple events separated by a space
  		types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
  		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 );
  					}
  				}
  			}
  
  			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 = dataPriv.hasData( elem ) && dataPriv.get( elem );
  
  		if ( !elemData || !( events = elemData.events ) ) {
  			return;
  		}
  
  		// Once for each type.namespace in types; type may be omitted
  		types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
  		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 data and the expando if it's no longer used
  		if ( jQuery.isEmptyObject( events ) ) {
  			dataPriv.remove( elem, "handle events" );
  		}
  	},
  
  	dispatch: function( nativeEvent ) {
  
  		// Make a writable jQuery.Event from the native event object
  		var event = jQuery.event.fix( nativeEvent );
  
  		var i, j, ret, matched, handleObj, handlerQueue,
  			args = new Array( arguments.length ),
  			handlers = ( dataPriv.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;
  
  		for ( i = 1; i < arguments.length; i++ ) {
  			args[ i ] = arguments[ i ];
  		}
  
  		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.rnamespace || event.rnamespace.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, handleObj, sel, matchedHandlers, matchedSelectors,
  			handlerQueue = [],
  			delegateCount = handlers.delegateCount,
  			cur = event.target;
  
  		// Find delegate handlers
  		if ( delegateCount &&
  
  			// Support: IE <=9
  			// Black-hole SVG <use> instance trees (trac-13180)
  			cur.nodeType &&
  
  			// Support: Firefox <=42
  			// Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)
  			// https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
  			// Support: IE 11 only
  			// ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)
  			!( event.type === "click" && event.button >= 1 ) ) {
  
  			for ( ; cur !== this; cur = cur.parentNode || this ) {
  
  				// Don't check non-elements (#13208)
  				// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
  				if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
  					matchedHandlers = [];
  					matchedSelectors = {};
  					for ( i = 0; i < delegateCount; i++ ) {
  						handleObj = handlers[ i ];
  
  						// Don't conflict with Object.prototype properties (#13203)
  						sel = handleObj.selector + " ";
  
  						if ( matchedSelectors[ sel ] === undefined ) {
  							matchedSelectors[ sel ] = handleObj.needsContext ?
  								jQuery( sel, this ).index( cur ) > -1 :
  								jQuery.find( sel, this, null, [ cur ] ).length;
  						}
  						if ( matchedSelectors[ sel ] ) {
  							matchedHandlers.push( handleObj );
  						}
  					}
  					if ( matchedHandlers.length ) {
  						handlerQueue.push( { elem: cur, handlers: matchedHandlers } );
  					}
  				}
  			}
  		}
  
  		// Add the remaining (directly-bound) handlers
  		cur = this;
  		if ( delegateCount < handlers.length ) {
  			handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
  		}
  
  		return handlerQueue;
  	},
  
  	addProp: function( name, hook ) {
  		Object.defineProperty( jQuery.Event.prototype, name, {
  			enumerable: true,
  			configurable: true,
  
  			get: jQuery.isFunction( hook ) ?
  				function() {
  					if ( this.originalEvent ) {
  							return hook( this.originalEvent );
  					}
  				} :
  				function() {
  					if ( this.originalEvent ) {
  							return this.originalEvent[ name ];
  					}
  				},
  
  			set: function( value ) {
  				Object.defineProperty( this, name, {
  					enumerable: true,
  					configurable: true,
  					writable: true,
  					value: value
  				} );
  			}
  		} );
  	},
  
  	fix: function( originalEvent ) {
  		return originalEvent[ jQuery.expando ] ?
  			originalEvent :
  			new jQuery.Event( originalEvent );
  	},
  
  	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 && nodeName( this, "input" ) ) {
  					this.click();
  					return false;
  				}
  			},
  
  			// For cross-browser consistency, don't fire native .click() on links
  			_default: function( event ) {
  				return 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 ) {
  					event.originalEvent.returnValue = event.result;
  				}
  			}
  		}
  	}
  };
  
  jQuery.removeEvent = function( elem, type, handle ) {
  
  	// This "if" is needed for plain objects
  	if ( elem.removeEventListener ) {
  		elem.removeEventListener( type, handle );
  	}
  };
  
  jQuery.Event = function( src, props ) {
  
  	// Allow instantiation without the 'new' keyword
  	if ( !( this instanceof jQuery.Event ) ) {
  		return new jQuery.Event( src, props );
  	}
  
  	// Event object
  	if ( src && src.type ) {
  		this.originalEvent = src;
  		this.type = src.type;
  
  		// Events bubbling up the document may have been marked as prevented
  		// by a handler lower down the tree; reflect the correct value.
  		this.isDefaultPrevented = src.defaultPrevented ||
  				src.defaultPrevented === undefined &&
  
  				// Support: Android <=2.3 only
  				src.returnValue === false ?
  			returnTrue :
  			returnFalse;
  
  		// Create target properties
  		// Support: Safari <=6 - 7 only
  		// Target should not be a text node (#504, #13143)
  		this.target = ( src.target && src.target.nodeType === 3 ) ?
  			src.target.parentNode :
  			src.target;
  
  		this.currentTarget = src.currentTarget;
  		this.relatedTarget = src.relatedTarget;
  
  	// 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
  // https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
  jQuery.Event.prototype = {
  	constructor: jQuery.Event,
  	isDefaultPrevented: returnFalse,
  	isPropagationStopped: returnFalse,
  	isImmediatePropagationStopped: returnFalse,
  	isSimulated: false,
  
  	preventDefault: function() {
  		var e = this.originalEvent;
  
  		this.isDefaultPrevented = returnTrue;
  
  		if ( e && !this.isSimulated ) {
  			e.preventDefault();
  		}
  	},
  	stopPropagation: function() {
  		var e = this.originalEvent;
  
  		this.isPropagationStopped = returnTrue;
  
  		if ( e && !this.isSimulated ) {
  			e.stopPropagation();
  		}
  	},
  	stopImmediatePropagation: function() {
  		var e = this.originalEvent;
  
  		this.isImmediatePropagationStopped = returnTrue;
  
  		if ( e && !this.isSimulated ) {
  			e.stopImmediatePropagation();
  		}
  
  		this.stopPropagation();
  	}
  };
  
  // Includes all common event props including KeyEvent and MouseEvent specific props
  jQuery.each( {
  	altKey: true,
  	bubbles: true,
  	cancelable: true,
  	changedTouches: true,
  	ctrlKey: true,
  	detail: true,
  	eventPhase: true,
  	metaKey: true,
  	pageX: true,
  	pageY: true,
  	shiftKey: true,
  	view: true,
  	"char": true,
  	charCode: true,
  	key: true,
  	keyCode: true,
  	button: true,
  	buttons: true,
  	clientX: true,
  	clientY: true,
  	offsetX: true,
  	offsetY: true,
  	pointerId: true,
  	pointerType: true,
  	screenX: true,
  	screenY: true,
  	targetTouches: true,
  	toElement: true,
  	touches: true,
  
  	which: function( event ) {
  		var button = event.button;
  
  		// Add which for key events
  		if ( event.which == null && rkeyEvent.test( event.type ) ) {
  			return event.charCode != null ? event.charCode : event.keyCode;
  		}
  
  		// Add which for click: 1 === left; 2 === middle; 3 === right
  		if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) {
  			if ( button & 1 ) {
  				return 1;
  			}
  
  			if ( button & 2 ) {
  				return 3;
  			}
  
  			if ( button & 4 ) {
  				return 2;
  			}
  
  			return 0;
  		}
  
  		return event.which;
  	}
  }, jQuery.event.addProp );
  
  // Create mouseenter/leave events using mouseover/out and event-time checks
  // so that event delegation works in jQuery.
  // Do the same for pointerenter/pointerleave and pointerover/pointerout
  //
  // Support: Safari 7 only
  // Safari sends mouseenter too often; see:
  // https://bugs.chromium.org/p/chromium/issues/detail?id=470258
  // for the description of the bug (it existed in older Chrome versions as well).
  jQuery.each( {
  	mouseenter: "mouseover",
  	mouseleave: "mouseout",
  	pointerenter: "pointerover",
  	pointerleave: "pointerout"
  }, 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 mouseenter/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;
  		}
  	};
  } );
  
  jQuery.fn.extend( {
  
  	on: function( types, selector, data, fn ) {
  		return on( this, types, selector, data, fn );
  	},
  	one: function( types, selector, data, fn ) {
  		return on( this, 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 );
  		} );
  	}
  } );
  
  
  var
  
  	/* eslint-disable max-len */
  
  	// See https://github.com/eslint/eslint/issues/3229
  	rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,
  
  	/* eslint-enable */
  
  	// Support: IE <=10 - 11, Edge 12 - 13
  	// In IE/Edge using regex groups here causes severe slowdowns.
  	// See https://connect.microsoft.com/IE/feedback/details/1736512/
  	rnoInnerhtml = /<script|<style|<link/i,
  
  	// checked="checked" or checked
  	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
  	rscriptTypeMasked = /^true\/(.*)/,
  	rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;
  
  // Prefer a tbody over its parent table for containing new rows
  function manipulationTarget( elem, content ) {
  	if ( nodeName( elem, "table" ) &&
  		nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) {
  
  		return jQuery( ">tbody", elem )[ 0 ] || elem;
  	}
  
  	return 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;
  }
  
  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 ( dataPriv.hasData( src ) ) {
  		pdataOld = dataPriv.access( src );
  		pdataCur = dataPriv.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 ( dataUser.hasData( src ) ) {
  		udataOld = dataUser.access( src );
  		udataCur = jQuery.extend( {}, udataOld );
  
  		dataUser.set( dest, udataCur );
  	}
  }
  
  // Fix IE bugs, see support tests
  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;
  	}
  }
  
  function domManip( collection, args, callback, ignored ) {
  
  	// Flatten any nested arrays
  	args = concat.apply( [], args );
  
  	var fragment, first, scripts, hasScripts, node, doc,
  		i = 0,
  		l = collection.length,
  		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 collection.each( function( index ) {
  			var self = collection.eq( index );
  			if ( isFunction ) {
  				args[ 0 ] = value.call( this, index, self.html() );
  			}
  			domManip( self, args, callback, ignored );
  		} );
  	}
  
  	if ( l ) {
  		fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );
  		first = fragment.firstChild;
  
  		if ( fragment.childNodes.length === 1 ) {
  			fragment = first;
  		}
  
  		// Require either new content or an interest in ignored elements to invoke the callback
  		if ( first || ignored ) {
  			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: Android <=4.0 only, PhantomJS 1 only
  						// push.apply(_, arraylike) throws on ancient WebKit
  						jQuery.merge( scripts, getAll( node, "script" ) );
  					}
  				}
  
  				callback.call( collection[ 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 || "" ) &&
  						!dataPriv.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 {
  							DOMEval( node.textContent.replace( rcleanScript, "" ), doc );
  						}
  					}
  				}
  			}
  		}
  	}
  
  	return collection;
  }
  
  function remove( elem, selector, keepData ) {
  	var node,
  		nodes = selector ? jQuery.filter( selector, elem ) : elem,
  		i = 0;
  
  	for ( ; ( node = nodes[ i ] ) != null; i++ ) {
  		if ( !keepData && node.nodeType === 1 ) {
  			jQuery.cleanData( getAll( node ) );
  		}
  
  		if ( node.parentNode ) {
  			if ( keepData && jQuery.contains( node.ownerDocument, node ) ) {
  				setGlobalEval( getAll( node, "script" ) );
  			}
  			node.parentNode.removeChild( node );
  		}
  	}
  
  	return elem;
  }
  
  jQuery.extend( {
  	htmlPrefilter: function( html ) {
  		return html.replace( rxhtmlTag, "<$1></$2>" );
  	},
  
  	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
  		var i, l, srcElements, destElements,
  			clone = elem.cloneNode( true ),
  			inPage = jQuery.contains( elem.ownerDocument, elem );
  
  		// Fix IE cloning issues
  		if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
  				!jQuery.isXMLDoc( elem ) ) {
  
  			// We eschew Sizzle here for performance reasons: https://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;
  	},
  
  	cleanData: function( elems ) {
  		var data, elem, type,
  			special = jQuery.event.special,
  			i = 0;
  
  		for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {
  			if ( acceptData( elem ) ) {
  				if ( ( data = elem[ dataPriv.expando ] ) ) {
  					if ( data.events ) {
  						for ( type in data.events ) {
  							if ( special[ type ] ) {
  								jQuery.event.remove( elem, type );
  
  							// This is a shortcut to avoid jQuery.event.remove's overhead
  							} else {
  								jQuery.removeEvent( elem, type, data.handle );
  							}
  						}
  					}
  
  					// Support: Chrome <=35 - 45+
  					// Assign undefined instead of using delete, see Data#remove
  					elem[ dataPriv.expando ] = undefined;
  				}
  				if ( elem[ dataUser.expando ] ) {
  
  					// Support: Chrome <=35 - 45+
  					// Assign undefined instead of using delete, see Data#remove
  					elem[ dataUser.expando ] = undefined;
  				}
  			}
  		}
  	}
  } );
  
  jQuery.fn.extend( {
  	detach: function( selector ) {
  		return remove( this, selector, true );
  	},
  
  	remove: function( selector ) {
  		return remove( this, selector );
  	},
  
  	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 domManip( this, arguments, function( elem ) {
  			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
  				var target = manipulationTarget( this, elem );
  				target.appendChild( elem );
  			}
  		} );
  	},
  
  	prepend: function() {
  		return domManip( this, 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 domManip( this, arguments, function( elem ) {
  			if ( this.parentNode ) {
  				this.parentNode.insertBefore( elem, this );
  			}
  		} );
  	},
  
  	after: function() {
  		return domManip( this, arguments, function( elem ) {
  			if ( this.parentNode ) {
  				this.parentNode.insertBefore( elem, this.nextSibling );
  			}
  		} );
  	},
  
  	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 = jQuery.htmlPrefilter( value );
  
  				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 ignored = [];
  
  		// Make the changes, replacing each non-ignored context element with the new content
  		return domManip( this, arguments, function( elem ) {
  			var parent = this.parentNode;
  
  			if ( jQuery.inArray( this, ignored ) < 0 ) {
  				jQuery.cleanData( getAll( this ) );
  				if ( parent ) {
  					parent.replaceChild( elem, this );
  				}
  			}
  
  		// Force callback invocation
  		}, ignored );
  	}
  } );
  
  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: Android <=4.0 only, PhantomJS 1 only
  			// .get() because push.apply(_, arraylike) throws on ancient WebKit
  			push.apply( ret, elems.get() );
  		}
  
  		return this.pushStack( ret );
  	};
  } );
  var rmargin = ( /^margin/ );
  
  var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
  
  var getStyles = function( elem ) {
  
  		// Support: IE <=11 only, Firefox <=30 (#15098, #14150)
  		// IE throws on elements created in popups
  		// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
  		var view = elem.ownerDocument.defaultView;
  
  		if ( !view || !view.opener ) {
  			view = window;
  		}
  
  		return view.getComputedStyle( elem );
  	};
  
  
  
  ( function() {
  
  	// Executing both pixelPosition & boxSizingReliable tests require only one layout
  	// so they're executed at the same time to save the second computation.
  	function computeStyleTests() {
  
  		// This is a singleton, we need to execute it only once
  		if ( !div ) {
  			return;
  		}
  
  		div.style.cssText =
  			"box-sizing:border-box;" +
  			"position:relative;display:block;" +
  			"margin:auto;border:1px;padding:1px;" +
  			"top:1%;width:50%";
  		div.innerHTML = "";
  		documentElement.appendChild( container );
  
  		var divStyle = window.getComputedStyle( div );
  		pixelPositionVal = divStyle.top !== "1%";
  
  		// Support: Android 4.0 - 4.3 only, Firefox <=3 - 44
  		reliableMarginLeftVal = divStyle.marginLeft === "2px";
  		boxSizingReliableVal = divStyle.width === "4px";
  
  		// Support: Android 4.0 - 4.3 only
  		// Some styles come back with percentage values, even though they shouldn't
  		div.style.marginRight = "50%";
  		pixelMarginRightVal = divStyle.marginRight === "4px";
  
  		documentElement.removeChild( container );
  
  		// Nullify the div so it wouldn't be stored in the memory and
  		// it will also be a sign that checks already performed
  		div = null;
  	}
  
  	var pixelPositionVal, boxSizingReliableVal, pixelMarginRightVal, reliableMarginLeftVal,
  		container = document.createElement( "div" ),
  		div = document.createElement( "div" );
  
  	// Finish early in limited (non-browser) environments
  	if ( !div.style ) {
  		return;
  	}
  
  	// Support: IE <=9 - 11 only
  	// Style of cloned element affects source element cloned (#8908)
  	div.style.backgroundClip = "content-box";
  	div.cloneNode( true ).style.backgroundClip = "";
  	support.clearCloneStyle = div.style.backgroundClip === "content-box";
  
  	container.style.cssText = "border:0;width:8px;height:0;top:0;left:-9999px;" +
  		"padding:0;margin-top:1px;position:absolute";
  	container.appendChild( div );
  
  	jQuery.extend( support, {
  		pixelPosition: function() {
  			computeStyleTests();
  			return pixelPositionVal;
  		},
  		boxSizingReliable: function() {
  			computeStyleTests();
  			return boxSizingReliableVal;
  		},
  		pixelMarginRight: function() {
  			computeStyleTests();
  			return pixelMarginRightVal;
  		},
  		reliableMarginLeft: function() {
  			computeStyleTests();
  			return reliableMarginLeftVal;
  		}
  	} );
  } )();
  
  
  function curCSS( elem, name, computed ) {
  	var width, minWidth, maxWidth, ret,
  
  		// Support: Firefox 51+
  		// Retrieving style before computed somehow
  		// fixes an issue with getting wrong values
  		// on detached elements
  		style = elem.style;
  
  	computed = computed || getStyles( elem );
  
  	// getPropertyValue is needed for:
  	//   .css('filter') (IE 9 only, #12537)
  	//   .css('--customProperty) (#3144)
  	if ( computed ) {
  		ret = computed.getPropertyValue( name ) || computed[ name ];
  
  		if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
  			ret = jQuery.style( elem, name );
  		}
  
  		// A tribute to the "awesome hack by Dean Edwards"
  		// Android Browser returns percentage for some values,
  		// but width seems to be reliably pixels.
  		// This is against the CSSOM draft spec:
  		// https://drafts.csswg.org/cssom/#resolved-values
  		if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) {
  
  			// Remember the original values
  			width = style.width;
  			minWidth = style.minWidth;
  			maxWidth = style.maxWidth;
  
  			// Put in the new values to get a computed value out
  			style.minWidth = style.maxWidth = style.width = ret;
  			ret = computed.width;
  
  			// Revert the changed values
  			style.width = width;
  			style.minWidth = minWidth;
  			style.maxWidth = maxWidth;
  		}
  	}
  
  	return ret !== undefined ?
  
  		// Support: IE <=9 - 11 only
  		// IE returns zIndex value as an integer.
  		ret + "" :
  		ret;
  }
  
  
  function addGetHookIf( conditionFn, hookFn ) {
  
  	// Define the hook, we'll check on the first run if it's really needed.
  	return {
  		get: function() {
  			if ( conditionFn() ) {
  
  				// Hook not needed (or it's not possible to use it due
  				// to missing dependency), remove it.
  				delete this.get;
  				return;
  			}
  
  			// Hook needed; redefine it so that the support test is not executed again.
  			return ( this.get = hookFn ).apply( this, arguments );
  		}
  	};
  }
  
  
  var
  
  	// Swappable if display is none or starts with table
  	// except "table", "table-cell", or "table-caption"
  	// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
  	rdisplayswap = /^(none|table(?!-c[ea]).+)/,
  	rcustomProp = /^--/,
  	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
  	cssNormalTransform = {
  		letterSpacing: "0",
  		fontWeight: "400"
  	},
  
  	cssPrefixes = [ "Webkit", "Moz", "ms" ],
  	emptyStyle = document.createElement( "div" ).style;
  
  // Return a css property mapped to a potentially vendor prefixed property
  function vendorPropName( name ) {
  
  	// Shortcut for names that are not vendor prefixed
  	if ( name in emptyStyle ) {
  		return name;
  	}
  
  	// Check for vendor prefixed names
  	var capName = name[ 0 ].toUpperCase() + name.slice( 1 ),
  		i = cssPrefixes.length;
  
  	while ( i-- ) {
  		name = cssPrefixes[ i ] + capName;
  		if ( name in emptyStyle ) {
  			return name;
  		}
  	}
  }
  
  // Return a property mapped along what jQuery.cssProps suggests or to
  // a vendor prefixed property.
  function finalPropName( name ) {
  	var ret = jQuery.cssProps[ name ];
  	if ( !ret ) {
  		ret = jQuery.cssProps[ name ] = vendorPropName( name ) || name;
  	}
  	return ret;
  }
  
  function setPositiveNumber( elem, value, subtract ) {
  
  	// Any relative (+/-) values have already been
  	// normalized at this point
  	var matches = rcssNum.exec( value );
  	return matches ?
  
  		// Guard against undefined "subtract", e.g., when used as in cssHooks
  		Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) :
  		value;
  }
  
  function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
  	var i,
  		val = 0;
  
  	// If we already have the right measurement, avoid augmentation
  	if ( extra === ( isBorderBox ? "border" : "content" ) ) {
  		i = 4;
  
  	// Otherwise initialize for horizontal or vertical properties
  	} else {
  		i = name === "width" ? 1 : 0;
  	}
  
  	for ( ; i < 4; i += 2 ) {
  
  		// Both box models exclude margin, so add it if we want it
  		if ( extra === "margin" ) {
  			val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
  		}
  
  		if ( isBorderBox ) {
  
  			// border-box includes padding, so remove it if we want content
  			if ( extra === "content" ) {
  				val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
  			}
  
  			// At this point, extra isn't border nor margin, so remove border
  			if ( extra !== "margin" ) {
  				val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
  			}
  		} else {
  
  			// At this point, extra isn't content, so add padding
  			val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
  
  			// At this point, extra isn't content nor padding, so add border
  			if ( extra !== "padding" ) {
  				val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
  			}
  		}
  	}
  
  	return val;
  }
  
  function getWidthOrHeight( elem, name, extra ) {
  
  	// Start with computed style
  	var valueIsBorderBox,
  		styles = getStyles( elem ),
  		val = curCSS( elem, name, styles ),
  		isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
  
  	// Computed unit is not pixels. Stop here and return.
  	if ( rnumnonpx.test( val ) ) {
  		return val;
  	}
  
  	// Check for style in case a browser which returns unreliable values
  	// for getComputedStyle silently falls back to the reliable elem.style
  	valueIsBorderBox = isBorderBox &&
  		( support.boxSizingReliable() || val === elem.style[ name ] );
  
  	// Fall back to offsetWidth/Height when value is "auto"
  	// This happens for inline elements with no explicit setting (gh-3571)
  	if ( val === "auto" ) {
  		val = elem[ "offset" + name[ 0 ].toUpperCase() + name.slice( 1 ) ];
  	}
  
  	// Normalize "", auto, and prepare for extra
  	val = parseFloat( val ) || 0;
  
  	// Use the active box-sizing model to add/subtract irrelevant styles
  	return ( val +
  		augmentWidthOrHeight(
  			elem,
  			name,
  			extra || ( isBorderBox ? "border" : "content" ),
  			valueIsBorderBox,
  			styles
  		)
  	) + "px";
  }
  
  jQuery.extend( {
  
  	// Add in style property hooks for overriding the default
  	// behavior of getting and setting a style property
  	cssHooks: {
  		opacity: {
  			get: function( elem, computed ) {
  				if ( computed ) {
  
  					// We should always get a number back from opacity
  					var ret = curCSS( elem, "opacity" );
  					return ret === "" ? "1" : ret;
  				}
  			}
  		}
  	},
  
  	// Don't automatically add "px" to these possibly-unitless properties
  	cssNumber: {
  		"animationIterationCount": true,
  		"columnCount": true,
  		"fillOpacity": true,
  		"flexGrow": true,
  		"flexShrink": true,
  		"fontWeight": true,
  		"lineHeight": true,
  		"opacity": true,
  		"order": true,
  		"orphans": true,
  		"widows": true,
  		"zIndex": true,
  		"zoom": true
  	},
  
  	// Add in properties whose names you wish to fix before
  	// setting or getting the value
  	cssProps: {
  		"float": "cssFloat"
  	},
  
  	// Get and set the style property on a DOM Node
  	style: function( elem, name, value, extra ) {
  
  		// Don't set styles on text and comment nodes
  		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
  			return;
  		}
  
  		// Make sure that we're working with the right name
  		var ret, type, hooks,
  			origName = jQuery.camelCase( name ),
  			isCustomProp = rcustomProp.test( name ),
  			style = elem.style;
  
  		// Make sure that we're working with the right name. We don't
  		// want to query the value if it is a CSS custom property
  		// since they are user-defined.
  		if ( !isCustomProp ) {
  			name = finalPropName( origName );
  		}
  
  		// Gets hook for the prefixed version, then unprefixed version
  		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
  
  		// Check if we're setting a value
  		if ( value !== undefined ) {
  			type = typeof value;
  
  			// Convert "+=" or "-=" to relative numbers (#7345)
  			if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
  				value = adjustCSS( elem, name, ret );
  
  				// Fixes bug #9237
  				type = "number";
  			}
  
  			// Make sure that null and NaN values aren't set (#7116)
  			if ( value == null || value !== value ) {
  				return;
  			}
  
  			// If a number was passed in, add the unit (except for certain CSS properties)
  			if ( type === "number" ) {
  				value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
  			}
  
  			// background-* props affect original clone's values
  			if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
  				style[ name ] = "inherit";
  			}
  
  			// If a hook was provided, use that value, otherwise just set the specified value
  			if ( !hooks || !( "set" in hooks ) ||
  				( value = hooks.set( elem, value, extra ) ) !== undefined ) {
  
  				if ( isCustomProp ) {
  					style.setProperty( name, value );
  				} else {
  					style[ name ] = value;
  				}
  			}
  
  		} else {
  
  			// If a hook was provided get the non-computed value from there
  			if ( hooks && "get" in hooks &&
  				( ret = hooks.get( elem, false, extra ) ) !== undefined ) {
  
  				return ret;
  			}
  
  			// Otherwise just get the value from the style object
  			return style[ name ];
  		}
  	},
  
  	css: function( elem, name, extra, styles ) {
  		var val, num, hooks,
  			origName = jQuery.camelCase( name ),
  			isCustomProp = rcustomProp.test( name );
  
  		// Make sure that we're working with the right name. We don't
  		// want to modify the value if it is a CSS custom property
  		// since they are user-defined.
  		if ( !isCustomProp ) {
  			name = finalPropName( origName );
  		}
  
  		// Try prefixed name followed by the unprefixed name
  		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
  
  		// If a hook was provided get the computed value from there
  		if ( hooks && "get" in hooks ) {
  			val = hooks.get( elem, true, extra );
  		}
  
  		// Otherwise, if a way to get the computed value exists, use that
  		if ( val === undefined ) {
  			val = curCSS( elem, name, styles );
  		}
  
  		// Convert "normal" to computed value
  		if ( val === "normal" && name in cssNormalTransform ) {
  			val = cssNormalTransform[ name ];
  		}
  
  		// Make numeric if forced or a qualifier was provided and val looks numeric
  		if ( extra === "" || extra ) {
  			num = parseFloat( val );
  			return extra === true || isFinite( num ) ? num || 0 : val;
  		}
  
  		return val;
  	}
  } );
  
  jQuery.each( [ "height", "width" ], function( i, name ) {
  	jQuery.cssHooks[ name ] = {
  		get: function( elem, computed, extra ) {
  			if ( computed ) {
  
  				// Certain elements can have dimension info if we invisibly show them
  				// but it must have a current display style that would benefit
  				return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
  
  					// Support: Safari 8+
  					// Table columns in Safari have non-zero offsetWidth & zero
  					// getBoundingClientRect().width unless display is changed.
  					// Support: IE <=11 only
  					// Running getBoundingClientRect on a disconnected node
  					// in IE throws an error.
  					( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
  						swap( elem, cssShow, function() {
  							return getWidthOrHeight( elem, name, extra );
  						} ) :
  						getWidthOrHeight( elem, name, extra );
  			}
  		},
  
  		set: function( elem, value, extra ) {
  			var matches,
  				styles = extra && getStyles( elem ),
  				subtract = extra && augmentWidthOrHeight(
  					elem,
  					name,
  					extra,
  					jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
  					styles
  				);
  
  			// Convert to pixels if value adjustment is needed
  			if ( subtract && ( matches = rcssNum.exec( value ) ) &&
  				( matches[ 3 ] || "px" ) !== "px" ) {
  
  				elem.style[ name ] = value;
  				value = jQuery.css( elem, name );
  			}
  
  			return setPositiveNumber( elem, value, subtract );
  		}
  	};
  } );
  
  jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
  	function( elem, computed ) {
  		if ( computed ) {
  			return ( parseFloat( curCSS( elem, "marginLeft" ) ) ||
  				elem.getBoundingClientRect().left -
  					swap( elem, { marginLeft: 0 }, function() {
  						return elem.getBoundingClientRect().left;
  					} )
  				) + "px";
  		}
  	}
  );
  
  // These hooks are used by animate to expand properties
  jQuery.each( {
  	margin: "",
  	padding: "",
  	border: "Width"
  }, function( prefix, suffix ) {
  	jQuery.cssHooks[ prefix + suffix ] = {
  		expand: function( value ) {
  			var i = 0,
  				expanded = {},
  
  				// Assumes a single number if not a string
  				parts = typeof value === "string" ? value.split( " " ) : [ value ];
  
  			for ( ; i < 4; i++ ) {
  				expanded[ prefix + cssExpand[ i ] + suffix ] =
  					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
  			}
  
  			return expanded;
  		}
  	};
  
  	if ( !rmargin.test( prefix ) ) {
  		jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
  	}
  } );
  
  jQuery.fn.extend( {
  	css: function( name, value ) {
  		return access( this, function( elem, name, value ) {
  			var styles, len,
  				map = {},
  				i = 0;
  
  			if ( Array.isArray( name ) ) {
  				styles = getStyles( elem );
  				len = name.length;
  
  				for ( ; i < len; i++ ) {
  					map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
  				}
  
  				return map;
  			}
  
  			return value !== undefined ?
  				jQuery.style( elem, name, value ) :
  				jQuery.css( elem, name );
  		}, name, value, arguments.length > 1 );
  	}
  } );
  
  
  function Tween( elem, options, prop, end, easing ) {
  	return new Tween.prototype.init( elem, options, prop, end, easing );
  }
  jQuery.Tween = Tween;
  
  Tween.prototype = {
  	constructor: Tween,
  	init: function( elem, options, prop, end, easing, unit ) {
  		this.elem = elem;
  		this.prop = prop;
  		this.easing = easing || jQuery.easing._default;
  		this.options = options;
  		this.start = this.now = this.cur();
  		this.end = end;
  		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
  	},
  	cur: function() {
  		var hooks = Tween.propHooks[ this.prop ];
  
  		return hooks && hooks.get ?
  			hooks.get( this ) :
  			Tween.propHooks._default.get( this );
  	},
  	run: function( percent ) {
  		var eased,
  			hooks = Tween.propHooks[ this.prop ];
  
  		if ( this.options.duration ) {
  			this.pos = eased = jQuery.easing[ this.easing ](
  				percent, this.options.duration * percent, 0, 1, this.options.duration
  			);
  		} else {
  			this.pos = eased = percent;
  		}
  		this.now = ( this.end - this.start ) * eased + this.start;
  
  		if ( this.options.step ) {
  			this.options.step.call( this.elem, this.now, this );
  		}
  
  		if ( hooks && hooks.set ) {
  			hooks.set( this );
  		} else {
  			Tween.propHooks._default.set( this );
  		}
  		return this;
  	}
  };
  
  Tween.prototype.init.prototype = Tween.prototype;
  
  Tween.propHooks = {
  	_default: {
  		get: function( tween ) {
  			var result;
  
  			// Use a property on the element directly when it is not a DOM element,
  			// or when there is no matching style property that exists.
  			if ( tween.elem.nodeType !== 1 ||
  				tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) {
  				return tween.elem[ tween.prop ];
  			}
  
  			// Passing an empty string as a 3rd parameter to .css will automatically
  			// attempt a parseFloat and fallback to a string if the parse fails.
  			// Simple values such as "10px" are parsed to Float;
  			// complex values such as "rotate(1rad)" are returned as-is.
  			result = jQuery.css( tween.elem, tween.prop, "" );
  
  			// Empty strings, null, undefined and "auto" are converted to 0.
  			return !result || result === "auto" ? 0 : result;
  		},
  		set: function( tween ) {
  
  			// Use step hook for back compat.
  			// Use cssHook if its there.
  			// Use .style if available and use plain properties where available.
  			if ( jQuery.fx.step[ tween.prop ] ) {
  				jQuery.fx.step[ tween.prop ]( tween );
  			} else if ( tween.elem.nodeType === 1 &&
  				( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null ||
  					jQuery.cssHooks[ tween.prop ] ) ) {
  				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
  			} else {
  				tween.elem[ tween.prop ] = tween.now;
  			}
  		}
  	}
  };
  
  // Support: IE <=9 only
  // Panic based approach to setting things on disconnected nodes
  Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
  	set: function( tween ) {
  		if ( tween.elem.nodeType && tween.elem.parentNode ) {
  			tween.elem[ tween.prop ] = tween.now;
  		}
  	}
  };
  
  jQuery.easing = {
  	linear: function( p ) {
  		return p;
  	},
  	swing: function( p ) {
  		return 0.5 - Math.cos( p * Math.PI ) / 2;
  	},
  	_default: "swing"
  };
  
  jQuery.fx = Tween.prototype.init;
  
  // Back compat <1.8 extension point
  jQuery.fx.step = {};
  
  
  
  
  var
  	fxNow, inProgress,
  	rfxtypes = /^(?:toggle|show|hide)$/,
  	rrun = /queueHooks$/;
  
  function schedule() {
  	if ( inProgress ) {
  		if ( document.hidden === false && window.requestAnimationFrame ) {
  			window.requestAnimationFrame( schedule );
  		} else {
  			window.setTimeout( schedule, jQuery.fx.interval );
  		}
  
  		jQuery.fx.tick();
  	}
  }
  
  // Animations created synchronously will run synchronously
  function createFxNow() {
  	window.setTimeout( function() {
  		fxNow = undefined;
  	} );
  	return ( fxNow = jQuery.now() );
  }
  
  // Generate parameters to create a standard animation
  function genFx( type, includeWidth ) {
  	var which,
  		i = 0,
  		attrs = { height: type };
  
  	// If we include width, step value is 1 to do all cssExpand values,
  	// otherwise step value is 2 to skip over Left and Right
  	includeWidth = includeWidth ? 1 : 0;
  	for ( ; i < 4; i += 2 - includeWidth ) {
  		which = cssExpand[ i ];
  		attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
  	}
  
  	if ( includeWidth ) {
  		attrs.opacity = attrs.width = type;
  	}
  
  	return attrs;
  }
  
  function createTween( value, prop, animation ) {
  	var tween,
  		collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ),
  		index = 0,
  		length = collection.length;
  	for ( ; index < length; index++ ) {
  		if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) {
  
  			// We're done with this property
  			return tween;
  		}
  	}
  }
  
  function defaultPrefilter( elem, props, opts ) {
  	var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display,
  		isBox = "width" in props || "height" in props,
  		anim = this,
  		orig = {},
  		style = elem.style,
  		hidden = elem.nodeType && isHiddenWithinTree( elem ),
  		dataShow = dataPriv.get( elem, "fxshow" );
  
  	// Queue-skipping animations hijack the fx hooks
  	if ( !opts.queue ) {
  		hooks = jQuery._queueHooks( elem, "fx" );
  		if ( hooks.unqueued == null ) {
  			hooks.unqueued = 0;
  			oldfire = hooks.empty.fire;
  			hooks.empty.fire = function() {
  				if ( !hooks.unqueued ) {
  					oldfire();
  				}
  			};
  		}
  		hooks.unqueued++;
  
  		anim.always( function() {
  
  			// Ensure the complete handler is called before this completes
  			anim.always( function() {
  				hooks.unqueued--;
  				if ( !jQuery.queue( elem, "fx" ).length ) {
  					hooks.empty.fire();
  				}
  			} );
  		} );
  	}
  
  	// Detect show/hide animations
  	for ( prop in props ) {
  		value = props[ prop ];
  		if ( rfxtypes.test( value ) ) {
  			delete props[ prop ];
  			toggle = toggle || value === "toggle";
  			if ( value === ( hidden ? "hide" : "show" ) ) {
  
  				// Pretend to be hidden if this is a "show" and
  				// there is still data from a stopped show/hide
  				if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
  					hidden = true;
  
  				// Ignore all other no-op show/hide data
  				} else {
  					continue;
  				}
  			}
  			orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
  		}
  	}
  
  	// Bail out if this is a no-op like .hide().hide()
  	propTween = !jQuery.isEmptyObject( props );
  	if ( !propTween && jQuery.isEmptyObject( orig ) ) {
  		return;
  	}
  
  	// Restrict "overflow" and "display" styles during box animations
  	if ( isBox && elem.nodeType === 1 ) {
  
  		// Support: IE <=9 - 11, Edge 12 - 13
  		// Record all 3 overflow attributes because IE does not infer the shorthand
  		// from identically-valued overflowX and overflowY
  		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
  
  		// Identify a display type, preferring old show/hide data over the CSS cascade
  		restoreDisplay = dataShow && dataShow.display;
  		if ( restoreDisplay == null ) {
  			restoreDisplay = dataPriv.get( elem, "display" );
  		}
  		display = jQuery.css( elem, "display" );
  		if ( display === "none" ) {
  			if ( restoreDisplay ) {
  				display = restoreDisplay;
  			} else {
  
  				// Get nonempty value(s) by temporarily forcing visibility
  				showHide( [ elem ], true );
  				restoreDisplay = elem.style.display || restoreDisplay;
  				display = jQuery.css( elem, "display" );
  				showHide( [ elem ] );
  			}
  		}
  
  		// Animate inline elements as inline-block
  		if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) {
  			if ( jQuery.css( elem, "float" ) === "none" ) {
  
  				// Restore the original display value at the end of pure show/hide animations
  				if ( !propTween ) {
  					anim.done( function() {
  						style.display = restoreDisplay;
  					} );
  					if ( restoreDisplay == null ) {
  						display = style.display;
  						restoreDisplay = display === "none" ? "" : display;
  					}
  				}
  				style.display = "inline-block";
  			}
  		}
  	}
  
  	if ( opts.overflow ) {
  		style.overflow = "hidden";
  		anim.always( function() {
  			style.overflow = opts.overflow[ 0 ];
  			style.overflowX = opts.overflow[ 1 ];
  			style.overflowY = opts.overflow[ 2 ];
  		} );
  	}
  
  	// Implement show/hide animations
  	propTween = false;
  	for ( prop in orig ) {
  
  		// General show/hide setup for this element animation
  		if ( !propTween ) {
  			if ( dataShow ) {
  				if ( "hidden" in dataShow ) {
  					hidden = dataShow.hidden;
  				}
  			} else {
  				dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } );
  			}
  
  			// Store hidden/visible for toggle so `.stop().toggle()` "reverses"
  			if ( toggle ) {
  				dataShow.hidden = !hidden;
  			}
  
  			// Show elements before animating them
  			if ( hidden ) {
  				showHide( [ elem ], true );
  			}
  
  			/* eslint-disable no-loop-func */
  
  			anim.done( function() {
  
  			/* eslint-enable no-loop-func */
  
  				// The final step of a "hide" animation is actually hiding the element
  				if ( !hidden ) {
  					showHide( [ elem ] );
  				}
  				dataPriv.remove( elem, "fxshow" );
  				for ( prop in orig ) {
  					jQuery.style( elem, prop, orig[ prop ] );
  				}
  			} );
  		}
  
  		// Per-property setup
  		propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
  		if ( !( prop in dataShow ) ) {
  			dataShow[ prop ] = propTween.start;
  			if ( hidden ) {
  				propTween.end = propTween.start;
  				propTween.start = 0;
  			}
  		}
  	}
  }
  
  function propFilter( props, specialEasing ) {
  	var index, name, easing, value, hooks;
  
  	// camelCase, specialEasing and expand cssHook pass
  	for ( index in props ) {
  		name = jQuery.camelCase( index );
  		easing = specialEasing[ name ];
  		value = props[ index ];
  		if ( Array.isArray( value ) ) {
  			easing = value[ 1 ];
  			value = props[ index ] = value[ 0 ];
  		}
  
  		if ( index !== name ) {
  			props[ name ] = value;
  			delete props[ index ];
  		}
  
  		hooks = jQuery.cssHooks[ name ];
  		if ( hooks && "expand" in hooks ) {
  			value = hooks.expand( value );
  			delete props[ name ];
  
  			// Not quite $.extend, this won't overwrite existing keys.
  			// Reusing 'index' because we have the correct "name"
  			for ( index in value ) {
  				if ( !( index in props ) ) {
  					props[ index ] = value[ index ];
  					specialEasing[ index ] = easing;
  				}
  			}
  		} else {
  			specialEasing[ name ] = easing;
  		}
  	}
  }
  
  function Animation( elem, properties, options ) {
  	var result,
  		stopped,
  		index = 0,
  		length = Animation.prefilters.length,
  		deferred = jQuery.Deferred().always( function() {
  
  			// Don't match elem in the :animated selector
  			delete tick.elem;
  		} ),
  		tick = function() {
  			if ( stopped ) {
  				return false;
  			}
  			var currentTime = fxNow || createFxNow(),
  				remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
  
  				// Support: Android 2.3 only
  				// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)
  				temp = remaining / animation.duration || 0,
  				percent = 1 - temp,
  				index = 0,
  				length = animation.tweens.length;
  
  			for ( ; index < length; index++ ) {
  				animation.tweens[ index ].run( percent );
  			}
  
  			deferred.notifyWith( elem, [ animation, percent, remaining ] );
  
  			// If there's more to do, yield
  			if ( percent < 1 && length ) {
  				return remaining;
  			}
  
  			// If this was an empty animation, synthesize a final progress notification
  			if ( !length ) {
  				deferred.notifyWith( elem, [ animation, 1, 0 ] );
  			}
  
  			// Resolve the animation and report its conclusion
  			deferred.resolveWith( elem, [ animation ] );
  			return false;
  		},
  		animation = deferred.promise( {
  			elem: elem,
  			props: jQuery.extend( {}, properties ),
  			opts: jQuery.extend( true, {
  				specialEasing: {},
  				easing: jQuery.easing._default
  			}, options ),
  			originalProperties: properties,
  			originalOptions: options,
  			startTime: fxNow || createFxNow(),
  			duration: options.duration,
  			tweens: [],
  			createTween: function( prop, end ) {
  				var tween = jQuery.Tween( elem, animation.opts, prop, end,
  						animation.opts.specialEasing[ prop ] || animation.opts.easing );
  				animation.tweens.push( tween );
  				return tween;
  			},
  			stop: function( gotoEnd ) {
  				var index = 0,
  
  					// If we are going to the end, we want to run all the tweens
  					// otherwise we skip this part
  					length = gotoEnd ? animation.tweens.length : 0;
  				if ( stopped ) {
  					return this;
  				}
  				stopped = true;
  				for ( ; index < length; index++ ) {
  					animation.tweens[ index ].run( 1 );
  				}
  
  				// Resolve when we played the last frame; otherwise, reject
  				if ( gotoEnd ) {
  					deferred.notifyWith( elem, [ animation, 1, 0 ] );
  					deferred.resolveWith( elem, [ animation, gotoEnd ] );
  				} else {
  					deferred.rejectWith( elem, [ animation, gotoEnd ] );
  				}
  				return this;
  			}
  		} ),
  		props = animation.props;
  
  	propFilter( props, animation.opts.specialEasing );
  
  	for ( ; index < length; index++ ) {
  		result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );
  		if ( result ) {
  			if ( jQuery.isFunction( result.stop ) ) {
  				jQuery._queueHooks( animation.elem, animation.opts.queue ).stop =
  					jQuery.proxy( result.stop, result );
  			}
  			return result;
  		}
  	}
  
  	jQuery.map( props, createTween, animation );
  
  	if ( jQuery.isFunction( animation.opts.start ) ) {
  		animation.opts.start.call( elem, animation );
  	}
  
  	// Attach callbacks from options
  	animation
  		.progress( animation.opts.progress )
  		.done( animation.opts.done, animation.opts.complete )
  		.fail( animation.opts.fail )
  		.always( animation.opts.always );
  
  	jQuery.fx.timer(
  		jQuery.extend( tick, {
  			elem: elem,
  			anim: animation,
  			queue: animation.opts.queue
  		} )
  	);
  
  	return animation;
  }
  
  jQuery.Animation = jQuery.extend( Animation, {
  
  	tweeners: {
  		"*": [ function( prop, value ) {
  			var tween = this.createTween( prop, value );
  			adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
  			return tween;
  		} ]
  	},
  
  	tweener: function( props, callback ) {
  		if ( jQuery.isFunction( props ) ) {
  			callback = props;
  			props = [ "*" ];
  		} else {
  			props = props.match( rnothtmlwhite );
  		}
  
  		var prop,
  			index = 0,
  			length = props.length;
  
  		for ( ; index < length; index++ ) {
  			prop = props[ index ];
  			Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || [];
  			Animation.tweeners[ prop ].unshift( callback );
  		}
  	},
  
  	prefilters: [ defaultPrefilter ],
  
  	prefilter: function( callback, prepend ) {
  		if ( prepend ) {
  			Animation.prefilters.unshift( callback );
  		} else {
  			Animation.prefilters.push( callback );
  		}
  	}
  } );
  
  jQuery.speed = function( speed, easing, fn ) {
  	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
  		complete: fn || !fn && easing ||
  			jQuery.isFunction( speed ) && speed,
  		duration: speed,
  		easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
  	};
  
  	// Go to the end state if fx are off
  	if ( jQuery.fx.off ) {
  		opt.duration = 0;
  
  	} else {
  		if ( typeof opt.duration !== "number" ) {
  			if ( opt.duration in jQuery.fx.speeds ) {
  				opt.duration = jQuery.fx.speeds[ opt.duration ];
  
  			} else {
  				opt.duration = jQuery.fx.speeds._default;
  			}
  		}
  	}
  
  	// Normalize opt.queue - true/undefined/null -> "fx"
  	if ( opt.queue == null || opt.queue === true ) {
  		opt.queue = "fx";
  	}
  
  	// Queueing
  	opt.old = opt.complete;
  
  	opt.complete = function() {
  		if ( jQuery.isFunction( opt.old ) ) {
  			opt.old.call( this );
  		}
  
  		if ( opt.queue ) {
  			jQuery.dequeue( this, opt.queue );
  		}
  	};
  
  	return opt;
  };
  
  jQuery.fn.extend( {
  	fadeTo: function( speed, to, easing, callback ) {
  
  		// Show any hidden elements after setting opacity to 0
  		return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show()
  
  			// Animate to the value specified
  			.end().animate( { opacity: to }, speed, easing, callback );
  	},
  	animate: function( prop, speed, easing, callback ) {
  		var empty = jQuery.isEmptyObject( prop ),
  			optall = jQuery.speed( speed, easing, callback ),
  			doAnimation = function() {
  
  				// Operate on a copy of prop so per-property easing won't be lost
  				var anim = Animation( this, jQuery.extend( {}, prop ), optall );
  
  				// Empty animations, or finishing resolves immediately
  				if ( empty || dataPriv.get( this, "finish" ) ) {
  					anim.stop( true );
  				}
  			};
  			doAnimation.finish = doAnimation;
  
  		return empty || optall.queue === false ?
  			this.each( doAnimation ) :
  			this.queue( optall.queue, doAnimation );
  	},
  	stop: function( type, clearQueue, gotoEnd ) {
  		var stopQueue = function( hooks ) {
  			var stop = hooks.stop;
  			delete hooks.stop;
  			stop( gotoEnd );
  		};
  
  		if ( typeof type !== "string" ) {
  			gotoEnd = clearQueue;
  			clearQueue = type;
  			type = undefined;
  		}
  		if ( clearQueue && type !== false ) {
  			this.queue( type || "fx", [] );
  		}
  
  		return this.each( function() {
  			var dequeue = true,
  				index = type != null && type + "queueHooks",
  				timers = jQuery.timers,
  				data = dataPriv.get( this );
  
  			if ( index ) {
  				if ( data[ index ] && data[ index ].stop ) {
  					stopQueue( data[ index ] );
  				}
  			} else {
  				for ( index in data ) {
  					if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
  						stopQueue( data[ index ] );
  					}
  				}
  			}
  
  			for ( index = timers.length; index--; ) {
  				if ( timers[ index ].elem === this &&
  					( type == null || timers[ index ].queue === type ) ) {
  
  					timers[ index ].anim.stop( gotoEnd );
  					dequeue = false;
  					timers.splice( index, 1 );
  				}
  			}
  
  			// Start the next in the queue if the last step wasn't forced.
  			// Timers currently will call their complete callbacks, which
  			// will dequeue but only if they were gotoEnd.
  			if ( dequeue || !gotoEnd ) {
  				jQuery.dequeue( this, type );
  			}
  		} );
  	},
  	finish: function( type ) {
  		if ( type !== false ) {
  			type = type || "fx";
  		}
  		return this.each( function() {
  			var index,
  				data = dataPriv.get( this ),
  				queue = data[ type + "queue" ],
  				hooks = data[ type + "queueHooks" ],
  				timers = jQuery.timers,
  				length = queue ? queue.length : 0;
  
  			// Enable finishing flag on private data
  			data.finish = true;
  
  			// Empty the queue first
  			jQuery.queue( this, type, [] );
  
  			if ( hooks && hooks.stop ) {
  				hooks.stop.call( this, true );
  			}
  
  			// Look for any active animations, and finish them
  			for ( index = timers.length; index--; ) {
  				if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
  					timers[ index ].anim.stop( true );
  					timers.splice( index, 1 );
  				}
  			}
  
  			// Look for any animations in the old queue and finish them
  			for ( index = 0; index < length; index++ ) {
  				if ( queue[ index ] && queue[ index ].finish ) {
  					queue[ index ].finish.call( this );
  				}
  			}
  
  			// Turn off finishing flag
  			delete data.finish;
  		} );
  	}
  } );
  
  jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) {
  	var cssFn = jQuery.fn[ name ];
  	jQuery.fn[ name ] = function( speed, easing, callback ) {
  		return speed == null || typeof speed === "boolean" ?
  			cssFn.apply( this, arguments ) :
  			this.animate( genFx( name, true ), speed, easing, callback );
  	};
  } );
  
  // Generate shortcuts for custom animations
  jQuery.each( {
  	slideDown: genFx( "show" ),
  	slideUp: genFx( "hide" ),
  	slideToggle: genFx( "toggle" ),
  	fadeIn: { opacity: "show" },
  	fadeOut: { opacity: "hide" },
  	fadeToggle: { opacity: "toggle" }
  }, function( name, props ) {
  	jQuery.fn[ name ] = function( speed, easing, callback ) {
  		return this.animate( props, speed, easing, callback );
  	};
  } );
  
  jQuery.timers = [];
  jQuery.fx.tick = function() {
  	var timer,
  		i = 0,
  		timers = jQuery.timers;
  
  	fxNow = jQuery.now();
  
  	for ( ; i < timers.length; i++ ) {
  		timer = timers[ i ];
  
  		// Run the timer and safely remove it when done (allowing for external removal)
  		if ( !timer() && timers[ i ] === timer ) {
  			timers.splice( i--, 1 );
  		}
  	}
  
  	if ( !timers.length ) {
  		jQuery.fx.stop();
  	}
  	fxNow = undefined;
  };
  
  jQuery.fx.timer = function( timer ) {
  	jQuery.timers.push( timer );
  	jQuery.fx.start();
  };
  
  jQuery.fx.interval = 13;
  jQuery.fx.start = function() {
  	if ( inProgress ) {
  		return;
  	}
  
  	inProgress = true;
  	schedule();
  };
  
  jQuery.fx.stop = function() {
  	inProgress = null;
  };
  
  jQuery.fx.speeds = {
  	slow: 600,
  	fast: 200,
  
  	// Default speed
  	_default: 400
  };
  
  
  // Based off of the plugin by Clint Helfers, with permission.
  // https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/
  jQuery.fn.delay = function( time, type ) {
  	time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
  	type = type || "fx";
  
  	return this.queue( type, function( next, hooks ) {
  		var timeout = window.setTimeout( next, time );
  		hooks.stop = function() {
  			window.clearTimeout( timeout );
  		};
  	} );
  };
  
  
  ( function() {
  	var input = document.createElement( "input" ),
  		select = document.createElement( "select" ),
  		opt = select.appendChild( document.createElement( "option" ) );
  
  	input.type = "checkbox";
  
  	// Support: Android <=4.3 only
  	// Default value for a checkbox should be "on"
  	support.checkOn = input.value !== "";
  
  	// Support: IE <=11 only
  	// Must access selectedIndex to make default options select
  	support.optSelected = opt.selected;
  
  	// Support: IE <=11 only
  	// An input loses its value after becoming a radio
  	input = document.createElement( "input" );
  	input.value = "t";
  	input.type = "radio";
  	support.radioValue = input.value === "t";
  } )();
  
  
  var boolHook,
  	attrHandle = jQuery.expr.attrHandle;
  
  jQuery.fn.extend( {
  	attr: function( name, value ) {
  		return access( this, jQuery.attr, name, value, arguments.length > 1 );
  	},
  
  	removeAttr: function( name ) {
  		return this.each( function() {
  			jQuery.removeAttr( this, name );
  		} );
  	}
  } );
  
  jQuery.extend( {
  	attr: function( elem, name, value ) {
  		var ret, hooks,
  			nType = elem.nodeType;
  
  		// Don't get/set attributes on text, comment and attribute nodes
  		if ( nType === 3 || nType === 8 || nType === 2 ) {
  			return;
  		}
  
  		// Fallback to prop when attributes are not supported
  		if ( typeof elem.getAttribute === "undefined" ) {
  			return jQuery.prop( elem, name, value );
  		}
  
  		// Attribute hooks are determined by the lowercase version
  		// Grab necessary hook if one is defined
  		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
  			hooks = jQuery.attrHooks[ name.toLowerCase() ] ||
  				( jQuery.expr.match.bool.test( name ) ? boolHook : undefined );
  		}
  
  		if ( value !== undefined ) {
  			if ( value === null ) {
  				jQuery.removeAttr( elem, name );
  				return;
  			}
  
  			if ( hooks && "set" in hooks &&
  				( ret = hooks.set( elem, value, name ) ) !== undefined ) {
  				return ret;
  			}
  
  			elem.setAttribute( name, value + "" );
  			return value;
  		}
  
  		if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
  			return ret;
  		}
  
  		ret = jQuery.find.attr( elem, name );
  
  		// Non-existent attributes return null, we normalize to undefined
  		return ret == null ? undefined : ret;
  	},
  
  	attrHooks: {
  		type: {
  			set: function( elem, value ) {
  				if ( !support.radioValue && value === "radio" &&
  					nodeName( elem, "input" ) ) {
  					var val = elem.value;
  					elem.setAttribute( "type", value );
  					if ( val ) {
  						elem.value = val;
  					}
  					return value;
  				}
  			}
  		}
  	},
  
  	removeAttr: function( elem, value ) {
  		var name,
  			i = 0,
  
  			// Attribute names can contain non-HTML whitespace characters
  			// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
  			attrNames = value && value.match( rnothtmlwhite );
  
  		if ( attrNames && elem.nodeType === 1 ) {
  			while ( ( name = attrNames[ i++ ] ) ) {
  				elem.removeAttribute( name );
  			}
  		}
  	}
  } );
  
  // Hooks for boolean attributes
  boolHook = {
  	set: function( elem, value, name ) {
  		if ( value === false ) {
  
  			// Remove boolean attributes when set to false
  			jQuery.removeAttr( elem, name );
  		} else {
  			elem.setAttribute( name, name );
  		}
  		return name;
  	}
  };
  
  jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
  	var getter = attrHandle[ name ] || jQuery.find.attr;
  
  	attrHandle[ name ] = function( elem, name, isXML ) {
  		var ret, handle,
  			lowercaseName = name.toLowerCase();
  
  		if ( !isXML ) {
  
  			// Avoid an infinite loop by temporarily removing this function from the getter
  			handle = attrHandle[ lowercaseName ];
  			attrHandle[ lowercaseName ] = ret;
  			ret = getter( elem, name, isXML ) != null ?
  				lowercaseName :
  				null;
  			attrHandle[ lowercaseName ] = handle;
  		}
  		return ret;
  	};
  } );
  
  
  
  
  var rfocusable = /^(?:input|select|textarea|button)$/i,
  	rclickable = /^(?:a|area)$/i;
  
  jQuery.fn.extend( {
  	prop: function( name, value ) {
  		return access( this, jQuery.prop, name, value, arguments.length > 1 );
  	},
  
  	removeProp: function( name ) {
  		return this.each( function() {
  			delete this[ jQuery.propFix[ name ] || name ];
  		} );
  	}
  } );
  
  jQuery.extend( {
  	prop: function( elem, name, value ) {
  		var ret, hooks,
  			nType = elem.nodeType;
  
  		// Don't get/set properties on text, comment and attribute nodes
  		if ( nType === 3 || nType === 8 || nType === 2 ) {
  			return;
  		}
  
  		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
  
  			// Fix name and attach hooks
  			name = jQuery.propFix[ name ] || name;
  			hooks = jQuery.propHooks[ name ];
  		}
  
  		if ( value !== undefined ) {
  			if ( hooks && "set" in hooks &&
  				( ret = hooks.set( elem, value, name ) ) !== undefined ) {
  				return ret;
  			}
  
  			return ( elem[ name ] = value );
  		}
  
  		if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
  			return ret;
  		}
  
  		return elem[ name ];
  	},
  
  	propHooks: {
  		tabIndex: {
  			get: function( elem ) {
  
  				// Support: IE <=9 - 11 only
  				// elem.tabIndex doesn't always return the
  				// correct value when it hasn't been explicitly set
  				// https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
  				// Use proper attribute retrieval(#12072)
  				var tabindex = jQuery.find.attr( elem, "tabindex" );
  
  				if ( tabindex ) {
  					return parseInt( tabindex, 10 );
  				}
  
  				if (
  					rfocusable.test( elem.nodeName ) ||
  					rclickable.test( elem.nodeName ) &&
  					elem.href
  				) {
  					return 0;
  				}
  
  				return -1;
  			}
  		}
  	},
  
  	propFix: {
  		"for": "htmlFor",
  		"class": "className"
  	}
  } );
  
  // Support: IE <=11 only
  // Accessing the selectedIndex property
  // forces the browser to respect setting selected
  // on the option
  // The getter ensures a default option is selected
  // when in an optgroup
  // eslint rule "no-unused-expressions" is disabled for this code
  // since it considers such accessions noop
  if ( !support.optSelected ) {
  	jQuery.propHooks.selected = {
  		get: function( elem ) {
  
  			/* eslint no-unused-expressions: "off" */
  
  			var parent = elem.parentNode;
  			if ( parent && parent.parentNode ) {
  				parent.parentNode.selectedIndex;
  			}
  			return null;
  		},
  		set: function( elem ) {
  
  			/* eslint no-unused-expressions: "off" */
  
  			var parent = elem.parentNode;
  			if ( parent ) {
  				parent.selectedIndex;
  
  				if ( parent.parentNode ) {
  					parent.parentNode.selectedIndex;
  				}
  			}
  		}
  	};
  }
  
  jQuery.each( [
  	"tabIndex",
  	"readOnly",
  	"maxLength",
  	"cellSpacing",
  	"cellPadding",
  	"rowSpan",
  	"colSpan",
  	"useMap",
  	"frameBorder",
  	"contentEditable"
  ], function() {
  	jQuery.propFix[ this.toLowerCase() ] = this;
  } );
  
  
  
  
  	// Strip and collapse whitespace according to HTML spec
  	// https://html.spec.whatwg.org/multipage/infrastructure.html#strip-and-collapse-whitespace
  	function stripAndCollapse( value ) {
  		var tokens = value.match( rnothtmlwhite ) || [];
  		return tokens.join( " " );
  	}
  
  
  function getClass( elem ) {
  	return elem.getAttribute && elem.getAttribute( "class" ) || "";
  }
  
  jQuery.fn.extend( {
  	addClass: function( value ) {
  		var classes, elem, cur, curValue, clazz, j, finalValue,
  			i = 0;
  
  		if ( jQuery.isFunction( value ) ) {
  			return this.each( function( j ) {
  				jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
  			} );
  		}
  
  		if ( typeof value === "string" && value ) {
  			classes = value.match( rnothtmlwhite ) || [];
  
  			while ( ( elem = this[ i++ ] ) ) {
  				curValue = getClass( elem );
  				cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
  
  				if ( cur ) {
  					j = 0;
  					while ( ( clazz = classes[ j++ ] ) ) {
  						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
  							cur += clazz + " ";
  						}
  					}
  
  					// Only assign if different to avoid unneeded rendering.
  					finalValue = stripAndCollapse( cur );
  					if ( curValue !== finalValue ) {
  						elem.setAttribute( "class", finalValue );
  					}
  				}
  			}
  		}
  
  		return this;
  	},
  
  	removeClass: function( value ) {
  		var classes, elem, cur, curValue, clazz, j, finalValue,
  			i = 0;
  
  		if ( jQuery.isFunction( value ) ) {
  			return this.each( function( j ) {
  				jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );
  			} );
  		}
  
  		if ( !arguments.length ) {
  			return this.attr( "class", "" );
  		}
  
  		if ( typeof value === "string" && value ) {
  			classes = value.match( rnothtmlwhite ) || [];
  
  			while ( ( elem = this[ i++ ] ) ) {
  				curValue = getClass( elem );
  
  				// This expression is here for better compressibility (see addClass)
  				cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
  
  				if ( cur ) {
  					j = 0;
  					while ( ( clazz = classes[ j++ ] ) ) {
  
  						// Remove *all* instances
  						while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
  							cur = cur.replace( " " + clazz + " ", " " );
  						}
  					}
  
  					// Only assign if different to avoid unneeded rendering.
  					finalValue = stripAndCollapse( cur );
  					if ( curValue !== finalValue ) {
  						elem.setAttribute( "class", finalValue );
  					}
  				}
  			}
  		}
  
  		return this;
  	},
  
  	toggleClass: function( value, stateVal ) {
  		var type = typeof value;
  
  		if ( typeof stateVal === "boolean" && type === "string" ) {
  			return stateVal ? this.addClass( value ) : this.removeClass( value );
  		}
  
  		if ( jQuery.isFunction( value ) ) {
  			return this.each( function( i ) {
  				jQuery( this ).toggleClass(
  					value.call( this, i, getClass( this ), stateVal ),
  					stateVal
  				);
  			} );
  		}
  
  		return this.each( function() {
  			var className, i, self, classNames;
  
  			if ( type === "string" ) {
  
  				// Toggle individual class names
  				i = 0;
  				self = jQuery( this );
  				classNames = value.match( rnothtmlwhite ) || [];
  
  				while ( ( className = classNames[ i++ ] ) ) {
  
  					// Check each className given, space separated list
  					if ( self.hasClass( className ) ) {
  						self.removeClass( className );
  					} else {
  						self.addClass( className );
  					}
  				}
  
  			// Toggle whole class name
  			} else if ( value === undefined || type === "boolean" ) {
  				className = getClass( this );
  				if ( className ) {
  
  					// Store className if set
  					dataPriv.set( this, "__className__", className );
  				}
  
  				// If the element has a class name or if we're passed `false`,
  				// then remove the whole classname (if there was one, the above saved it).
  				// Otherwise bring back whatever was previously saved (if anything),
  				// falling back to the empty string if nothing was stored.
  				if ( this.setAttribute ) {
  					this.setAttribute( "class",
  						className || value === false ?
  						"" :
  						dataPriv.get( this, "__className__" ) || ""
  					);
  				}
  			}
  		} );
  	},
  
  	hasClass: function( selector ) {
  		var className, elem,
  			i = 0;
  
  		className = " " + selector + " ";
  		while ( ( elem = this[ i++ ] ) ) {
  			if ( elem.nodeType === 1 &&
  				( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) {
  					return true;
  			}
  		}
  
  		return false;
  	}
  } );
  
  
  
  
  var rreturn = /\r/g;
  
  jQuery.fn.extend( {
  	val: function( value ) {
  		var hooks, ret, isFunction,
  			elem = this[ 0 ];
  
  		if ( !arguments.length ) {
  			if ( elem ) {
  				hooks = jQuery.valHooks[ elem.type ] ||
  					jQuery.valHooks[ elem.nodeName.toLowerCase() ];
  
  				if ( hooks &&
  					"get" in hooks &&
  					( ret = hooks.get( elem, "value" ) ) !== undefined
  				) {
  					return ret;
  				}
  
  				ret = elem.value;
  
  				// Handle most common string cases
  				if ( typeof ret === "string" ) {
  					return ret.replace( rreturn, "" );
  				}
  
  				// Handle cases where value is null/undef or number
  				return ret == null ? "" : ret;
  			}
  
  			return;
  		}
  
  		isFunction = jQuery.isFunction( value );
  
  		return this.each( function( i ) {
  			var val;
  
  			if ( this.nodeType !== 1 ) {
  				return;
  			}
  
  			if ( isFunction ) {
  				val = value.call( this, i, jQuery( this ).val() );
  			} else {
  				val = value;
  			}
  
  			// Treat null/undefined as ""; convert numbers to string
  			if ( val == null ) {
  				val = "";
  
  			} else if ( typeof val === "number" ) {
  				val += "";
  
  			} else if ( Array.isArray( val ) ) {
  				val = jQuery.map( val, function( value ) {
  					return value == null ? "" : value + "";
  				} );
  			}
  
  			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
  
  			// If set returns undefined, fall back to normal setting
  			if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) {
  				this.value = val;
  			}
  		} );
  	}
  } );
  
  jQuery.extend( {
  	valHooks: {
  		option: {
  			get: function( elem ) {
  
  				var val = jQuery.find.attr( elem, "value" );
  				return val != null ?
  					val :
  
  					// Support: IE <=10 - 11 only
  					// option.text throws exceptions (#14686, #14858)
  					// Strip and collapse whitespace
  					// https://html.spec.whatwg.org/#strip-and-collapse-whitespace
  					stripAndCollapse( jQuery.text( elem ) );
  			}
  		},
  		select: {
  			get: function( elem ) {
  				var value, option, i,
  					options = elem.options,
  					index = elem.selectedIndex,
  					one = elem.type === "select-one",
  					values = one ? null : [],
  					max = one ? index + 1 : options.length;
  
  				if ( index < 0 ) {
  					i = max;
  
  				} else {
  					i = one ? index : 0;
  				}
  
  				// Loop through all the selected options
  				for ( ; i < max; i++ ) {
  					option = options[ i ];
  
  					// Support: IE <=9 only
  					// IE8-9 doesn't update selected after form reset (#2551)
  					if ( ( option.selected || i === index ) &&
  
  							// Don't return options that are disabled or in a disabled optgroup
  							!option.disabled &&
  							( !option.parentNode.disabled ||
  								!nodeName( option.parentNode, "optgroup" ) ) ) {
  
  						// Get the specific value for the option
  						value = jQuery( option ).val();
  
  						// We don't need an array for one selects
  						if ( one ) {
  							return value;
  						}
  
  						// Multi-Selects return an array
  						values.push( value );
  					}
  				}
  
  				return values;
  			},
  
  			set: function( elem, value ) {
  				var optionSet, option,
  					options = elem.options,
  					values = jQuery.makeArray( value ),
  					i = options.length;
  
  				while ( i-- ) {
  					option = options[ i ];
  
  					/* eslint-disable no-cond-assign */
  
  					if ( option.selected =
  						jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
  					) {
  						optionSet = true;
  					}
  
  					/* eslint-enable no-cond-assign */
  				}
  
  				// Force browsers to behave consistently when non-matching value is set
  				if ( !optionSet ) {
  					elem.selectedIndex = -1;
  				}
  				return values;
  			}
  		}
  	}
  } );
  
  // Radios and checkboxes getter/setter
  jQuery.each( [ "radio", "checkbox" ], function() {
  	jQuery.valHooks[ this ] = {
  		set: function( elem, value ) {
  			if ( Array.isArray( value ) ) {
  				return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );
  			}
  		}
  	};
  	if ( !support.checkOn ) {
  		jQuery.valHooks[ this ].get = function( elem ) {
  			return elem.getAttribute( "value" ) === null ? "on" : elem.value;
  		};
  	}
  } );
  
  
  
  
  // Return jQuery for attributes-only inclusion
  
  
  var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/;
  
  jQuery.extend( jQuery.event, {
  
  	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( "." ) > -1 ) {
  
  			// 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.rnamespace = 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 = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] &&
  				dataPriv.get( cur, "handle" );
  			if ( handle ) {
  				handle.apply( cur, data );
  			}
  
  			// Native handler
  			handle = ontype && cur[ ontype ];
  			if ( handle && handle.apply && 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 ) &&
  				acceptData( elem ) ) {
  
  				// Call a native DOM method on the target with the same 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;
  	},
  
  	// Piggyback on a donor event to simulate a different one
  	// Used only for `focus(in | out)` events
  	simulate: function( type, elem, event ) {
  		var e = jQuery.extend(
  			new jQuery.Event(),
  			event,
  			{
  				type: type,
  				isSimulated: true
  			}
  		);
  
  		jQuery.event.trigger( e, null, elem );
  	}
  
  } );
  
  jQuery.fn.extend( {
  
  	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 );
  		}
  	}
  } );
  
  
  jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " +
  	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
  	"change select submit keydown keypress keyup contextmenu" ).split( " " ),
  	function( i, name ) {
  
  	// Handle event binding
  	jQuery.fn[ name ] = function( data, fn ) {
  		return arguments.length > 0 ?
  			this.on( name, null, data, fn ) :
  			this.trigger( name );
  	};
  } );
  
  jQuery.fn.extend( {
  	hover: function( fnOver, fnOut ) {
  		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
  	}
  } );
  
  
  
  
  support.focusin = "onfocusin" in window;
  
  
  // Support: Firefox <=44
  // Firefox doesn't have focus(in | out) events
  // Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
  //
  // Support: Chrome <=48 - 49, Safari <=9.0 - 9.1
  // focus(in | out) events fire after focus & blur events,
  // which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
  // Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857
  if ( !support.focusin ) {
  	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 ) );
  		};
  
  		jQuery.event.special[ fix ] = {
  			setup: function() {
  				var doc = this.ownerDocument || this,
  					attaches = dataPriv.access( doc, fix );
  
  				if ( !attaches ) {
  					doc.addEventListener( orig, handler, true );
  				}
  				dataPriv.access( doc, fix, ( attaches || 0 ) + 1 );
  			},
  			teardown: function() {
  				var doc = this.ownerDocument || this,
  					attaches = dataPriv.access( doc, fix ) - 1;
  
  				if ( !attaches ) {
  					doc.removeEventListener( orig, handler, true );
  					dataPriv.remove( doc, fix );
  
  				} else {
  					dataPriv.access( doc, fix, attaches );
  				}
  			}
  		};
  	} );
  }
  var location = window.location;
  
  var nonce = jQuery.now();
  
  var rquery = ( /\?/ );
  
  
  
  // Cross-browser xml parsing
  jQuery.parseXML = function( data ) {
  	var xml;
  	if ( !data || typeof data !== "string" ) {
  		return null;
  	}
  
  	// Support: IE 9 - 11 only
  	// IE throws on parseFromString with invalid input.
  	try {
  		xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" );
  	} catch ( e ) {
  		xml = undefined;
  	}
  
  	if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
  		jQuery.error( "Invalid XML: " + data );
  	}
  	return xml;
  };
  
  
  var
  	rbracket = /\[\]$/,
  	rCRLF = /\r?\n/g,
  	rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
  	rsubmittable = /^(?:input|select|textarea|keygen)/i;
  
  function buildParams( prefix, obj, traditional, add ) {
  	var name;
  
  	if ( Array.isArray( obj ) ) {
  
  		// Serialize array item.
  		jQuery.each( obj, function( i, v ) {
  			if ( traditional || rbracket.test( prefix ) ) {
  
  				// Treat each array item as a scalar.
  				add( prefix, v );
  
  			} else {
  
  				// Item is non-scalar (array or object), encode its numeric index.
  				buildParams(
  					prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]",
  					v,
  					traditional,
  					add
  				);
  			}
  		} );
  
  	} else if ( !traditional && jQuery.type( obj ) === "object" ) {
  
  		// Serialize object item.
  		for ( name in obj ) {
  			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
  		}
  
  	} else {
  
  		// Serialize scalar item.
  		add( prefix, obj );
  	}
  }
  
  // Serialize an array of form elements or a set of
  // key/values into a query string
  jQuery.param = function( a, traditional ) {
  	var prefix,
  		s = [],
  		add = function( key, valueOrFunction ) {
  
  			// If value is a function, invoke it and use its return value
  			var value = jQuery.isFunction( valueOrFunction ) ?
  				valueOrFunction() :
  				valueOrFunction;
  
  			s[ s.length ] = encodeURIComponent( key ) + "=" +
  				encodeURIComponent( value == null ? "" : value );
  		};
  
  	// If an array was passed in, assume that it is an array of form elements.
  	if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
  
  		// Serialize the form elements
  		jQuery.each( a, function() {
  			add( this.name, this.value );
  		} );
  
  	} else {
  
  		// If traditional, encode the "old" way (the way 1.3.2 or older
  		// did it), otherwise encode params recursively.
  		for ( prefix in a ) {
  			buildParams( prefix, a[ prefix ], traditional, add );
  		}
  	}
  
  	// Return the resulting serialization
  	return s.join( "&" );
  };
  
  jQuery.fn.extend( {
  	serialize: function() {
  		return jQuery.param( this.serializeArray() );
  	},
  	serializeArray: function() {
  		return this.map( function() {
  
  			// Can add propHook for "elements" to filter or add form elements
  			var elements = jQuery.prop( this, "elements" );
  			return elements ? jQuery.makeArray( elements ) : this;
  		} )
  		.filter( function() {
  			var type = this.type;
  
  			// Use .is( ":disabled" ) so that fieldset[disabled] works
  			return this.name && !jQuery( this ).is( ":disabled" ) &&
  				rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
  				( this.checked || !rcheckableType.test( type ) );
  		} )
  		.map( function( i, elem ) {
  			var val = jQuery( this ).val();
  
  			if ( val == null ) {
  				return null;
  			}
  
  			if ( Array.isArray( val ) ) {
  				return jQuery.map( val, function( val ) {
  					return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
  				} );
  			}
  
  			return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
  		} ).get();
  	}
  } );
  
  
  var
  	r20 = /%20/g,
  	rhash = /#.*$/,
  	rantiCache = /([?&])_=[^&]*/,
  	rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,
  
  	// #7653, #8125, #8152: local protocol detection
  	rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
  	rnoContent = /^(?:GET|HEAD)$/,
  	rprotocol = /^\/\//,
  
  	/* Prefilters
  	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
  	 * 2) These are called:
  	 *    - BEFORE asking for a transport
  	 *    - AFTER param serialization (s.data is a string if s.processData is true)
  	 * 3) key is the dataType
  	 * 4) the catchall symbol "*" can be used
  	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
  	 */
  	prefilters = {},
  
  	/* Transports bindings
  	 * 1) key is the dataType
  	 * 2) the catchall symbol "*" can be used
  	 * 3) selection will start with transport dataType and THEN go to "*" if needed
  	 */
  	transports = {},
  
  	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
  	allTypes = "*/".concat( "*" ),
  
  	// Anchor tag for parsing the document origin
  	originAnchor = document.createElement( "a" );
  	originAnchor.href = location.href;
  
  // Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
  function addToPrefiltersOrTransports( structure ) {
  
  	// dataTypeExpression is optional and defaults to "*"
  	return function( dataTypeExpression, func ) {
  
  		if ( typeof dataTypeExpression !== "string" ) {
  			func = dataTypeExpression;
  			dataTypeExpression = "*";
  		}
  
  		var dataType,
  			i = 0,
  			dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || [];
  
  		if ( jQuery.isFunction( func ) ) {
  
  			// For each dataType in the dataTypeExpression
  			while ( ( dataType = dataTypes[ i++ ] ) ) {
  
  				// Prepend if requested
  				if ( dataType[ 0 ] === "+" ) {
  					dataType = dataType.slice( 1 ) || "*";
  					( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func );
  
  				// Otherwise append
  				} else {
  					( structure[ dataType ] = structure[ dataType ] || [] ).push( func );
  				}
  			}
  		}
  	};
  }
  
  // Base inspection function for prefilters and transports
  function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
  
  	var inspected = {},
  		seekingTransport = ( structure === transports );
  
  	function inspect( dataType ) {
  		var selected;
  		inspected[ dataType ] = true;
  		jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
  			var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
  			if ( typeof dataTypeOrTransport === "string" &&
  				!seekingTransport && !inspected[ dataTypeOrTransport ] ) {
  
  				options.dataTypes.unshift( dataTypeOrTransport );
  				inspect( dataTypeOrTransport );
  				return false;
  			} else if ( seekingTransport ) {
  				return !( selected = dataTypeOrTransport );
  			}
  		} );
  		return selected;
  	}
  
  	return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
  }
  
  // A special extend for ajax options
  // that takes "flat" options (not to be deep extended)
  // Fixes #9887
  function ajaxExtend( target, src ) {
  	var key, deep,
  		flatOptions = jQuery.ajaxSettings.flatOptions || {};
  
  	for ( key in src ) {
  		if ( src[ key ] !== undefined ) {
  			( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
  		}
  	}
  	if ( deep ) {
  		jQuery.extend( true, target, deep );
  	}
  
  	return target;
  }
  
  /* Handles responses to an ajax request:
   * - finds the right dataType (mediates between content-type and expected dataType)
   * - returns the corresponding response
   */
  function ajaxHandleResponses( s, jqXHR, responses ) {
  
  	var ct, type, finalDataType, firstDataType,
  		contents = s.contents,
  		dataTypes = s.dataTypes;
  
  	// Remove auto dataType and get content-type in the process
  	while ( dataTypes[ 0 ] === "*" ) {
  		dataTypes.shift();
  		if ( ct === undefined ) {
  			ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" );
  		}
  	}
  
  	// Check if we're dealing with a known content-type
  	if ( ct ) {
  		for ( type in contents ) {
  			if ( contents[ type ] && contents[ type ].test( ct ) ) {
  				dataTypes.unshift( type );
  				break;
  			}
  		}
  	}
  
  	// Check to see if we have a response for the expected dataType
  	if ( dataTypes[ 0 ] in responses ) {
  		finalDataType = dataTypes[ 0 ];
  	} else {
  
  		// Try convertible dataTypes
  		for ( type in responses ) {
  			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) {
  				finalDataType = type;
  				break;
  			}
  			if ( !firstDataType ) {
  				firstDataType = type;
  			}
  		}
  
  		// Or just use first one
  		finalDataType = finalDataType || firstDataType;
  	}
  
  	// If we found a dataType
  	// We add the dataType to the list if needed
  	// and return the corresponding response
  	if ( finalDataType ) {
  		if ( finalDataType !== dataTypes[ 0 ] ) {
  			dataTypes.unshift( finalDataType );
  		}
  		return responses[ finalDataType ];
  	}
  }
  
  /* Chain conversions given the request and the original response
   * Also sets the responseXXX fields on the jqXHR instance
   */
  function ajaxConvert( s, response, jqXHR, isSuccess ) {
  	var conv2, current, conv, tmp, prev,
  		converters = {},
  
  		// Work with a copy of dataTypes in case we need to modify it for conversion
  		dataTypes = s.dataTypes.slice();
  
  	// Create converters map with lowercased keys
  	if ( dataTypes[ 1 ] ) {
  		for ( conv in s.converters ) {
  			converters[ conv.toLowerCase() ] = s.converters[ conv ];
  		}
  	}
  
  	current = dataTypes.shift();
  
  	// Convert to each sequential dataType
  	while ( current ) {
  
  		if ( s.responseFields[ current ] ) {
  			jqXHR[ s.responseFields[ current ] ] = response;
  		}
  
  		// Apply the dataFilter if provided
  		if ( !prev && isSuccess && s.dataFilter ) {
  			response = s.dataFilter( response, s.dataType );
  		}
  
  		prev = current;
  		current = dataTypes.shift();
  
  		if ( current ) {
  
  			// There's only work to do if current dataType is non-auto
  			if ( current === "*" ) {
  
  				current = prev;
  
  			// Convert response if prev dataType is non-auto and differs from current
  			} else if ( prev !== "*" && prev !== current ) {
  
  				// Seek a direct converter
  				conv = converters[ prev + " " + current ] || converters[ "* " + current ];
  
  				// If none found, seek a pair
  				if ( !conv ) {
  					for ( conv2 in converters ) {
  
  						// If conv2 outputs current
  						tmp = conv2.split( " " );
  						if ( tmp[ 1 ] === current ) {
  
  							// If prev can be converted to accepted input
  							conv = converters[ prev + " " + tmp[ 0 ] ] ||
  								converters[ "* " + tmp[ 0 ] ];
  							if ( conv ) {
  
  								// Condense equivalence converters
  								if ( conv === true ) {
  									conv = converters[ conv2 ];
  
  								// Otherwise, insert the intermediate dataType
  								} else if ( converters[ conv2 ] !== true ) {
  									current = tmp[ 0 ];
  									dataTypes.unshift( tmp[ 1 ] );
  								}
  								break;
  							}
  						}
  					}
  				}
  
  				// Apply converter (if not an equivalence)
  				if ( conv !== true ) {
  
  					// Unless errors are allowed to bubble, catch and return them
  					if ( conv && s.throws ) {
  						response = conv( response );
  					} else {
  						try {
  							response = conv( response );
  						} catch ( e ) {
  							return {
  								state: "parsererror",
  								error: conv ? e : "No conversion from " + prev + " to " + current
  							};
  						}
  					}
  				}
  			}
  		}
  	}
  
  	return { state: "success", data: response };
  }
  
  jQuery.extend( {
  
  	// Counter for holding the number of active queries
  	active: 0,
  
  	// Last-Modified header cache for next request
  	lastModified: {},
  	etag: {},
  
  	ajaxSettings: {
  		url: location.href,
  		type: "GET",
  		isLocal: rlocalProtocol.test( location.protocol ),
  		global: true,
  		processData: true,
  		async: true,
  		contentType: "application/x-www-form-urlencoded; charset=UTF-8",
  
  		/*
  		timeout: 0,
  		data: null,
  		dataType: null,
  		username: null,
  		password: null,
  		cache: null,
  		throws: false,
  		traditional: false,
  		headers: {},
  		*/
  
  		accepts: {
  			"*": allTypes,
  			text: "text/plain",
  			html: "text/html",
  			xml: "application/xml, text/xml",
  			json: "application/json, text/javascript"
  		},
  
  		contents: {
  			xml: /\bxml\b/,
  			html: /\bhtml/,
  			json: /\bjson\b/
  		},
  
  		responseFields: {
  			xml: "responseXML",
  			text: "responseText",
  			json: "responseJSON"
  		},
  
  		// Data converters
  		// Keys separate source (or catchall "*") and destination types with a single space
  		converters: {
  
  			// Convert anything to text
  			"* text": String,
  
  			// Text to html (true = no transformation)
  			"text html": true,
  
  			// Evaluate text as a json expression
  			"text json": JSON.parse,
  
  			// Parse text as xml
  			"text xml": jQuery.parseXML
  		},
  
  		// For options that shouldn't be deep extended:
  		// you can add your own custom options here if
  		// and when you create one that shouldn't be
  		// deep extended (see ajaxExtend)
  		flatOptions: {
  			url: true,
  			context: true
  		}
  	},
  
  	// Creates a full fledged settings object into target
  	// with both ajaxSettings and settings fields.
  	// If target is omitted, writes into ajaxSettings.
  	ajaxSetup: function( target, settings ) {
  		return settings ?
  
  			// Building a settings object
  			ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
  
  			// Extending ajaxSettings
  			ajaxExtend( jQuery.ajaxSettings, target );
  	},
  
  	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
  	ajaxTransport: addToPrefiltersOrTransports( transports ),
  
  	// Main method
  	ajax: function( url, options ) {
  
  		// If url is an object, simulate pre-1.5 signature
  		if ( typeof url === "object" ) {
  			options = url;
  			url = undefined;
  		}
  
  		// Force options to be an object
  		options = options || {};
  
  		var transport,
  
  			// URL without anti-cache param
  			cacheURL,
  
  			// Response headers
  			responseHeadersString,
  			responseHeaders,
  
  			// timeout handle
  			timeoutTimer,
  
  			// Url cleanup var
  			urlAnchor,
  
  			// Request state (becomes false upon send and true upon completion)
  			completed,
  
  			// To know if global events are to be dispatched
  			fireGlobals,
  
  			// Loop variable
  			i,
  
  			// uncached part of the url
  			uncached,
  
  			// Create the final options object
  			s = jQuery.ajaxSetup( {}, options ),
  
  			// Callbacks context
  			callbackContext = s.context || s,
  
  			// Context for global events is callbackContext if it is a DOM node or jQuery collection
  			globalEventContext = s.context &&
  				( callbackContext.nodeType || callbackContext.jquery ) ?
  					jQuery( callbackContext ) :
  					jQuery.event,
  
  			// Deferreds
  			deferred = jQuery.Deferred(),
  			completeDeferred = jQuery.Callbacks( "once memory" ),
  
  			// Status-dependent callbacks
  			statusCode = s.statusCode || {},
  
  			// Headers (they are sent all at once)
  			requestHeaders = {},
  			requestHeadersNames = {},
  
  			// Default abort message
  			strAbort = "canceled",
  
  			// Fake xhr
  			jqXHR = {
  				readyState: 0,
  
  				// Builds headers hashtable if needed
  				getResponseHeader: function( key ) {
  					var match;
  					if ( completed ) {
  						if ( !responseHeaders ) {
  							responseHeaders = {};
  							while ( ( match = rheaders.exec( responseHeadersString ) ) ) {
  								responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ];
  							}
  						}
  						match = responseHeaders[ key.toLowerCase() ];
  					}
  					return match == null ? null : match;
  				},
  
  				// Raw string
  				getAllResponseHeaders: function() {
  					return completed ? responseHeadersString : null;
  				},
  
  				// Caches the header
  				setRequestHeader: function( name, value ) {
  					if ( completed == null ) {
  						name = requestHeadersNames[ name.toLowerCase() ] =
  							requestHeadersNames[ name.toLowerCase() ] || name;
  						requestHeaders[ name ] = value;
  					}
  					return this;
  				},
  
  				// Overrides response content-type header
  				overrideMimeType: function( type ) {
  					if ( completed == null ) {
  						s.mimeType = type;
  					}
  					return this;
  				},
  
  				// Status-dependent callbacks
  				statusCode: function( map ) {
  					var code;
  					if ( map ) {
  						if ( completed ) {
  
  							// Execute the appropriate callbacks
  							jqXHR.always( map[ jqXHR.status ] );
  						} else {
  
  							// Lazy-add the new callbacks in a way that preserves old ones
  							for ( code in map ) {
  								statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
  							}
  						}
  					}
  					return this;
  				},
  
  				// Cancel the request
  				abort: function( statusText ) {
  					var finalText = statusText || strAbort;
  					if ( transport ) {
  						transport.abort( finalText );
  					}
  					done( 0, finalText );
  					return this;
  				}
  			};
  
  		// Attach deferreds
  		deferred.promise( jqXHR );
  
  		// Add protocol if not provided (prefilters might expect it)
  		// Handle falsy url in the settings object (#10093: consistency with old signature)
  		// We also use the url parameter if available
  		s.url = ( ( url || s.url || location.href ) + "" )
  			.replace( rprotocol, location.protocol + "//" );
  
  		// Alias method option to type as per ticket #12004
  		s.type = options.method || options.type || s.method || s.type;
  
  		// Extract dataTypes list
  		s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ];
  
  		// A cross-domain request is in order when the origin doesn't match the current origin.
  		if ( s.crossDomain == null ) {
  			urlAnchor = document.createElement( "a" );
  
  			// Support: IE <=8 - 11, Edge 12 - 13
  			// IE throws exception on accessing the href property if url is malformed,
  			// e.g. http://example.com:80x/
  			try {
  				urlAnchor.href = s.url;
  
  				// Support: IE <=8 - 11 only
  				// Anchor's host property isn't correctly set when s.url is relative
  				urlAnchor.href = urlAnchor.href;
  				s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !==
  					urlAnchor.protocol + "//" + urlAnchor.host;
  			} catch ( e ) {
  
  				// If there is an error parsing the URL, assume it is crossDomain,
  				// it can be rejected by the transport if it is invalid
  				s.crossDomain = true;
  			}
  		}
  
  		// Convert data if not already a string
  		if ( s.data && s.processData && typeof s.data !== "string" ) {
  			s.data = jQuery.param( s.data, s.traditional );
  		}
  
  		// Apply prefilters
  		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
  
  		// If request was aborted inside a prefilter, stop there
  		if ( completed ) {
  			return jqXHR;
  		}
  
  		// We can fire global events as of now if asked to
  		// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
  		fireGlobals = jQuery.event && s.global;
  
  		// Watch for a new set of requests
  		if ( fireGlobals && jQuery.active++ === 0 ) {
  			jQuery.event.trigger( "ajaxStart" );
  		}
  
  		// Uppercase the type
  		s.type = s.type.toUpperCase();
  
  		// Determine if request has content
  		s.hasContent = !rnoContent.test( s.type );
  
  		// Save the URL in case we're toying with the If-Modified-Since
  		// and/or If-None-Match header later on
  		// Remove hash to simplify url manipulation
  		cacheURL = s.url.replace( rhash, "" );
  
  		// More options handling for requests with no content
  		if ( !s.hasContent ) {
  
  			// Remember the hash so we can put it back
  			uncached = s.url.slice( cacheURL.length );
  
  			// If data is available, append data to url
  			if ( s.data ) {
  				cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data;
  
  				// #9682: remove data so that it's not used in an eventual retry
  				delete s.data;
  			}
  
  			// Add or update anti-cache param if needed
  			if ( s.cache === false ) {
  				cacheURL = cacheURL.replace( rantiCache, "$1" );
  				uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached;
  			}
  
  			// Put hash and anti-cache on the URL that will be requested (gh-1732)
  			s.url = cacheURL + uncached;
  
  		// Change '%20' to '+' if this is encoded form body content (gh-2658)
  		} else if ( s.data && s.processData &&
  			( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) {
  			s.data = s.data.replace( r20, "+" );
  		}
  
  		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
  		if ( s.ifModified ) {
  			if ( jQuery.lastModified[ cacheURL ] ) {
  				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
  			}
  			if ( jQuery.etag[ cacheURL ] ) {
  				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
  			}
  		}
  
  		// Set the correct header, if data is being sent
  		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
  			jqXHR.setRequestHeader( "Content-Type", s.contentType );
  		}
  
  		// Set the Accepts header for the server, depending on the dataType
  		jqXHR.setRequestHeader(
  			"Accept",
  			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ?
  				s.accepts[ s.dataTypes[ 0 ] ] +
  					( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
  				s.accepts[ "*" ]
  		);
  
  		// Check for headers option
  		for ( i in s.headers ) {
  			jqXHR.setRequestHeader( i, s.headers[ i ] );
  		}
  
  		// Allow custom headers/mimetypes and early abort
  		if ( s.beforeSend &&
  			( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) {
  
  			// Abort if not done already and return
  			return jqXHR.abort();
  		}
  
  		// Aborting is no longer a cancellation
  		strAbort = "abort";
  
  		// Install callbacks on deferreds
  		completeDeferred.add( s.complete );
  		jqXHR.done( s.success );
  		jqXHR.fail( s.error );
  
  		// Get transport
  		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
  
  		// If no transport, we auto-abort
  		if ( !transport ) {
  			done( -1, "No Transport" );
  		} else {
  			jqXHR.readyState = 1;
  
  			// Send global event
  			if ( fireGlobals ) {
  				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
  			}
  
  			// If request was aborted inside ajaxSend, stop there
  			if ( completed ) {
  				return jqXHR;
  			}
  
  			// Timeout
  			if ( s.async && s.timeout > 0 ) {
  				timeoutTimer = window.setTimeout( function() {
  					jqXHR.abort( "timeout" );
  				}, s.timeout );
  			}
  
  			try {
  				completed = false;
  				transport.send( requestHeaders, done );
  			} catch ( e ) {
  
  				// Rethrow post-completion exceptions
  				if ( completed ) {
  					throw e;
  				}
  
  				// Propagate others as results
  				done( -1, e );
  			}
  		}
  
  		// Callback for when everything is done
  		function done( status, nativeStatusText, responses, headers ) {
  			var isSuccess, success, error, response, modified,
  				statusText = nativeStatusText;
  
  			// Ignore repeat invocations
  			if ( completed ) {
  				return;
  			}
  
  			completed = true;
  
  			// Clear timeout if it exists
  			if ( timeoutTimer ) {
  				window.clearTimeout( timeoutTimer );
  			}
  
  			// Dereference transport for early garbage collection
  			// (no matter how long the jqXHR object will be used)
  			transport = undefined;
  
  			// Cache response headers
  			responseHeadersString = headers || "";
  
  			// Set readyState
  			jqXHR.readyState = status > 0 ? 4 : 0;
  
  			// Determine if successful
  			isSuccess = status >= 200 && status < 300 || status === 304;
  
  			// Get response data
  			if ( responses ) {
  				response = ajaxHandleResponses( s, jqXHR, responses );
  			}
  
  			// Convert no matter what (that way responseXXX fields are always set)
  			response = ajaxConvert( s, response, jqXHR, isSuccess );
  
  			// If successful, handle type chaining
  			if ( isSuccess ) {
  
  				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
  				if ( s.ifModified ) {
  					modified = jqXHR.getResponseHeader( "Last-Modified" );
  					if ( modified ) {
  						jQuery.lastModified[ cacheURL ] = modified;
  					}
  					modified = jqXHR.getResponseHeader( "etag" );
  					if ( modified ) {
  						jQuery.etag[ cacheURL ] = modified;
  					}
  				}
  
  				// if no content
  				if ( status === 204 || s.type === "HEAD" ) {
  					statusText = "nocontent";
  
  				// if not modified
  				} else if ( status === 304 ) {
  					statusText = "notmodified";
  
  				// If we have data, let's convert it
  				} else {
  					statusText = response.state;
  					success = response.data;
  					error = response.error;
  					isSuccess = !error;
  				}
  			} else {
  
  				// Extract error from statusText and normalize for non-aborts
  				error = statusText;
  				if ( status || !statusText ) {
  					statusText = "error";
  					if ( status < 0 ) {
  						status = 0;
  					}
  				}
  			}
  
  			// Set data for the fake xhr object
  			jqXHR.status = status;
  			jqXHR.statusText = ( nativeStatusText || statusText ) + "";
  
  			// Success/Error
  			if ( isSuccess ) {
  				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
  			} else {
  				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
  			}
  
  			// Status-dependent callbacks
  			jqXHR.statusCode( statusCode );
  			statusCode = undefined;
  
  			if ( fireGlobals ) {
  				globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
  					[ jqXHR, s, isSuccess ? success : error ] );
  			}
  
  			// Complete
  			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
  
  			if ( fireGlobals ) {
  				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
  
  				// Handle the global AJAX counter
  				if ( !( --jQuery.active ) ) {
  					jQuery.event.trigger( "ajaxStop" );
  				}
  			}
  		}
  
  		return jqXHR;
  	},
  
  	getJSON: function( url, data, callback ) {
  		return jQuery.get( url, data, callback, "json" );
  	},
  
  	getScript: function( url, callback ) {
  		return jQuery.get( url, undefined, callback, "script" );
  	}
  } );
  
  jQuery.each( [ "get", "post" ], function( i, method ) {
  	jQuery[ method ] = function( url, data, callback, type ) {
  
  		// Shift arguments if data argument was omitted
  		if ( jQuery.isFunction( data ) ) {
  			type = type || callback;
  			callback = data;
  			data = undefined;
  		}
  
  		// The url can be an options object (which then must have .url)
  		return jQuery.ajax( jQuery.extend( {
  			url: url,
  			type: method,
  			dataType: type,
  			data: data,
  			success: callback
  		}, jQuery.isPlainObject( url ) && url ) );
  	};
  } );
  
  
  jQuery._evalUrl = function( url ) {
  	return jQuery.ajax( {
  		url: url,
  
  		// Make this explicit, since user can override this through ajaxSetup (#11264)
  		type: "GET",
  		dataType: "script",
  		cache: true,
  		async: false,
  		global: false,
  		"throws": true
  	} );
  };
  
  
  jQuery.fn.extend( {
  	wrapAll: function( html ) {
  		var wrap;
  
  		if ( this[ 0 ] ) {
  			if ( jQuery.isFunction( html ) ) {
  				html = html.call( this[ 0 ] );
  			}
  
  			// The elements to wrap the target around
  			wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );
  
  			if ( this[ 0 ].parentNode ) {
  				wrap.insertBefore( this[ 0 ] );
  			}
  
  			wrap.map( function() {
  				var elem = this;
  
  				while ( elem.firstElementChild ) {
  					elem = elem.firstElementChild;
  				}
  
  				return elem;
  			} ).append( this );
  		}
  
  		return this;
  	},
  
  	wrapInner: function( html ) {
  		if ( jQuery.isFunction( html ) ) {
  			return this.each( function( i ) {
  				jQuery( this ).wrapInner( html.call( this, i ) );
  			} );
  		}
  
  		return this.each( function() {
  			var self = jQuery( this ),
  				contents = self.contents();
  
  			if ( contents.length ) {
  				contents.wrapAll( html );
  
  			} else {
  				self.append( html );
  			}
  		} );
  	},
  
  	wrap: function( html ) {
  		var isFunction = jQuery.isFunction( html );
  
  		return this.each( function( i ) {
  			jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html );
  		} );
  	},
  
  	unwrap: function( selector ) {
  		this.parent( selector ).not( "body" ).each( function() {
  			jQuery( this ).replaceWith( this.childNodes );
  		} );
  		return this;
  	}
  } );
  
  
  jQuery.expr.pseudos.hidden = function( elem ) {
  	return !jQuery.expr.pseudos.visible( elem );
  };
  jQuery.expr.pseudos.visible = function( elem ) {
  	return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
  };
  
  
  
  
  jQuery.ajaxSettings.xhr = function() {
  	try {
  		return new window.XMLHttpRequest();
  	} catch ( e ) {}
  };
  
  var xhrSuccessStatus = {
  
  		// File protocol always yields status code 0, assume 200
  		0: 200,
  
  		// Support: IE <=9 only
  		// #1450: sometimes IE returns 1223 when it should be 204
  		1223: 204
  	},
  	xhrSupported = jQuery.ajaxSettings.xhr();
  
  support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
  support.ajax = xhrSupported = !!xhrSupported;
  
  jQuery.ajaxTransport( function( options ) {
  	var callback, errorCallback;
  
  	// Cross domain only allowed if supported through XMLHttpRequest
  	if ( support.cors || xhrSupported && !options.crossDomain ) {
  		return {
  			send: function( headers, complete ) {
  				var i,
  					xhr = options.xhr();
  
  				xhr.open(
  					options.type,
  					options.url,
  					options.async,
  					options.username,
  					options.password
  				);
  
  				// Apply custom fields if provided
  				if ( options.xhrFields ) {
  					for ( i in options.xhrFields ) {
  						xhr[ i ] = options.xhrFields[ i ];
  					}
  				}
  
  				// Override mime type if needed
  				if ( options.mimeType && xhr.overrideMimeType ) {
  					xhr.overrideMimeType( options.mimeType );
  				}
  
  				// X-Requested-With header
  				// For cross-domain requests, seeing as conditions for a preflight are
  				// akin to a jigsaw puzzle, we simply never set it to be sure.
  				// (it can always be set on a per-request basis or even using ajaxSetup)
  				// For same-domain requests, won't change header if already provided.
  				if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) {
  					headers[ "X-Requested-With" ] = "XMLHttpRequest";
  				}
  
  				// Set headers
  				for ( i in headers ) {
  					xhr.setRequestHeader( i, headers[ i ] );
  				}
  
  				// Callback
  				callback = function( type ) {
  					return function() {
  						if ( callback ) {
  							callback = errorCallback = xhr.onload =
  								xhr.onerror = xhr.onabort = xhr.onreadystatechange = null;
  
  							if ( type === "abort" ) {
  								xhr.abort();
  							} else if ( type === "error" ) {
  
  								// Support: IE <=9 only
  								// On a manual native abort, IE9 throws
  								// errors on any property access that is not readyState
  								if ( typeof xhr.status !== "number" ) {
  									complete( 0, "error" );
  								} else {
  									complete(
  
  										// File: protocol always yields status 0; see #8605, #14207
  										xhr.status,
  										xhr.statusText
  									);
  								}
  							} else {
  								complete(
  									xhrSuccessStatus[ xhr.status ] || xhr.status,
  									xhr.statusText,
  
  									// Support: IE <=9 only
  									// IE9 has no XHR2 but throws on binary (trac-11426)
  									// For XHR2 non-text, let the caller handle it (gh-2498)
  									( xhr.responseType || "text" ) !== "text"  ||
  									typeof xhr.responseText !== "string" ?
  										{ binary: xhr.response } :
  										{ text: xhr.responseText },
  									xhr.getAllResponseHeaders()
  								);
  							}
  						}
  					};
  				};
  
  				// Listen to events
  				xhr.onload = callback();
  				errorCallback = xhr.onerror = callback( "error" );
  
  				// Support: IE 9 only
  				// Use onreadystatechange to replace onabort
  				// to handle uncaught aborts
  				if ( xhr.onabort !== undefined ) {
  					xhr.onabort = errorCallback;
  				} else {
  					xhr.onreadystatechange = function() {
  
  						// Check readyState before timeout as it changes
  						if ( xhr.readyState === 4 ) {
  
  							// Allow onerror to be called first,
  							// but that will not handle a native abort
  							// Also, save errorCallback to a variable
  							// as xhr.onerror cannot be accessed
  							window.setTimeout( function() {
  								if ( callback ) {
  									errorCallback();
  								}
  							} );
  						}
  					};
  				}
  
  				// Create the abort callback
  				callback = callback( "abort" );
  
  				try {
  
  					// Do send the request (this may raise an exception)
  					xhr.send( options.hasContent && options.data || null );
  				} catch ( e ) {
  
  					// #14683: Only rethrow if this hasn't been notified as an error yet
  					if ( callback ) {
  						throw e;
  					}
  				}
  			},
  
  			abort: function() {
  				if ( callback ) {
  					callback();
  				}
  			}
  		};
  	}
  } );
  
  
  
  
  // Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432)
  jQuery.ajaxPrefilter( function( s ) {
  	if ( s.crossDomain ) {
  		s.contents.script = false;
  	}
  } );
  
  // Install script dataType
  jQuery.ajaxSetup( {
  	accepts: {
  		script: "text/javascript, application/javascript, " +
  			"application/ecmascript, application/x-ecmascript"
  	},
  	contents: {
  		script: /\b(?:java|ecma)script\b/
  	},
  	converters: {
  		"text script": function( text ) {
  			jQuery.globalEval( text );
  			return text;
  		}
  	}
  } );
  
  // Handle cache's special case and crossDomain
  jQuery.ajaxPrefilter( "script", function( s ) {
  	if ( s.cache === undefined ) {
  		s.cache = false;
  	}
  	if ( s.crossDomain ) {
  		s.type = "GET";
  	}
  } );
  
  // Bind script tag hack transport
  jQuery.ajaxTransport( "script", function( s ) {
  
  	// This transport only deals with cross domain requests
  	if ( s.crossDomain ) {
  		var script, callback;
  		return {
  			send: function( _, complete ) {
  				script = jQuery( "<script>" ).prop( {
  					charset: s.scriptCharset,
  					src: s.url
  				} ).on(
  					"load error",
  					callback = function( evt ) {
  						script.remove();
  						callback = null;
  						if ( evt ) {
  							complete( evt.type === "error" ? 404 : 200, evt.type );
  						}
  					}
  				);
  
  				// Use native DOM manipulation to avoid our domManip AJAX trickery
  				document.head.appendChild( script[ 0 ] );
  			},
  			abort: function() {
  				if ( callback ) {
  					callback();
  				}
  			}
  		};
  	}
  } );
  
  
  
  
  var oldCallbacks = [],
  	rjsonp = /(=)\?(?=&|$)|\?\?/;
  
  // Default jsonp settings
  jQuery.ajaxSetup( {
  	jsonp: "callback",
  	jsonpCallback: function() {
  		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
  		this[ callback ] = true;
  		return callback;
  	}
  } );
  
  // Detect, normalize options and install callbacks for jsonp requests
  jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
  
  	var callbackName, overwritten, responseContainer,
  		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
  			"url" :
  			typeof s.data === "string" &&
  				( s.contentType || "" )
  					.indexOf( "application/x-www-form-urlencoded" ) === 0 &&
  				rjsonp.test( s.data ) && "data"
  		);
  
  	// Handle iff the expected data type is "jsonp" or we have a parameter to set
  	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
  
  		// Get callback name, remembering preexisting value associated with it
  		callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
  			s.jsonpCallback() :
  			s.jsonpCallback;
  
  		// Insert callback into url or form data
  		if ( jsonProp ) {
  			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
  		} else if ( s.jsonp !== false ) {
  			s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
  		}
  
  		// Use data converter to retrieve json after script execution
  		s.converters[ "script json" ] = function() {
  			if ( !responseContainer ) {
  				jQuery.error( callbackName + " was not called" );
  			}
  			return responseContainer[ 0 ];
  		};
  
  		// Force json dataType
  		s.dataTypes[ 0 ] = "json";
  
  		// Install callback
  		overwritten = window[ callbackName ];
  		window[ callbackName ] = function() {
  			responseContainer = arguments;
  		};
  
  		// Clean-up function (fires after converters)
  		jqXHR.always( function() {
  
  			// If previous value didn't exist - remove it
  			if ( overwritten === undefined ) {
  				jQuery( window ).removeProp( callbackName );
  
  			// Otherwise restore preexisting value
  			} else {
  				window[ callbackName ] = overwritten;
  			}
  
  			// Save back as free
  			if ( s[ callbackName ] ) {
  
  				// Make sure that re-using the options doesn't screw things around
  				s.jsonpCallback = originalSettings.jsonpCallback;
  
  				// Save the callback name for future use
  				oldCallbacks.push( callbackName );
  			}
  
  			// Call if it was a function and we have a response
  			if ( responseContainer && jQuery.isFunction( overwritten ) ) {
  				overwritten( responseContainer[ 0 ] );
  			}
  
  			responseContainer = overwritten = undefined;
  		} );
  
  		// Delegate to script
  		return "script";
  	}
  } );
  
  
  
  
  // Support: Safari 8 only
  // In Safari 8 documents created via document.implementation.createHTMLDocument
  // collapse sibling forms: the second one becomes a child of the first one.
  // Because of that, this security measure has to be disabled in Safari 8.
  // https://bugs.webkit.org/show_bug.cgi?id=137337
  support.createHTMLDocument = ( function() {
  	var body = document.implementation.createHTMLDocument( "" ).body;
  	body.innerHTML = "<form></form><form></form>";
  	return body.childNodes.length === 2;
  } )();
  
  
  // Argument "data" should be string of html
  // context (optional): If specified, the fragment will be created in this context,
  // defaults to document
  // keepScripts (optional): If true, will include scripts passed in the html string
  jQuery.parseHTML = function( data, context, keepScripts ) {
  	if ( typeof data !== "string" ) {
  		return [];
  	}
  	if ( typeof context === "boolean" ) {
  		keepScripts = context;
  		context = false;
  	}
  
  	var base, parsed, scripts;
  
  	if ( !context ) {
  
  		// Stop scripts or inline event handlers from being executed immediately
  		// by using document.implementation
  		if ( support.createHTMLDocument ) {
  			context = document.implementation.createHTMLDocument( "" );
  
  			// Set the base href for the created document
  			// so any parsed elements with URLs
  			// are based on the document's URL (gh-2965)
  			base = context.createElement( "base" );
  			base.href = document.location.href;
  			context.head.appendChild( base );
  		} else {
  			context = document;
  		}
  	}
  
  	parsed = rsingleTag.exec( data );
  	scripts = !keepScripts && [];
  
  	// Single tag
  	if ( parsed ) {
  		return [ context.createElement( parsed[ 1 ] ) ];
  	}
  
  	parsed = buildFragment( [ data ], context, scripts );
  
  	if ( scripts && scripts.length ) {
  		jQuery( scripts ).remove();
  	}
  
  	return jQuery.merge( [], parsed.childNodes );
  };
  
  
  /**
   * Load a url into a page
   */
  jQuery.fn.load = function( url, params, callback ) {
  	var selector, type, response,
  		self = this,
  		off = url.indexOf( " " );
  
  	if ( off > -1 ) {
  		selector = stripAndCollapse( url.slice( off ) );
  		url = url.slice( 0, off );
  	}
  
  	// If it's a function
  	if ( jQuery.isFunction( params ) ) {
  
  		// We assume that it's the callback
  		callback = params;
  		params = undefined;
  
  	// Otherwise, build a param string
  	} else if ( params && typeof params === "object" ) {
  		type = "POST";
  	}
  
  	// If we have elements to modify, make the request
  	if ( self.length > 0 ) {
  		jQuery.ajax( {
  			url: url,
  
  			// If "type" variable is undefined, then "GET" method will be used.
  			// Make value of this field explicit since
  			// user can override it through ajaxSetup method
  			type: type || "GET",
  			dataType: "html",
  			data: params
  		} ).done( function( responseText ) {
  
  			// Save response for use in complete callback
  			response = arguments;
  
  			self.html( selector ?
  
  				// If a selector was specified, locate the right elements in a dummy div
  				// Exclude scripts to avoid IE 'Permission Denied' errors
  				jQuery( "<div>" ).append( jQuery.parseHTML( responseText ) ).find( selector ) :
  
  				// Otherwise use the full result
  				responseText );
  
  		// If the request succeeds, this function gets "data", "status", "jqXHR"
  		// but they are ignored because response was set above.
  		// If it fails, this function gets "jqXHR", "status", "error"
  		} ).always( callback && function( jqXHR, status ) {
  			self.each( function() {
  				callback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] );
  			} );
  		} );
  	}
  
  	return this;
  };
  
  
  
  
  // Attach a bunch of functions for handling common AJAX events
  jQuery.each( [
  	"ajaxStart",
  	"ajaxStop",
  	"ajaxComplete",
  	"ajaxError",
  	"ajaxSuccess",
  	"ajaxSend"
  ], function( i, type ) {
  	jQuery.fn[ type ] = function( fn ) {
  		return this.on( type, fn );
  	};
  } );
  
  
  
  
  jQuery.expr.pseudos.animated = function( elem ) {
  	return jQuery.grep( jQuery.timers, function( fn ) {
  		return elem === fn.elem;
  	} ).length;
  };
  
  
  
  
  jQuery.offset = {
  	setOffset: function( elem, options, i ) {
  		var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
  			position = jQuery.css( elem, "position" ),
  			curElem = jQuery( elem ),
  			props = {};
  
  		// Set position first, in-case top/left are set even on static elem
  		if ( position === "static" ) {
  			elem.style.position = "relative";
  		}
  
  		curOffset = curElem.offset();
  		curCSSTop = jQuery.css( elem, "top" );
  		curCSSLeft = jQuery.css( elem, "left" );
  		calculatePosition = ( position === "absolute" || position === "fixed" ) &&
  			( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1;
  
  		// Need to be able to calculate position if either
  		// top or left is auto and position is either absolute or fixed
  		if ( calculatePosition ) {
  			curPosition = curElem.position();
  			curTop = curPosition.top;
  			curLeft = curPosition.left;
  
  		} else {
  			curTop = parseFloat( curCSSTop ) || 0;
  			curLeft = parseFloat( curCSSLeft ) || 0;
  		}
  
  		if ( jQuery.isFunction( options ) ) {
  
  			// Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
  			options = options.call( elem, i, jQuery.extend( {}, curOffset ) );
  		}
  
  		if ( options.top != null ) {
  			props.top = ( options.top - curOffset.top ) + curTop;
  		}
  		if ( options.left != null ) {
  			props.left = ( options.left - curOffset.left ) + curLeft;
  		}
  
  		if ( "using" in options ) {
  			options.using.call( elem, props );
  
  		} else {
  			curElem.css( props );
  		}
  	}
  };
  
  jQuery.fn.extend( {
  	offset: function( options ) {
  
  		// Preserve chaining for setter
  		if ( arguments.length ) {
  			return options === undefined ?
  				this :
  				this.each( function( i ) {
  					jQuery.offset.setOffset( this, options, i );
  				} );
  		}
  
  		var doc, docElem, rect, win,
  			elem = this[ 0 ];
  
  		if ( !elem ) {
  			return;
  		}
  
  		// Return zeros for disconnected and hidden (display: none) elements (gh-2310)
  		// Support: IE <=11 only
  		// Running getBoundingClientRect on a
  		// disconnected node in IE throws an error
  		if ( !elem.getClientRects().length ) {
  			return { top: 0, left: 0 };
  		}
  
  		rect = elem.getBoundingClientRect();
  
  		doc = elem.ownerDocument;
  		docElem = doc.documentElement;
  		win = doc.defaultView;
  
  		return {
  			top: rect.top + win.pageYOffset - docElem.clientTop,
  			left: rect.left + win.pageXOffset - docElem.clientLeft
  		};
  	},
  
  	position: function() {
  		if ( !this[ 0 ] ) {
  			return;
  		}
  
  		var offsetParent, offset,
  			elem = this[ 0 ],
  			parentOffset = { top: 0, left: 0 };
  
  		// Fixed elements are offset from window (parentOffset = {top:0, left: 0},
  		// because it is its only offset parent
  		if ( jQuery.css( elem, "position" ) === "fixed" ) {
  
  			// Assume getBoundingClientRect is there when computed position is fixed
  			offset = elem.getBoundingClientRect();
  
  		} else {
  
  			// Get *real* offsetParent
  			offsetParent = this.offsetParent();
  
  			// Get correct offsets
  			offset = this.offset();
  			if ( !nodeName( offsetParent[ 0 ], "html" ) ) {
  				parentOffset = offsetParent.offset();
  			}
  
  			// Add offsetParent borders
  			parentOffset = {
  				top: parentOffset.top + jQuery.css( offsetParent[ 0 ], "borderTopWidth", true ),
  				left: parentOffset.left + jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true )
  			};
  		}
  
  		// Subtract parent offsets and element margins
  		return {
  			top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
  			left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
  		};
  	},
  
  	// This method will return documentElement in the following cases:
  	// 1) For the element inside the iframe without offsetParent, this method will return
  	//    documentElement of the parent window
  	// 2) For the hidden or detached element
  	// 3) For body or html element, i.e. in case of the html node - it will return itself
  	//
  	// but those exceptions were never presented as a real life use-cases
  	// and might be considered as more preferable results.
  	//
  	// This logic, however, is not guaranteed and can change at any point in the future
  	offsetParent: function() {
  		return this.map( function() {
  			var offsetParent = this.offsetParent;
  
  			while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) {
  				offsetParent = offsetParent.offsetParent;
  			}
  
  			return offsetParent || documentElement;
  		} );
  	}
  } );
  
  // Create scrollLeft and scrollTop methods
  jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
  	var top = "pageYOffset" === prop;
  
  	jQuery.fn[ method ] = function( val ) {
  		return access( this, function( elem, method, val ) {
  
  			// Coalesce documents and windows
  			var win;
  			if ( jQuery.isWindow( elem ) ) {
  				win = elem;
  			} else if ( elem.nodeType === 9 ) {
  				win = elem.defaultView;
  			}
  
  			if ( val === undefined ) {
  				return win ? win[ prop ] : elem[ method ];
  			}
  
  			if ( win ) {
  				win.scrollTo(
  					!top ? val : win.pageXOffset,
  					top ? val : win.pageYOffset
  				);
  
  			} else {
  				elem[ method ] = val;
  			}
  		}, method, val, arguments.length );
  	};
  } );
  
  // Support: Safari <=7 - 9.1, Chrome <=37 - 49
  // Add the top/left cssHooks using jQuery.fn.position
  // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
  // Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347
  // getComputedStyle returns percent when specified for top/left/bottom/right;
  // rather than make the css module depend on the offset module, just check for it here
  jQuery.each( [ "top", "left" ], function( i, prop ) {
  	jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
  		function( elem, computed ) {
  			if ( computed ) {
  				computed = curCSS( elem, prop );
  
  				// If curCSS returns percentage, fallback to offset
  				return rnumnonpx.test( computed ) ?
  					jQuery( elem ).position()[ prop ] + "px" :
  					computed;
  			}
  		}
  	);
  } );
  
  
  // Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
  jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
  	jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name },
  		function( defaultExtra, funcName ) {
  
  		// Margin is only for outerHeight, outerWidth
  		jQuery.fn[ funcName ] = function( margin, value ) {
  			var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
  				extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
  
  			return access( this, function( elem, type, value ) {
  				var doc;
  
  				if ( jQuery.isWindow( elem ) ) {
  
  					// $( window ).outerWidth/Height return w/h including scrollbars (gh-1729)
  					return funcName.indexOf( "outer" ) === 0 ?
  						elem[ "inner" + name ] :
  						elem.document.documentElement[ "client" + name ];
  				}
  
  				// Get document width or height
  				if ( elem.nodeType === 9 ) {
  					doc = elem.documentElement;
  
  					// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
  					// whichever is greatest
  					return Math.max(
  						elem.body[ "scroll" + name ], doc[ "scroll" + name ],
  						elem.body[ "offset" + name ], doc[ "offset" + name ],
  						doc[ "client" + name ]
  					);
  				}
  
  				return value === undefined ?
  
  					// Get width or height on the element, requesting but not forcing parseFloat
  					jQuery.css( elem, type, extra ) :
  
  					// Set width or height on the element
  					jQuery.style( elem, type, value, extra );
  			}, type, chainable ? margin : undefined, chainable );
  		};
  	} );
  } );
  
  
  jQuery.fn.extend( {
  
  	bind: function( types, data, fn ) {
  		return this.on( types, null, data, fn );
  	},
  	unbind: function( types, fn ) {
  		return this.off( types, null, fn );
  	},
  
  	delegate: function( selector, types, data, fn ) {
  		return this.on( types, selector, data, fn );
  	},
  	undelegate: function( selector, types, fn ) {
  
  		// ( namespace ) or ( selector, types [, fn] )
  		return arguments.length === 1 ?
  			this.off( selector, "**" ) :
  			this.off( types, selector || "**", fn );
  	}
  } );
  
  jQuery.holdReady = function( hold ) {
  	if ( hold ) {
  		jQuery.readyWait++;
  	} else {
  		jQuery.ready( true );
  	}
  };
  jQuery.isArray = Array.isArray;
  jQuery.parseJSON = JSON.parse;
  jQuery.nodeName = nodeName;
  
  
  
  
  // Register as a named AMD module, since jQuery can be concatenated with other
  // files that may use define, but not via a proper concatenation script that
  // understands anonymous AMD modules. A named AMD is safest and most robust
  // way to register. Lowercase jquery is used because AMD module names are
  // derived from file names, and jQuery is normally delivered in a lowercase
  // file name. Do this after creating the global so that if an AMD module wants
  // to call noConflict to hide this version of jQuery, it will work.
  
  // Note that for maximum portability, libraries that are not jQuery should
  // declare themselves as anonymous modules, and avoid setting a global if an
  // AMD loader is present. jQuery is a special case. For more information, see
  // https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
  
  if ( typeof define === "function" && define.amd ) {
  	define( "jquery", [], function() {
  		return jQuery;
  	} );
  }
  
  
  
  
  var
  
  	// Map over jQuery in case of overwrite
  	_jQuery = window.jQuery,
  
  	// Map over the $ in case of overwrite
  	_$ = window.$;
  
  jQuery.noConflict = function( deep ) {
  	if ( window.$ === jQuery ) {
  		window.$ = _$;
  	}
  
  	if ( deep && window.jQuery === jQuery ) {
  		window.jQuery = _jQuery;
  	}
  
  	return jQuery;
  };
  
  // Expose jQuery and $ identifiers, even in AMD
  // (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
  // and CommonJS for browser emulators (#13566)
  if ( !noGlobal ) {
  	window.jQuery = window.$ = jQuery;
  }
  
  
  
  
  return jQuery;
  } );
  (function($, undefined) {
  
  /**
   * Unobtrusive scripting adapter for jQuery
   * https://github.com/rails/jquery-ujs
   *
   * Requires jQuery 1.8.0 or later.
   *
   * Released under the MIT license
   *
   */
  
    // Cut down on the number of issues from people inadvertently including jquery_ujs twice
    // by detecting and raising an error when it happens.
    'use strict';
  
    if ( $.rails !== undefined ) {
      $.error('jquery-ujs has already been loaded!');
    }
  
    // Shorthand to make it a little easier to call public rails functions from within rails.js
    var rails;
    var $document = $(document);
  
    $.rails = rails = {
      // Link elements bound by jquery-ujs
      linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]',
  
      // Button elements bound by jquery-ujs
      buttonClickSelector: 'button[data-remote]:not([form]):not(form button), button[data-confirm]:not([form]):not(form button)',
  
      // Select elements bound by jquery-ujs
      inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]',
  
      // Form elements bound by jquery-ujs
      formSubmitSelector: 'form',
  
      // Form input elements bound by jquery-ujs
      formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])',
  
      // Form input elements disabled during form submission
      disableSelector: 'input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled',
  
      // Form input elements re-enabled after form submission
      enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled',
  
      // Form required input elements
      requiredInputSelector: 'input[name][required]:not([disabled]), textarea[name][required]:not([disabled])',
  
      // Form file input elements
      fileInputSelector: 'input[name][type=file]:not([disabled])',
  
      // Link onClick disable selector with possible reenable after remote submission
      linkDisableSelector: 'a[data-disable-with], a[data-disable]',
  
      // Button onClick disable selector with possible reenable after remote submission
      buttonDisableSelector: 'button[data-remote][data-disable-with], button[data-remote][data-disable]',
  
      // Up-to-date Cross-Site Request Forgery token
      csrfToken: function() {
       return $('meta[name=csrf-token]').attr('content');
      },
  
      // URL param that must contain the CSRF token
      csrfParam: function() {
       return $('meta[name=csrf-param]').attr('content');
      },
  
      // Make sure that every Ajax request sends the CSRF token
      CSRFProtection: function(xhr) {
        var token = rails.csrfToken();
        if (token) xhr.setRequestHeader('X-CSRF-Token', token);
      },
  
      // Make sure that all forms have actual up-to-date tokens (cached forms contain old ones)
      refreshCSRFTokens: function(){
        $('form input[name="' + rails.csrfParam() + '"]').val(rails.csrfToken());
      },
  
      // Triggers an event on an element and returns false if the event result is false
      fire: function(obj, name, data) {
        var event = $.Event(name);
        obj.trigger(event, data);
        return event.result !== false;
      },
  
      // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm
      confirm: function(message) {
        return confirm(message);
      },
  
      // Default ajax function, may be overridden with custom function in $.rails.ajax
      ajax: function(options) {
        return $.ajax(options);
      },
  
      // Default way to get an element's href. May be overridden at $.rails.href.
      href: function(element) {
        return element[0].href;
      },
  
      // Checks "data-remote" if true to handle the request through a XHR request.
      isRemote: function(element) {
        return element.data('remote') !== undefined && element.data('remote') !== false;
      },
  
      // Submits "remote" forms and links with ajax
      handleRemote: function(element) {
        var method, url, data, withCredentials, dataType, options;
  
        if (rails.fire(element, 'ajax:before')) {
          withCredentials = element.data('with-credentials') || null;
          dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType);
  
          if (element.is('form')) {
            method = element.data('ujs:submit-button-formmethod') || element.attr('method');
            url = element.data('ujs:submit-button-formaction') || element.attr('action');
            data = $(element[0]).serializeArray();
            // memoized value from clicked submit button
            var button = element.data('ujs:submit-button');
            if (button) {
              data.push(button);
              element.data('ujs:submit-button', null);
            }
            element.data('ujs:submit-button-formmethod', null);
            element.data('ujs:submit-button-formaction', null);
          } else if (element.is(rails.inputChangeSelector)) {
            method = element.data('method');
            url = element.data('url');
            data = element.serialize();
            if (element.data('params')) data = data + '&' + element.data('params');
          } else if (element.is(rails.buttonClickSelector)) {
            method = element.data('method') || 'get';
            url = element.data('url');
            data = element.serialize();
            if (element.data('params')) data = data + '&' + element.data('params');
          } else {
            method = element.data('method');
            url = rails.href(element);
            data = element.data('params') || null;
          }
  
          options = {
            type: method || 'GET', data: data, dataType: dataType,
            // stopping the "ajax:beforeSend" event will cancel the ajax request
            beforeSend: function(xhr, settings) {
              if (settings.dataType === undefined) {
                xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
              }
              if (rails.fire(element, 'ajax:beforeSend', [xhr, settings])) {
                element.trigger('ajax:send', xhr);
              } else {
                return false;
              }
            },
            success: function(data, status, xhr) {
              element.trigger('ajax:success', [data, status, xhr]);
            },
            complete: function(xhr, status) {
              element.trigger('ajax:complete', [xhr, status]);
            },
            error: function(xhr, status, error) {
              element.trigger('ajax:error', [xhr, status, error]);
            },
            crossDomain: rails.isCrossDomain(url)
          };
  
          // There is no withCredentials for IE6-8 when
          // "Enable native XMLHTTP support" is disabled
          if (withCredentials) {
            options.xhrFields = {
              withCredentials: withCredentials
            };
          }
  
          // Only pass url to `ajax` options if not blank
          if (url) { options.url = url; }
  
          return rails.ajax(options);
        } else {
          return false;
        }
      },
  
      // Determines if the request is a cross domain request.
      isCrossDomain: function(url) {
        var originAnchor = document.createElement('a');
        originAnchor.href = location.href;
        var urlAnchor = document.createElement('a');
  
        try {
          urlAnchor.href = url;
          // This is a workaround to a IE bug.
          urlAnchor.href = urlAnchor.href;
  
          // If URL protocol is false or is a string containing a single colon
          // *and* host are false, assume it is not a cross-domain request
          // (should only be the case for IE7 and IE compatibility mode).
          // Otherwise, evaluate protocol and host of the URL against the origin
          // protocol and host.
          return !(((!urlAnchor.protocol || urlAnchor.protocol === ':') && !urlAnchor.host) ||
            (originAnchor.protocol + '//' + originAnchor.host ===
              urlAnchor.protocol + '//' + urlAnchor.host));
        } catch (e) {
          // If there is an error parsing the URL, assume it is crossDomain.
          return true;
        }
      },
  
      // Handles "data-method" on links such as:
      // <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
      handleMethod: function(link) {
        var href = rails.href(link),
          method = link.data('method'),
          target = link.attr('target'),
          csrfToken = rails.csrfToken(),
          csrfParam = rails.csrfParam(),
          form = $('<form method="post" action="' + href + '"></form>'),
          metadataInput = '<input name="_method" value="' + method + '" type="hidden" />';
  
        if (csrfParam !== undefined && csrfToken !== undefined && !rails.isCrossDomain(href)) {
          metadataInput += '<input name="' + csrfParam + '" value="' + csrfToken + '" type="hidden" />';
        }
  
        if (target) { form.attr('target', target); }
  
        form.hide().append(metadataInput).appendTo('body');
        form.submit();
      },
  
      // Helper function that returns form elements that match the specified CSS selector
      // If form is actually a "form" element this will return associated elements outside the from that have
      // the html form attribute set
      formElements: function(form, selector) {
        return form.is('form') ? $(form[0].elements).filter(selector) : form.find(selector);
      },
  
      /* Disables form elements:
        - Caches element value in 'ujs:enable-with' data store
        - Replaces element text with value of 'data-disable-with' attribute
        - Sets disabled property to true
      */
      disableFormElements: function(form) {
        rails.formElements(form, rails.disableSelector).each(function() {
          rails.disableFormElement($(this));
        });
      },
  
      disableFormElement: function(element) {
        var method, replacement;
  
        method = element.is('button') ? 'html' : 'val';
        replacement = element.data('disable-with');
  
        if (replacement !== undefined) {
          element.data('ujs:enable-with', element[method]());
          element[method](replacement);
        }
  
        element.prop('disabled', true);
        element.data('ujs:disabled', true);
      },
  
      /* Re-enables disabled form elements:
        - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
        - Sets disabled property to false
      */
      enableFormElements: function(form) {
        rails.formElements(form, rails.enableSelector).each(function() {
          rails.enableFormElement($(this));
        });
      },
  
      enableFormElement: function(element) {
        var method = element.is('button') ? 'html' : 'val';
        if (element.data('ujs:enable-with') !== undefined) {
          element[method](element.data('ujs:enable-with'));
          element.removeData('ujs:enable-with'); // clean up cache
        }
        element.prop('disabled', false);
        element.removeData('ujs:disabled');
      },
  
     /* For 'data-confirm' attribute:
        - Fires `confirm` event
        - Shows the confirmation dialog
        - Fires the `confirm:complete` event
  
        Returns `true` if no function stops the chain and user chose yes; `false` otherwise.
        Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog.
        Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function
        return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog.
     */
      allowAction: function(element) {
        var message = element.data('confirm'),
            answer = false, callback;
        if (!message) { return true; }
  
        if (rails.fire(element, 'confirm')) {
          try {
            answer = rails.confirm(message);
          } catch (e) {
            (console.error || console.log).call(console, e.stack || e);
          }
          callback = rails.fire(element, 'confirm:complete', [answer]);
        }
        return answer && callback;
      },
  
      // Helper function which checks for blank inputs in a form that match the specified CSS selector
      blankInputs: function(form, specifiedSelector, nonBlank) {
        var foundInputs = $(),
          input,
          valueToCheck,
          radiosForNameWithNoneSelected,
          radioName,
          selector = specifiedSelector || 'input,textarea',
          requiredInputs = form.find(selector),
          checkedRadioButtonNames = {};
  
        requiredInputs.each(function() {
          input = $(this);
          if (input.is('input[type=radio]')) {
  
            // Don't count unchecked required radio as blank if other radio with same name is checked,
            // regardless of whether same-name radio input has required attribute or not. The spec
            // states https://www.w3.org/TR/html5/forms.html#the-required-attribute
            radioName = input.attr('name');
  
            // Skip if we've already seen the radio with this name.
            if (!checkedRadioButtonNames[radioName]) {
  
              // If none checked
              if (form.find('input[type=radio]:checked[name="' + radioName + '"]').length === 0) {
                radiosForNameWithNoneSelected = form.find(
                  'input[type=radio][name="' + radioName + '"]');
                foundInputs = foundInputs.add(radiosForNameWithNoneSelected);
              }
  
              // We only need to check each name once.
              checkedRadioButtonNames[radioName] = radioName;
            }
          } else {
            valueToCheck = input.is('input[type=checkbox],input[type=radio]') ? input.is(':checked') : !!input.val();
            if (valueToCheck === nonBlank) {
              foundInputs = foundInputs.add(input);
            }
          }
        });
        return foundInputs.length ? foundInputs : false;
      },
  
      // Helper function which checks for non-blank inputs in a form that match the specified CSS selector
      nonBlankInputs: function(form, specifiedSelector) {
        return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank
      },
  
      // Helper function, needed to provide consistent behavior in IE
      stopEverything: function(e) {
        $(e.target).trigger('ujs:everythingStopped');
        e.stopImmediatePropagation();
        return false;
      },
  
      //  Replace element's html with the 'data-disable-with' after storing original html
      //  and prevent clicking on it
      disableElement: function(element) {
        var replacement = element.data('disable-with');
  
        if (replacement !== undefined) {
          element.data('ujs:enable-with', element.html()); // store enabled state
          element.html(replacement);
        }
  
        element.bind('click.railsDisable', function(e) { // prevent further clicking
          return rails.stopEverything(e);
        });
        element.data('ujs:disabled', true);
      },
  
      // Restore element to its original state which was disabled by 'disableElement' above
      enableElement: function(element) {
        if (element.data('ujs:enable-with') !== undefined) {
          element.html(element.data('ujs:enable-with')); // set to old enabled state
          element.removeData('ujs:enable-with'); // clean up cache
        }
        element.unbind('click.railsDisable'); // enable element
        element.removeData('ujs:disabled');
      }
    };
  
    if (rails.fire($document, 'rails:attachBindings')) {
  
      $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }});
  
      // This event works the same as the load event, except that it fires every
      // time the page is loaded.
      //
      // See https://github.com/rails/jquery-ujs/issues/357
      // See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching
      $(window).on('pageshow.rails', function () {
        $($.rails.enableSelector).each(function () {
          var element = $(this);
  
          if (element.data('ujs:disabled')) {
            $.rails.enableFormElement(element);
          }
        });
  
        $($.rails.linkDisableSelector).each(function () {
          var element = $(this);
  
          if (element.data('ujs:disabled')) {
            $.rails.enableElement(element);
          }
        });
      });
  
      $document.on('ajax:complete', rails.linkDisableSelector, function() {
          rails.enableElement($(this));
      });
  
      $document.on('ajax:complete', rails.buttonDisableSelector, function() {
          rails.enableFormElement($(this));
      });
  
      $document.on('click.rails', rails.linkClickSelector, function(e) {
        var link = $(this), method = link.data('method'), data = link.data('params'), metaClick = e.metaKey || e.ctrlKey;
        if (!rails.allowAction(link)) return rails.stopEverything(e);
  
        if (!metaClick && link.is(rails.linkDisableSelector)) rails.disableElement(link);
  
        if (rails.isRemote(link)) {
          if (metaClick && (!method || method === 'GET') && !data) { return true; }
  
          var handleRemote = rails.handleRemote(link);
          // Response from rails.handleRemote() will either be false or a deferred object promise.
          if (handleRemote === false) {
            rails.enableElement(link);
          } else {
            handleRemote.fail( function() { rails.enableElement(link); } );
          }
          return false;
  
        } else if (method) {
          rails.handleMethod(link);
          return false;
        }
      });
  
      $document.on('click.rails', rails.buttonClickSelector, function(e) {
        var button = $(this);
  
        if (!rails.allowAction(button) || !rails.isRemote(button)) return rails.stopEverything(e);
  
        if (button.is(rails.buttonDisableSelector)) rails.disableFormElement(button);
  
        var handleRemote = rails.handleRemote(button);
        // Response from rails.handleRemote() will either be false or a deferred object promise.
        if (handleRemote === false) {
          rails.enableFormElement(button);
        } else {
          handleRemote.fail( function() { rails.enableFormElement(button); } );
        }
        return false;
      });
  
      $document.on('change.rails', rails.inputChangeSelector, function(e) {
        var link = $(this);
        if (!rails.allowAction(link) || !rails.isRemote(link)) return rails.stopEverything(e);
  
        rails.handleRemote(link);
        return false;
      });
  
      $document.on('submit.rails', rails.formSubmitSelector, function(e) {
        var form = $(this),
          remote = rails.isRemote(form),
          blankRequiredInputs,
          nonBlankFileInputs;
  
        if (!rails.allowAction(form)) return rails.stopEverything(e);
  
        // Skip other logic when required values are missing or file upload is present
        if (form.attr('novalidate') === undefined) {
          if (form.data('ujs:formnovalidate-button') === undefined) {
            blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector, false);
            if (blankRequiredInputs && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
              return rails.stopEverything(e);
            }
          } else {
            // Clear the formnovalidate in case the next button click is not on a formnovalidate button
            // Not strictly necessary to do here, since it is also reset on each button click, but just to be certain
            form.data('ujs:formnovalidate-button', undefined);
          }
        }
  
        if (remote) {
          nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);
          if (nonBlankFileInputs) {
            // Slight timeout so that the submit button gets properly serialized
            // (make it easy for event handler to serialize form without disabled values)
            setTimeout(function(){ rails.disableFormElements(form); }, 13);
            var aborted = rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);
  
            // Re-enable form elements if event bindings return false (canceling normal form submission)
            if (!aborted) { setTimeout(function(){ rails.enableFormElements(form); }, 13); }
  
            return aborted;
          }
  
          rails.handleRemote(form);
          return false;
  
        } else {
          // Slight timeout so that the submit button gets properly serialized
          setTimeout(function(){ rails.disableFormElements(form); }, 13);
        }
      });
  
      $document.on('click.rails', rails.formInputClickSelector, function(event) {
        var button = $(this);
  
        if (!rails.allowAction(button)) return rails.stopEverything(event);
  
        // Register the pressed submit button
        var name = button.attr('name'),
          data = name ? {name:name, value:button.val()} : null;
  
        var form = button.closest('form');
        if (form.length === 0) {
          form = $('#' + button.attr('form'));
        }
        form.data('ujs:submit-button', data);
  
        // Save attributes from button
        form.data('ujs:formnovalidate-button', button.attr('formnovalidate'));
        form.data('ujs:submit-button-formaction', button.attr('formaction'));
        form.data('ujs:submit-button-formmethod', button.attr('formmethod'));
      });
  
      $document.on('ajax:send.rails', rails.formSubmitSelector, function(event) {
        if (this === event.target) rails.disableFormElements($(this));
      });
  
      $document.on('ajax:complete.rails', rails.formSubmitSelector, function(event) {
        if (this === event.target) rails.enableFormElements($(this));
      });
  
      $(function(){
        rails.refreshCSRFTokens();
      });
    }
  
  })( jQuery );
  /*
   * Metadata - jQuery plugin for parsing metadata from elements
   *
   * Copyright (c) 2006 John Resig, Yehuda Katz, Jörn Zaefferer, Paul McLanahan
   *
   * Dual licensed under the MIT and GPL licenses:
   *   http://www.opensource.org/licenses/mit-license.php
   *   http://www.gnu.org/licenses/gpl.html
   *
   */
  
  /**
   * Sets the type of metadata to use. Metadata is encoded in JSON, and each property
   * in the JSON will become a property of the element itself.
   *
   * There are three supported types of metadata storage:
   *
   *   attr:  Inside an attribute. The name parameter indicates *which* attribute.
   *
   *   class: Inside the class attribute, wrapped in curly braces: { }
   *
   *   elem:  Inside a child element (e.g. a script tag). The
   *          name parameter indicates *which* element.
   *
   * The metadata for an element is loaded the first time the element is accessed via jQuery.
   *
   * As a result, you can define the metadata type, use $(expr) to load the metadata into the elements
   * matched by expr, then redefine the metadata type and run another $(expr) for other elements.
   *
   * @name $.metadata.setType
   *
   * @example <p id="one" class="some_class {item_id: 1, item_label: 'Label'}">This is a p</p>
   * @before $.metadata.setType("class")
   * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
   * @desc Reads metadata from the class attribute
   *
   * @example <p id="one" class="some_class" data="{item_id: 1, item_label: 'Label'}">This is a p</p>
   * @before $.metadata.setType("attr", "data")
   * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
   * @desc Reads metadata from a "data" attribute
   *
   * @example <p id="one" class="some_class"><script>{item_id: 1, item_label: 'Label'}</script>This is a p</p>
   * @before $.metadata.setType("elem", "script")
   * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
   * @desc Reads metadata from a nested script element
   *
   * @param String type The encoding type
   * @param String name The name of the attribute to be used to get metadata (optional)
   * @cat Plugins/Metadata
   * @descr Sets the type of encoding to be used when loading metadata for the first time
   * @type undefined
   * @see metadata()
   */
  
  
  (function($) {
  
  $.extend({
  	metadata : {
  		defaults : {
  			type: 'class',
  			name: 'metadata',
  			cre: /(\{.*\})/,
  			single: 'metadata'
  		},
  		setType: function( type, name ){
  			this.defaults.type = type;
  			this.defaults.name = name;
  		},
  		get: function( elem, opts ){
  			var data, m, e, attr,
  				settings = $.extend({},this.defaults,opts);
  			// check for empty string in single property
  			if ( !settings.single.length ) { settings.single = 'metadata'; }
  
  			data = $.data(elem, settings.single);
  			// returned cached data if it already exists
  			if ( data ) { return data; }
  
  			data = "{}";
  
  			if ( settings.type === "class" ) {
  				m = settings.cre.exec( elem.className );
  				if ( m ) { data = m[1]; }
  			} else if ( settings.type === "elem" ) {
  				if( !elem.getElementsByTagName ) { return undefined; }
  				e = elem.getElementsByTagName(settings.name);
  				if ( e.length ) { data = $.trim(e[0].innerHTML); }
  			} else if ( elem.getAttribute !== undefined ) {
  				attr = elem.getAttribute( settings.name );
  				if ( attr ) { data = attr; }
  			}
  
  			if ( data.indexOf( '{' ) <0 ) { data = "{" + data + "}"; }
  
  			/*jshint evil:true */
  			data = eval("(" + data + ")");
  
  			$.data( elem, settings.single, data );
  			return data;
  		}
  	}
  });
  
  /**
   * Returns the metadata object for the first member of the jQuery object.
   *
   * @name metadata
   * @descr Returns element's metadata object
   * @param Object opts An object contianing settings to override the defaults
   * @type jQuery
   * @cat Plugins/Metadata
   */
  $.fn.metadata = function( opts ){
  	return $.metadata.get( this[0], opts );
  };
  
  })(jQuery);
  /*! TableSorter (FORK) v2.28.9 *//*
  * Client-side table sorting with ease!
  * @requires jQuery v1.2.6+
  *
  * Copyright (c) 2007 Christian Bach
  * fork maintained by Rob Garrison
  *
  * Examples and original docs at: http://tablesorter.com
  * Dual licensed under the MIT and GPL licenses:
  * http://www.opensource.org/licenses/mit-license.php
  * http://www.gnu.org/licenses/gpl.html
  *
  * @type jQuery
  * @name tablesorter (FORK)
  * @cat Plugins/Tablesorter
  * @author Christian Bach - christian.bach@polyester.se
  * @contributor Rob Garrison - https://github.com/Mottie/tablesorter
  * @docs (fork) - https://mottie.github.io/tablesorter/docs/
  */
  /*jshint browser:true, jquery:true, unused:false, expr: true */
  
  ;( function( $ ) {
  	'use strict';
  	var ts = $.tablesorter = {
  
  		version : '2.28.9',
  
  		parsers : [],
  		widgets : [],
  		defaults : {
  
  			// *** appearance
  			theme            : 'default',  // adds tablesorter-{theme} to the table for styling
  			widthFixed       : false,      // adds colgroup to fix widths of columns
  			showProcessing   : false,      // show an indeterminate timer icon in the header when the table is sorted or filtered.
  
  			headerTemplate   : '{content}',// header layout template (HTML ok); {content} = innerHTML, {icon} = <i/> // class from cssIcon
  			onRenderTemplate : null,       // function( index, template ){ return template; }, // template is a string
  			onRenderHeader   : null,       // function( index ){}, // nothing to return
  
  			// *** functionality
  			cancelSelection  : true,       // prevent text selection in the header
  			tabIndex         : true,       // add tabindex to header for keyboard accessibility
  			dateFormat       : 'mmddyyyy', // other options: 'ddmmyyy' or 'yyyymmdd'
  			sortMultiSortKey : 'shiftKey', // key used to select additional columns
  			sortResetKey     : 'ctrlKey',  // key used to remove sorting on a column
  			usNumberFormat   : true,       // false for German '1.234.567,89' or French '1 234 567,89'
  			delayInit        : false,      // if false, the parsed table contents will not update until the first sort
  			serverSideSorting: false,      // if true, server-side sorting should be performed because client-side sorting will be disabled, but the ui and events will still be used.
  			resort           : true,       // default setting to trigger a resort after an 'update', 'addRows', 'updateCell', etc has completed
  
  			// *** sort options
  			headers          : {},         // set sorter, string, empty, locked order, sortInitialOrder, filter, etc.
  			ignoreCase       : true,       // ignore case while sorting
  			sortForce        : null,       // column(s) first sorted; always applied
  			sortList         : [],         // Initial sort order; applied initially; updated when manually sorted
  			sortAppend       : null,       // column(s) sorted last; always applied
  			sortStable       : false,      // when sorting two rows with exactly the same content, the original sort order is maintained
  
  			sortInitialOrder : 'asc',      // sort direction on first click
  			sortLocaleCompare: false,      // replace equivalent character (accented characters)
  			sortReset        : false,      // third click on the header will reset column to default - unsorted
  			sortRestart      : false,      // restart sort to 'sortInitialOrder' when clicking on previously unsorted columns
  
  			emptyTo          : 'bottom',   // sort empty cell to bottom, top, none, zero, emptyMax, emptyMin
  			stringTo         : 'max',      // sort strings in numerical column as max, min, top, bottom, zero
  			duplicateSpan    : true,       // colspan cells in the tbody will have duplicated content in the cache for each spanned column
  			textExtraction   : 'basic',    // text extraction method/function - function( node, table, cellIndex ){}
  			textAttribute    : 'data-text',// data-attribute that contains alternate cell text (used in default textExtraction function)
  			textSorter       : null,       // choose overall or specific column sorter function( a, b, direction, table, columnIndex ) [alt: ts.sortText]
  			numberSorter     : null,       // choose overall numeric sorter function( a, b, direction, maxColumnValue )
  
  			// *** widget options
  			initWidgets      : true,       // apply widgets on tablesorter initialization
  			widgetClass      : 'widget-{name}', // table class name template to match to include a widget
  			widgets          : [],         // method to add widgets, e.g. widgets: ['zebra']
  			widgetOptions    : {
  				zebra : [ 'even', 'odd' ]  // zebra widget alternating row class names
  			},
  
  			// *** callbacks
  			initialized      : null,       // function( table ){},
  
  			// *** extra css class names
  			tableClass       : '',
  			cssAsc           : '',
  			cssDesc          : '',
  			cssNone          : '',
  			cssHeader        : '',
  			cssHeaderRow     : '',
  			cssProcessing    : '', // processing icon applied to header during sort/filter
  
  			cssChildRow      : 'tablesorter-childRow', // class name indiciating that a row is to be attached to its parent
  			cssInfoBlock     : 'tablesorter-infoOnly', // don't sort tbody with this class name (only one class name allowed here!)
  			cssNoSort        : 'tablesorter-noSort',   // class name added to element inside header; clicking on it won't cause a sort
  			cssIgnoreRow     : 'tablesorter-ignoreRow',// header row to ignore; cells within this row will not be added to c.$headers
  
  			cssIcon          : 'tablesorter-icon', // if this class does not exist, the {icon} will not be added from the headerTemplate
  			cssIconNone      : '', // class name added to the icon when there is no column sort
  			cssIconAsc       : '', // class name added to the icon when the column has an ascending sort
  			cssIconDesc      : '', // class name added to the icon when the column has a descending sort
  
  			// *** events
  			pointerClick     : 'click',
  			pointerDown      : 'mousedown',
  			pointerUp        : 'mouseup',
  
  			// *** selectors
  			selectorHeaders  : '> thead th, > thead td',
  			selectorSort     : 'th, td', // jQuery selector of content within selectorHeaders that is clickable to trigger a sort
  			selectorRemove   : '.remove-me',
  
  			// *** advanced
  			debug            : false,
  
  			// *** Internal variables
  			headerList: [],
  			empties: {},
  			strings: {},
  			parsers: [],
  
  			// *** parser options for validator; values must be falsy!
  			globalize: 0,
  			imgAttr: 0
  
  			// removed: widgetZebra: { css: ['even', 'odd'] }
  
  		},
  
  		// internal css classes - these will ALWAYS be added to
  		// the table and MUST only contain one class name - fixes #381
  		css : {
  			table      : 'tablesorter',
  			cssHasChild: 'tablesorter-hasChildRow',
  			childRow   : 'tablesorter-childRow',
  			colgroup   : 'tablesorter-colgroup',
  			header     : 'tablesorter-header',
  			headerRow  : 'tablesorter-headerRow',
  			headerIn   : 'tablesorter-header-inner',
  			icon       : 'tablesorter-icon',
  			processing : 'tablesorter-processing',
  			sortAsc    : 'tablesorter-headerAsc',
  			sortDesc   : 'tablesorter-headerDesc',
  			sortNone   : 'tablesorter-headerUnSorted'
  		},
  
  		// labels applied to sortable headers for accessibility (aria) support
  		language : {
  			sortAsc      : 'Ascending sort applied, ',
  			sortDesc     : 'Descending sort applied, ',
  			sortNone     : 'No sort applied, ',
  			sortDisabled : 'sorting is disabled',
  			nextAsc      : 'activate to apply an ascending sort',
  			nextDesc     : 'activate to apply a descending sort',
  			nextNone     : 'activate to remove the sort'
  		},
  
  		regex : {
  			templateContent : /\{content\}/g,
  			templateIcon    : /\{icon\}/g,
  			templateName    : /\{name\}/i,
  			spaces          : /\s+/g,
  			nonWord         : /\W/g,
  			formElements    : /(input|select|button|textarea)/i,
  
  			// *** sort functions ***
  			// regex used in natural sort
  			// chunk/tokenize numbers & letters
  			chunk  : /(^([+\-]?(?:\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi,
  			// replace chunks @ ends
  			chunks : /(^\\0|\\0$)/,
  			hex    : /^0x[0-9a-f]+$/i,
  
  			// *** formatFloat ***
  			comma                : /,/g,
  			digitNonUS           : /[\s|\.]/g,
  			digitNegativeTest    : /^\s*\([.\d]+\)/,
  			digitNegativeReplace : /^\s*\(([.\d]+)\)/,
  
  			// *** isDigit ***
  			digitTest    : /^[\-+(]?\d+[)]?$/,
  			digitReplace : /[,.'"\s]/g
  
  		},
  
  		// digit sort, text location
  		string : {
  			max      : 1,
  			min      : -1,
  			emptymin : 1,
  			emptymax : -1,
  			zero     : 0,
  			none     : 0,
  			'null'   : 0,
  			top      : true,
  			bottom   : false
  		},
  
  		keyCodes : {
  			enter : 13
  		},
  
  		// placeholder date parser data (globalize)
  		dates : {},
  
  		// These methods can be applied on table.config instance
  		instanceMethods : {},
  
  		/*
  		▄█████ ██████ ██████ ██  ██ █████▄
  		▀█▄    ██▄▄     ██   ██  ██ ██▄▄██
  		   ▀█▄ ██▀▀     ██   ██  ██ ██▀▀▀
  		█████▀ ██████   ██   ▀████▀ ██
  		*/
  
  		setup : function( table, c ) {
  			// if no thead or tbody, or tablesorter is already present, quit
  			if ( !table || !table.tHead || table.tBodies.length === 0 || table.hasInitialized === true ) {
  				if ( c.debug ) {
  					if ( table.hasInitialized ) {
  						console.warn( 'Stopping initialization. Tablesorter has already been initialized' );
  					} else {
  						console.error( 'Stopping initialization! No table, thead or tbody', table );
  					}
  				}
  				return;
  			}
  
  			var tmp = '',
  				$table = $( table ),
  				meta = $.metadata;
  			// initialization flag
  			table.hasInitialized = false;
  			// table is being processed flag
  			table.isProcessing = true;
  			// make sure to store the config object
  			table.config = c;
  			// save the settings where they read
  			$.data( table, 'tablesorter', c );
  			if ( c.debug ) {
  				console[ console.group ? 'group' : 'log' ]( 'Initializing tablesorter v' + ts.version );
  				$.data( table, 'startoveralltimer', new Date() );
  			}
  
  			// removing this in version 3 (only supports jQuery 1.7+)
  			c.supportsDataObject = ( function( version ) {
  				version[ 0 ] = parseInt( version[ 0 ], 10 );
  				return ( version[ 0 ] > 1 ) || ( version[ 0 ] === 1 && parseInt( version[ 1 ], 10 ) >= 4 );
  			})( $.fn.jquery.split( '.' ) );
  			// ensure case insensitivity
  			c.emptyTo = c.emptyTo.toLowerCase();
  			c.stringTo = c.stringTo.toLowerCase();
  			c.last = { sortList : [], clickedIndex : -1 };
  			// add table theme class only if there isn't already one there
  			if ( !/tablesorter\-/.test( $table.attr( 'class' ) ) ) {
  				tmp = ( c.theme !== '' ? ' tablesorter-' + c.theme : '' );
  			}
  			c.table = table;
  			c.$table = $table
  				.addClass( ts.css.table + ' ' + c.tableClass + tmp )
  				.attr( 'role', 'grid' );
  			c.$headers = $table.find( c.selectorHeaders );
  
  			// give the table a unique id, which will be used in namespace binding
  			if ( !c.namespace ) {
  				c.namespace = '.tablesorter' + Math.random().toString( 16 ).slice( 2 );
  			} else {
  				// make sure namespace starts with a period & doesn't have weird characters
  				c.namespace = '.' + c.namespace.replace( ts.regex.nonWord, '' );
  			}
  
  			c.$table.children().children( 'tr' ).attr( 'role', 'row' );
  			c.$tbodies = $table.children( 'tbody:not(.' + c.cssInfoBlock + ')' ).attr({
  				'aria-live' : 'polite',
  				'aria-relevant' : 'all'
  			});
  			if ( c.$table.children( 'caption' ).length ) {
  				tmp = c.$table.children( 'caption' )[ 0 ];
  				if ( !tmp.id ) { tmp.id = c.namespace.slice( 1 ) + 'caption'; }
  				c.$table.attr( 'aria-labelledby', tmp.id );
  			}
  			c.widgetInit = {}; // keep a list of initialized widgets
  			// change textExtraction via data-attribute
  			c.textExtraction = c.$table.attr( 'data-text-extraction' ) || c.textExtraction || 'basic';
  			// build headers
  			ts.buildHeaders( c );
  			// fixate columns if the users supplies the fixedWidth option
  			// do this after theme has been applied
  			ts.fixColumnWidth( table );
  			// add widgets from class name
  			ts.addWidgetFromClass( table );
  			// add widget options before parsing (e.g. grouping widget has parser settings)
  			ts.applyWidgetOptions( table );
  			// try to auto detect column type, and store in tables config
  			ts.setupParsers( c );
  			// start total row count at zero
  			c.totalRows = 0;
  			ts.validateOptions( c );
  			// build the cache for the tbody cells
  			// delayInit will delay building the cache until the user starts a sort
  			if ( !c.delayInit ) { ts.buildCache( c ); }
  			// bind all header events and methods
  			ts.bindEvents( table, c.$headers, true );
  			ts.bindMethods( c );
  			// get sort list from jQuery data or metadata
  			// in jQuery < 1.4, an error occurs when calling $table.data()
  			if ( c.supportsDataObject && typeof $table.data().sortlist !== 'undefined' ) {
  				c.sortList = $table.data().sortlist;
  			} else if ( meta && ( $table.metadata() && $table.metadata().sortlist ) ) {
  				c.sortList = $table.metadata().sortlist;
  			}
  			// apply widget init code
  			ts.applyWidget( table, true );
  			// if user has supplied a sort list to constructor
  			if ( c.sortList.length > 0 ) {
  				ts.sortOn( c, c.sortList, {}, !c.initWidgets );
  			} else {
  				ts.setHeadersCss( c );
  				if ( c.initWidgets ) {
  					// apply widget format
  					ts.applyWidget( table, false );
  				}
  			}
  
  			// show processesing icon
  			if ( c.showProcessing ) {
  				$table
  				.unbind( 'sortBegin' + c.namespace + ' sortEnd' + c.namespace )
  				.bind( 'sortBegin' + c.namespace + ' sortEnd' + c.namespace, function( e ) {
  					clearTimeout( c.timerProcessing );
  					ts.isProcessing( table );
  					if ( e.type === 'sortBegin' ) {
  						c.timerProcessing = setTimeout( function() {
  							ts.isProcessing( table, true );
  						}, 500 );
  					}
  				});
  			}
  
  			// initialized
  			table.hasInitialized = true;
  			table.isProcessing = false;
  			if ( c.debug ) {
  				console.log( 'Overall initialization time:' + ts.benchmark( $.data( table, 'startoveralltimer' ) ) );
  				if ( c.debug && console.groupEnd ) { console.groupEnd(); }
  			}
  			$table.triggerHandler( 'tablesorter-initialized', table );
  			if ( typeof c.initialized === 'function' ) {
  				c.initialized( table );
  			}
  		},
  
  		bindMethods : function( c ) {
  			var $table = c.$table,
  				namespace = c.namespace,
  				events = ( 'sortReset update updateRows updateAll updateHeaders addRows updateCell updateComplete ' +
  					'sorton appendCache updateCache applyWidgetId applyWidgets refreshWidgets destroy mouseup ' +
  					'mouseleave ' ).split( ' ' )
  					.join( namespace + ' ' );
  			// apply easy methods that trigger bound events
  			$table
  			.unbind( events.replace( ts.regex.spaces, ' ' ) )
  			.bind( 'sortReset' + namespace, function( e, callback ) {
  				e.stopPropagation();
  				// using this.config to ensure functions are getting a non-cached version of the config
  				ts.sortReset( this.config, function( table ) {
  					if (table.isApplyingWidgets) {
  						// multiple triggers in a row... filterReset, then sortReset - see #1361
  						// wait to update widgets
  						setTimeout( function() {
  							ts.applyWidget( table, '', callback );
  						}, 100 );
  					} else {
  						ts.applyWidget( table, '', callback );
  					}
  				});
  			})
  			.bind( 'updateAll' + namespace, function( e, resort, callback ) {
  				e.stopPropagation();
  				ts.updateAll( this.config, resort, callback );
  			})
  			.bind( 'update' + namespace + ' updateRows' + namespace, function( e, resort, callback ) {
  				e.stopPropagation();
  				ts.update( this.config, resort, callback );
  			})
  			.bind( 'updateHeaders' + namespace, function( e, callback ) {
  				e.stopPropagation();
  				ts.updateHeaders( this.config, callback );
  			})
  			.bind( 'updateCell' + namespace, function( e, cell, resort, callback ) {
  				e.stopPropagation();
  				ts.updateCell( this.config, cell, resort, callback );
  			})
  			.bind( 'addRows' + namespace, function( e, $row, resort, callback ) {
  				e.stopPropagation();
  				ts.addRows( this.config, $row, resort, callback );
  			})
  			.bind( 'updateComplete' + namespace, function() {
  				this.isUpdating = false;
  			})
  			.bind( 'sorton' + namespace, function( e, list, callback, init ) {
  				e.stopPropagation();
  				ts.sortOn( this.config, list, callback, init );
  			})
  			.bind( 'appendCache' + namespace, function( e, callback, init ) {
  				e.stopPropagation();
  				ts.appendCache( this.config, init );
  				if ( $.isFunction( callback ) ) {
  					callback( this );
  				}
  			})
  			// $tbodies variable is used by the tbody sorting widget
  			.bind( 'updateCache' + namespace, function( e, callback, $tbodies ) {
  				e.stopPropagation();
  				ts.updateCache( this.config, callback, $tbodies );
  			})
  			.bind( 'applyWidgetId' + namespace, function( e, id ) {
  				e.stopPropagation();
  				ts.applyWidgetId( this, id );
  			})
  			.bind( 'applyWidgets' + namespace, function( e, init ) {
  				e.stopPropagation();
  				// apply widgets
  				ts.applyWidget( this, init );
  			})
  			.bind( 'refreshWidgets' + namespace, function( e, all, dontapply ) {
  				e.stopPropagation();
  				ts.refreshWidgets( this, all, dontapply );
  			})
  			.bind( 'removeWidget' + namespace, function( e, name, refreshing ) {
  				e.stopPropagation();
  				ts.removeWidget( this, name, refreshing );
  			})
  			.bind( 'destroy' + namespace, function( e, removeClasses, callback ) {
  				e.stopPropagation();
  				ts.destroy( this, removeClasses, callback );
  			})
  			.bind( 'resetToLoadState' + namespace, function( e ) {
  				e.stopPropagation();
  				// remove all widgets
  				ts.removeWidget( this, true, false );
  				var tmp = $.extend( true, {}, c.originalSettings );
  				// restore original settings; this clears out current settings, but does not clear
  				// values saved to storage.
  				c = $.extend( true, {}, ts.defaults, tmp );
  				c.originalSettings = tmp;
  				this.hasInitialized = false;
  				// setup the entire table again
  				ts.setup( this, c );
  			});
  		},
  
  		bindEvents : function( table, $headers, core ) {
  			table = $( table )[ 0 ];
  			var tmp,
  				c = table.config,
  				namespace = c.namespace,
  				downTarget = null;
  			if ( core !== true ) {
  				$headers.addClass( namespace.slice( 1 ) + '_extra_headers' );
  				tmp = $.fn.closest ? $headers.closest( 'table' )[ 0 ] : $headers.parents( 'table' )[ 0 ];
  				if ( tmp && tmp.nodeName === 'TABLE' && tmp !== table ) {
  					$( tmp ).addClass( namespace.slice( 1 ) + '_extra_table' );
  				}
  			}
  			tmp = ( c.pointerDown + ' ' + c.pointerUp + ' ' + c.pointerClick + ' sort keyup ' )
  				.replace( ts.regex.spaces, ' ' )
  				.split( ' ' )
  				.join( namespace + ' ' );
  			// apply event handling to headers and/or additional headers (stickyheaders, scroller, etc)
  			$headers
  			// http://stackoverflow.com/questions/5312849/jquery-find-self;
  			.find( c.selectorSort )
  			.add( $headers.filter( c.selectorSort ) )
  			.unbind( tmp )
  			.bind( tmp, function( e, external ) {
  				var $cell, cell, temp,
  					$target = $( e.target ),
  					// wrap event type in spaces, so the match doesn't trigger on inner words
  					type = ' ' + e.type + ' ';
  				// only recognize left clicks
  				if ( ( ( e.which || e.button ) !== 1 && !type.match( ' ' + c.pointerClick + ' | sort | keyup ' ) ) ||
  					// allow pressing enter
  					( type === ' keyup ' && e.which !== ts.keyCodes.enter ) ||
  					// allow triggering a click event (e.which is undefined) & ignore physical clicks
  					( type.match( ' ' + c.pointerClick + ' ' ) && typeof e.which !== 'undefined' ) ) {
  					return;
  				}
  				// ignore mouseup if mousedown wasn't on the same target
  				if ( type.match( ' ' + c.pointerUp + ' ' ) && downTarget !== e.target && external !== true ) {
  					return;
  				}
  				// set target on mousedown
  				if ( type.match( ' ' + c.pointerDown + ' ' ) ) {
  					downTarget = e.target;
  					// preventDefault needed or jQuery v1.3.2 and older throws an
  					// "Uncaught TypeError: handler.apply is not a function" error
  					temp = $target.jquery.split( '.' );
  					if ( temp[ 0 ] === '1' && temp[ 1 ] < 4 ) { e.preventDefault(); }
  					return;
  				}
  				downTarget = null;
  				// prevent sort being triggered on form elements
  				if ( ts.regex.formElements.test( e.target.nodeName ) ||
  					// nosort class name, or elements within a nosort container
  					$target.hasClass( c.cssNoSort ) || $target.parents( '.' + c.cssNoSort ).length > 0 ||
  					// elements within a button
  					$target.parents( 'button' ).length > 0 ) {
  					return !c.cancelSelection;
  				}
  				if ( c.delayInit && ts.isEmptyObject( c.cache ) ) {
  					ts.buildCache( c );
  				}
  				// jQuery v1.2.6 doesn't have closest()
  				$cell = $.fn.closest ? $( this ).closest( 'th, td' ) :
  					/TH|TD/.test( this.nodeName ) ? $( this ) : $( this ).parents( 'th, td' );
  				// reference original table headers and find the same cell
  				// don't use $headers or IE8 throws an error - see #987
  				temp = $headers.index( $cell );
  				c.last.clickedIndex = ( temp < 0 ) ? $cell.attr( 'data-column' ) : temp;
  				// use column index if $headers is undefined
  				cell = c.$headers[ c.last.clickedIndex ];
  				if ( cell && !cell.sortDisabled ) {
  					ts.initSort( c, cell, e );
  				}
  			});
  			if ( c.cancelSelection ) {
  				// cancel selection
  				$headers
  					.attr( 'unselectable', 'on' )
  					.bind( 'selectstart', false )
  					.css({
  						'user-select' : 'none',
  						'MozUserSelect' : 'none' // not needed for jQuery 1.8+
  					});
  			}
  		},
  
  		buildHeaders : function( c ) {
  			var $temp, icon, timer, indx;
  			c.headerList = [];
  			c.headerContent = [];
  			c.sortVars = [];
  			if ( c.debug ) {
  				timer = new Date();
  			}
  			// children tr in tfoot - see issue #196 & #547
  			// don't pass table.config to computeColumnIndex here - widgets (math) pass it to "quickly" index tbody cells
  			c.columns = ts.computeColumnIndex( c.$table.children( 'thead, tfoot' ).children( 'tr' ) );
  			// add icon if cssIcon option exists
  			icon = c.cssIcon ?
  				'<i class="' + ( c.cssIcon === ts.css.icon ? ts.css.icon : c.cssIcon + ' ' + ts.css.icon ) + '"></i>' :
  				'';
  			// redefine c.$headers here in case of an updateAll that replaces or adds an entire header cell - see #683
  			c.$headers = $( $.map( c.$table.find( c.selectorHeaders ), function( elem, index ) {
  				var configHeaders, header, column, template, tmp,
  					$elem = $( elem );
  				// ignore cell (don't add it to c.$headers) if row has ignoreRow class
  				if ( $elem.parent().hasClass( c.cssIgnoreRow ) ) { return; }
  				// make sure to get header cell & not column indexed cell
  				configHeaders = ts.getColumnData( c.table, c.headers, index, true );
  				// save original header content
  				c.headerContent[ index ] = $elem.html();
  				// if headerTemplate is empty, don't reformat the header cell
  				if ( c.headerTemplate !== '' && !$elem.find( '.' + ts.css.headerIn ).length ) {
  					// set up header template
  					template = c.headerTemplate
  						.replace( ts.regex.templateContent, $elem.html() )
  						.replace( ts.regex.templateIcon, $elem.find( '.' + ts.css.icon ).length ? '' : icon );
  					if ( c.onRenderTemplate ) {
  						header = c.onRenderTemplate.apply( $elem, [ index, template ] );
  						// only change t if something is returned
  						if ( header && typeof header === 'string' ) {
  							template = header;
  						}
  					}
  					$elem.html( '<div class="' + ts.css.headerIn + '">' + template + '</div>' ); // faster than wrapInner
  				}
  				if ( c.onRenderHeader ) {
  					c.onRenderHeader.apply( $elem, [ index, c, c.$table ] );
  				}
  				column = parseInt( $elem.attr( 'data-column' ), 10 );
  				elem.column = column;
  				tmp = ts.getOrder( ts.getData( $elem, configHeaders, 'sortInitialOrder' ) || c.sortInitialOrder );
  				// this may get updated numerous times if there are multiple rows
  				c.sortVars[ column ] = {
  					count : -1, // set to -1 because clicking on the header automatically adds one
  					order:  tmp ?
  						( c.sortReset ? [ 1, 0, 2 ] : [ 1, 0 ] ) : // desc, asc, unsorted
  						( c.sortReset ? [ 0, 1, 2 ] : [ 0, 1 ] ),  // asc, desc, unsorted
  					lockedOrder : false
  				};
  				tmp = ts.getData( $elem, configHeaders, 'lockedOrder' ) || false;
  				if ( typeof tmp !== 'undefined' && tmp !== false ) {
  					c.sortVars[ column ].lockedOrder = true;
  					c.sortVars[ column ].order = ts.getOrder( tmp ) ? [ 1, 1 ] : [ 0, 0 ];
  				}
  				// add cell to headerList
  				c.headerList[ index ] = elem;
  				// add to parent in case there are multiple rows
  				$elem
  					.addClass( ts.css.header + ' ' + c.cssHeader )
  					.parent()
  					.addClass( ts.css.headerRow + ' ' + c.cssHeaderRow )
  					.attr( 'role', 'row' );
  				// allow keyboard cursor to focus on element
  				if ( c.tabIndex ) {
  					$elem.attr( 'tabindex', 0 );
  				}
  				return elem;
  			}) );
  			// cache headers per column
  			c.$headerIndexed = [];
  			for ( indx = 0; indx < c.columns; indx++ ) {
  				// colspan in header making a column undefined
  				if ( ts.isEmptyObject( c.sortVars[ indx ] ) ) {
  					c.sortVars[ indx ] = {};
  				}
  				$temp = c.$headers.filter( '[data-column="' + indx + '"]' );
  				// target sortable column cells, unless there are none, then use non-sortable cells
  				// .last() added in jQuery 1.4; use .filter(':last') to maintain compatibility with jQuery v1.2.6
  				c.$headerIndexed[ indx ] = $temp.length ?
  					$temp.not( '.sorter-false' ).length ?
  						$temp.not( '.sorter-false' ).filter( ':last' ) :
  						$temp.filter( ':last' ) :
  					$();
  			}
  			c.$table.find( c.selectorHeaders ).attr({
  				scope: 'col',
  				role : 'columnheader'
  			});
  			// enable/disable sorting
  			ts.updateHeader( c );
  			if ( c.debug ) {
  				console.log( 'Built headers:' + ts.benchmark( timer ) );
  				console.log( c.$headers );
  			}
  		},
  
  		// Use it to add a set of methods to table.config which will be available for all tables.
  		// This should be done before table initialization
  		addInstanceMethods : function( methods ) {
  			$.extend( ts.instanceMethods, methods );
  		},
  
  		/*
  		█████▄ ▄████▄ █████▄ ▄█████ ██████ █████▄ ▄█████
  		██▄▄██ ██▄▄██ ██▄▄██ ▀█▄    ██▄▄   ██▄▄██ ▀█▄
  		██▀▀▀  ██▀▀██ ██▀██     ▀█▄ ██▀▀   ██▀██     ▀█▄
  		██     ██  ██ ██  ██ █████▀ ██████ ██  ██ █████▀
  		*/
  		setupParsers : function( c, $tbodies ) {
  			var rows, list, span, max, colIndex, indx, header, configHeaders,
  				noParser, parser, extractor, time, tbody, len,
  				table = c.table,
  				tbodyIndex = 0,
  				debug = {};
  			// update table bodies in case we start with an empty table
  			c.$tbodies = c.$table.children( 'tbody:not(.' + c.cssInfoBlock + ')' );
  			tbody = typeof $tbodies === 'undefined' ? c.$tbodies : $tbodies;
  			len = tbody.length;
  			if ( len === 0 ) {
  				return c.debug ? console.warn( 'Warning: *Empty table!* Not building a parser cache' ) : '';
  			} else if ( c.debug ) {
  				time = new Date();
  				console[ console.group ? 'group' : 'log' ]( 'Detecting parsers for each column' );
  			}
  			list = {
  				extractors: [],
  				parsers: []
  			};
  			while ( tbodyIndex < len ) {
  				rows = tbody[ tbodyIndex ].rows;
  				if ( rows.length ) {
  					colIndex = 0;
  					max = c.columns;
  					for ( indx = 0; indx < max; indx++ ) {
  						header = c.$headerIndexed[ colIndex ];
  						if ( header && header.length ) {
  							// get column indexed table cell; adding true parameter fixes #1362 but
  							// it would break backwards compatibility...
  							configHeaders = ts.getColumnData( table, c.headers, colIndex ); // , true );
  							// get column parser/extractor
  							extractor = ts.getParserById( ts.getData( header, configHeaders, 'extractor' ) );
  							parser = ts.getParserById( ts.getData( header, configHeaders, 'sorter' ) );
  							noParser = ts.getData( header, configHeaders, 'parser' ) === 'false';
  							// empty cells behaviour - keeping emptyToBottom for backwards compatibility
  							c.empties[colIndex] = (
  								ts.getData( header, configHeaders, 'empty' ) ||
  								c.emptyTo || ( c.emptyToBottom ? 'bottom' : 'top' ) ).toLowerCase();
  							// text strings behaviour in numerical sorts
  							c.strings[colIndex] = (
  								ts.getData( header, configHeaders, 'string' ) ||
  								c.stringTo ||
  								'max' ).toLowerCase();
  							if ( noParser ) {
  								parser = ts.getParserById( 'no-parser' );
  							}
  							if ( !extractor ) {
  								// For now, maybe detect someday
  								extractor = false;
  							}
  							if ( !parser ) {
  								parser = ts.detectParserForColumn( c, rows, -1, colIndex );
  							}
  							if ( c.debug ) {
  								debug[ '(' + colIndex + ') ' + header.text() ] = {
  									parser : parser.id,
  									extractor : extractor ? extractor.id : 'none',
  									string : c.strings[ colIndex ],
  									empty  : c.empties[ colIndex ]
  								};
  							}
  							list.parsers[ colIndex ] = parser;
  							list.extractors[ colIndex ] = extractor;
  							span = header[ 0 ].colSpan - 1;
  							if ( span > 0 ) {
  								colIndex += span;
  								max += span;
  								while ( span + 1 > 0 ) {
  									// set colspan columns to use the same parsers & extractors
  									list.parsers[ colIndex - span ] = parser;
  									list.extractors[ colIndex - span ] = extractor;
  									span--;
  								}
  							}
  						}
  						colIndex++;
  					}
  				}
  				tbodyIndex += ( list.parsers.length ) ? len : 1;
  			}
  			if ( c.debug ) {
  				if ( !ts.isEmptyObject( debug ) ) {
  					console[ console.table ? 'table' : 'log' ]( debug );
  				} else {
  					console.warn( '  No parsers detected!' );
  				}
  				console.log( 'Completed detecting parsers' + ts.benchmark( time ) );
  				if ( console.groupEnd ) { console.groupEnd(); }
  			}
  			c.parsers = list.parsers;
  			c.extractors = list.extractors;
  		},
  
  		addParser : function( parser ) {
  			var indx,
  				len = ts.parsers.length,
  				add = true;
  			for ( indx = 0; indx < len; indx++ ) {
  				if ( ts.parsers[ indx ].id.toLowerCase() === parser.id.toLowerCase() ) {
  					add = false;
  				}
  			}
  			if ( add ) {
  				ts.parsers[ ts.parsers.length ] = parser;
  			}
  		},
  
  		getParserById : function( name ) {
  			/*jshint eqeqeq:false */
  			if ( name == 'false' ) { return false; }
  			var indx,
  				len = ts.parsers.length;
  			for ( indx = 0; indx < len; indx++ ) {
  				if ( ts.parsers[ indx ].id.toLowerCase() === ( name.toString() ).toLowerCase() ) {
  					return ts.parsers[ indx ];
  				}
  			}
  			return false;
  		},
  
  		detectParserForColumn : function( c, rows, rowIndex, cellIndex ) {
  			var cur, $node, row,
  				indx = ts.parsers.length,
  				node = false,
  				nodeValue = '',
  				keepLooking = true;
  			while ( nodeValue === '' && keepLooking ) {
  				rowIndex++;
  				row = rows[ rowIndex ];
  				// stop looking after 50 empty rows
  				if ( row && rowIndex < 50 ) {
  					if ( row.className.indexOf( ts.cssIgnoreRow ) < 0 ) {
  						node = rows[ rowIndex ].cells[ cellIndex ];
  						nodeValue = ts.getElementText( c, node, cellIndex );
  						$node = $( node );
  						if ( c.debug ) {
  							console.log( 'Checking if value was empty on row ' + rowIndex + ', column: ' +
  								cellIndex + ': "' + nodeValue + '"' );
  						}
  					}
  				} else {
  					keepLooking = false;
  				}
  			}
  			while ( --indx >= 0 ) {
  				cur = ts.parsers[ indx ];
  				// ignore the default text parser because it will always be true
  				if ( cur && cur.id !== 'text' && cur.is && cur.is( nodeValue, c.table, node, $node ) ) {
  					return cur;
  				}
  			}
  			// nothing found, return the generic parser (text)
  			return ts.getParserById( 'text' );
  		},
  
  		getElementText : function( c, node, cellIndex ) {
  			if ( !node ) { return ''; }
  			var tmp,
  				extract = c.textExtraction || '',
  				// node could be a jquery object
  				// http://jsperf.com/jquery-vs-instanceof-jquery/2
  				$node = node.jquery ? node : $( node );
  			if ( typeof extract === 'string' ) {
  				// check data-attribute first when set to 'basic'; don't use node.innerText - it's really slow!
  				// http://www.kellegous.com/j/2013/02/27/innertext-vs-textcontent/
  				if ( extract === 'basic' && typeof ( tmp = $node.attr( c.textAttribute ) ) !== 'undefined' ) {
  					return $.trim( tmp );
  				}
  				return $.trim( node.textContent || $node.text() );
  			} else {
  				if ( typeof extract === 'function' ) {
  					return $.trim( extract( $node[ 0 ], c.table, cellIndex ) );
  				} else if ( typeof ( tmp = ts.getColumnData( c.table, extract, cellIndex ) ) === 'function' ) {
  					return $.trim( tmp( $node[ 0 ], c.table, cellIndex ) );
  				}
  			}
  			// fallback
  			return $.trim( $node[ 0 ].textContent || $node.text() );
  		},
  
  		// centralized function to extract/parse cell contents
  		getParsedText : function( c, cell, colIndex, txt ) {
  			if ( typeof txt === 'undefined' ) {
  				txt = ts.getElementText( c, cell, colIndex );
  			}
  			// if no parser, make sure to return the txt
  			var val = '' + txt,
  				parser = c.parsers[ colIndex ],
  				extractor = c.extractors[ colIndex ];
  			if ( parser ) {
  				// do extract before parsing, if there is one
  				if ( extractor && typeof extractor.format === 'function' ) {
  					txt = extractor.format( txt, c.table, cell, colIndex );
  				}
  				// allow parsing if the string is empty, previously parsing would change it to zero,
  				// in case the parser needs to extract data from the table cell attributes
  				val = parser.id === 'no-parser' ? '' :
  					// make sure txt is a string (extractor may have converted it)
  					parser.format( '' + txt, c.table, cell, colIndex );
  				if ( c.ignoreCase && typeof val === 'string' ) {
  					val = val.toLowerCase();
  				}
  			}
  			return val;
  		},
  
  		/*
  		▄████▄ ▄████▄ ▄████▄ ██  ██ ██████
  		██  ▀▀ ██▄▄██ ██  ▀▀ ██▄▄██ ██▄▄
  		██  ▄▄ ██▀▀██ ██  ▄▄ ██▀▀██ ██▀▀
  		▀████▀ ██  ██ ▀████▀ ██  ██ ██████
  		*/
  		buildCache : function( c, callback, $tbodies ) {
  			var cache, val, txt, rowIndex, colIndex, tbodyIndex, $tbody, $row,
  				cols, $cells, cell, cacheTime, totalRows, rowData, prevRowData,
  				colMax, span, cacheIndex, hasParser, max, len, index,
  				table = c.table,
  				parsers = c.parsers;
  			// update tbody variable
  			c.$tbodies = c.$table.children( 'tbody:not(.' + c.cssInfoBlock + ')' );
  			$tbody = typeof $tbodies === 'undefined' ? c.$tbodies : $tbodies,
  			c.cache = {};
  			c.totalRows = 0;
  			// if no parsers found, return - it's an empty table.
  			if ( !parsers ) {
  				return c.debug ? console.warn( 'Warning: *Empty table!* Not building a cache' ) : '';
  			}
  			if ( c.debug ) {
  				cacheTime = new Date();
  			}
  			// processing icon
  			if ( c.showProcessing ) {
  				ts.isProcessing( table, true );
  			}
  			for ( tbodyIndex = 0; tbodyIndex < $tbody.length; tbodyIndex++ ) {
  				colMax = []; // column max value per tbody
  				cache = c.cache[ tbodyIndex ] = {
  					normalized: [] // array of normalized row data; last entry contains 'rowData' above
  					// colMax: #   // added at the end
  				};
  
  				totalRows = ( $tbody[ tbodyIndex ] && $tbody[ tbodyIndex ].rows.length ) || 0;
  				for ( rowIndex = 0; rowIndex < totalRows; ++rowIndex ) {
  					rowData = {
  						// order: original row order #
  						// $row : jQuery Object[]
  						child: [], // child row text (filter widget)
  						raw: []    // original row text
  					};
  					/** Add the table data to main data array */
  					$row = $( $tbody[ tbodyIndex ].rows[ rowIndex ] );
  					cols = [];
  					// ignore "remove-me" rows
  					if ( $row.hasClass( c.selectorRemove.slice(1) ) ) {
  						continue;
  					}
  					// if this is a child row, add it to the last row's children and continue to the next row
  					// ignore child row class, if it is the first row
  					if ( $row.hasClass( c.cssChildRow ) && rowIndex !== 0 ) {
  						len = cache.normalized.length - 1;
  						prevRowData = cache.normalized[ len ][ c.columns ];
  						prevRowData.$row = prevRowData.$row.add( $row );
  						// add 'hasChild' class name to parent row
  						if ( !$row.prev().hasClass( c.cssChildRow ) ) {
  							$row.prev().addClass( ts.css.cssHasChild );
  						}
  						// save child row content (un-parsed!)
  						$cells = $row.children( 'th, td' );
  						len = prevRowData.child.length;
  						prevRowData.child[ len ] = [];
  						// child row content does not account for colspans/rowspans; so indexing may be off
  						cacheIndex = 0;
  						max = c.columns;
  						for ( colIndex = 0; colIndex < max; colIndex++ ) {
  							cell = $cells[ colIndex ];
  							if ( cell ) {
  								prevRowData.child[ len ][ colIndex ] = ts.getParsedText( c, cell, colIndex );
  								span = $cells[ colIndex ].colSpan - 1;
  								if ( span > 0 ) {
  									cacheIndex += span;
  									max += span;
  								}
  							}
  							cacheIndex++;
  						}
  						// go to the next for loop
  						continue;
  					}
  					rowData.$row = $row;
  					rowData.order = rowIndex; // add original row position to rowCache
  					cacheIndex = 0;
  					max = c.columns;
  					for ( colIndex = 0; colIndex < max; ++colIndex ) {
  						cell = $row[ 0 ].cells[ colIndex ];
  						if ( cell && cacheIndex < c.columns ) {
  							hasParser = typeof parsers[ cacheIndex ] !== 'undefined';
  							if ( !hasParser && c.debug ) {
  								console.warn( 'No parser found for row: ' + rowIndex + ', column: ' + colIndex +
  									'; cell containing: "' + $(cell).text() + '"; does it have a header?' );
  							}
  							val = ts.getElementText( c, cell, cacheIndex );
  							rowData.raw[ cacheIndex ] = val; // save original row text
  							// save raw column text even if there is no parser set
  							txt = ts.getParsedText( c, cell, cacheIndex, val );
  							cols[ cacheIndex ] = txt;
  							if ( hasParser && ( parsers[ cacheIndex ].type || '' ).toLowerCase() === 'numeric' ) {
  								// determine column max value (ignore sign)
  								colMax[ cacheIndex ] = Math.max( Math.abs( txt ) || 0, colMax[ cacheIndex ] || 0 );
  							}
  							// allow colSpan in tbody
  							span = cell.colSpan - 1;
  							if ( span > 0 ) {
  								index = 0;
  								while ( index <= span ) {
  									// duplicate text (or not) to spanned columns
  									// instead of setting duplicate span to empty string, use textExtraction to try to get a value
  									// see http://stackoverflow.com/q/36449711/145346
  									txt = c.duplicateSpan || index === 0 ?
  										val :
  										typeof c.textExtraction !== 'string' ?
  											ts.getElementText( c, cell, cacheIndex + index ) || '' :
  											'';
  									rowData.raw[ cacheIndex + index ] = txt;
  									cols[ cacheIndex + index ] = txt;
  									index++;
  								}
  								cacheIndex += span;
  								max += span;
  							}
  						}
  						cacheIndex++;
  					}
  					// ensure rowData is always in the same location (after the last column)
  					cols[ c.columns ] = rowData;
  					cache.normalized[ cache.normalized.length ] = cols;
  				}
  				cache.colMax = colMax;
  				// total up rows, not including child rows
  				c.totalRows += cache.normalized.length;
  
  			}
  			if ( c.showProcessing ) {
  				ts.isProcessing( table ); // remove processing icon
  			}
  			if ( c.debug ) {
  				len = Math.min( 5, c.cache[ 0 ].normalized.length );
  				console[ console.group ? 'group' : 'log' ]( 'Building cache for ' + c.totalRows +
  					' rows (showing ' + len + ' rows in log) and ' + c.columns + ' columns' +
  					ts.benchmark( cacheTime ) );
  				val = {};
  				for ( colIndex = 0; colIndex < c.columns; colIndex++ ) {
  					for ( cacheIndex = 0; cacheIndex < len; cacheIndex++ ) {
  						if ( !val[ 'row: ' + cacheIndex ] ) {
  							val[ 'row: ' + cacheIndex ] = {};
  						}
  						val[ 'row: ' + cacheIndex ][ c.$headerIndexed[ colIndex ].text() ] =
  							c.cache[ 0 ].normalized[ cacheIndex ][ colIndex ];
  					}
  				}
  				console[ console.table ? 'table' : 'log' ]( val );
  				if ( console.groupEnd ) { console.groupEnd(); }
  			}
  			if ( $.isFunction( callback ) ) {
  				callback( table );
  			}
  		},
  
  		getColumnText : function( table, column, callback, rowFilter ) {
  			table = $( table )[0];
  			var tbodyIndex, rowIndex, cache, row, tbodyLen, rowLen, raw, parsed, $cell, result,
  				hasCallback = typeof callback === 'function',
  				allColumns = column === 'all',
  				data = { raw : [], parsed: [], $cell: [] },
  				c = table.config;
  			if ( ts.isEmptyObject( c ) ) {
  				if ( c.debug ) {
  					console.warn( 'No cache found - aborting getColumnText function!' );
  				}
  			} else {
  				tbodyLen = c.$tbodies.length;
  				for ( tbodyIndex = 0; tbodyIndex < tbodyLen; tbodyIndex++ ) {
  					cache = c.cache[ tbodyIndex ].normalized;
  					rowLen = cache.length;
  					for ( rowIndex = 0; rowIndex < rowLen; rowIndex++ ) {
  						row = cache[ rowIndex ];
  						if ( rowFilter && !row[ c.columns ].$row.is( rowFilter ) ) {
  							continue;
  						}
  						result = true;
  						parsed = ( allColumns ) ? row.slice( 0, c.columns ) : row[ column ];
  						row = row[ c.columns ];
  						raw = ( allColumns ) ? row.raw : row.raw[ column ];
  						$cell = ( allColumns ) ? row.$row.children() : row.$row.children().eq( column );
  						if ( hasCallback ) {
  							result = callback({
  								tbodyIndex : tbodyIndex,
  								rowIndex : rowIndex,
  								parsed : parsed,
  								raw : raw,
  								$row : row.$row,
  								$cell : $cell
  							});
  						}
  						if ( result !== false ) {
  							data.parsed[ data.parsed.length ] = parsed;
  							data.raw[ data.raw.length ] = raw;
  							data.$cell[ data.$cell.length ] = $cell;
  						}
  					}
  				}
  				// return everything
  				return data;
  			}
  		},
  
  		/*
  		██  ██ █████▄ █████▄ ▄████▄ ██████ ██████
  		██  ██ ██▄▄██ ██  ██ ██▄▄██   ██   ██▄▄
  		██  ██ ██▀▀▀  ██  ██ ██▀▀██   ██   ██▀▀
  		▀████▀ ██     █████▀ ██  ██   ██   ██████
  		*/
  		setHeadersCss : function( c ) {
  			var $sorted, indx, column,
  				list = c.sortList,
  				len = list.length,
  				none = ts.css.sortNone + ' ' + c.cssNone,
  				css = [ ts.css.sortAsc + ' ' + c.cssAsc, ts.css.sortDesc + ' ' + c.cssDesc ],
  				cssIcon = [ c.cssIconAsc, c.cssIconDesc, c.cssIconNone ],
  				aria = [ 'ascending', 'descending' ],
  				// find the footer
  				$headers = c.$table
  					.find( 'tfoot tr' )
  					.children( 'td, th' )
  					.add( $( c.namespace + '_extra_headers' ) )
  					.removeClass( css.join( ' ' ) );
  			// remove all header information
  			c.$headers
  				.add( $( 'thead ' + c.namespace + '_extra_headers' ) )
  				.removeClass( css.join( ' ' ) )
  				.addClass( none )
  				.attr( 'aria-sort', 'none' )
  				.find( '.' + ts.css.icon )
  				.removeClass( cssIcon.join( ' ' ) )
  				.addClass( cssIcon[ 2 ] );
  			for ( indx = 0; indx < len; indx++ ) {
  				// direction = 2 means reset!
  				if ( list[ indx ][ 1 ] !== 2 ) {
  					// multicolumn sorting updating - see #1005
  					// .not(function(){}) needs jQuery 1.4
  					// filter(function(i, el){}) <- el is undefined in jQuery v1.2.6
  					$sorted = c.$headers.filter( function( i ) {
  						// only include headers that are in the sortList (this includes colspans)
  						var include = true,
  							$el = c.$headers.eq( i ),
  							col = parseInt( $el.attr( 'data-column' ), 10 ),
  							end = col + c.$headers[ i ].colSpan;
  						for ( ; col < end; col++ ) {
  							include = include ? include || ts.isValueInArray( col, c.sortList ) > -1 : false;
  						}
  						return include;
  					});
  
  					// choose the :last in case there are nested columns
  					$sorted = $sorted
  						.not( '.sorter-false' )
  						.filter( '[data-column="' + list[ indx ][ 0 ] + '"]' + ( len === 1 ? ':last' : '' ) );
  					if ( $sorted.length ) {
  						for ( column = 0; column < $sorted.length; column++ ) {
  							if ( !$sorted[ column ].sortDisabled ) {
  								$sorted
  									.eq( column )
  									.removeClass( none )
  									.addClass( css[ list[ indx ][ 1 ] ] )
  									.attr( 'aria-sort', aria[ list[ indx ][ 1 ] ] )
  									.find( '.' + ts.css.icon )
  									.removeClass( cssIcon[ 2 ] )
  									.addClass( cssIcon[ list[ indx ][ 1 ] ] );
  							}
  						}
  						// add sorted class to footer & extra headers, if they exist
  						if ( $headers.length ) {
  							$headers
  								.filter( '[data-column="' + list[ indx ][ 0 ] + '"]' )
  								.removeClass( none )
  								.addClass( css[ list[ indx ][ 1 ] ] );
  						}
  					}
  				}
  			}
  			// add verbose aria labels
  			len = c.$headers.length;
  			for ( indx = 0; indx < len; indx++ ) {
  				ts.setColumnAriaLabel( c, c.$headers.eq( indx ) );
  			}
  		},
  
  		// nextSort (optional), lets you disable next sort text
  		setColumnAriaLabel : function( c, $header, nextSort ) {
  			if ( $header.length ) {
  				var column = parseInt( $header.attr( 'data-column' ), 10 ),
  					vars = c.sortVars[ column ],
  					tmp = $header.hasClass( ts.css.sortAsc ) ?
  						'sortAsc' :
  						$header.hasClass( ts.css.sortDesc ) ? 'sortDesc' : 'sortNone',
  					txt = $.trim( $header.text() ) + ': ' + ts.language[ tmp ];
  				if ( $header.hasClass( 'sorter-false' ) || nextSort === false ) {
  					txt += ts.language.sortDisabled;
  				} else {
  					tmp = ( vars.count + 1 ) % vars.order.length;
  					nextSort = vars.order[ tmp ];
  					// if nextSort
  					txt += ts.language[ nextSort === 0 ? 'nextAsc' : nextSort === 1 ? 'nextDesc' : 'nextNone' ];
  				}
  				$header.attr( 'aria-label', txt );
  			}
  		},
  
  		updateHeader : function( c ) {
  			var index, isDisabled, $header, col,
  				table = c.table,
  				len = c.$headers.length;
  			for ( index = 0; index < len; index++ ) {
  				$header = c.$headers.eq( index );
  				col = ts.getColumnData( table, c.headers, index, true );
  				// add 'sorter-false' class if 'parser-false' is set
  				isDisabled = ts.getData( $header, col, 'sorter' ) === 'false' || ts.getData( $header, col, 'parser' ) === 'false';
  				ts.setColumnSort( c, $header, isDisabled );
  			}
  		},
  
  		setColumnSort : function( c, $header, isDisabled ) {
  			var id = c.table.id;
  			$header[ 0 ].sortDisabled = isDisabled;
  			$header[ isDisabled ? 'addClass' : 'removeClass' ]( 'sorter-false' )
  				.attr( 'aria-disabled', '' + isDisabled );
  			// disable tab index on disabled cells
  			if ( c.tabIndex ) {
  				if ( isDisabled ) {
  					$header.removeAttr( 'tabindex' );
  				} else {
  					$header.attr( 'tabindex', '0' );
  				}
  			}
  			// aria-controls - requires table ID
  			if ( id ) {
  				if ( isDisabled ) {
  					$header.removeAttr( 'aria-controls' );
  				} else {
  					$header.attr( 'aria-controls', id );
  				}
  			}
  		},
  
  		updateHeaderSortCount : function( c, list ) {
  			var col, dir, group, indx, primary, temp, val, order,
  				sortList = list || c.sortList,
  				len = sortList.length;
  			c.sortList = [];
  			for ( indx = 0; indx < len; indx++ ) {
  				val = sortList[ indx ];
  				// ensure all sortList values are numeric - fixes #127
  				col = parseInt( val[ 0 ], 10 );
  				// prevents error if sorton array is wrong
  				if ( col < c.columns ) {
  
  					// set order if not already defined - due to colspan header without associated header cell
  					// adding this check prevents a javascript error
  					if ( !c.sortVars[ col ].order ) {
  						if ( ts.getOrder( c.sortInitialOrder ) ) {
  							order = c.sortReset ? [ 1, 0, 2 ] : [ 1, 0 ];
  						} else {
  							order = c.sortReset ? [ 0, 1, 2 ] : [ 0, 1 ];
  						}
  						c.sortVars[ col ].order = order;
  						c.sortVars[ col ].count = 0;
  					}
  
  					order = c.sortVars[ col ].order;
  					dir = ( '' + val[ 1 ] ).match( /^(1|d|s|o|n)/ );
  					dir = dir ? dir[ 0 ] : '';
  					// 0/(a)sc (default), 1/(d)esc, (s)ame, (o)pposite, (n)ext
  					switch ( dir ) {
  						case '1' : case 'd' : // descending
  							dir = 1;
  							break;
  						case 's' : // same direction (as primary column)
  							// if primary sort is set to 's', make it ascending
  							dir = primary || 0;
  							break;
  						case 'o' :
  							temp = order[ ( primary || 0 ) % order.length ];
  							// opposite of primary column; but resets if primary resets
  							dir = temp === 0 ? 1 : temp === 1 ? 0 : 2;
  							break;
  						case 'n' :
  							dir = order[ ( ++c.sortVars[ col ].count ) % order.length ];
  							break;
  						default : // ascending
  							dir = 0;
  							break;
  					}
  					primary = indx === 0 ? dir : primary;
  					group = [ col, parseInt( dir, 10 ) || 0 ];
  					c.sortList[ c.sortList.length ] = group;
  					dir = $.inArray( group[ 1 ], order ); // fixes issue #167
  					c.sortVars[ col ].count = dir >= 0 ? dir : group[ 1 ] % order.length;
  				}
  			}
  		},
  
  		updateAll : function( c, resort, callback ) {
  			var table = c.table;
  			table.isUpdating = true;
  			ts.refreshWidgets( table, true, true );
  			ts.buildHeaders( c );
  			ts.bindEvents( table, c.$headers, true );
  			ts.bindMethods( c );
  			ts.commonUpdate( c, resort, callback );
  		},
  
  		update : function( c, resort, callback ) {
  			var table = c.table;
  			table.isUpdating = true;
  			// update sorting (if enabled/disabled)
  			ts.updateHeader( c );
  			ts.commonUpdate( c, resort, callback );
  		},
  
  		// simple header update - see #989
  		updateHeaders : function( c, callback ) {
  			c.table.isUpdating = true;
  			ts.buildHeaders( c );
  			ts.bindEvents( c.table, c.$headers, true );
  			ts.resortComplete( c, callback );
  		},
  
  		updateCell : function( c, cell, resort, callback ) {
  			if ( ts.isEmptyObject( c.cache ) ) {
  				// empty table, do an update instead - fixes #1099
  				ts.updateHeader( c );
  				ts.commonUpdate( c, resort, callback );
  				return;
  			}
  			c.table.isUpdating = true;
  			c.$table.find( c.selectorRemove ).remove();
  			// get position from the dom
  			var tmp, indx, row, icell, cache, len,
  				$tbodies = c.$tbodies,
  				$cell = $( cell ),
  				// update cache - format: function( s, table, cell, cellIndex )
  				// no closest in jQuery v1.2.6
  				tbodyIndex = $tbodies
  					.index( $.fn.closest ? $cell.closest( 'tbody' ) : $cell.parents( 'tbody' ).filter( ':first' ) ),
  				tbcache = c.cache[ tbodyIndex ],
  				$row = $.fn.closest ? $cell.closest( 'tr' ) : $cell.parents( 'tr' ).filter( ':first' );
  			cell = $cell[ 0 ]; // in case cell is a jQuery object
  			// tbody may not exist if update is initialized while tbody is removed for processing
  			if ( $tbodies.length && tbodyIndex >= 0 ) {
  				row = $tbodies.eq( tbodyIndex ).find( 'tr' ).not( '.' + c.cssChildRow ).index( $row );
  				cache = tbcache.normalized[ row ];
  				len = $row[ 0 ].cells.length;
  				if ( len !== c.columns ) {
  					// colspan in here somewhere!
  					icell = 0;
  					tmp = false;
  					for ( indx = 0; indx < len; indx++ ) {
  						if ( !tmp && $row[ 0 ].cells[ indx ] !== cell ) {
  							icell += $row[ 0 ].cells[ indx ].colSpan;
  						} else {
  							tmp = true;
  						}
  					}
  				} else {
  					icell = $cell.index();
  				}
  				tmp = ts.getElementText( c, cell, icell ); // raw
  				cache[ c.columns ].raw[ icell ] = tmp;
  				tmp = ts.getParsedText( c, cell, icell, tmp );
  				cache[ icell ] = tmp; // parsed
  				if ( ( c.parsers[ icell ].type || '' ).toLowerCase() === 'numeric' ) {
  					// update column max value (ignore sign)
  					tbcache.colMax[ icell ] = Math.max( Math.abs( tmp ) || 0, tbcache.colMax[ icell ] || 0 );
  				}
  				tmp = resort !== 'undefined' ? resort : c.resort;
  				if ( tmp !== false ) {
  					// widgets will be reapplied
  					ts.checkResort( c, tmp, callback );
  				} else {
  					// don't reapply widgets is resort is false, just in case it causes
  					// problems with element focus
  					ts.resortComplete( c, callback );
  				}
  			} else {
  				if ( c.debug ) {
  					console.error( 'updateCell aborted, tbody missing or not within the indicated table' );
  				}
  				c.table.isUpdating = false;
  			}
  		},
  
  		addRows : function( c, $row, resort, callback ) {
  			var txt, val, tbodyIndex, rowIndex, rows, cellIndex, len, order,
  				cacheIndex, rowData, cells, cell, span,
  				// allow passing a row string if only one non-info tbody exists in the table
  				valid = typeof $row === 'string' && c.$tbodies.length === 1 && /<tr/.test( $row || '' ),
  				table = c.table;
  			if ( valid ) {
  				$row = $( $row );
  				c.$tbodies.append( $row );
  			} else if ( !$row ||
  				// row is a jQuery object?
  				!( $row instanceof jQuery ) ||
  				// row contained in the table?
  				( $.fn.closest ? $row.closest( 'table' )[ 0 ] : $row.parents( 'table' )[ 0 ] ) !== c.table ) {
  				if ( c.debug ) {
  					console.error( 'addRows method requires (1) a jQuery selector reference to rows that have already ' +
  						'been added to the table, or (2) row HTML string to be added to a table with only one tbody' );
  				}
  				return false;
  			}
  			table.isUpdating = true;
  			if ( ts.isEmptyObject( c.cache ) ) {
  				// empty table, do an update instead - fixes #450
  				ts.updateHeader( c );
  				ts.commonUpdate( c, resort, callback );
  			} else {
  				rows = $row.filter( 'tr' ).attr( 'role', 'row' ).length;
  				tbodyIndex = c.$tbodies.index( $row.parents( 'tbody' ).filter( ':first' ) );
  				// fixes adding rows to an empty table - see issue #179
  				if ( !( c.parsers && c.parsers.length ) ) {
  					ts.setupParsers( c );
  				}
  				// add each row
  				for ( rowIndex = 0; rowIndex < rows; rowIndex++ ) {
  					cacheIndex = 0;
  					len = $row[ rowIndex ].cells.length;
  					order = c.cache[ tbodyIndex ].normalized.length;
  					cells = [];
  					rowData = {
  						child : [],
  						raw : [],
  						$row : $row.eq( rowIndex ),
  						order : order
  					};
  					// add each cell
  					for ( cellIndex = 0; cellIndex < len; cellIndex++ ) {
  						cell = $row[ rowIndex ].cells[ cellIndex ];
  						txt = ts.getElementText( c, cell, cacheIndex );
  						rowData.raw[ cacheIndex ] = txt;
  						val = ts.getParsedText( c, cell, cacheIndex, txt );
  						cells[ cacheIndex ] = val;
  						if ( ( c.parsers[ cacheIndex ].type || '' ).toLowerCase() === 'numeric' ) {
  							// update column max value (ignore sign)
  							c.cache[ tbodyIndex ].colMax[ cacheIndex ] =
  								Math.max( Math.abs( val ) || 0, c.cache[ tbodyIndex ].colMax[ cacheIndex ] || 0 );
  						}
  						span = cell.colSpan - 1;
  						if ( span > 0 ) {
  							cacheIndex += span;
  						}
  						cacheIndex++;
  					}
  					// add the row data to the end
  					cells[ c.columns ] = rowData;
  					// update cache
  					c.cache[ tbodyIndex ].normalized[ order ] = cells;
  				}
  				// resort using current settings
  				ts.checkResort( c, resort, callback );
  			}
  		},
  
  		updateCache : function( c, callback, $tbodies ) {
  			// rebuild parsers
  			if ( !( c.parsers && c.parsers.length ) ) {
  				ts.setupParsers( c, $tbodies );
  			}
  			// rebuild the cache map
  			ts.buildCache( c, callback, $tbodies );
  		},
  
  		// init flag (true) used by pager plugin to prevent widget application
  		// renamed from appendToTable
  		appendCache : function( c, init ) {
  			var parsed, totalRows, $tbody, $curTbody, rowIndex, tbodyIndex, appendTime,
  				table = c.table,
  				wo = c.widgetOptions,
  				$tbodies = c.$tbodies,
  				rows = [],
  				cache = c.cache;
  			// empty table - fixes #206/#346
  			if ( ts.isEmptyObject( cache ) ) {
  				// run pager appender in case the table was just emptied
  				return c.appender ? c.appender( table, rows ) :
  					table.isUpdating ? c.$table.triggerHandler( 'updateComplete', table ) : ''; // Fixes #532
  			}
  			if ( c.debug ) {
  				appendTime = new Date();
  			}
  			for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
  				$tbody = $tbodies.eq( tbodyIndex );
  				if ( $tbody.length ) {
  					// detach tbody for manipulation
  					$curTbody = ts.processTbody( table, $tbody, true );
  					parsed = cache[ tbodyIndex ].normalized;
  					totalRows = parsed.length;
  					for ( rowIndex = 0; rowIndex < totalRows; rowIndex++ ) {
  						rows[rows.length] = parsed[ rowIndex ][ c.columns ].$row;
  						// removeRows used by the pager plugin; don't render if using ajax - fixes #411
  						if ( !c.appender || ( c.pager && ( !c.pager.removeRows || !wo.pager_removeRows ) && !c.pager.ajax ) ) {
  							$curTbody.append( parsed[ rowIndex ][ c.columns ].$row );
  						}
  					}
  					// restore tbody
  					ts.processTbody( table, $curTbody, false );
  				}
  			}
  			if ( c.appender ) {
  				c.appender( table, rows );
  			}
  			if ( c.debug ) {
  				console.log( 'Rebuilt table' + ts.benchmark( appendTime ) );
  			}
  			// apply table widgets; but not before ajax completes
  			if ( !init && !c.appender ) {
  				ts.applyWidget( table );
  			}
  			if ( table.isUpdating ) {
  				c.$table.triggerHandler( 'updateComplete', table );
  			}
  		},
  
  		commonUpdate : function( c, resort, callback ) {
  			// remove rows/elements before update
  			c.$table.find( c.selectorRemove ).remove();
  			// rebuild parsers
  			ts.setupParsers( c );
  			// rebuild the cache map
  			ts.buildCache( c );
  			ts.checkResort( c, resort, callback );
  		},
  
  		/*
  		▄█████ ▄████▄ █████▄ ██████ ██ █████▄ ▄████▄
  		▀█▄    ██  ██ ██▄▄██   ██   ██ ██  ██ ██ ▄▄▄
  		   ▀█▄ ██  ██ ██▀██    ██   ██ ██  ██ ██ ▀██
  		█████▀ ▀████▀ ██  ██   ██   ██ ██  ██ ▀████▀
  		*/
  		initSort : function( c, cell, event ) {
  			if ( c.table.isUpdating ) {
  				// let any updates complete before initializing a sort
  				return setTimeout( function(){
  					ts.initSort( c, cell, event );
  				}, 50 );
  			}
  
  			var arry, indx, headerIndx, dir, temp, tmp, $header,
  				notMultiSort = !event[ c.sortMultiSortKey ],
  				table = c.table,
  				len = c.$headers.length,
  				// get current column index
  				col = parseInt( $( cell ).attr( 'data-column' ), 10 ),
  				order = c.sortVars[ col ].order;
  
  			// Only call sortStart if sorting is enabled
  			c.$table.triggerHandler( 'sortStart', table );
  			// get current column sort order
  			tmp = ( c.sortVars[ col ].count + 1 ) % order.length;
  			c.sortVars[ col ].count = event[ c.sortResetKey ] ? 2 : tmp;
  			// reset all sorts on non-current column - issue #30
  			if ( c.sortRestart ) {
  				for ( headerIndx = 0; headerIndx < len; headerIndx++ ) {
  					$header = c.$headers.eq( headerIndx );
  					tmp = parseInt( $header.attr( 'data-column' ), 10 );
  					// only reset counts on columns that weren't just clicked on and if not included in a multisort
  					if ( col !== tmp && ( notMultiSort || $header.hasClass( ts.css.sortNone ) ) ) {
  						c.sortVars[ tmp ].count = -1;
  					}
  				}
  			}
  			// user only wants to sort on one column
  			if ( notMultiSort ) {
  				// flush the sort list
  				c.sortList = [];
  				c.last.sortList = [];
  				if ( c.sortForce !== null ) {
  					arry = c.sortForce;
  					for ( indx = 0; indx < arry.length; indx++ ) {
  						if ( arry[ indx ][ 0 ] !== col ) {
  							c.sortList[ c.sortList.length ] = arry[ indx ];
  						}
  					}
  				}
  				// add column to sort list
  				dir = order[ c.sortVars[ col ].count ];
  				if ( dir < 2 ) {
  					c.sortList[ c.sortList.length ] = [ col, dir ];
  					// add other columns if header spans across multiple
  					if ( cell.colSpan > 1 ) {
  						for ( indx = 1; indx < cell.colSpan; indx++ ) {
  							c.sortList[ c.sortList.length ] = [ col + indx, dir ];
  							// update count on columns in colSpan
  							c.sortVars[ col + indx ].count = $.inArray( dir, order );
  						}
  					}
  				}
  				// multi column sorting
  			} else {
  				// get rid of the sortAppend before adding more - fixes issue #115 & #523
  				c.sortList = $.extend( [], c.last.sortList );
  
  				// the user has clicked on an already sorted column
  				if ( ts.isValueInArray( col, c.sortList ) >= 0 ) {
  					// reverse the sorting direction
  					for ( indx = 0; indx < c.sortList.length; indx++ ) {
  						tmp = c.sortList[ indx ];
  						if ( tmp[ 0 ] === col ) {
  							// order.count seems to be incorrect when compared to cell.count
  							tmp[ 1 ] = order[ c.sortVars[ col ].count ];
  							if ( tmp[1] === 2 ) {
  								c.sortList.splice( indx, 1 );
  								c.sortVars[ col ].count = -1;
  							}
  						}
  					}
  				} else {
  					// add column to sort list array
  					dir = order[ c.sortVars[ col ].count ];
  					if ( dir < 2 ) {
  						c.sortList[ c.sortList.length ] = [ col, dir ];
  						// add other columns if header spans across multiple
  						if ( cell.colSpan > 1 ) {
  							for ( indx = 1; indx < cell.colSpan; indx++ ) {
  								c.sortList[ c.sortList.length ] = [ col + indx, dir ];
  								// update count on columns in colSpan
  								c.sortVars[ col + indx ].count = $.inArray( dir, order );
  							}
  						}
  					}
  				}
  			}
  			// save sort before applying sortAppend
  			c.last.sortList = $.extend( [], c.sortList );
  			if ( c.sortList.length && c.sortAppend ) {
  				arry = $.isArray( c.sortAppend ) ? c.sortAppend : c.sortAppend[ c.sortList[ 0 ][ 0 ] ];
  				if ( !ts.isEmptyObject( arry ) ) {
  					for ( indx = 0; indx < arry.length; indx++ ) {
  						if ( arry[ indx ][ 0 ] !== col && ts.isValueInArray( arry[ indx ][ 0 ], c.sortList ) < 0 ) {
  							dir = arry[ indx ][ 1 ];
  							temp = ( '' + dir ).match( /^(a|d|s|o|n)/ );
  							if ( temp ) {
  								tmp = c.sortList[ 0 ][ 1 ];
  								switch ( temp[ 0 ] ) {
  									case 'd' :
  										dir = 1;
  										break;
  									case 's' :
  										dir = tmp;
  										break;
  									case 'o' :
  										dir = tmp === 0 ? 1 : 0;
  										break;
  									case 'n' :
  										dir = ( tmp + 1 ) % order.length;
  										break;
  									default:
  										dir = 0;
  										break;
  								}
  							}
  							c.sortList[ c.sortList.length ] = [ arry[ indx ][ 0 ], dir ];
  						}
  					}
  				}
  			}
  			// sortBegin event triggered immediately before the sort
  			c.$table.triggerHandler( 'sortBegin', table );
  			// setTimeout needed so the processing icon shows up
  			setTimeout( function() {
  				// set css for headers
  				ts.setHeadersCss( c );
  				ts.multisort( c );
  				ts.appendCache( c );
  				c.$table.triggerHandler( 'sortBeforeEnd', table );
  				c.$table.triggerHandler( 'sortEnd', table );
  			}, 1 );
  		},
  
  		// sort multiple columns
  		multisort : function( c ) { /*jshint loopfunc:true */
  			var tbodyIndex, sortTime, colMax, rows, tmp,
  				table = c.table,
  				sorter = [],
  				dir = 0,
  				textSorter = c.textSorter || '',
  				sortList = c.sortList,
  				sortLen = sortList.length,
  				len = c.$tbodies.length;
  			if ( c.serverSideSorting || ts.isEmptyObject( c.cache ) ) {
  				// empty table - fixes #206/#346
  				return;
  			}
  			if ( c.debug ) { sortTime = new Date(); }
  			// cache textSorter to optimize speed
  			if ( typeof textSorter === 'object' ) {
  				colMax = c.columns;
  				while ( colMax-- ) {
  					tmp = ts.getColumnData( table, textSorter, colMax );
  					if ( typeof tmp === 'function' ) {
  						sorter[ colMax ] = tmp;
  					}
  				}
  			}
  			for ( tbodyIndex = 0; tbodyIndex < len; tbodyIndex++ ) {
  				colMax = c.cache[ tbodyIndex ].colMax;
  				rows = c.cache[ tbodyIndex ].normalized;
  
  				rows.sort( function( a, b ) {
  					var sortIndex, num, col, order, sort, x, y;
  					// rows is undefined here in IE, so don't use it!
  					for ( sortIndex = 0; sortIndex < sortLen; sortIndex++ ) {
  						col = sortList[ sortIndex ][ 0 ];
  						order = sortList[ sortIndex ][ 1 ];
  						// sort direction, true = asc, false = desc
  						dir = order === 0;
  
  						if ( c.sortStable && a[ col ] === b[ col ] && sortLen === 1 ) {
  							return a[ c.columns ].order - b[ c.columns ].order;
  						}
  
  						// fallback to natural sort since it is more robust
  						num = /n/i.test( ts.getSortType( c.parsers, col ) );
  						if ( num && c.strings[ col ] ) {
  							// sort strings in numerical columns
  							if ( typeof ( ts.string[ c.strings[ col ] ] ) === 'boolean' ) {
  								num = ( dir ? 1 : -1 ) * ( ts.string[ c.strings[ col ] ] ? -1 : 1 );
  							} else {
  								num = ( c.strings[ col ] ) ? ts.string[ c.strings[ col ] ] || 0 : 0;
  							}
  							// fall back to built-in numeric sort
  							// var sort = $.tablesorter['sort' + s]( a[col], b[col], dir, colMax[col], table );
  							sort = c.numberSorter ? c.numberSorter( a[ col ], b[ col ], dir, colMax[ col ], table ) :
  								ts[ 'sortNumeric' + ( dir ? 'Asc' : 'Desc' ) ]( a[ col ], b[ col ], num, colMax[ col ], col, c );
  						} else {
  							// set a & b depending on sort direction
  							x = dir ? a : b;
  							y = dir ? b : a;
  							// text sort function
  							if ( typeof textSorter === 'function' ) {
  								// custom OVERALL text sorter
  								sort = textSorter( x[ col ], y[ col ], dir, col, table );
  							} else if ( typeof sorter[ col ] === 'function' ) {
  								// custom text sorter for a SPECIFIC COLUMN
  								sort = sorter[ col ]( x[ col ], y[ col ], dir, col, table );
  							} else {
  								// fall back to natural sort
  								sort = ts[ 'sortNatural' + ( dir ? 'Asc' : 'Desc' ) ]( a[ col ], b[ col ], col, c );
  							}
  						}
  						if ( sort ) { return sort; }
  					}
  					return a[ c.columns ].order - b[ c.columns ].order;
  				});
  			}
  			if ( c.debug ) {
  				console.log( 'Applying sort ' + sortList.toString() + ts.benchmark( sortTime ) );
  			}
  		},
  
  		resortComplete : function( c, callback ) {
  			if ( c.table.isUpdating ) {
  				c.$table.triggerHandler( 'updateComplete', c.table );
  			}
  			if ( $.isFunction( callback ) ) {
  				callback( c.table );
  			}
  		},
  
  		checkResort : function( c, resort, callback ) {
  			var sortList = $.isArray( resort ) ? resort : c.sortList,
  				// if no resort parameter is passed, fallback to config.resort (true by default)
  				resrt = typeof resort === 'undefined' ? c.resort : resort;
  			// don't try to resort if the table is still processing
  			// this will catch spamming of the updateCell method
  			if ( resrt !== false && !c.serverSideSorting && !c.table.isProcessing ) {
  				if ( sortList.length ) {
  					ts.sortOn( c, sortList, function() {
  						ts.resortComplete( c, callback );
  					}, true );
  				} else {
  					ts.sortReset( c, function() {
  						ts.resortComplete( c, callback );
  						ts.applyWidget( c.table, false );
  					} );
  				}
  			} else {
  				ts.resortComplete( c, callback );
  				ts.applyWidget( c.table, false );
  			}
  		},
  
  		sortOn : function( c, list, callback, init ) {
  			var table = c.table;
  			c.$table.triggerHandler( 'sortStart', table );
  			// update header count index
  			ts.updateHeaderSortCount( c, list );
  			// set css for headers
  			ts.setHeadersCss( c );
  			// fixes #346
  			if ( c.delayInit && ts.isEmptyObject( c.cache ) ) {
  				ts.buildCache( c );
  			}
  			c.$table.triggerHandler( 'sortBegin', table );
  			// sort the table and append it to the dom
  			ts.multisort( c );
  			ts.appendCache( c, init );
  			c.$table.triggerHandler( 'sortBeforeEnd', table );
  			c.$table.triggerHandler( 'sortEnd', table );
  			ts.applyWidget( table );
  			if ( $.isFunction( callback ) ) {
  				callback( table );
  			}
  		},
  
  		sortReset : function( c, callback ) {
  			c.sortList = [];
  			ts.setHeadersCss( c );
  			ts.multisort( c );
  			ts.appendCache( c );
  			var indx;
  			for (indx = 0; indx < c.columns; indx++) {
  				c.sortVars[ indx ].count = -1;
  			}
  			if ( $.isFunction( callback ) ) {
  				callback( c.table );
  			}
  		},
  
  		getSortType : function( parsers, column ) {
  			return ( parsers && parsers[ column ] ) ? parsers[ column ].type || '' : '';
  		},
  
  		getOrder : function( val ) {
  			// look for 'd' in 'desc' order; return true
  			return ( /^d/i.test( val ) || val === 1 );
  		},
  
  		// Natural sort - https://github.com/overset/javascript-natural-sort (date sorting removed)
  		// this function will only accept strings, or you'll see 'TypeError: undefined is not a function'
  		// I could add a = a.toString(); b = b.toString(); but it'll slow down the sort overall
  		sortNatural : function( a, b ) {
  			if ( a === b ) { return 0; }
  			var aNum, bNum, aFloat, bFloat, indx, max,
  				regex = ts.regex;
  			// first try and sort Hex codes
  			if ( regex.hex.test( b ) ) {
  				aNum = parseInt( ( a || '' ).match( regex.hex ), 16 );
  				bNum = parseInt( ( b || '' ).match( regex.hex ), 16 );
  				if ( aNum < bNum ) { return -1; }
  				if ( aNum > bNum ) { return 1; }
  			}
  			// chunk/tokenize
  			aNum = ( a || '' ).replace( regex.chunk, '\\0$1\\0' ).replace( regex.chunks, '' ).split( '\\0' );
  			bNum = ( b || '' ).replace( regex.chunk, '\\0$1\\0' ).replace( regex.chunks, '' ).split( '\\0' );
  			max = Math.max( aNum.length, bNum.length );
  			// natural sorting through split numeric strings and default strings
  			for ( indx = 0; indx < max; indx++ ) {
  				// find floats not starting with '0', string or 0 if not defined
  				aFloat = isNaN( aNum[ indx ] ) ? aNum[ indx ] || 0 : parseFloat( aNum[ indx ] ) || 0;
  				bFloat = isNaN( bNum[ indx ] ) ? bNum[ indx ] || 0 : parseFloat( bNum[ indx ] ) || 0;
  				// handle numeric vs string comparison - number < string - (Kyle Adams)
  				if ( isNaN( aFloat ) !== isNaN( bFloat ) ) { return isNaN( aFloat ) ? 1 : -1; }
  				// rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
  				if ( typeof aFloat !== typeof bFloat ) {
  					aFloat += '';
  					bFloat += '';
  				}
  				if ( aFloat < bFloat ) { return -1; }
  				if ( aFloat > bFloat ) { return 1; }
  			}
  			return 0;
  		},
  
  		sortNaturalAsc : function( a, b, col, c ) {
  			if ( a === b ) { return 0; }
  			var empty = ts.string[ ( c.empties[ col ] || c.emptyTo ) ];
  			if ( a === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? -1 : 1 ) : -empty || -1; }
  			if ( b === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? 1 : -1 ) : empty || 1; }
  			return ts.sortNatural( a, b );
  		},
  
  		sortNaturalDesc : function( a, b, col, c ) {
  			if ( a === b ) { return 0; }
  			var empty = ts.string[ ( c.empties[ col ] || c.emptyTo ) ];
  			if ( a === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? -1 : 1 ) : empty || 1; }
  			if ( b === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? 1 : -1 ) : -empty || -1; }
  			return ts.sortNatural( b, a );
  		},
  
  		// basic alphabetical sort
  		sortText : function( a, b ) {
  			return a > b ? 1 : ( a < b ? -1 : 0 );
  		},
  
  		// return text string value by adding up ascii value
  		// so the text is somewhat sorted when using a digital sort
  		// this is NOT an alphanumeric sort
  		getTextValue : function( val, num, max ) {
  			if ( max ) {
  				// make sure the text value is greater than the max numerical value (max)
  				var indx,
  					len = val ? val.length : 0,
  					n = max + num;
  				for ( indx = 0; indx < len; indx++ ) {
  					n += val.charCodeAt( indx );
  				}
  				return num * n;
  			}
  			return 0;
  		},
  
  		sortNumericAsc : function( a, b, num, max, col, c ) {
  			if ( a === b ) { return 0; }
  			var empty = ts.string[ ( c.empties[ col ] || c.emptyTo ) ];
  			if ( a === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? -1 : 1 ) : -empty || -1; }
  			if ( b === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? 1 : -1 ) : empty || 1; }
  			if ( isNaN( a ) ) { a = ts.getTextValue( a, num, max ); }
  			if ( isNaN( b ) ) { b = ts.getTextValue( b, num, max ); }
  			return a - b;
  		},
  
  		sortNumericDesc : function( a, b, num, max, col, c ) {
  			if ( a === b ) { return 0; }
  			var empty = ts.string[ ( c.empties[ col ] || c.emptyTo ) ];
  			if ( a === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? -1 : 1 ) : empty || 1; }
  			if ( b === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? 1 : -1 ) : -empty || -1; }
  			if ( isNaN( a ) ) { a = ts.getTextValue( a, num, max ); }
  			if ( isNaN( b ) ) { b = ts.getTextValue( b, num, max ); }
  			return b - a;
  		},
  
  		sortNumeric : function( a, b ) {
  			return a - b;
  		},
  
  		/*
  		██ ██ ██ ██ █████▄ ▄████▄ ██████ ██████ ▄█████
  		██ ██ ██ ██ ██  ██ ██ ▄▄▄ ██▄▄     ██   ▀█▄
  		██ ██ ██ ██ ██  ██ ██ ▀██ ██▀▀     ██      ▀█▄
  		███████▀ ██ █████▀ ▀████▀ ██████   ██   █████▀
  		*/
  		addWidget : function( widget ) {
  			if ( widget.id && !ts.isEmptyObject( ts.getWidgetById( widget.id ) ) ) {
  				console.warn( '"' + widget.id + '" widget was loaded more than once!' );
  			}
  			ts.widgets[ ts.widgets.length ] = widget;
  		},
  
  		hasWidget : function( $table, name ) {
  			$table = $( $table );
  			return $table.length && $table[ 0 ].config && $table[ 0 ].config.widgetInit[ name ] || false;
  		},
  
  		getWidgetById : function( name ) {
  			var indx, widget,
  				len = ts.widgets.length;
  			for ( indx = 0; indx < len; indx++ ) {
  				widget = ts.widgets[ indx ];
  				if ( widget && widget.id && widget.id.toLowerCase() === name.toLowerCase() ) {
  					return widget;
  				}
  			}
  		},
  
  		applyWidgetOptions : function( table ) {
  			var indx, widget, wo,
  				c = table.config,
  				len = c.widgets.length;
  			if ( len ) {
  				for ( indx = 0; indx < len; indx++ ) {
  					widget = ts.getWidgetById( c.widgets[ indx ] );
  					if ( widget && widget.options ) {
  						wo = $.extend( true, {}, widget.options );
  						c.widgetOptions = $.extend( true, wo, c.widgetOptions );
  						// add widgetOptions to defaults for option validator
  						$.extend( true, ts.defaults.widgetOptions, widget.options );
  					}
  				}
  			}
  		},
  
  		addWidgetFromClass : function( table ) {
  			var len, indx,
  				c = table.config,
  				// look for widgets to apply from table class
  				// don't match from 'ui-widget-content'; use \S instead of \w to include widgets
  				// with dashes in the name, e.g. "widget-test-2" extracts out "test-2"
  				regex = '^' + c.widgetClass.replace( ts.regex.templateName, '(\\S+)+' ) + '$',
  				widgetClass = new RegExp( regex, 'g' ),
  				// split up table class (widget id's can include dashes) - stop using match
  				// otherwise only one widget gets extracted, see #1109
  				widgets = ( table.className || '' ).split( ts.regex.spaces );
  			if ( widgets.length ) {
  				len = widgets.length;
  				for ( indx = 0; indx < len; indx++ ) {
  					if ( widgets[ indx ].match( widgetClass ) ) {
  						c.widgets[ c.widgets.length ] = widgets[ indx ].replace( widgetClass, '$1' );
  					}
  				}
  			}
  		},
  
  		applyWidgetId : function( table, id, init ) {
  			table = $(table)[0];
  			var applied, time, name,
  				c = table.config,
  				wo = c.widgetOptions,
  				widget = ts.getWidgetById( id );
  			if ( widget ) {
  				name = widget.id;
  				applied = false;
  				// add widget name to option list so it gets reapplied after sorting, filtering, etc
  				if ( $.inArray( name, c.widgets ) < 0 ) {
  					c.widgets[ c.widgets.length ] = name;
  				}
  				if ( c.debug ) { time = new Date(); }
  
  				if ( init || !( c.widgetInit[ name ] ) ) {
  					// set init flag first to prevent calling init more than once (e.g. pager)
  					c.widgetInit[ name ] = true;
  					if ( table.hasInitialized ) {
  						// don't reapply widget options on tablesorter init
  						ts.applyWidgetOptions( table );
  					}
  					if ( typeof widget.init === 'function' ) {
  						applied = true;
  						if ( c.debug ) {
  							console[ console.group ? 'group' : 'log' ]( 'Initializing ' + name + ' widget' );
  						}
  						widget.init( table, widget, c, wo );
  					}
  				}
  				if ( !init && typeof widget.format === 'function' ) {
  					applied = true;
  					if ( c.debug ) {
  						console[ console.group ? 'group' : 'log' ]( 'Updating ' + name + ' widget' );
  					}
  					widget.format( table, c, wo, false );
  				}
  				if ( c.debug ) {
  					if ( applied ) {
  						console.log( 'Completed ' + ( init ? 'initializing ' : 'applying ' ) + name + ' widget' + ts.benchmark( time ) );
  						if ( console.groupEnd ) { console.groupEnd(); }
  					}
  				}
  			}
  		},
  
  		applyWidget : function( table, init, callback ) {
  			table = $( table )[ 0 ]; // in case this is called externally
  			var indx, len, names, widget, time,
  				c = table.config,
  				widgets = [];
  			// prevent numerous consecutive widget applications
  			if ( init !== false && table.hasInitialized && ( table.isApplyingWidgets || table.isUpdating ) ) {
  				return;
  			}
  			if ( c.debug ) { time = new Date(); }
  			ts.addWidgetFromClass( table );
  			// prevent "tablesorter-ready" from firing multiple times in a row
  			clearTimeout( c.timerReady );
  			if ( c.widgets.length ) {
  				table.isApplyingWidgets = true;
  				// ensure unique widget ids
  				c.widgets = $.grep( c.widgets, function( val, index ) {
  					return $.inArray( val, c.widgets ) === index;
  				});
  				names = c.widgets || [];
  				len = names.length;
  				// build widget array & add priority as needed
  				for ( indx = 0; indx < len; indx++ ) {
  					widget = ts.getWidgetById( names[ indx ] );
  					if ( widget && widget.id ) {
  						// set priority to 10 if not defined
  						if ( !widget.priority ) { widget.priority = 10; }
  						widgets[ indx ] = widget;
  					} else if ( c.debug ) {
  						console.warn( '"' + names[ indx ] + '" widget code does not exist!' );
  					}
  				}
  				// sort widgets by priority
  				widgets.sort( function( a, b ) {
  					return a.priority < b.priority ? -1 : a.priority === b.priority ? 0 : 1;
  				});
  				// add/update selected widgets
  				len = widgets.length;
  				if ( c.debug ) {
  					console[ console.group ? 'group' : 'log' ]( 'Start ' + ( init ? 'initializing' : 'applying' ) + ' widgets' );
  				}
  				for ( indx = 0; indx < len; indx++ ) {
  					widget = widgets[ indx ];
  					if ( widget && widget.id ) {
  						ts.applyWidgetId( table, widget.id, init );
  					}
  				}
  				if ( c.debug && console.groupEnd ) { console.groupEnd(); }
  			}
  			c.timerReady = setTimeout( function() {
  				table.isApplyingWidgets = false;
  				$.data( table, 'lastWidgetApplication', new Date() );
  				c.$table.triggerHandler( 'tablesorter-ready' );
  				// callback executed on init only
  				if ( !init && typeof callback === 'function' ) {
  					callback( table );
  				}
  				if ( c.debug ) {
  					widget = c.widgets.length;
  					console.log( 'Completed ' +
  						( init === true ? 'initializing ' : 'applying ' ) + widget +
  						' widget' + ( widget !== 1 ? 's' : '' ) + ts.benchmark( time ) );
  				}
  			}, 10 );
  		},
  
  		removeWidget : function( table, name, refreshing ) {
  			table = $( table )[ 0 ];
  			var index, widget, indx, len,
  				c = table.config;
  			// if name === true, add all widgets from $.tablesorter.widgets
  			if ( name === true ) {
  				name = [];
  				len = ts.widgets.length;
  				for ( indx = 0; indx < len; indx++ ) {
  					widget = ts.widgets[ indx ];
  					if ( widget && widget.id ) {
  						name[ name.length ] = widget.id;
  					}
  				}
  			} else {
  				// name can be either an array of widgets names,
  				// or a space/comma separated list of widget names
  				name = ( $.isArray( name ) ? name.join( ',' ) : name || '' ).toLowerCase().split( /[\s,]+/ );
  			}
  			len = name.length;
  			for ( index = 0; index < len; index++ ) {
  				widget = ts.getWidgetById( name[ index ] );
  				indx = $.inArray( name[ index ], c.widgets );
  				// don't remove the widget from config.widget if refreshing
  				if ( indx >= 0 && refreshing !== true ) {
  					c.widgets.splice( indx, 1 );
  				}
  				if ( widget && widget.remove ) {
  					if ( c.debug ) {
  						console.log( ( refreshing ? 'Refreshing' : 'Removing' ) + ' "' + name[ index ] + '" widget' );
  					}
  					widget.remove( table, c, c.widgetOptions, refreshing );
  					c.widgetInit[ name[ index ] ] = false;
  				}
  			}
  		},
  
  		refreshWidgets : function( table, doAll, dontapply ) {
  			table = $( table )[ 0 ]; // see issue #243
  			var indx, widget,
  				c = table.config,
  				curWidgets = c.widgets,
  				widgets = ts.widgets,
  				len = widgets.length,
  				list = [],
  				callback = function( table ) {
  					$( table ).triggerHandler( 'refreshComplete' );
  				};
  			// remove widgets not defined in config.widgets, unless doAll is true
  			for ( indx = 0; indx < len; indx++ ) {
  				widget = widgets[ indx ];
  				if ( widget && widget.id && ( doAll || $.inArray( widget.id, curWidgets ) < 0 ) ) {
  					list[ list.length ] = widget.id;
  				}
  			}
  			ts.removeWidget( table, list.join( ',' ), true );
  			if ( dontapply !== true ) {
  				// call widget init if
  				ts.applyWidget( table, doAll || false, callback );
  				if ( doAll ) {
  					// apply widget format
  					ts.applyWidget( table, false, callback );
  				}
  			} else {
  				callback( table );
  			}
  		},
  
  		/*
  		██  ██ ██████ ██ ██     ██ ██████ ██ ██████ ▄█████
  		██  ██   ██   ██ ██     ██   ██   ██ ██▄▄   ▀█▄
  		██  ██   ██   ██ ██     ██   ██   ██ ██▀▀      ▀█▄
  		▀████▀   ██   ██ ██████ ██   ██   ██ ██████ █████▀
  		*/
  		benchmark : function( diff ) {
  			return ( ' (' + ( new Date().getTime() - diff.getTime() ) + ' ms)' );
  		},
  		// deprecated ts.log
  		log : function() {
  			console.log( arguments );
  		},
  
  		// $.isEmptyObject from jQuery v1.4
  		isEmptyObject : function( obj ) {
  			/*jshint forin: false */
  			for ( var name in obj ) {
  				return false;
  			}
  			return true;
  		},
  
  		isValueInArray : function( column, arry ) {
  			var indx,
  				len = arry && arry.length || 0;
  			for ( indx = 0; indx < len; indx++ ) {
  				if ( arry[ indx ][ 0 ] === column ) {
  					return indx;
  				}
  			}
  			return -1;
  		},
  
  		formatFloat : function( str, table ) {
  			if ( typeof str !== 'string' || str === '' ) { return str; }
  			// allow using formatFloat without a table; defaults to US number format
  			var num,
  				usFormat = table && table.config ? table.config.usNumberFormat !== false :
  					typeof table !== 'undefined' ? table : true;
  			if ( usFormat ) {
  				// US Format - 1,234,567.89 -> 1234567.89
  				str = str.replace( ts.regex.comma, '' );
  			} else {
  				// German Format = 1.234.567,89 -> 1234567.89
  				// French Format = 1 234 567,89 -> 1234567.89
  				str = str.replace( ts.regex.digitNonUS, '' ).replace( ts.regex.comma, '.' );
  			}
  			if ( ts.regex.digitNegativeTest.test( str ) ) {
  				// make (#) into a negative number -> (10) = -10
  				str = str.replace( ts.regex.digitNegativeReplace, '-$1' );
  			}
  			num = parseFloat( str );
  			// return the text instead of zero
  			return isNaN( num ) ? $.trim( str ) : num;
  		},
  
  		isDigit : function( str ) {
  			// replace all unwanted chars and match
  			return isNaN( str ) ?
  				ts.regex.digitTest.test( str.toString().replace( ts.regex.digitReplace, '' ) ) :
  				str !== '';
  		},
  
  		// computeTableHeaderCellIndexes from:
  		// http://www.javascripttoolbox.com/lib/table/examples.php
  		// http://www.javascripttoolbox.com/temp/table_cellindex.html
  		computeColumnIndex : function( $rows, c ) {
  			var i, j, k, l, cell, cells, rowIndex, rowSpan, colSpan, firstAvailCol,
  				// total columns has been calculated, use it to set the matrixrow
  				columns = c && c.columns || 0,
  				matrix = [],
  				matrixrow = new Array( columns );
  			for ( i = 0; i < $rows.length; i++ ) {
  				cells = $rows[ i ].cells;
  				for ( j = 0; j < cells.length; j++ ) {
  					cell = cells[ j ];
  					rowIndex = cell.parentNode.rowIndex;
  					rowSpan = cell.rowSpan || 1;
  					colSpan = cell.colSpan || 1;
  					if ( typeof matrix[ rowIndex ] === 'undefined' ) {
  						matrix[ rowIndex ] = [];
  					}
  					// Find first available column in the first row
  					for ( k = 0; k < matrix[ rowIndex ].length + 1; k++ ) {
  						if ( typeof matrix[ rowIndex ][ k ] === 'undefined' ) {
  							firstAvailCol = k;
  							break;
  						}
  					}
  					// jscs:disable disallowEmptyBlocks
  					if ( columns && cell.cellIndex === firstAvailCol ) {
  						// don't to anything
  					} else if ( cell.setAttribute ) {
  						// jscs:enable disallowEmptyBlocks
  						// add data-column (setAttribute = IE8+)
  						cell.setAttribute( 'data-column', firstAvailCol );
  					} else {
  						// remove once we drop support for IE7 - 1/12/2016
  						$( cell ).attr( 'data-column', firstAvailCol );
  					}
  					for ( k = rowIndex; k < rowIndex + rowSpan; k++ ) {
  						if ( typeof matrix[ k ] === 'undefined' ) {
  							matrix[ k ] = [];
  						}
  						matrixrow = matrix[ k ];
  						for ( l = firstAvailCol; l < firstAvailCol + colSpan; l++ ) {
  							matrixrow[ l ] = 'x';
  						}
  					}
  				}
  			}
  			return matrixrow.length;
  		},
  
  		// automatically add a colgroup with col elements set to a percentage width
  		fixColumnWidth : function( table ) {
  			table = $( table )[ 0 ];
  			var overallWidth, percent, $tbodies, len, index,
  				c = table.config,
  				$colgroup = c.$table.children( 'colgroup' );
  			// remove plugin-added colgroup, in case we need to refresh the widths
  			if ( $colgroup.length && $colgroup.hasClass( ts.css.colgroup ) ) {
  				$colgroup.remove();
  			}
  			if ( c.widthFixed && c.$table.children( 'colgroup' ).length === 0 ) {
  				$colgroup = $( '<colgroup class="' + ts.css.colgroup + '">' );
  				overallWidth = c.$table.width();
  				// only add col for visible columns - fixes #371
  				$tbodies = c.$tbodies.find( 'tr:first' ).children( ':visible' );
  				len = $tbodies.length;
  				for ( index = 0; index < len; index++ ) {
  					percent = parseInt( ( $tbodies.eq( index ).width() / overallWidth ) * 1000, 10 ) / 10 + '%';
  					$colgroup.append( $( '<col>' ).css( 'width', percent ) );
  				}
  				c.$table.prepend( $colgroup );
  			}
  		},
  
  		// get sorter, string, empty, etc options for each column from
  		// jQuery data, metadata, header option or header class name ('sorter-false')
  		// priority = jQuery data > meta > headers option > header class name
  		getData : function( header, configHeader, key ) {
  			var meta, cl4ss,
  				val = '',
  				$header = $( header );
  			if ( !$header.length ) { return ''; }
  			meta = $.metadata ? $header.metadata() : false;
  			cl4ss = ' ' + ( $header.attr( 'class' ) || '' );
  			if ( typeof $header.data( key ) !== 'undefined' ||
  				typeof $header.data( key.toLowerCase() ) !== 'undefined' ) {
  				// 'data-lockedOrder' is assigned to 'lockedorder'; but 'data-locked-order' is assigned to 'lockedOrder'
  				// 'data-sort-initial-order' is assigned to 'sortInitialOrder'
  				val += $header.data( key ) || $header.data( key.toLowerCase() );
  			} else if ( meta && typeof meta[ key ] !== 'undefined' ) {
  				val += meta[ key ];
  			} else if ( configHeader && typeof configHeader[ key ] !== 'undefined' ) {
  				val += configHeader[ key ];
  			} else if ( cl4ss !== ' ' && cl4ss.match( ' ' + key + '-' ) ) {
  				// include sorter class name 'sorter-text', etc; now works with 'sorter-my-custom-parser'
  				val = cl4ss.match( new RegExp( '\\s' + key + '-([\\w-]+)' ) )[ 1 ] || '';
  			}
  			return $.trim( val );
  		},
  
  		getColumnData : function( table, obj, indx, getCell, $headers ) {
  			if ( typeof obj !== 'object' || obj === null ) {
  				return obj;
  			}
  			table = $( table )[ 0 ];
  			var $header, key,
  				c = table.config,
  				$cells = ( $headers || c.$headers ),
  				// c.$headerIndexed is not defined initially
  				$cell = c.$headerIndexed && c.$headerIndexed[ indx ] ||
  					$cells.filter( '[data-column="' + indx + '"]:last' );
  			if ( typeof obj[ indx ] !== 'undefined' ) {
  				return getCell ? obj[ indx ] : obj[ $cells.index( $cell ) ];
  			}
  			for ( key in obj ) {
  				if ( typeof key === 'string' ) {
  					$header = $cell
  						// header cell with class/id
  						.filter( key )
  						// find elements within the header cell with cell/id
  						.add( $cell.find( key ) );
  					if ( $header.length ) {
  						return obj[ key ];
  					}
  				}
  			}
  			return;
  		},
  
  		// *** Process table ***
  		// add processing indicator
  		isProcessing : function( $table, toggle, $headers ) {
  			$table = $( $table );
  			var c = $table[ 0 ].config,
  				// default to all headers
  				$header = $headers || $table.find( '.' + ts.css.header );
  			if ( toggle ) {
  				// don't use sortList if custom $headers used
  				if ( typeof $headers !== 'undefined' && c.sortList.length > 0 ) {
  					// get headers from the sortList
  					$header = $header.filter( function() {
  						// get data-column from attr to keep compatibility with jQuery 1.2.6
  						return this.sortDisabled ?
  							false :
  							ts.isValueInArray( parseFloat( $( this ).attr( 'data-column' ) ), c.sortList ) >= 0;
  					});
  				}
  				$table.add( $header ).addClass( ts.css.processing + ' ' + c.cssProcessing );
  			} else {
  				$table.add( $header ).removeClass( ts.css.processing + ' ' + c.cssProcessing );
  			}
  		},
  
  		// detach tbody but save the position
  		// don't use tbody because there are portions that look for a tbody index (updateCell)
  		processTbody : function( table, $tb, getIt ) {
  			table = $( table )[ 0 ];
  			if ( getIt ) {
  				table.isProcessing = true;
  				$tb.before( '<colgroup class="tablesorter-savemyplace"/>' );
  				return $.fn.detach ? $tb.detach() : $tb.remove();
  			}
  			var holdr = $( table ).find( 'colgroup.tablesorter-savemyplace' );
  			$tb.insertAfter( holdr );
  			holdr.remove();
  			table.isProcessing = false;
  		},
  
  		clearTableBody : function( table ) {
  			$( table )[ 0 ].config.$tbodies.children().detach();
  		},
  
  		// used when replacing accented characters during sorting
  		characterEquivalents : {
  			'a' : '\u00e1\u00e0\u00e2\u00e3\u00e4\u0105\u00e5', // áàâãäąå
  			'A' : '\u00c1\u00c0\u00c2\u00c3\u00c4\u0104\u00c5', // ÁÀÂÃÄĄÅ
  			'c' : '\u00e7\u0107\u010d', // çćč
  			'C' : '\u00c7\u0106\u010c', // ÇĆČ
  			'e' : '\u00e9\u00e8\u00ea\u00eb\u011b\u0119', // éèêëěę
  			'E' : '\u00c9\u00c8\u00ca\u00cb\u011a\u0118', // ÉÈÊËĚĘ
  			'i' : '\u00ed\u00ec\u0130\u00ee\u00ef\u0131', // íìİîïı
  			'I' : '\u00cd\u00cc\u0130\u00ce\u00cf', // ÍÌİÎÏ
  			'o' : '\u00f3\u00f2\u00f4\u00f5\u00f6\u014d', // óòôõöō
  			'O' : '\u00d3\u00d2\u00d4\u00d5\u00d6\u014c', // ÓÒÔÕÖŌ
  			'ss': '\u00df', // ß (s sharp)
  			'SS': '\u1e9e', // ẞ (Capital sharp s)
  			'u' : '\u00fa\u00f9\u00fb\u00fc\u016f', // úùûüů
  			'U' : '\u00da\u00d9\u00db\u00dc\u016e' // ÚÙÛÜŮ
  		},
  
  		replaceAccents : function( str ) {
  			var chr,
  				acc = '[',
  				eq = ts.characterEquivalents;
  			if ( !ts.characterRegex ) {
  				ts.characterRegexArray = {};
  				for ( chr in eq ) {
  					if ( typeof chr === 'string' ) {
  						acc += eq[ chr ];
  						ts.characterRegexArray[ chr ] = new RegExp( '[' + eq[ chr ] + ']', 'g' );
  					}
  				}
  				ts.characterRegex = new RegExp( acc + ']' );
  			}
  			if ( ts.characterRegex.test( str ) ) {
  				for ( chr in eq ) {
  					if ( typeof chr === 'string' ) {
  						str = str.replace( ts.characterRegexArray[ chr ], chr );
  					}
  				}
  			}
  			return str;
  		},
  
  		validateOptions : function( c ) {
  			var setting, setting2, typ, timer,
  				// ignore options containing an array
  				ignore = 'headers sortForce sortList sortAppend widgets'.split( ' ' ),
  				orig = c.originalSettings;
  			if ( orig ) {
  				if ( c.debug ) {
  					timer = new Date();
  				}
  				for ( setting in orig ) {
  					typ = typeof ts.defaults[setting];
  					if ( typ === 'undefined' ) {
  						console.warn( 'Tablesorter Warning! "table.config.' + setting + '" option not recognized' );
  					} else if ( typ === 'object' ) {
  						for ( setting2 in orig[setting] ) {
  							typ = ts.defaults[setting] && typeof ts.defaults[setting][setting2];
  							if ( $.inArray( setting, ignore ) < 0 && typ === 'undefined' ) {
  								console.warn( 'Tablesorter Warning! "table.config.' + setting + '.' + setting2 + '" option not recognized' );
  							}
  						}
  					}
  				}
  				if ( c.debug ) {
  					console.log( 'validate options time:' + ts.benchmark( timer ) );
  				}
  			}
  		},
  
  		// restore headers
  		restoreHeaders : function( table ) {
  			var index, $cell,
  				c = $( table )[ 0 ].config,
  				$headers = c.$table.find( c.selectorHeaders ),
  				len = $headers.length;
  			// don't use c.$headers here in case header cells were swapped
  			for ( index = 0; index < len; index++ ) {
  				$cell = $headers.eq( index );
  				// only restore header cells if it is wrapped
  				// because this is also used by the updateAll method
  				if ( $cell.find( '.' + ts.css.headerIn ).length ) {
  					$cell.html( c.headerContent[ index ] );
  				}
  			}
  		},
  
  		destroy : function( table, removeClasses, callback ) {
  			table = $( table )[ 0 ];
  			if ( !table.hasInitialized ) { return; }
  			// remove all widgets
  			ts.removeWidget( table, true, false );
  			var events,
  				$t = $( table ),
  				c = table.config,
  				debug = c.debug,
  				$h = $t.find( 'thead:first' ),
  				$r = $h.find( 'tr.' + ts.css.headerRow ).removeClass( ts.css.headerRow + ' ' + c.cssHeaderRow ),
  				$f = $t.find( 'tfoot:first > tr' ).children( 'th, td' );
  			if ( removeClasses === false && $.inArray( 'uitheme', c.widgets ) >= 0 ) {
  				// reapply uitheme classes, in case we want to maintain appearance
  				$t.triggerHandler( 'applyWidgetId', [ 'uitheme' ] );
  				$t.triggerHandler( 'applyWidgetId', [ 'zebra' ] );
  			}
  			// remove widget added rows, just in case
  			$h.find( 'tr' ).not( $r ).remove();
  			// disable tablesorter - not using .unbind( namespace ) because namespacing was
  			// added in jQuery v1.4.3 - see http://api.jquery.com/event.namespace/
  			events = 'sortReset update updateRows updateAll updateHeaders updateCell addRows updateComplete sorton ' +
  				'appendCache updateCache applyWidgetId applyWidgets refreshWidgets removeWidget destroy mouseup mouseleave ' +
  				'keypress sortBegin sortEnd resetToLoadState '.split( ' ' )
  				.join( c.namespace + ' ' );
  			$t
  				.removeData( 'tablesorter' )
  				.unbind( events.replace( ts.regex.spaces, ' ' ) );
  			c.$headers
  				.add( $f )
  				.removeClass( [ ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc, ts.css.sortNone ].join( ' ' ) )
  				.removeAttr( 'data-column' )
  				.removeAttr( 'aria-label' )
  				.attr( 'aria-disabled', 'true' );
  			$r
  				.find( c.selectorSort )
  				.unbind( ( 'mousedown mouseup keypress '.split( ' ' ).join( c.namespace + ' ' ) ).replace( ts.regex.spaces, ' ' ) );
  			ts.restoreHeaders( table );
  			$t.toggleClass( ts.css.table + ' ' + c.tableClass + ' tablesorter-' + c.theme, removeClasses === false );
  			// clear flag in case the plugin is initialized again
  			table.hasInitialized = false;
  			delete table.config.cache;
  			if ( typeof callback === 'function' ) {
  				callback( table );
  			}
  			if ( debug ) {
  				console.log( 'tablesorter has been removed' );
  			}
  		}
  
  	};
  
  	$.fn.tablesorter = function( settings ) {
  		return this.each( function() {
  			var table = this,
  			// merge & extend config options
  			c = $.extend( true, {}, ts.defaults, settings, ts.instanceMethods );
  			// save initial settings
  			c.originalSettings = settings;
  			// create a table from data (build table widget)
  			if ( !table.hasInitialized && ts.buildTable && this.nodeName !== 'TABLE' ) {
  				// return the table (in case the original target is the table's container)
  				ts.buildTable( table, c );
  			} else {
  				ts.setup( table, c );
  			}
  		});
  	};
  
  	// set up debug logs
  	if ( !( window.console && window.console.log ) ) {
  		// access $.tablesorter.logs for browsers that don't have a console...
  		ts.logs = [];
  		/*jshint -W020 */
  		console = {};
  		console.log = console.warn = console.error = console.table = function() {
  			var arg = arguments.length > 1 ? arguments : arguments[0];
  			ts.logs[ ts.logs.length ] = { date: Date.now(), log: arg };
  		};
  	}
  
  	// add default parsers
  	ts.addParser({
  		id : 'no-parser',
  		is : function() {
  			return false;
  		},
  		format : function() {
  			return '';
  		},
  		type : 'text'
  	});
  
  	ts.addParser({
  		id : 'text',
  		is : function() {
  			return true;
  		},
  		format : function( str, table ) {
  			var c = table.config;
  			if ( str ) {
  				str = $.trim( c.ignoreCase ? str.toLocaleLowerCase() : str );
  				str = c.sortLocaleCompare ? ts.replaceAccents( str ) : str;
  			}
  			return str;
  		},
  		type : 'text'
  	});
  
  	ts.regex.nondigit = /[^\w,. \-()]/g;
  	ts.addParser({
  		id : 'digit',
  		is : function( str ) {
  			return ts.isDigit( str );
  		},
  		format : function( str, table ) {
  			var num = ts.formatFloat( ( str || '' ).replace( ts.regex.nondigit, '' ), table );
  			return str && typeof num === 'number' ? num :
  				str ? $.trim( str && table.config.ignoreCase ? str.toLocaleLowerCase() : str ) : str;
  		},
  		type : 'numeric'
  	});
  
  	ts.regex.currencyReplace = /[+\-,. ]/g;
  	ts.regex.currencyTest = /^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/;
  	ts.addParser({
  		id : 'currency',
  		is : function( str ) {
  			str = ( str || '' ).replace( ts.regex.currencyReplace, '' );
  			// test for £$€¤¥¢
  			return ts.regex.currencyTest.test( str );
  		},
  		format : function( str, table ) {
  			var num = ts.formatFloat( ( str || '' ).replace( ts.regex.nondigit, '' ), table );
  			return str && typeof num === 'number' ? num :
  				str ? $.trim( str && table.config.ignoreCase ? str.toLocaleLowerCase() : str ) : str;
  		},
  		type : 'numeric'
  	});
  
  	// too many protocols to add them all https://en.wikipedia.org/wiki/URI_scheme
  	// now, this regex can be updated before initialization
  	ts.regex.urlProtocolTest = /^(https?|ftp|file):\/\//;
  	ts.regex.urlProtocolReplace = /(https?|ftp|file):\/\/(www\.)?/;
  	ts.addParser({
  		id : 'url',
  		is : function( str ) {
  			return ts.regex.urlProtocolTest.test( str );
  		},
  		format : function( str ) {
  			return str ? $.trim( str.replace( ts.regex.urlProtocolReplace, '' ) ) : str;
  		},
  		type : 'text'
  	});
  
  	ts.regex.dash = /-/g;
  	ts.regex.isoDate = /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/;
  	ts.addParser({
  		id : 'isoDate',
  		is : function( str ) {
  			return ts.regex.isoDate.test( str );
  		},
  		format : function( str, table ) {
  			var date = str ? new Date( str.replace( ts.regex.dash, '/' ) ) : str;
  			return date instanceof Date && isFinite( date ) ? date.getTime() : str;
  		},
  		type : 'numeric'
  	});
  
  	ts.regex.percent = /%/g;
  	ts.regex.percentTest = /(\d\s*?%|%\s*?\d)/;
  	ts.addParser({
  		id : 'percent',
  		is : function( str ) {
  			return ts.regex.percentTest.test( str ) && str.length < 15;
  		},
  		format : function( str, table ) {
  			return str ? ts.formatFloat( str.replace( ts.regex.percent, '' ), table ) : str;
  		},
  		type : 'numeric'
  	});
  
  	// added image parser to core v2.17.9
  	ts.addParser({
  		id : 'image',
  		is : function( str, table, node, $node ) {
  			return $node.find( 'img' ).length > 0;
  		},
  		format : function( str, table, cell ) {
  			return $( cell ).find( 'img' ).attr( table.config.imgAttr || 'alt' ) || str;
  		},
  		parsed : true, // filter widget flag
  		type : 'text'
  	});
  
  	ts.regex.dateReplace = /(\S)([AP]M)$/i; // used by usLongDate & time parser
  	ts.regex.usLongDateTest1 = /^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4})(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?$/i;
  	ts.regex.usLongDateTest2 = /^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i;
  	ts.addParser({
  		id : 'usLongDate',
  		is : function( str ) {
  			// two digit years are not allowed cross-browser
  			// Jan 01, 2013 12:34:56 PM or 01 Jan 2013
  			return ts.regex.usLongDateTest1.test( str ) || ts.regex.usLongDateTest2.test( str );
  		},
  		format : function( str, table ) {
  			var date = str ? new Date( str.replace( ts.regex.dateReplace, '$1 $2' ) ) : str;
  			return date instanceof Date && isFinite( date ) ? date.getTime() : str;
  		},
  		type : 'numeric'
  	});
  
  	// testing for ##-##-#### or ####-##-##, so it's not perfect; time can be included
  	ts.regex.shortDateTest = /(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/;
  	// escaped "-" because JSHint in Firefox was showing it as an error
  	ts.regex.shortDateReplace = /[\-.,]/g;
  	// XXY covers MDY & DMY formats
  	ts.regex.shortDateXXY = /(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/;
  	ts.regex.shortDateYMD = /(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/;
  	ts.convertFormat = function( dateString, format ) {
  		dateString = ( dateString || '' )
  			.replace( ts.regex.spaces, ' ' )
  			.replace( ts.regex.shortDateReplace, '/' );
  		if ( format === 'mmddyyyy' ) {
  			dateString = dateString.replace( ts.regex.shortDateXXY, '$3/$1/$2' );
  		} else if ( format === 'ddmmyyyy' ) {
  			dateString = dateString.replace( ts.regex.shortDateXXY, '$3/$2/$1' );
  		} else if ( format === 'yyyymmdd' ) {
  			dateString = dateString.replace( ts.regex.shortDateYMD, '$1/$2/$3' );
  		}
  		var date = new Date( dateString );
  		return date instanceof Date && isFinite( date ) ? date.getTime() : '';
  	};
  
  	ts.addParser({
  		id : 'shortDate', // 'mmddyyyy', 'ddmmyyyy' or 'yyyymmdd'
  		is : function( str ) {
  			str = ( str || '' ).replace( ts.regex.spaces, ' ' ).replace( ts.regex.shortDateReplace, '/' );
  			return ts.regex.shortDateTest.test( str );
  		},
  		format : function( str, table, cell, cellIndex ) {
  			if ( str ) {
  				var c = table.config,
  					$header = c.$headerIndexed[ cellIndex ],
  					format = $header.length && $header.data( 'dateFormat' ) ||
  						ts.getData( $header, ts.getColumnData( table, c.headers, cellIndex ), 'dateFormat' ) ||
  						c.dateFormat;
  				// save format because getData can be slow...
  				if ( $header.length ) {
  					$header.data( 'dateFormat', format );
  				}
  				return ts.convertFormat( str, format ) || str;
  			}
  			return str;
  		},
  		type : 'numeric'
  	});
  
  	// match 24 hour time & 12 hours time + am/pm - see http://regexr.com/3c3tk
  	ts.regex.timeTest = /^(0?[1-9]|1[0-2]):([0-5]\d)(\s[AP]M)$|^((?:[01]\d|[2][0-4]):[0-5]\d)$/i;
  	ts.regex.timeMatch = /(0?[1-9]|1[0-2]):([0-5]\d)(\s[AP]M)|((?:[01]\d|[2][0-4]):[0-5]\d)/i;
  	ts.addParser({
  		id : 'time',
  		is : function( str ) {
  			return ts.regex.timeTest.test( str );
  		},
  		format : function( str, table ) {
  			// isolate time... ignore month, day and year
  			var temp,
  				timePart = ( str || '' ).match( ts.regex.timeMatch ),
  				orig = new Date( str ),
  				// no time component? default to 00:00 by leaving it out, but only if str is defined
  				time = str && ( timePart !== null ? timePart[ 0 ] : '00:00 AM' ),
  				date = time ? new Date( '2000/01/01 ' + time.replace( ts.regex.dateReplace, '$1 $2' ) ) : time;
  			if ( date instanceof Date && isFinite( date ) ) {
  				temp = orig instanceof Date && isFinite( orig ) ? orig.getTime() : 0;
  				// if original string was a valid date, add it to the decimal so the column sorts in some kind of order
  				// luckily new Date() ignores the decimals
  				return temp ? parseFloat( date.getTime() + '.' + orig.getTime() ) : date.getTime();
  			}
  			return str;
  		},
  		type : 'numeric'
  	});
  
  	ts.addParser({
  		id : 'metadata',
  		is : function() {
  			return false;
  		},
  		format : function( str, table, cell ) {
  			var c = table.config,
  			p = ( !c.parserMetadataName ) ? 'sortValue' : c.parserMetadataName;
  			return $( cell ).metadata()[ p ];
  		},
  		type : 'numeric'
  	});
  
  	/*
  		██████ ██████ █████▄ █████▄ ▄████▄
  		  ▄█▀  ██▄▄   ██▄▄██ ██▄▄██ ██▄▄██
  		▄█▀    ██▀▀   ██▀▀██ ██▀▀█  ██▀▀██
  		██████ ██████ █████▀ ██  ██ ██  ██
  		*/
  	// add default widgets
  	ts.addWidget({
  		id : 'zebra',
  		priority : 90,
  		format : function( table, c, wo ) {
  			var $visibleRows, $row, count, isEven, tbodyIndex, rowIndex, len,
  				child = new RegExp( c.cssChildRow, 'i' ),
  				$tbodies = c.$tbodies.add( $( c.namespace + '_extra_table' ).children( 'tbody:not(.' + c.cssInfoBlock + ')' ) );
  			for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
  				// loop through the visible rows
  				count = 0;
  				$visibleRows = $tbodies.eq( tbodyIndex ).children( 'tr:visible' ).not( c.selectorRemove );
  				len = $visibleRows.length;
  				for ( rowIndex = 0; rowIndex < len; rowIndex++ ) {
  					$row = $visibleRows.eq( rowIndex );
  					// style child rows the same way the parent row was styled
  					if ( !child.test( $row[ 0 ].className ) ) { count++; }
  					isEven = ( count % 2 === 0 );
  					$row
  						.removeClass( wo.zebra[ isEven ? 1 : 0 ] )
  						.addClass( wo.zebra[ isEven ? 0 : 1 ] );
  				}
  			}
  		},
  		remove : function( table, c, wo, refreshing ) {
  			if ( refreshing ) { return; }
  			var tbodyIndex, $tbody,
  				$tbodies = c.$tbodies,
  				toRemove = ( wo.zebra || [ 'even', 'odd' ] ).join( ' ' );
  			for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ){
  				$tbody = ts.processTbody( table, $tbodies.eq( tbodyIndex ), true ); // remove tbody
  				$tbody.children().removeClass( toRemove );
  				ts.processTbody( table, $tbody, false ); // restore tbody
  			}
  		}
  	});
  
  })( jQuery );
  /*** This file is dynamically generated ***
  █████▄ ▄████▄   █████▄ ▄████▄ ██████   ███████▄ ▄████▄ █████▄ ██ ██████ ██  ██
  ██  ██ ██  ██   ██  ██ ██  ██   ██     ██ ██ ██ ██  ██ ██  ██ ██ ██▄▄   ██▄▄██
  ██  ██ ██  ██   ██  ██ ██  ██   ██     ██ ██ ██ ██  ██ ██  ██ ██ ██▀▀    ▀▀▀██
  █████▀ ▀████▀   ██  ██ ▀████▀   ██     ██ ██ ██ ▀████▀ █████▀ ██ ██     █████▀
  */
  /*! tablesorter (FORK) - updated 05-03-2017 (v2.28.9)*/
  /* Includes widgets ( storage,uitheme,columns,filter,stickyHeaders,resizable,saveSort ) */
  
  (function(factory) {
  	if (typeof define === 'function' && define.amd) {
  		define(['jquery'], factory);
  	} else if (typeof module === 'object' && typeof module.exports === 'object') {
  		module.exports = factory(require('jquery'));
  	} else {
  		factory(jQuery);
  	}
  }(function(jQuery) {
  
  /*! Widget: storage - updated 4/18/2017 (v2.28.8) */
  /*global JSON:false */
  ;(function ($, window, document) {
  	'use strict';
  
  	var ts = $.tablesorter || {};
  
  	// update defaults for validator; these values must be falsy!
  	$.extend(true, ts.defaults, {
  		fixedUrl: '',
  		widgetOptions: {
  			storage_fixedUrl: '',
  			storage_group: '',
  			storage_page: '',
  			storage_storageType: '',
  			storage_tableId: '',
  			storage_useSessionStorage: ''
  		}
  	});
  
  	// *** Store data in local storage, with a cookie fallback ***
  	/* IE7 needs JSON library for JSON.stringify - (http://caniuse.com/#search=json)
  	   if you need it, then include https://github.com/douglascrockford/JSON-js
  
  	   $.parseJSON is not available is jQuery versions older than 1.4.1, using older
  	   versions will only allow storing information for one page at a time
  
  	   // *** Save data (JSON format only) ***
  	   // val must be valid JSON... use http://jsonlint.com/ to ensure it is valid
  	   var val = { "mywidget" : "data1" }; // valid JSON uses double quotes
  	   // $.tablesorter.storage(table, key, val);
  	   $.tablesorter.storage(table, 'tablesorter-mywidget', val);
  
  	   // *** Get data: $.tablesorter.storage(table, key); ***
  	   v = $.tablesorter.storage(table, 'tablesorter-mywidget');
  	   // val may be empty, so also check for your data
  	   val = (v && v.hasOwnProperty('mywidget')) ? v.mywidget : '';
  	   alert(val); // 'data1' if saved, or '' if not
  	*/
  	ts.storage = function(table, key, value, options) {
  		table = $(table)[0];
  		var cookieIndex, cookies, date,
  			hasStorage = false,
  			values = {},
  			c = table.config,
  			wo = c && c.widgetOptions,
  			storageType = (
  				( options && options.storageType ) || ( wo && wo.storage_storageType )
  			).toString().charAt(0).toLowerCase(),
  			// deprecating "useSessionStorage"; any storageType setting overrides it
  			session = storageType ? '' :
  				( options && options.useSessionStorage ) || ( wo && wo.storage_useSessionStorage ),
  			$table = $(table),
  			// id from (1) options ID, (2) table 'data-table-group' attribute, (3) widgetOptions.storage_tableId,
  			// (4) table ID, then (5) table index
  			id = options && options.id ||
  				$table.attr( options && options.group || wo && wo.storage_group || 'data-table-group') ||
  				wo && wo.storage_tableId || table.id || $('.tablesorter').index( $table ),
  			// url from (1) options url, (2) table 'data-table-page' attribute, (3) widgetOptions.storage_fixedUrl,
  			// (4) table.config.fixedUrl (deprecated), then (5) window location path
  			url = options && options.url ||
  				$table.attr(options && options.page || wo && wo.storage_page || 'data-table-page') ||
  				wo && wo.storage_fixedUrl || c && c.fixedUrl || window.location.pathname;
  
  		// skip if using cookies
  		if (storageType !== 'c') {
  			storageType = (storageType === 's' || session) ? 'sessionStorage' : 'localStorage';
  			// https://gist.github.com/paulirish/5558557
  			if (storageType in window) {
  				try {
  					window[storageType].setItem('_tmptest', 'temp');
  					hasStorage = true;
  					window[storageType].removeItem('_tmptest');
  				} catch (error) {
  					if (c && c.debug) {
  						console.warn( storageType + ' is not supported in this browser' );
  					}
  				}
  			}
  		}
  		if (c.debug) {
  			console.log('Storage widget using', hasStorage ? storageType : 'cookies');
  		}
  		// *** get value ***
  		if ($.parseJSON) {
  			if (hasStorage) {
  				values = $.parseJSON( window[storageType][key] || 'null' ) || {};
  			} else {
  				// old browser, using cookies
  				cookies = document.cookie.split(/[;\s|=]/);
  				// add one to get from the key to the value
  				cookieIndex = $.inArray(key, cookies) + 1;
  				values = (cookieIndex !== 0) ? $.parseJSON(cookies[cookieIndex] || 'null') || {} : {};
  			}
  		}
  		// allow value to be an empty string too
  		if (typeof value !== 'undefined' && window.JSON && JSON.hasOwnProperty('stringify')) {
  			// add unique identifiers = url pathname > table ID/index on page > data
  			if (!values[url]) {
  				values[url] = {};
  			}
  			values[url][id] = value;
  			// *** set value ***
  			if (hasStorage) {
  				window[storageType][key] = JSON.stringify(values);
  			} else {
  				date = new Date();
  				date.setTime(date.getTime() + (31536e+6)); // 365 days
  				document.cookie = key + '=' + (JSON.stringify(values)).replace(/\"/g, '\"') + '; expires=' + date.toGMTString() + '; path=/';
  			}
  		} else {
  			return values && values[url] ? values[url][id] : '';
  		}
  	};
  
  })(jQuery, window, document);
  
  /*! Widget: uitheme - updated 12/8/2016 (v2.28.1) */
  ;(function ($) {
  	'use strict';
  	var ts = $.tablesorter || {};
  
  	ts.themes = {
  		'bootstrap' : {
  			table        : 'table table-bordered table-striped',
  			caption      : 'caption',
  			// header class names
  			header       : 'bootstrap-header', // give the header a gradient background (theme.bootstrap_2.css)
  			sortNone     : '',
  			sortAsc      : '',
  			sortDesc     : '',
  			active       : '', // applied when column is sorted
  			hover        : '', // custom css required - a defined bootstrap style may not override other classes
  			// icon class names
  			icons        : '', // add 'icon-white' to make them white; this icon class is added to the <i> in the header
  			iconSortNone : 'bootstrap-icon-unsorted', // class name added to icon when column is not sorted
  			iconSortAsc  : 'icon-chevron-up glyphicon glyphicon-chevron-up', // class name added to icon when column has ascending sort
  			iconSortDesc : 'icon-chevron-down glyphicon glyphicon-chevron-down', // class name added to icon when column has descending sort
  			filterRow    : '', // filter row class
  			footerRow    : '',
  			footerCells  : '',
  			even         : '', // even row zebra striping
  			odd          : ''  // odd row zebra striping
  		},
  		'jui' : {
  			table        : 'ui-widget ui-widget-content ui-corner-all', // table classes
  			caption      : 'ui-widget-content',
  			// header class names
  			header       : 'ui-widget-header ui-corner-all ui-state-default', // header classes
  			sortNone     : '',
  			sortAsc      : '',
  			sortDesc     : '',
  			active       : 'ui-state-active', // applied when column is sorted
  			hover        : 'ui-state-hover',  // hover class
  			// icon class names
  			icons        : 'ui-icon', // icon class added to the <i> in the header
  			iconSortNone : 'ui-icon-carat-2-n-s ui-icon-caret-2-n-s', // class name added to icon when column is not sorted
  			iconSortAsc  : 'ui-icon-carat-1-n ui-icon-caret-1-n', // class name added to icon when column has ascending sort
  			iconSortDesc : 'ui-icon-carat-1-s ui-icon-caret-1-s', // class name added to icon when column has descending sort
  			filterRow    : '',
  			footerRow    : '',
  			footerCells  : '',
  			even         : 'ui-widget-content', // even row zebra striping
  			odd          : 'ui-state-default'   // odd row zebra striping
  		}
  	};
  
  	$.extend(ts.css, {
  		wrapper : 'tablesorter-wrapper' // ui theme & resizable
  	});
  
  	ts.addWidget({
  		id: 'uitheme',
  		priority: 10,
  		format: function(table, c, wo) {
  			var i, tmp, hdr, icon, time, $header, $icon, $tfoot, $h, oldtheme, oldremove, oldIconRmv, hasOldTheme,
  				themesAll = ts.themes,
  				$table = c.$table.add( $( c.namespace + '_extra_table' ) ),
  				$headers = c.$headers.add( $( c.namespace + '_extra_headers' ) ),
  				theme = c.theme || 'jui',
  				themes = themesAll[theme] || {},
  				remove = $.trim( [ themes.sortNone, themes.sortDesc, themes.sortAsc, themes.active ].join( ' ' ) ),
  				iconRmv = $.trim( [ themes.iconSortNone, themes.iconSortDesc, themes.iconSortAsc ].join( ' ' ) );
  			if (c.debug) { time = new Date(); }
  			// initialization code - run once
  			if (!$table.hasClass('tablesorter-' + theme) || c.theme !== c.appliedTheme || !wo.uitheme_applied) {
  				wo.uitheme_applied = true;
  				oldtheme = themesAll[c.appliedTheme] || {};
  				hasOldTheme = !$.isEmptyObject(oldtheme);
  				oldremove =  hasOldTheme ? [ oldtheme.sortNone, oldtheme.sortDesc, oldtheme.sortAsc, oldtheme.active ].join( ' ' ) : '';
  				oldIconRmv = hasOldTheme ? [ oldtheme.iconSortNone, oldtheme.iconSortDesc, oldtheme.iconSortAsc ].join( ' ' ) : '';
  				if (hasOldTheme) {
  					wo.zebra[0] = $.trim( ' ' + wo.zebra[0].replace(' ' + oldtheme.even, '') );
  					wo.zebra[1] = $.trim( ' ' + wo.zebra[1].replace(' ' + oldtheme.odd, '') );
  					c.$tbodies.children().removeClass( [ oldtheme.even, oldtheme.odd ].join(' ') );
  				}
  				// update zebra stripes
  				if (themes.even) { wo.zebra[0] += ' ' + themes.even; }
  				if (themes.odd) { wo.zebra[1] += ' ' + themes.odd; }
  				// add caption style
  				$table.children('caption')
  					.removeClass(oldtheme.caption || '')
  					.addClass(themes.caption);
  				// add table/footer class names
  				$tfoot = $table
  					// remove other selected themes
  					.removeClass( (c.appliedTheme ? 'tablesorter-' + (c.appliedTheme || '') : '') + ' ' + (oldtheme.table || '') )
  					.addClass('tablesorter-' + theme + ' ' + (themes.table || '')) // add theme widget class name
  					.children('tfoot');
  				c.appliedTheme = c.theme;
  
  				if ($tfoot.length) {
  					$tfoot
  						// if oldtheme.footerRow or oldtheme.footerCells are undefined, all class names are removed
  						.children('tr').removeClass(oldtheme.footerRow || '').addClass(themes.footerRow)
  						.children('th, td').removeClass(oldtheme.footerCells || '').addClass(themes.footerCells);
  				}
  				// update header classes
  				$headers
  					.removeClass( (hasOldTheme ? [ oldtheme.header, oldtheme.hover, oldremove ].join(' ') : '') || '' )
  					.addClass(themes.header)
  					.not('.sorter-false')
  					.unbind('mouseenter.tsuitheme mouseleave.tsuitheme')
  					.bind('mouseenter.tsuitheme mouseleave.tsuitheme', function(event) {
  						// toggleClass with switch added in jQuery 1.3
  						$(this)[ event.type === 'mouseenter' ? 'addClass' : 'removeClass' ](themes.hover || '');
  					});
  
  				$headers.each(function(){
  					var $this = $(this);
  					if (!$this.find('.' + ts.css.wrapper).length) {
  						// Firefox needs this inner div to position the icon & resizer correctly
  						$this.wrapInner('<div class="' + ts.css.wrapper + '" style="position:relative;height:100%;width:100%"></div>');
  					}
  				});
  				if (c.cssIcon) {
  					// if c.cssIcon is '', then no <i> is added to the header
  					$headers
  						.find('.' + ts.css.icon)
  						.removeClass(hasOldTheme ? [ oldtheme.icons, oldIconRmv ].join(' ') : '')
  						.addClass(themes.icons || '');
  				}
  				// filter widget initializes after uitheme
  				if (ts.hasWidget( c.table, 'filter' )) {
  					tmp = function() {
  						$table.children('thead').children('.' + ts.css.filterRow)
  							.removeClass(hasOldTheme ? oldtheme.filterRow || '' : '')
  							.addClass(themes.filterRow || '');
  					};
  					if (wo.filter_initialized) {
  						tmp();
  					} else {
  						$table.one('filterInit', function() {
  							tmp();
  						});
  					}
  				}
  			}
  			for (i = 0; i < c.columns; i++) {
  				$header = c.$headers
  					.add($(c.namespace + '_extra_headers'))
  					.not('.sorter-false')
  					.filter('[data-column="' + i + '"]');
  				$icon = (ts.css.icon) ? $header.find('.' + ts.css.icon) : $();
  				$h = $headers.not('.sorter-false').filter('[data-column="' + i + '"]:last');
  				if ($h.length) {
  					$header.removeClass(remove);
  					$icon.removeClass(iconRmv);
  					if ($h[0].sortDisabled) {
  						// no sort arrows for disabled columns!
  						$icon.removeClass(themes.icons || '');
  					} else {
  						hdr = themes.sortNone;
  						icon = themes.iconSortNone;
  						if ($h.hasClass(ts.css.sortAsc)) {
  							hdr = [ themes.sortAsc, themes.active ].join(' ');
  							icon = themes.iconSortAsc;
  						} else if ($h.hasClass(ts.css.sortDesc)) {
  							hdr = [ themes.sortDesc, themes.active ].join(' ');
  							icon = themes.iconSortDesc;
  						}
  						$header.addClass(hdr);
  						$icon.addClass(icon || '');
  					}
  				}
  			}
  			if (c.debug) {
  				console.log('Applying ' + theme + ' theme' + ts.benchmark(time));
  			}
  		},
  		remove: function(table, c, wo, refreshing) {
  			if (!wo.uitheme_applied) { return; }
  			var $table = c.$table,
  				theme = c.appliedTheme || 'jui',
  				themes = ts.themes[ theme ] || ts.themes.jui,
  				$headers = $table.children('thead').children(),
  				remove = themes.sortNone + ' ' + themes.sortDesc + ' ' + themes.sortAsc,
  				iconRmv = themes.iconSortNone + ' ' + themes.iconSortDesc + ' ' + themes.iconSortAsc;
  			$table.removeClass('tablesorter-' + theme + ' ' + themes.table);
  			wo.uitheme_applied = false;
  			if (refreshing) { return; }
  			$table.find(ts.css.header).removeClass(themes.header);
  			$headers
  				.unbind('mouseenter.tsuitheme mouseleave.tsuitheme') // remove hover
  				.removeClass(themes.hover + ' ' + remove + ' ' + themes.active)
  				.filter('.' + ts.css.filterRow)
  				.removeClass(themes.filterRow);
  			$headers.find('.' + ts.css.icon).removeClass(themes.icons + ' ' + iconRmv);
  		}
  	});
  
  })(jQuery);
  
  /*! Widget: columns */
  ;(function ($) {
  	'use strict';
  	var ts = $.tablesorter || {};
  
  	ts.addWidget({
  		id: 'columns',
  		priority: 30,
  		options : {
  			columns : [ 'primary', 'secondary', 'tertiary' ]
  		},
  		format: function(table, c, wo) {
  			var $tbody, tbodyIndex, $rows, rows, $row, $cells, remove, indx,
  			$table = c.$table,
  			$tbodies = c.$tbodies,
  			sortList = c.sortList,
  			len = sortList.length,
  			// removed c.widgetColumns support
  			css = wo && wo.columns || [ 'primary', 'secondary', 'tertiary' ],
  			last = css.length - 1;
  			remove = css.join(' ');
  			// check if there is a sort (on initialization there may not be one)
  			for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
  				$tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // detach tbody
  				$rows = $tbody.children('tr');
  				// loop through the visible rows
  				$rows.each(function() {
  					$row = $(this);
  					if (this.style.display !== 'none') {
  						// remove all columns class names
  						$cells = $row.children().removeClass(remove);
  						// add appropriate column class names
  						if (sortList && sortList[0]) {
  							// primary sort column class
  							$cells.eq(sortList[0][0]).addClass(css[0]);
  							if (len > 1) {
  								for (indx = 1; indx < len; indx++) {
  									// secondary, tertiary, etc sort column classes
  									$cells.eq(sortList[indx][0]).addClass( css[indx] || css[last] );
  								}
  							}
  						}
  					}
  				});
  				ts.processTbody(table, $tbody, false);
  			}
  			// add classes to thead and tfoot
  			rows = wo.columns_thead !== false ? [ 'thead tr' ] : [];
  			if (wo.columns_tfoot !== false) {
  				rows.push('tfoot tr');
  			}
  			if (rows.length) {
  				$rows = $table.find( rows.join(',') ).children().removeClass(remove);
  				if (len) {
  					for (indx = 0; indx < len; indx++) {
  						// add primary. secondary, tertiary, etc sort column classes
  						$rows.filter('[data-column="' + sortList[indx][0] + '"]').addClass(css[indx] || css[last]);
  					}
  				}
  			}
  		},
  		remove: function(table, c, wo) {
  			var tbodyIndex, $tbody,
  				$tbodies = c.$tbodies,
  				remove = (wo.columns || [ 'primary', 'secondary', 'tertiary' ]).join(' ');
  			c.$headers.removeClass(remove);
  			c.$table.children('tfoot').children('tr').children('th, td').removeClass(remove);
  			for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
  				$tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody
  				$tbody.children('tr').each(function() {
  					$(this).children().removeClass(remove);
  				});
  				ts.processTbody(table, $tbody, false); // restore tbody
  			}
  		}
  	});
  
  })(jQuery);
  
  /*! Widget: filter - updated 4/18/2017 (v2.28.8) *//*
   * Requires tablesorter v2.8+ and jQuery 1.7+
   * by Rob Garrison
   */
  ;( function ( $ ) {
  	'use strict';
  	var tsf, tsfRegex,
  		ts = $.tablesorter || {},
  		tscss = ts.css,
  		tskeyCodes = ts.keyCodes;
  
  	$.extend( tscss, {
  		filterRow      : 'tablesorter-filter-row',
  		filter         : 'tablesorter-filter',
  		filterDisabled : 'disabled',
  		filterRowHide  : 'hideme'
  	});
  
  	$.extend( tskeyCodes, {
  		backSpace : 8,
  		escape : 27,
  		space : 32,
  		left : 37,
  		down : 40
  	});
  
  	ts.addWidget({
  		id: 'filter',
  		priority: 50,
  		options : {
  			filter_cellFilter    : '',    // css class name added to the filter cell ( string or array )
  			filter_childRows     : false, // if true, filter includes child row content in the search
  			filter_childByColumn : false, // ( filter_childRows must be true ) if true = search child rows by column; false = search all child row text grouped
  			filter_childWithSibs : true,  // if true, include matching child row siblings
  			filter_columnAnyMatch: true,  // if true, allows using '#:{query}' in AnyMatch searches ( column:query )
  			filter_columnFilters : true,  // if true, a filter will be added to the top of each table column
  			filter_cssFilter     : '',    // css class name added to the filter row & each input in the row ( tablesorter-filter is ALWAYS added )
  			filter_defaultAttrib : 'data-value', // data attribute in the header cell that contains the default filter value
  			filter_defaultFilter : {},    // add a default column filter type '~{query}' to make fuzzy searches default; '{q1} AND {q2}' to make all searches use a logical AND.
  			filter_excludeFilter : {},    // filters to exclude, per column
  			filter_external      : '',    // jQuery selector string ( or jQuery object ) of external filters
  			filter_filteredRow   : 'filtered', // class added to filtered rows; define in css with "display:none" to hide the filtered-out rows
  			filter_formatter     : null,  // add custom filter elements to the filter row
  			filter_functions     : null,  // add custom filter functions using this option
  			filter_hideEmpty     : true,  // hide filter row when table is empty
  			filter_hideFilters   : false, // collapse filter row when mouse leaves the area
  			filter_ignoreCase    : true,  // if true, make all searches case-insensitive
  			filter_liveSearch    : true,  // if true, search column content while the user types ( with a delay )
  			filter_matchType     : { 'input': 'exact', 'select': 'exact' }, // global query settings ('exact' or 'match'); overridden by "filter-match" or "filter-exact" class
  			filter_onlyAvail     : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available ( visible ) options within the drop down
  			filter_placeholder   : { search : '', select : '' }, // default placeholder text ( overridden by any header 'data-placeholder' setting )
  			filter_reset         : null,  // jQuery selector string of an element used to reset the filters
  			filter_resetOnEsc    : true,  // Reset filter input when the user presses escape - normalized across browsers
  			filter_saveFilters   : false, // Use the $.tablesorter.storage utility to save the most recent filters
  			filter_searchDelay   : 300,   // typing delay in milliseconds before starting a search
  			filter_searchFiltered: true,  // allow searching through already filtered rows in special circumstances; will speed up searching in large tables if true
  			filter_selectSource  : null,  // include a function to return an array of values to be added to the column filter select
  			filter_selectSourceSeparator : '|', // filter_selectSource array text left of the separator is added to the option value, right into the option text
  			filter_serversideFiltering : false, // if true, must perform server-side filtering b/c client-side filtering is disabled, but the ui and events will still be used.
  			filter_startsWith    : false, // if true, filter start from the beginning of the cell contents
  			filter_useParsedData : false  // filter all data using parsed content
  		},
  		format: function( table, c, wo ) {
  			if ( !c.$table.hasClass( 'hasFilters' ) ) {
  				tsf.init( table, c, wo );
  			}
  		},
  		remove: function( table, c, wo, refreshing ) {
  			var tbodyIndex, $tbody,
  				$table = c.$table,
  				$tbodies = c.$tbodies,
  				events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterAndSortReset filterEnd search '
  					.split( ' ' ).join( c.namespace + 'filter ' );
  			$table
  				.removeClass( 'hasFilters' )
  				// add filter namespace to all BUT search
  				.unbind( events.replace( ts.regex.spaces, ' ' ) )
  				// remove the filter row even if refreshing, because the column might have been moved
  				.find( '.' + tscss.filterRow ).remove();
  			wo.filter_initialized = false;
  			if ( refreshing ) { return; }
  			for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
  				$tbody = ts.processTbody( table, $tbodies.eq( tbodyIndex ), true ); // remove tbody
  				$tbody.children().removeClass( wo.filter_filteredRow ).show();
  				ts.processTbody( table, $tbody, false ); // restore tbody
  			}
  			if ( wo.filter_reset ) {
  				$( document ).undelegate( wo.filter_reset, 'click' + c.namespace + 'filter' );
  			}
  		}
  	});
  
  	tsf = ts.filter = {
  
  		// regex used in filter 'check' functions - not for general use and not documented
  		regex: {
  			regex     : /^\/((?:\\\/|[^\/])+)\/([migyu]{0,5})?$/, // regex to test for regex
  			child     : /tablesorter-childRow/, // child row class name; this gets updated in the script
  			filtered  : /filtered/, // filtered (hidden) row class name; updated in the script
  			type      : /undefined|number/, // check type
  			exact     : /(^[\"\'=]+)|([\"\'=]+$)/g, // exact match (allow '==')
  			operators : /[<>=]/g, // replace operators
  			query     : '(q|query)', // replace filter queries
  			wild01    : /\?/g, // wild card match 0 or 1
  			wild0More : /\*/g, // wild care match 0 or more
  			quote     : /\"/g,
  			isNeg1    : /(>=?\s*-\d)/,
  			isNeg2    : /(<=?\s*\d)/
  		},
  		// function( c, data ) { }
  		// c = table.config
  		// data.$row = jQuery object of the row currently being processed
  		// data.$cells = jQuery object of all cells within the current row
  		// data.filters = array of filters for all columns ( some may be undefined )
  		// data.filter = filter for the current column
  		// data.iFilter = same as data.filter, except lowercase ( if wo.filter_ignoreCase is true )
  		// data.exact = table cell text ( or parsed data if column parser enabled; may be a number & not a string )
  		// data.iExact = same as data.exact, except lowercase ( if wo.filter_ignoreCase is true; may be a number & not a string )
  		// data.cache = table cell text from cache, so it has been parsed ( & in all lower case if c.ignoreCase is true )
  		// data.cacheArray = An array of parsed content from each table cell in the row being processed
  		// data.index = column index; table = table element ( DOM )
  		// data.parsed = array ( by column ) of boolean values ( from filter_useParsedData or 'filter-parsed' class )
  		types: {
  			or : function( c, data, vars ) {
  				// look for "|", but not if it is inside of a regular expression
  				if ( ( tsfRegex.orTest.test( data.iFilter ) || tsfRegex.orSplit.test( data.filter ) ) &&
  					// this test for regex has potential to slow down the overall search
  					!tsfRegex.regex.test( data.filter ) ) {
  					var indx, filterMatched, query, regex,
  						// duplicate data but split filter
  						data2 = $.extend( {}, data ),
  						filter = data.filter.split( tsfRegex.orSplit ),
  						iFilter = data.iFilter.split( tsfRegex.orSplit ),
  						len = filter.length;
  					for ( indx = 0; indx < len; indx++ ) {
  						data2.nestedFilters = true;
  						data2.filter = '' + ( tsf.parseFilter( c, filter[ indx ], data ) || '' );
  						data2.iFilter = '' + ( tsf.parseFilter( c, iFilter[ indx ], data ) || '' );
  						query = '(' + ( tsf.parseFilter( c, data2.filter, data ) || '' ) + ')';
  						try {
  							// use try/catch, because query may not be a valid regex if "|" is contained within a partial regex search,
  							// e.g "/(Alex|Aar" -> Uncaught SyntaxError: Invalid regular expression: /(/(Alex)/: Unterminated group
  							regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
  							// filterMatched = data2.filter === '' && indx > 0 ? true
  							// look for an exact match with the 'or' unless the 'filter-match' class is found
  							filterMatched = regex.test( data2.exact ) || tsf.processTypes( c, data2, vars );
  							if ( filterMatched ) {
  								return filterMatched;
  							}
  						} catch ( error ) {
  							return null;
  						}
  					}
  					// may be null from processing types
  					return filterMatched || false;
  				}
  				return null;
  			},
  			// Look for an AND or && operator ( logical and )
  			and : function( c, data, vars ) {
  				if ( tsfRegex.andTest.test( data.filter ) ) {
  					var indx, filterMatched, result, query, regex,
  						// duplicate data but split filter
  						data2 = $.extend( {}, data ),
  						filter = data.filter.split( tsfRegex.andSplit ),
  						iFilter = data.iFilter.split( tsfRegex.andSplit ),
  						len = filter.length;
  					for ( indx = 0; indx < len; indx++ ) {
  						data2.nestedFilters = true;
  						data2.filter = '' + ( tsf.parseFilter( c, filter[ indx ], data ) || '' );
  						data2.iFilter = '' + ( tsf.parseFilter( c, iFilter[ indx ], data ) || '' );
  						query = ( '(' + ( tsf.parseFilter( c, data2.filter, data ) || '' ) + ')' )
  							// replace wild cards since /(a*)/i will match anything
  							.replace( tsfRegex.wild01, '\\S{1}' ).replace( tsfRegex.wild0More, '\\S*' );
  						try {
  							// use try/catch just in case RegExp is invalid
  							regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
  							// look for an exact match with the 'and' unless the 'filter-match' class is found
  							result = ( regex.test( data2.exact ) || tsf.processTypes( c, data2, vars ) );
  							if ( indx === 0 ) {
  								filterMatched = result;
  							} else {
  								filterMatched = filterMatched && result;
  							}
  						} catch ( error ) {
  							return null;
  						}
  					}
  					// may be null from processing types
  					return filterMatched || false;
  				}
  				return null;
  			},
  			// Look for regex
  			regex: function( c, data ) {
  				if ( tsfRegex.regex.test( data.filter ) ) {
  					var matches,
  						// cache regex per column for optimal speed
  						regex = data.filter_regexCache[ data.index ] || tsfRegex.regex.exec( data.filter ),
  						isRegex = regex instanceof RegExp;
  					try {
  						if ( !isRegex ) {
  							// force case insensitive search if ignoreCase option set?
  							// if ( c.ignoreCase && !regex[2] ) { regex[2] = 'i'; }
  							data.filter_regexCache[ data.index ] = regex = new RegExp( regex[1], regex[2] );
  						}
  						matches = regex.test( data.exact );
  					} catch ( error ) {
  						matches = false;
  					}
  					return matches;
  				}
  				return null;
  			},
  			// Look for operators >, >=, < or <=
  			operators: function( c, data ) {
  				// ignore empty strings... because '' < 10 is true
  				if ( tsfRegex.operTest.test( data.iFilter ) && data.iExact !== '' ) {
  					var cachedValue, result, txt,
  						table = c.table,
  						parsed = data.parsed[ data.index ],
  						query = ts.formatFloat( data.iFilter.replace( tsfRegex.operators, '' ), table ),
  						parser = c.parsers[ data.index ] || {},
  						savedSearch = query;
  					// parse filter value in case we're comparing numbers ( dates )
  					if ( parsed || parser.type === 'numeric' ) {
  						txt = $.trim( '' + data.iFilter.replace( tsfRegex.operators, '' ) );
  						result = tsf.parseFilter( c, txt, data, true );
  						query = ( typeof result === 'number' && result !== '' && !isNaN( result ) ) ? result : query;
  					}
  					// iExact may be numeric - see issue #149;
  					// check if cached is defined, because sometimes j goes out of range? ( numeric columns )
  					if ( ( parsed || parser.type === 'numeric' ) && !isNaN( query ) &&
  						typeof data.cache !== 'undefined' ) {
  						cachedValue = data.cache;
  					} else {
  						txt = isNaN( data.iExact ) ? data.iExact.replace( ts.regex.nondigit, '' ) : data.iExact;
  						cachedValue = ts.formatFloat( txt, table );
  					}
  					if ( tsfRegex.gtTest.test( data.iFilter ) ) {
  						result = tsfRegex.gteTest.test( data.iFilter ) ? cachedValue >= query : cachedValue > query;
  					} else if ( tsfRegex.ltTest.test( data.iFilter ) ) {
  						result = tsfRegex.lteTest.test( data.iFilter ) ? cachedValue <= query : cachedValue < query;
  					}
  					// keep showing all rows if nothing follows the operator
  					if ( !result && savedSearch === '' ) {
  						result = true;
  					}
  					return result;
  				}
  				return null;
  			},
  			// Look for a not match
  			notMatch: function( c, data ) {
  				if ( tsfRegex.notTest.test( data.iFilter ) ) {
  					var indx,
  						txt = data.iFilter.replace( '!', '' ),
  						filter = tsf.parseFilter( c, txt, data ) || '';
  					if ( tsfRegex.exact.test( filter ) ) {
  						// look for exact not matches - see #628
  						filter = filter.replace( tsfRegex.exact, '' );
  						return filter === '' ? true : $.trim( filter ) !== data.iExact;
  					} else {
  						indx = data.iExact.search( $.trim( filter ) );
  						return filter === '' ? true :
  							// return true if not found
  							data.anyMatch ? indx < 0 :
  							// return false if found
  							!( c.widgetOptions.filter_startsWith ? indx === 0 : indx >= 0 );
  					}
  				}
  				return null;
  			},
  			// Look for quotes or equals to get an exact match; ignore type since iExact could be numeric
  			exact: function( c, data ) {
  				/*jshint eqeqeq:false */
  				if ( tsfRegex.exact.test( data.iFilter ) ) {
  					var txt = data.iFilter.replace( tsfRegex.exact, '' ),
  						filter = tsf.parseFilter( c, txt, data ) || '';
  					return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact;
  				}
  				return null;
  			},
  			// Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu!
  			range : function( c, data ) {
  				if ( tsfRegex.toTest.test( data.iFilter ) ) {
  					var result, tmp, range1, range2,
  						table = c.table,
  						index = data.index,
  						parsed = data.parsed[index],
  						// make sure the dash is for a range and not indicating a negative number
  						query = data.iFilter.split( tsfRegex.toSplit );
  
  					tmp = query[0].replace( ts.regex.nondigit, '' ) || '';
  					range1 = ts.formatFloat( tsf.parseFilter( c, tmp, data ), table );
  					tmp = query[1].replace( ts.regex.nondigit, '' ) || '';
  					range2 = ts.formatFloat( tsf.parseFilter( c, tmp, data ), table );
  					// parse filter value in case we're comparing numbers ( dates )
  					if ( parsed || c.parsers[ index ].type === 'numeric' ) {
  						result = c.parsers[ index ].format( '' + query[0], table, c.$headers.eq( index ), index );
  						range1 = ( result !== '' && !isNaN( result ) ) ? result : range1;
  						result = c.parsers[ index ].format( '' + query[1], table, c.$headers.eq( index ), index );
  						range2 = ( result !== '' && !isNaN( result ) ) ? result : range2;
  					}
  					if ( ( parsed || c.parsers[ index ].type === 'numeric' ) && !isNaN( range1 ) && !isNaN( range2 ) ) {
  						result = data.cache;
  					} else {
  						tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.regex.nondigit, '' ) : data.iExact;
  						result = ts.formatFloat( tmp, table );
  					}
  					if ( range1 > range2 ) {
  						tmp = range1; range1 = range2; range2 = tmp; // swap
  					}
  					return ( result >= range1 && result <= range2 ) || ( range1 === '' || range2 === '' );
  				}
  				return null;
  			},
  			// Look for wild card: ? = single, * = multiple, or | = logical OR
  			wild : function( c, data ) {
  				if ( tsfRegex.wildOrTest.test( data.iFilter ) ) {
  					var query = '' + ( tsf.parseFilter( c, data.iFilter, data ) || '' );
  					// look for an exact match with the 'or' unless the 'filter-match' class is found
  					if ( !tsfRegex.wildTest.test( query ) && data.nestedFilters ) {
  						query = data.isMatch ? query : '^(' + query + ')$';
  					}
  					// parsing the filter may not work properly when using wildcards =/
  					try {
  						return new RegExp(
  							query.replace( tsfRegex.wild01, '\\S{1}' ).replace( tsfRegex.wild0More, '\\S*' ),
  							c.widgetOptions.filter_ignoreCase ? 'i' : ''
  						)
  						.test( data.exact );
  					} catch ( error ) {
  						return null;
  					}
  				}
  				return null;
  			},
  			// fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license )
  			fuzzy: function( c, data ) {
  				if ( tsfRegex.fuzzyTest.test( data.iFilter ) ) {
  					var indx,
  						patternIndx = 0,
  						len = data.iExact.length,
  						txt = data.iFilter.slice( 1 ),
  						pattern = tsf.parseFilter( c, txt, data ) || '';
  					for ( indx = 0; indx < len; indx++ ) {
  						if ( data.iExact[ indx ] === pattern[ patternIndx ] ) {
  							patternIndx += 1;
  						}
  					}
  					return patternIndx === pattern.length;
  				}
  				return null;
  			}
  		},
  		init: function( table ) {
  			// filter language options
  			ts.language = $.extend( true, {}, {
  				to  : 'to',
  				or  : 'or',
  				and : 'and'
  			}, ts.language );
  
  			var options, string, txt, $header, column, val, fxn, noSelect,
  				c = table.config,
  				wo = c.widgetOptions;
  			c.$table.addClass( 'hasFilters' );
  			c.lastSearch = [];
  
  			// define timers so using clearTimeout won't cause an undefined error
  			wo.filter_searchTimer = null;
  			wo.filter_initTimer = null;
  			wo.filter_formatterCount = 0;
  			wo.filter_formatterInit = [];
  			wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]';
  			wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]';
  
  			val = '\\{' + tsfRegex.query + '\\}';
  			$.extend( tsfRegex, {
  				child : new RegExp( c.cssChildRow ),
  				filtered : new RegExp( wo.filter_filteredRow ),
  				alreadyFiltered : new RegExp( '(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i' ),
  				toTest : new RegExp( '\\s+(-|' + ts.language.to + ')\\s+', 'i' ),
  				toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)', 'gi' ),
  				andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ),
  				andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ),
  				orTest : new RegExp( '(\\||\\s+' + ts.language.or + '\\s+)', 'i' ),
  				orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ),
  				iQuery : new RegExp( val, 'i' ),
  				igQuery : new RegExp( val, 'ig' ),
  				operTest : /^[<>]=?/,
  				gtTest  : />/,
  				gteTest : />=/,
  				ltTest  : /</,
  				lteTest : /<=/,
  				notTest : /^\!/,
  				wildOrTest : /[\?\*\|]/,
  				wildTest : /\?\*/,
  				fuzzyTest : /^~/,
  				exactTest : /[=\"\|!]/
  			});
  
  			// don't build filter row if columnFilters is false or all columns are set to 'filter-false'
  			// see issue #156
  			val = c.$headers.filter( '.filter-false, .parser-false' ).length;
  			if ( wo.filter_columnFilters !== false && val !== c.$headers.length ) {
  				// build filter row
  				tsf.buildRow( table, c, wo );
  			}
  
  			txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset ' +
  				'filterAndSortReset filterResetSaved filterEnd search '.split( ' ' ).join( c.namespace + 'filter ' );
  			c.$table.bind( txt, function( event, filter ) {
  				val = wo.filter_hideEmpty &&
  					$.isEmptyObject( c.cache ) &&
  					!( c.delayInit && event.type === 'appendCache' );
  				// hide filter row using the 'filtered' class name
  				c.$table.find( '.' + tscss.filterRow ).toggleClass( wo.filter_filteredRow, val ); // fixes #450
  				if ( !/(search|filter)/.test( event.type ) ) {
  					event.stopPropagation();
  					tsf.buildDefault( table, true );
  				}
  				// Add filterAndSortReset - see #1361
  				if ( event.type === 'filterReset' || event.type === 'filterAndSortReset' ) {
  					c.$table.find( '.' + tscss.filter ).add( wo.filter_$externalFilters ).val( '' );
  					if ( event.type === 'filterAndSortReset' ) {
  						ts.sortReset( this.config, function() {
  							tsf.searching( table, [] );
  						});
  					} else {
  						tsf.searching( table, [] );
  					}
  				} else if ( event.type === 'filterResetSaved' ) {
  					ts.storage( table, 'tablesorter-filters', '' );
  				} else if ( event.type === 'filterEnd' ) {
  					tsf.buildDefault( table, true );
  				} else {
  					// send false argument to force a new search; otherwise if the filter hasn't changed,
  					// it will return
  					filter = event.type === 'search' ? filter :
  						event.type === 'updateComplete' ? c.$table.data( 'lastSearch' ) : '';
  					if ( /(update|add)/.test( event.type ) && event.type !== 'updateComplete' ) {
  						// force a new search since content has changed
  						c.lastCombinedFilter = null;
  						c.lastSearch = [];
  						// update filterFormatters after update (& small delay) - Fixes #1237
  						setTimeout(function(){
  							c.$table.triggerHandler( 'filterFomatterUpdate' );
  						}, 100);
  					}
  					// pass true ( skipFirst ) to prevent the tablesorter.setFilters function from skipping the first
  					// input ensures all inputs are updated when a search is triggered on the table
  					// $( 'table' ).trigger( 'search', [...] );
  					tsf.searching( table, filter, true );
  				}
  				return false;
  			});
  
  			// reset button/link
  			if ( wo.filter_reset ) {
  				if ( wo.filter_reset instanceof $ ) {
  					// reset contains a jQuery object, bind to it
  					wo.filter_reset.click( function() {
  						c.$table.triggerHandler( 'filterReset' );
  					});
  				} else if ( $( wo.filter_reset ).length ) {
  					// reset is a jQuery selector, use event delegation
  					$( document )
  						.undelegate( wo.filter_reset, 'click' + c.namespace + 'filter' )
  						.delegate( wo.filter_reset, 'click' + c.namespace + 'filter', function() {
  							// trigger a reset event, so other functions ( filter_formatter ) know when to reset
  							c.$table.triggerHandler( 'filterReset' );
  						});
  				}
  			}
  			if ( wo.filter_functions ) {
  				for ( column = 0; column < c.columns; column++ ) {
  					fxn = ts.getColumnData( table, wo.filter_functions, column );
  					if ( fxn ) {
  						// remove 'filter-select' from header otherwise the options added here are replaced with
  						// all options
  						$header = c.$headerIndexed[ column ].removeClass( 'filter-select' );
  						// don't build select if 'filter-false' or 'parser-false' set
  						noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) );
  						options = '';
  						if ( fxn === true && noSelect ) {
  							tsf.buildSelect( table, column );
  						} else if ( typeof fxn === 'object' && noSelect ) {
  							// add custom drop down list
  							for ( string in fxn ) {
  								if ( typeof string === 'string' ) {
  									options += options === '' ?
  										'<option value="">' +
  											( $header.data( 'placeholder' ) ||
  												$header.attr( 'data-placeholder' ) ||
  												wo.filter_placeholder.select ||
  												''
  											) +
  										'</option>' : '';
  									val = string;
  									txt = string;
  									if ( string.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) {
  										val = string.split( wo.filter_selectSourceSeparator );
  										txt = val[1];
  										val = val[0];
  									}
  									options += '<option ' +
  										( txt === val ? '' : 'data-function-name="' + string + '" ' ) +
  										'value="' + val + '">' + txt + '</option>';
  								}
  							}
  							c.$table
  								.find( 'thead' )
  								.find( 'select.' + tscss.filter + '[data-column="' + column + '"]' )
  								.append( options );
  							txt = wo.filter_selectSource;
  							fxn = typeof txt === 'function' ? true : ts.getColumnData( table, txt, column );
  							if ( fxn ) {
  								// updating so the extra options are appended
  								tsf.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) );
  							}
  						}
  					}
  				}
  			}
  			// not really updating, but if the column has both the 'filter-select' class &
  			// filter_functions set to true, it would append the same options twice.
  			tsf.buildDefault( table, true );
  
  			tsf.bindSearch( table, c.$table.find( '.' + tscss.filter ), true );
  			if ( wo.filter_external ) {
  				tsf.bindSearch( table, wo.filter_external );
  			}
  
  			if ( wo.filter_hideFilters ) {
  				tsf.hideFilters( c );
  			}
  
  			// show processing icon
  			if ( c.showProcessing ) {
  				txt = 'filterStart filterEnd '.split( ' ' ).join( c.namespace + 'filter ' );
  				c.$table
  					.unbind( txt.replace( ts.regex.spaces, ' ' ) )
  					.bind( txt, function( event, columns ) {
  					// only add processing to certain columns to all columns
  					$header = ( columns ) ?
  						c.$table
  							.find( '.' + tscss.header )
  							.filter( '[data-column]' )
  							.filter( function() {
  								return columns[ $( this ).data( 'column' ) ] !== '';
  							}) : '';
  					ts.isProcessing( table, event.type === 'filterStart', columns ? $header : '' );
  				});
  			}
  
  			// set filtered rows count ( intially unfiltered )
  			c.filteredRows = c.totalRows;
  
  			// add default values
  			txt = 'tablesorter-initialized pagerBeforeInitialized '.split( ' ' ).join( c.namespace + 'filter ' );
  			c.$table
  			.unbind( txt.replace( ts.regex.spaces, ' ' ) )
  			.bind( txt, function() {
  				tsf.completeInit( this );
  			});
  			// if filter widget is added after pager has initialized; then set filter init flag
  			if ( c.pager && c.pager.initialized && !wo.filter_initialized ) {
  				c.$table.triggerHandler( 'filterFomatterUpdate' );
  				setTimeout( function() {
  					tsf.filterInitComplete( c );
  				}, 100 );
  			} else if ( !wo.filter_initialized ) {
  				tsf.completeInit( table );
  			}
  		},
  		completeInit: function( table ) {
  			// redefine 'c' & 'wo' so they update properly inside this callback
  			var c = table.config,
  				wo = c.widgetOptions,
  				filters = tsf.setDefaults( table, c, wo ) || [];
  			if ( filters.length ) {
  				// prevent delayInit from triggering a cache build if filters are empty
  				if ( !( c.delayInit && filters.join( '' ) === '' ) ) {
  					ts.setFilters( table, filters, true );
  				}
  			}
  			c.$table.triggerHandler( 'filterFomatterUpdate' );
  			// trigger init after setTimeout to prevent multiple filterStart/End/Init triggers
  			setTimeout( function() {
  				if ( !wo.filter_initialized ) {
  					tsf.filterInitComplete( c );
  				}
  			}, 100 );
  		},
  
  		// $cell parameter, but not the config, is passed to the filter_formatters,
  		// so we have to work with it instead
  		formatterUpdated: function( $cell, column ) {
  			// prevent error if $cell is undefined - see #1056
  			var wo = $cell && $cell.closest( 'table' )[0].config.widgetOptions;
  			if ( wo && !wo.filter_initialized ) {
  				// add updates by column since this function
  				// may be called numerous times before initialization
  				wo.filter_formatterInit[ column ] = 1;
  			}
  		},
  		filterInitComplete: function( c ) {
  			var indx, len,
  				wo = c.widgetOptions,
  				count = 0,
  				completed = function() {
  					wo.filter_initialized = true;
  					// update lastSearch - it gets cleared often
  					c.lastSearch = c.$table.data( 'lastSearch' );
  					c.$table.triggerHandler( 'filterInit', c );
  					tsf.findRows( c.table, c.lastSearch || [] );
  				};
  			if ( $.isEmptyObject( wo.filter_formatter ) ) {
  				completed();
  			} else {
  				len = wo.filter_formatterInit.length;
  				for ( indx = 0; indx < len; indx++ ) {
  					if ( wo.filter_formatterInit[ indx ] === 1 ) {
  						count++;
  					}
  				}
  				clearTimeout( wo.filter_initTimer );
  				if ( !wo.filter_initialized && count === wo.filter_formatterCount ) {
  					// filter widget initialized
  					completed();
  				} else if ( !wo.filter_initialized ) {
  					// fall back in case a filter_formatter doesn't call
  					// $.tablesorter.filter.formatterUpdated( $cell, column ), and the count is off
  					wo.filter_initTimer = setTimeout( function() {
  						completed();
  					}, 500 );
  				}
  			}
  		},
  		// encode or decode filters for storage; see #1026
  		processFilters: function( filters, encode ) {
  			var indx,
  				// fixes #1237; previously returning an encoded "filters" value
  				result = [],
  				mode = encode ? encodeURIComponent : decodeURIComponent,
  				len = filters.length;
  			for ( indx = 0; indx < len; indx++ ) {
  				if ( filters[ indx ] ) {
  					result[ indx ] = mode( filters[ indx ] );
  				}
  			}
  			return result;
  		},
  		setDefaults: function( table, c, wo ) {
  			var isArray, saved, indx, col, $filters,
  				// get current ( default ) filters
  				filters = ts.getFilters( table ) || [];
  			if ( wo.filter_saveFilters && ts.storage ) {
  				saved = ts.storage( table, 'tablesorter-filters' ) || [];
  				isArray = $.isArray( saved );
  				// make sure we're not just getting an empty array
  				if ( !( isArray && saved.join( '' ) === '' || !isArray ) ) {
  					filters = tsf.processFilters( saved );
  				}
  			}
  			// if no filters saved, then check default settings
  			if ( filters.join( '' ) === '' ) {
  				// allow adding default setting to external filters
  				$filters = c.$headers.add( wo.filter_$externalFilters )
  					.filter( '[' + wo.filter_defaultAttrib + ']' );
  				for ( indx = 0; indx <= c.columns; indx++ ) {
  					// include data-column='all' external filters
  					col = indx === c.columns ? 'all' : indx;
  					filters[ indx ] = $filters
  						.filter( '[data-column="' + col + '"]' )
  						.attr( wo.filter_defaultAttrib ) || filters[indx] || '';
  				}
  			}
  			c.$table.data( 'lastSearch', filters );
  			return filters;
  		},
  		parseFilter: function( c, filter, data, parsed ) {
  			return parsed || data.parsed[ data.index ] ?
  				c.parsers[ data.index ].format( filter, c.table, [], data.index ) :
  				filter;
  		},
  		buildRow: function( table, c, wo ) {
  			var $filter, col, column, $header, makeSelect, disabled, name, ffxn, tmp,
  				// c.columns defined in computeThIndexes()
  				cellFilter = wo.filter_cellFilter,
  				columns = c.columns,
  				arry = $.isArray( cellFilter ),
  				buildFilter = '<tr role="row" class="' + tscss.filterRow + ' ' + c.cssIgnoreRow + '">';
  			for ( column = 0; column < columns; column++ ) {
  				if ( c.$headerIndexed[ column ].length ) {
  					// account for entire column set with colspan. See #1047
  					tmp = c.$headerIndexed[ column ] && c.$headerIndexed[ column ][0].colSpan || 0;
  					if ( tmp > 1 ) {
  						buildFilter += '<td data-column="' + column + '-' + ( column + tmp - 1 ) + '" colspan="' + tmp + '"';
  					} else {
  						buildFilter += '<td data-column="' + column + '"';
  					}
  					if ( arry ) {
  						buildFilter += ( cellFilter[ column ] ? ' class="' + cellFilter[ column ] + '"' : '' );
  					} else {
  						buildFilter += ( cellFilter !== '' ? ' class="' + cellFilter + '"' : '' );
  					}
  					buildFilter += '></td>';
  				}
  			}
  			c.$filters = $( buildFilter += '</tr>' )
  				.appendTo( c.$table.children( 'thead' ).eq( 0 ) )
  				.children( 'td' );
  			// build each filter input
  			for ( column = 0; column < columns; column++ ) {
  				disabled = false;
  				// assuming last cell of a column is the main column
  				$header = c.$headerIndexed[ column ];
  				if ( $header && $header.length ) {
  					// $filter = c.$filters.filter( '[data-column="' + column + '"]' );
  					$filter = tsf.getColumnElm( c, c.$filters, column );
  					ffxn = ts.getColumnData( table, wo.filter_functions, column );
  					makeSelect = ( wo.filter_functions && ffxn && typeof ffxn !== 'function' ) ||
  						$header.hasClass( 'filter-select' );
  					// get data from jQuery data, metadata, headers option or header class name
  					col = ts.getColumnData( table, c.headers, column );
  					disabled = ts.getData( $header[0], col, 'filter' ) === 'false' ||
  						ts.getData( $header[0], col, 'parser' ) === 'false';
  
  					if ( makeSelect ) {
  						buildFilter = $( '<select>' ).appendTo( $filter );
  					} else {
  						ffxn = ts.getColumnData( table, wo.filter_formatter, column );
  						if ( ffxn ) {
  							wo.filter_formatterCount++;
  							buildFilter = ffxn( $filter, column );
  							// no element returned, so lets go find it
  							if ( buildFilter && buildFilter.length === 0 ) {
  								buildFilter = $filter.children( 'input' );
  							}
  							// element not in DOM, so lets attach it
  							if ( buildFilter && ( buildFilter.parent().length === 0 ||
  								( buildFilter.parent().length && buildFilter.parent()[0] !== $filter[0] ) ) ) {
  								$filter.append( buildFilter );
  							}
  						} else {
  							buildFilter = $( '<input type="search">' ).appendTo( $filter );
  						}
  						if ( buildFilter ) {
  							tmp = $header.data( 'placeholder' ) ||
  								$header.attr( 'data-placeholder' ) ||
  								wo.filter_placeholder.search || '';
  							buildFilter.attr( 'placeholder', tmp );
  						}
  					}
  					if ( buildFilter ) {
  						// add filter class name
  						name = ( $.isArray( wo.filter_cssFilter ) ?
  							( typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '' ) :
  							wo.filter_cssFilter ) || '';
  						// copy data-column from table cell (it will include colspan)
  						buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', $filter.attr( 'data-column' ) );
  						if ( disabled ) {
  							buildFilter.attr( 'placeholder', '' ).addClass( tscss.filterDisabled )[0].disabled = true;
  						}
  					}
  				}
  			}
  		},
  		bindSearch: function( table, $el, internal ) {
  			table = $( table )[0];
  			$el = $( $el ); // allow passing a selector string
  			if ( !$el.length ) { return; }
  			var tmp,
  				c = table.config,
  				wo = c.widgetOptions,
  				namespace = c.namespace + 'filter',
  				$ext = wo.filter_$externalFilters;
  			if ( internal !== true ) {
  				// save anyMatch element
  				tmp = wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector;
  				wo.filter_$anyMatch = $el.filter( tmp );
  				if ( $ext && $ext.length ) {
  					wo.filter_$externalFilters = wo.filter_$externalFilters.add( $el );
  				} else {
  					wo.filter_$externalFilters = $el;
  				}
  				// update values ( external filters added after table initialization )
  				ts.setFilters( table, c.$table.data( 'lastSearch' ) || [], internal === false );
  			}
  			// unbind events
  			tmp = ( 'keypress keyup keydown search change input '.split( ' ' ).join( namespace + ' ' ) );
  			$el
  			// use data attribute instead of jQuery data since the head is cloned without including
  			// the data/binding
  			.attr( 'data-lastSearchTime', new Date().getTime() )
  			.unbind( tmp.replace( ts.regex.spaces, ' ' ) )
  			.bind( 'keydown' + namespace, function( event ) {
  				if ( event.which === tskeyCodes.escape && !table.config.widgetOptions.filter_resetOnEsc ) {
  					// prevent keypress event
  					return false;
  				}
  			})
  			.bind( 'keyup' + namespace, function( event ) {
  				wo = table.config.widgetOptions; // make sure "wo" isn't cached
  				var column = parseInt( $( this ).attr( 'data-column' ), 10 ),
  					liveSearch = typeof wo.filter_liveSearch === 'boolean' ? wo.filter_liveSearch :
  						ts.getColumnData( table, wo.filter_liveSearch, column );
  				if ( typeof liveSearch === 'undefined' ) {
  					liveSearch = wo.filter_liveSearch.fallback || false;
  				}
  				$( this ).attr( 'data-lastSearchTime', new Date().getTime() );
  				// emulate what webkit does.... escape clears the filter
  				if ( event.which === tskeyCodes.escape ) {
  					// make sure to restore the last value on escape
  					this.value = wo.filter_resetOnEsc ? '' : c.lastSearch[column];
  				// live search
  				} else if ( liveSearch === false ) {
  					return;
  					// don't return if the search value is empty ( all rows need to be revealed )
  				} else if ( this.value !== '' && (
  					// liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace
  					( typeof liveSearch === 'number' && this.value.length < liveSearch ) ||
  					// let return & backspace continue on, but ignore arrows & non-valid characters
  					( event.which !== tskeyCodes.enter && event.which !== tskeyCodes.backSpace &&
  						( event.which < tskeyCodes.space || ( event.which >= tskeyCodes.left && event.which <= tskeyCodes.down ) ) ) ) ) {
  					return;
  				}
  				// change event = no delay; last true flag tells getFilters to skip newest timed input
  				tsf.searching( table, true, true, column );
  			})
  			// include change for select - fixes #473
  			.bind( 'search change keypress input '.split( ' ' ).join( namespace + ' ' ), function( event ) {
  				// don't get cached data, in case data-column changes dynamically
  				var column = parseInt( $( this ).attr( 'data-column' ), 10 ),
  					liveSearch = typeof wo.filter_liveSearch === 'boolean' ?
  						wo.filter_liveSearch :
  						ts.getColumnData( table, wo.filter_liveSearch, column );
  				if ( table.config.widgetOptions.filter_initialized &&
  					// immediate search if user presses enter
  					( event.which === tskeyCodes.enter ||
  						// immediate search if a "search" is triggered on the input
  						event.type === 'search' ||
  						// change & input events must be ignored if liveSearch !== true
  						( event.type === 'change' || event.type === 'input' ) &&
  						// prevent search if liveSearch is a number
  						liveSearch === true &&
  						// don't allow 'change' or 'input' event to process if the input value
  						// is the same - fixes #685
  						this.value !== c.lastSearch[column]
  					)
  				) {
  					event.preventDefault();
  					// init search with no delay
  					$( this ).attr( 'data-lastSearchTime', new Date().getTime() );
  					tsf.searching( table, event.type !== 'keypress', true, column );
  				}
  			});
  		},
  		searching: function( table, filter, skipFirst, column ) {
  			var liveSearch,
  				wo = table.config.widgetOptions;
  			if (typeof column === 'undefined') {
  				// no delay
  				liveSearch = false;
  			} else {
  				liveSearch = typeof wo.filter_liveSearch === 'boolean' ?
  					wo.filter_liveSearch :
  					// get column setting, or set to fallback value, or default to false
  					ts.getColumnData( table, wo.filter_liveSearch, column );
  				if ( typeof liveSearch === 'undefined' ) {
  					liveSearch = wo.filter_liveSearch.fallback || false;
  				}
  			}
  			clearTimeout( wo.filter_searchTimer );
  			if ( typeof filter === 'undefined' || filter === true ) {
  				// delay filtering
  				wo.filter_searchTimer = setTimeout( function() {
  					tsf.checkFilters( table, filter, skipFirst );
  				}, liveSearch ? wo.filter_searchDelay : 10 );
  			} else {
  				// skip delay
  				tsf.checkFilters( table, filter, skipFirst );
  			}
  		},
  		equalFilters: function (c, filter1, filter2) {
  			var indx,
  				f1 = [],
  				f2 = [],
  				len = c.columns + 1; // add one to include anyMatch filter
  			filter1 = $.isArray(filter1) ? filter1 : [];
  			filter2 = $.isArray(filter2) ? filter2 : [];
  			for (indx = 0; indx < len; indx++) {
  				f1[indx] = filter1[indx] || '';
  				f2[indx] = filter2[indx] || '';
  			}
  			return f1.join(',') === f2.join(',');
  		},
  		checkFilters: function( table, filter, skipFirst ) {
  			var c = table.config,
  				wo = c.widgetOptions,
  				filterArray = $.isArray( filter ),
  				filters = ( filterArray ) ? filter : ts.getFilters( table, true ),
  				currentFilters = filters || []; // current filter values
  			// prevent errors if delay init is set
  			if ( $.isEmptyObject( c.cache ) ) {
  				// update cache if delayInit set & pager has initialized ( after user initiates a search )
  				if ( c.delayInit && ( !c.pager || c.pager && c.pager.initialized ) ) {
  					ts.updateCache( c, function() {
  						tsf.checkFilters( table, false, skipFirst );
  					});
  				}
  				return;
  			}
  			// add filter array back into inputs
  			if ( filterArray ) {
  				ts.setFilters( table, filters, false, skipFirst !== true );
  				if ( !wo.filter_initialized ) {
  					c.lastSearch = [];
  					c.lastCombinedFilter = '';
  				}
  			}
  			if ( wo.filter_hideFilters ) {
  				// show/hide filter row as needed
  				c.$table
  					.find( '.' + tscss.filterRow )
  					.triggerHandler( tsf.hideFiltersCheck( c ) ? 'mouseleave' : 'mouseenter' );
  			}
  			// return if the last search is the same; but filter === false when updating the search
  			// see example-widget-filter.html filter toggle buttons
  			if ( tsf.equalFilters(c, c.lastSearch, currentFilters) && filter !== false ) {
  				return;
  			} else if ( filter === false ) {
  				// force filter refresh
  				c.lastCombinedFilter = '';
  				c.lastSearch = [];
  			}
  			// define filter inside it is false
  			filters = filters || [];
  			// convert filters to strings - see #1070
  			filters = Array.prototype.map ?
  				filters.map( String ) :
  				// for IE8 & older browsers - maybe not the best method
  				filters.join( '\ufffd' ).split( '\ufffd' );
  
  			if ( wo.filter_initialized ) {
  				c.$table.triggerHandler( 'filterStart', [ filters ] );
  			}
  			if ( c.showProcessing ) {
  				// give it time for the processing icon to kick in
  				setTimeout( function() {
  					tsf.findRows( table, filters, currentFilters );
  					return false;
  				}, 30 );
  			} else {
  				tsf.findRows( table, filters, currentFilters );
  				return false;
  			}
  		},
  		hideFiltersCheck: function( c ) {
  			if (typeof c.widgetOptions.filter_hideFilters === 'function') {
  				var val = c.widgetOptions.filter_hideFilters( c );
  				if (typeof val === 'boolean') {
  					return val;
  				}
  			}
  			return ts.getFilters( c.$table ).join( '' ) === '';
  		},
  		hideFilters: function( c, $table ) {
  			var timer;
  			( $table || c.$table )
  				.find( '.' + tscss.filterRow )
  				.addClass( tscss.filterRowHide )
  				.bind( 'mouseenter mouseleave', function( e ) {
  					// save event object - http://bugs.jquery.com/ticket/12140
  					var event = e,
  						$row = $( this );
  					clearTimeout( timer );
  					timer = setTimeout( function() {
  						if ( /enter|over/.test( event.type ) ) {
  							$row.removeClass( tscss.filterRowHide );
  						} else {
  							// don't hide if input has focus
  							// $( ':focus' ) needs jQuery 1.6+
  							if ( $( document.activeElement ).closest( 'tr' )[0] !== $row[0] ) {
  								// don't hide row if any filter has a value
  								$row.toggleClass( tscss.filterRowHide, tsf.hideFiltersCheck( c ) );
  							}
  						}
  					}, 200 );
  				})
  				.find( 'input, select' ).bind( 'focus blur', function( e ) {
  					var event = e,
  						$row = $( this ).closest( 'tr' );
  					clearTimeout( timer );
  					timer = setTimeout( function() {
  						clearTimeout( timer );
  						// don't hide row if any filter has a value
  						$row.toggleClass( tscss.filterRowHide, tsf.hideFiltersCheck( c ) && event.type !== 'focus' );
  					}, 200 );
  				});
  		},
  		defaultFilter: function( filter, mask ) {
  			if ( filter === '' ) { return filter; }
  			var regex = tsfRegex.iQuery,
  				maskLen = mask.match( tsfRegex.igQuery ).length,
  				query = maskLen > 1 ? $.trim( filter ).split( /\s/ ) : [ $.trim( filter ) ],
  				len = query.length - 1,
  				indx = 0,
  				val = mask;
  			if ( len < 1 && maskLen > 1 ) {
  				// only one 'word' in query but mask has >1 slots
  				query[1] = query[0];
  			}
  			// replace all {query} with query words...
  			// if query = 'Bob', then convert mask from '!{query}' to '!Bob'
  			// if query = 'Bob Joe Frank', then convert mask '{q} OR {q}' to 'Bob OR Joe OR Frank'
  			while ( regex.test( val ) ) {
  				val = val.replace( regex, query[indx++] || '' );
  				if ( regex.test( val ) && indx < len && ( query[indx] || '' ) !== '' ) {
  					val = mask.replace( regex, val );
  				}
  			}
  			return val;
  		},
  		getLatestSearch: function( $input ) {
  			if ( $input ) {
  				return $input.sort( function( a, b ) {
  					return $( b ).attr( 'data-lastSearchTime' ) - $( a ).attr( 'data-lastSearchTime' );
  				});
  			}
  			return $input || $();
  		},
  		findRange: function( c, val, ignoreRanges ) {
  			// look for multiple columns '1-3,4-6,8' in data-column
  			var temp, ranges, range, start, end, singles, i, indx, len,
  				columns = [];
  			if ( /^[0-9]+$/.test( val ) ) {
  				// always return an array
  				return [ parseInt( val, 10 ) ];
  			}
  			// process column range
  			if ( !ignoreRanges && /-/.test( val ) ) {
  				ranges = val.match( /(\d+)\s*-\s*(\d+)/g );
  				len = ranges ? ranges.length : 0;
  				for ( indx = 0; indx < len; indx++ ) {
  					range = ranges[indx].split( /\s*-\s*/ );
  					start = parseInt( range[0], 10 ) || 0;
  					end = parseInt( range[1], 10 ) || ( c.columns - 1 );
  					if ( start > end ) {
  						temp = start; start = end; end = temp; // swap
  					}
  					if ( end >= c.columns ) {
  						end = c.columns - 1;
  					}
  					for ( ; start <= end; start++ ) {
  						columns[ columns.length ] = start;
  					}
  					// remove processed range from val
  					val = val.replace( ranges[ indx ], '' );
  				}
  			}
  			// process single columns
  			if ( !ignoreRanges && /,/.test( val ) ) {
  				singles = val.split( /\s*,\s*/ );
  				len = singles.length;
  				for ( i = 0; i < len; i++ ) {
  					if ( singles[ i ] !== '' ) {
  						indx = parseInt( singles[ i ], 10 );
  						if ( indx < c.columns ) {
  							columns[ columns.length ] = indx;
  						}
  					}
  				}
  			}
  			// return all columns
  			if ( !columns.length ) {
  				for ( indx = 0; indx < c.columns; indx++ ) {
  					columns[ columns.length ] = indx;
  				}
  			}
  			return columns;
  		},
  		getColumnElm: function( c, $elements, column ) {
  			// data-column may contain multiple columns '1-3,5-6,8'
  			// replaces: c.$filters.filter( '[data-column="' + column + '"]' );
  			return $elements.filter( function() {
  				var cols = tsf.findRange( c, $( this ).attr( 'data-column' ) );
  				return $.inArray( column, cols ) > -1;
  			});
  		},
  		multipleColumns: function( c, $input ) {
  			// look for multiple columns '1-3,4-6,8' in data-column
  			var wo = c.widgetOptions,
  				// only target 'all' column inputs on initialization
  				// & don't target 'all' column inputs if they don't exist
  				targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length,
  				val = $.trim( tsf.getLatestSearch( $input ).attr( 'data-column' ) || '' );
  			return tsf.findRange( c, val, !targets );
  		},
  		processTypes: function( c, data, vars ) {
  			var ffxn,
  				filterMatched = null,
  				matches = null;
  			for ( ffxn in tsf.types ) {
  				if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) {
  					matches = tsf.types[ffxn]( c, data, vars );
  					if ( matches !== null ) {
  						filterMatched = matches;
  					}
  				}
  			}
  			return filterMatched;
  		},
  		matchType: function( c, columnIndex ) {
  			var isMatch,
  				wo = c.widgetOptions,
  				$el = c.$headerIndexed[ columnIndex ];
  			// filter-exact > filter-match > filter_matchType for type
  			if ( $el.hasClass( 'filter-exact' ) ) {
  				isMatch = false;
  			} else if ( $el.hasClass( 'filter-match' ) ) {
  				isMatch = true;
  			} else {
  				// filter-select is not applied when filter_functions are used, so look for a select
  				if ( wo.filter_columnFilters ) {
  					$el = c.$filters
  						.find( '.' + tscss.filter )
  						.add( wo.filter_$externalFilters )
  						.filter( '[data-column="' + columnIndex + '"]' );
  				} else if ( wo.filter_$externalFilters ) {
  					$el = wo.filter_$externalFilters.filter( '[data-column="' + columnIndex + '"]' );
  				}
  				isMatch = $el.length ?
  					c.widgetOptions.filter_matchType[ ( $el[ 0 ].nodeName || '' ).toLowerCase() ] === 'match' :
  					// default to exact, if no inputs found
  					false;
  			}
  			return isMatch;
  		},
  		processRow: function( c, data, vars ) {
  			var result, filterMatched,
  				fxn, ffxn, txt,
  				wo = c.widgetOptions,
  				showRow = true,
  				hasAnyMatchInput = wo.filter_$anyMatch && wo.filter_$anyMatch.length,
  
  				// if wo.filter_$anyMatch data-column attribute is changed dynamically
  				// we don't want to do an "anyMatch" search on one column using data
  				// for the entire row - see #998
  				columnIndex = wo.filter_$anyMatch && wo.filter_$anyMatch.length ?
  					// look for multiple columns '1-3,4-6,8'
  					tsf.multipleColumns( c, wo.filter_$anyMatch ) :
  					[];
  			data.$cells = data.$row.children();
  			if ( data.anyMatchFlag && columnIndex.length > 1 || ( data.anyMatchFilter && !hasAnyMatchInput ) ) {
  				data.anyMatch = true;
  				data.isMatch = true;
  				data.rowArray = data.$cells.map( function( i ) {
  					if ( $.inArray( i, columnIndex ) > -1 || ( data.anyMatchFilter && !hasAnyMatchInput ) ) {
  						if ( data.parsed[ i ] ) {
  							txt = data.cacheArray[ i ];
  						} else {
  							txt = data.rawArray[ i ];
  							txt = $.trim( wo.filter_ignoreCase ? txt.toLowerCase() : txt );
  							if ( c.sortLocaleCompare ) {
  								txt = ts.replaceAccents( txt );
  							}
  						}
  						return txt;
  					}
  				}).get();
  				data.filter = data.anyMatchFilter;
  				data.iFilter = data.iAnyMatchFilter;
  				data.exact = data.rowArray.join( ' ' );
  				data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact;
  				data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' );
  				vars.excludeMatch = vars.noAnyMatch;
  				filterMatched = tsf.processTypes( c, data, vars );
  				if ( filterMatched !== null ) {
  					showRow = filterMatched;
  				} else {
  					if ( wo.filter_startsWith ) {
  						showRow = false;
  						// data.rowArray may not contain all columns
  						columnIndex = Math.min( c.columns, data.rowArray.length );
  						while ( !showRow && columnIndex > 0 ) {
  							columnIndex--;
  							showRow = showRow || data.rowArray[ columnIndex ].indexOf( data.iFilter ) === 0;
  						}
  					} else {
  						showRow = ( data.iExact + data.childRowText ).indexOf( data.iFilter ) >= 0;
  					}
  				}
  				data.anyMatch = false;
  				// no other filters to process
  				if ( data.filters.join( '' ) === data.filter ) {
  					return showRow;
  				}
  			}
  
  			for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) {
  				data.filter = data.filters[ columnIndex ];
  				data.index = columnIndex;
  
  				// filter types to exclude, per column
  				vars.excludeMatch = vars.excludeFilter[ columnIndex ];
  
  				// ignore if filter is empty or disabled
  				if ( data.filter ) {
  					data.cache = data.cacheArray[ columnIndex ];
  					result = data.parsed[ columnIndex ] ? data.cache : data.rawArray[ columnIndex ] || '';
  					data.exact = c.sortLocaleCompare ? ts.replaceAccents( result ) : result; // issue #405
  					data.iExact = !tsfRegex.type.test( typeof data.exact ) && wo.filter_ignoreCase ?
  						data.exact.toLowerCase() : data.exact;
  					data.isMatch = tsf.matchType( c, columnIndex );
  
  					result = showRow; // if showRow is true, show that row
  
  					// in case select filter option has a different value vs text 'a - z|A through Z'
  					ffxn = wo.filter_columnFilters ?
  						c.$filters.add( wo.filter_$externalFilters )
  							.filter( '[data-column="' + columnIndex + '"]' )
  							.find( 'select option:selected' )
  							.attr( 'data-function-name' ) || '' : '';
  					// replace accents - see #357
  					if ( c.sortLocaleCompare ) {
  						data.filter = ts.replaceAccents( data.filter );
  					}
  
  					// replace column specific default filters - see #1088
  					if ( wo.filter_defaultFilter && tsfRegex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) {
  						data.filter = tsf.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] );
  					}
  
  					// data.iFilter = case insensitive ( if wo.filter_ignoreCase is true ),
  					// data.filter = case sensitive
  					data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter;
  					fxn = vars.functions[ columnIndex ];
  					filterMatched = null;
  					if ( fxn ) {
  						if ( fxn === true ) {
  							// default selector uses exact match unless 'filter-match' class is found
  							filterMatched = data.isMatch ?
  								// data.iExact may be a number
  								( '' + data.iExact ).search( data.iFilter ) >= 0 :
  								data.filter === data.exact;
  						} else if ( typeof fxn === 'function' ) {
  							// filter callback( exact cell content, parser normalized content,
  							// filter input value, column index, jQuery row object )
  							filterMatched = fxn( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data );
  						} else if ( typeof fxn[ ffxn || data.filter ] === 'function' ) {
  							// selector option function
  							txt = ffxn || data.filter;
  							filterMatched =
  								fxn[ txt ]( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data );
  						}
  					}
  					if ( filterMatched === null ) {
  						// cycle through the different filters
  						// filters return a boolean or null if nothing matches
  						filterMatched = tsf.processTypes( c, data, vars );
  						if ( filterMatched !== null ) {
  							result = filterMatched;
  						// Look for match, and add child row data for matching
  						} else {
  							txt = ( data.iExact + data.childRowText ).indexOf( tsf.parseFilter( c, data.iFilter, data ) );
  							result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) );
  						}
  					} else {
  						result = filterMatched;
  					}
  					showRow = ( result ) ? showRow : false;
  				}
  			}
  			return showRow;
  		},
  		findRows: function( table, filters, currentFilters ) {
  			if (
  				tsf.equalFilters(table.config, table.config.lastSearch, currentFilters) ||
  				!table.config.widgetOptions.filter_initialized
  			) {
  				return;
  			}
  			var len, norm_rows, rowData, $rows, $row, rowIndex, tbodyIndex, $tbody, columnIndex,
  				isChild, childRow, lastSearch, showRow, showParent, time, val, indx,
  				notFiltered, searchFiltered, query, injected, res, id, txt,
  				storedFilters = $.extend( [], filters ),
  				c = table.config,
  				wo = c.widgetOptions,
  				// data object passed to filters; anyMatch is a flag for the filters
  				data = {
  					anyMatch: false,
  					filters: filters,
  					// regex filter type cache
  					filter_regexCache : []
  				},
  				vars = {
  					// anyMatch really screws up with these types of filters
  					noAnyMatch: [ 'range',  'operators' ],
  					// cache filter variables that use ts.getColumnData in the main loop
  					functions : [],
  					excludeFilter : [],
  					defaultColFilter : [],
  					defaultAnyFilter : ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || ''
  				};
  
  			// parse columns after formatter, in case the class is added at that point
  			data.parsed = [];
  			for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) {
  				data.parsed[ columnIndex ] = wo.filter_useParsedData ||
  					// parser has a "parsed" parameter
  					( c.parsers && c.parsers[ columnIndex ] && c.parsers[ columnIndex ].parsed ||
  					// getData may not return 'parsed' if other 'filter-' class names exist
  					// ( e.g. <th class="filter-select filter-parsed"> )
  					ts.getData && ts.getData( c.$headerIndexed[ columnIndex ],
  						ts.getColumnData( table, c.headers, columnIndex ), 'filter' ) === 'parsed' ||
  					c.$headerIndexed[ columnIndex ].hasClass( 'filter-parsed' ) );
  
  				vars.functions[ columnIndex ] =
  					ts.getColumnData( table, wo.filter_functions, columnIndex ) ||
  					c.$headerIndexed[ columnIndex ].hasClass( 'filter-select' );
  				vars.defaultColFilter[ columnIndex ] =
  					ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || '';
  				vars.excludeFilter[ columnIndex ] =
  					( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split( /\s+/ );
  			}
  
  			if ( c.debug ) {
  				console.log( 'Filter: Starting filter widget search', filters );
  				time = new Date();
  			}
  			// filtered rows count
  			c.filteredRows = 0;
  			c.totalRows = 0;
  			currentFilters = ( storedFilters || [] );
  
  			for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
  				$tbody = ts.processTbody( table, c.$tbodies.eq( tbodyIndex ), true );
  				// skip child rows & widget added ( removable ) rows - fixes #448 thanks to @hempel!
  				// $rows = $tbody.children( 'tr' ).not( c.selectorRemove );
  				columnIndex = c.columns;
  				// convert stored rows into a jQuery object
  				norm_rows = c.cache[ tbodyIndex ].normalized;
  				$rows = $( $.map( norm_rows, function( el ) {
  					return el[ columnIndex ].$row.get();
  				}) );
  
  				if ( currentFilters.join('') === '' || wo.filter_serversideFiltering ) {
  					$rows
  						.removeClass( wo.filter_filteredRow )
  						.not( '.' + c.cssChildRow )
  						.css( 'display', '' );
  				} else {
  					// filter out child rows
  					$rows = $rows.not( '.' + c.cssChildRow );
  					len = $rows.length;
  
  					if ( ( wo.filter_$anyMatch && wo.filter_$anyMatch.length ) ||
  						typeof filters[c.columns] !== 'undefined' ) {
  						data.anyMatchFlag = true;
  						data.anyMatchFilter = '' + (
  							filters[ c.columns ] ||
  							wo.filter_$anyMatch && tsf.getLatestSearch( wo.filter_$anyMatch ).val() ||
  							''
  						);
  						if ( wo.filter_columnAnyMatch ) {
  							// specific columns search
  							query = data.anyMatchFilter.split( tsfRegex.andSplit );
  							injected = false;
  							for ( indx = 0; indx < query.length; indx++ ) {
  								res = query[ indx ].split( ':' );
  								if ( res.length > 1 ) {
  									// make the column a one-based index ( non-developers start counting from one :P )
  									if ( isNaN( res[0] ) ) {
  										$.each( c.headerContent, function( i, txt ) {
  											// multiple matches are possible
  											if ( txt.toLowerCase().indexOf( res[0] ) > -1 ) {
  												id = i;
  												filters[ id ] = res[1];
  											}
  										});
  									} else {
  										id = parseInt( res[0], 10 ) - 1;
  									}
  									if ( id >= 0 && id < c.columns ) { // if id is an integer
  										filters[ id ] = res[1];
  										query.splice( indx, 1 );
  										indx--;
  										injected = true;
  									}
  								}
  							}
  							if ( injected ) {
  								data.anyMatchFilter = query.join( ' && ' );
  							}
  						}
  					}
  
  					// optimize searching only through already filtered rows - see #313
  					searchFiltered = wo.filter_searchFiltered;
  					lastSearch = c.lastSearch || c.$table.data( 'lastSearch' ) || [];
  					if ( searchFiltered ) {
  						// cycle through all filters; include last ( columnIndex + 1 = match any column ). Fixes #669
  						for ( indx = 0; indx < columnIndex + 1; indx++ ) {
  							val = filters[indx] || '';
  							// break out of loop if we've already determined not to search filtered rows
  							if ( !searchFiltered ) { indx = columnIndex; }
  							// search already filtered rows if...
  							searchFiltered = searchFiltered && lastSearch.length &&
  								// there are no changes from beginning of filter
  								val.indexOf( lastSearch[indx] || '' ) === 0 &&
  								// if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string
  								!tsfRegex.alreadyFiltered.test( val ) &&
  								// if we are not doing exact matches, using '|' ( logical or ) or not '!'
  								!tsfRegex.exactTest.test( val ) &&
  								// don't search only filtered if the value is negative
  								// ( '> -10' => '> -100' will ignore hidden rows )
  								!( tsfRegex.isNeg1.test( val ) || tsfRegex.isNeg2.test( val ) ) &&
  								// if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593
  								!( val !== '' && c.$filters && c.$filters.filter( '[data-column="' + indx + '"]' ).find( 'select' ).length &&
  									!tsf.matchType( c, indx ) );
  						}
  					}
  					notFiltered = $rows.not( '.' + wo.filter_filteredRow ).length;
  					// can't search when all rows are hidden - this happens when looking for exact matches
  					if ( searchFiltered && notFiltered === 0 ) { searchFiltered = false; }
  					if ( c.debug ) {
  						console.log( 'Filter: Searching through ' +
  							( searchFiltered && notFiltered < len ? notFiltered : 'all' ) + ' rows' );
  					}
  					if ( data.anyMatchFlag ) {
  						if ( c.sortLocaleCompare ) {
  							// replace accents
  							data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter );
  						}
  						if ( wo.filter_defaultFilter && tsfRegex.iQuery.test( vars.defaultAnyFilter ) ) {
  							data.anyMatchFilter = tsf.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter );
  							// clear search filtered flag because default filters are not saved to the last search
  							searchFiltered = false;
  						}
  						// make iAnyMatchFilter lowercase unless both filter widget & core ignoreCase options are true
  						// when c.ignoreCase is true, the cache contains all lower case data
  						data.iAnyMatchFilter = !( wo.filter_ignoreCase && c.ignoreCase ) ?
  							data.anyMatchFilter :
  							data.anyMatchFilter.toLowerCase();
  					}
  
  					// loop through the rows
  					for ( rowIndex = 0; rowIndex < len; rowIndex++ ) {
  
  						txt = $rows[ rowIndex ].className;
  						// the first row can never be a child row
  						isChild = rowIndex && tsfRegex.child.test( txt );
  						// skip child rows & already filtered rows
  						if ( isChild || ( searchFiltered && tsfRegex.filtered.test( txt ) ) ) {
  							continue;
  						}
  
  						data.$row = $rows.eq( rowIndex );
  						data.rowIndex = rowIndex;
  						data.cacheArray = norm_rows[ rowIndex ];
  						rowData = data.cacheArray[ c.columns ];
  						data.rawArray = rowData.raw;
  						data.childRowText = '';
  
  						if ( !wo.filter_childByColumn ) {
  							txt = '';
  							// child row cached text
  							childRow = rowData.child;
  							// so, if 'table.config.widgetOptions.filter_childRows' is true and there is
  							// a match anywhere in the child row, then it will make the row visible
  							// checked here so the option can be changed dynamically
  							for ( indx = 0; indx < childRow.length; indx++ ) {
  								txt += ' ' + childRow[indx].join( ' ' ) || '';
  							}
  							data.childRowText = wo.filter_childRows ?
  								( wo.filter_ignoreCase ? txt.toLowerCase() : txt ) :
  								'';
  						}
  
  						showRow = false;
  						showParent = tsf.processRow( c, data, vars );
  						$row = rowData.$row;
  
  						// don't pass reference to val
  						val = showParent ? true : false;
  						childRow = rowData.$row.filter( ':gt(0)' );
  						if ( wo.filter_childRows && childRow.length ) {
  							if ( wo.filter_childByColumn ) {
  								if ( !wo.filter_childWithSibs ) {
  									// hide all child rows
  									childRow.addClass( wo.filter_filteredRow );
  									// if only showing resulting child row, only include parent
  									$row = $row.eq( 0 );
  								}
  								// cycle through each child row
  								for ( indx = 0; indx < childRow.length; indx++ ) {
  									data.$row = childRow.eq( indx );
  									data.cacheArray = rowData.child[ indx ];
  									data.rawArray = data.cacheArray;
  									val = tsf.processRow( c, data, vars );
  									// use OR comparison on child rows
  									showRow = showRow || val;
  									if ( !wo.filter_childWithSibs && val ) {
  										childRow.eq( indx ).removeClass( wo.filter_filteredRow );
  									}
  								}
  							}
  							// keep parent row match even if no child matches... see #1020
  							showRow = showRow || showParent;
  						} else {
  							showRow = val;
  						}
  						$row
  							.toggleClass( wo.filter_filteredRow, !showRow )[0]
  							.display = showRow ? '' : 'none';
  					}
  				}
  				c.filteredRows += $rows.not( '.' + wo.filter_filteredRow ).length;
  				c.totalRows += $rows.length;
  				ts.processTbody( table, $tbody, false );
  			}
  			// lastCombinedFilter is no longer used internally
  			c.lastCombinedFilter = storedFilters.join(''); // save last search
  			// don't save 'filters' directly since it may have altered ( AnyMatch column searches )
  			c.lastSearch = storedFilters;
  			c.$table.data( 'lastSearch', storedFilters );
  			if ( wo.filter_saveFilters && ts.storage ) {
  				ts.storage( table, 'tablesorter-filters', tsf.processFilters( storedFilters, true ) );
  			}
  			if ( c.debug ) {
  				console.log( 'Completed filter widget search' + ts.benchmark(time) );
  			}
  			if ( wo.filter_initialized ) {
  				c.$table.triggerHandler( 'filterBeforeEnd', c );
  				c.$table.triggerHandler( 'filterEnd', c );
  			}
  			setTimeout( function() {
  				ts.applyWidget( c.table ); // make sure zebra widget is applied
  			}, 0 );
  		},
  		getOptionSource: function( table, column, onlyAvail ) {
  			table = $( table )[0];
  			var c = table.config,
  				wo = c.widgetOptions,
  				arry = false,
  				source = wo.filter_selectSource,
  				last = c.$table.data( 'lastSearch' ) || [],
  				fxn = typeof source === 'function' ? true : ts.getColumnData( table, source, column );
  
  			if ( onlyAvail && last[column] !== '' ) {
  				onlyAvail = false;
  			}
  
  			// filter select source option
  			if ( fxn === true ) {
  				// OVERALL source
  				arry = source( table, column, onlyAvail );
  			} else if ( fxn instanceof $ || ( $.type( fxn ) === 'string' && fxn.indexOf( '</option>' ) >= 0 ) ) {
  				// selectSource is a jQuery object or string of options
  				return fxn;
  			} else if ( $.isArray( fxn ) ) {
  				arry = fxn;
  			} else if ( $.type( source ) === 'object' && fxn ) {
  				// custom select source function for a SPECIFIC COLUMN
  				arry = fxn( table, column, onlyAvail );
  			}
  			if ( arry === false ) {
  				// fall back to original method
  				arry = tsf.getOptions( table, column, onlyAvail );
  			}
  
  			return tsf.processOptions( table, column, arry );
  
  		},
  		processOptions: function( table, column, arry ) {
  			if ( !$.isArray( arry ) ) {
  				return false;
  			}
  			table = $( table )[0];
  			var cts, txt, indx, len, parsedTxt, str,
  				c = table.config,
  				validColumn = typeof column !== 'undefined' && column !== null && column >= 0 && column < c.columns,
  				direction = validColumn ? c.$headerIndexed[ column ].hasClass( 'filter-select-sort-desc' ) : false,
  				parsed = [];
  			// get unique elements and sort the list
  			// if $.tablesorter.sortText exists ( not in the original tablesorter ),
  			// then natural sort the list otherwise use a basic sort
  			arry = $.grep( arry, function( value, indx ) {
  				if ( value.text ) {
  					return true;
  				}
  				return $.inArray( value, arry ) === indx;
  			});
  			if ( validColumn && c.$headerIndexed[ column ].hasClass( 'filter-select-nosort' ) ) {
  				// unsorted select options
  				return arry;
  			} else {
  				len = arry.length;
  				// parse select option values
  				for ( indx = 0; indx < len; indx++ ) {
  					txt = arry[ indx ];
  					// check for object
  					str = txt.text ? txt.text : txt;
  					// sortNatural breaks if you don't pass it strings
  					parsedTxt = ( validColumn && c.parsers && c.parsers.length &&
  						c.parsers[ column ].format( str, table, [], column ) || str ).toString();
  					parsedTxt = c.widgetOptions.filter_ignoreCase ? parsedTxt.toLowerCase() : parsedTxt;
  					// parse array data using set column parser; this DOES NOT pass the original
  					// table cell to the parser format function
  					if ( txt.text ) {
  						txt.parsed = parsedTxt;
  						parsed[ parsed.length ] = txt;
  					} else {
  						parsed[ parsed.length ] = {
  							text : txt,
  							// check parser length - fixes #934
  							parsed : parsedTxt
  						};
  					}
  				}
  				// sort parsed select options
  				cts = c.textSorter || '';
  				parsed.sort( function( a, b ) {
  					var x = direction ? b.parsed : a.parsed,
  						y = direction ? a.parsed : b.parsed;
  					if ( validColumn && typeof cts === 'function' ) {
  						// custom OVERALL text sorter
  						return cts( x, y, true, column, table );
  					} else if ( validColumn && typeof cts === 'object' && cts.hasOwnProperty( column ) ) {
  						// custom text sorter for a SPECIFIC COLUMN
  						return cts[column]( x, y, true, column, table );
  					} else if ( ts.sortNatural ) {
  						// fall back to natural sort
  						return ts.sortNatural( x, y );
  					}
  					// using an older version! do a basic sort
  					return true;
  				});
  				// rebuild arry from sorted parsed data
  				arry = [];
  				len = parsed.length;
  				for ( indx = 0; indx < len; indx++ ) {
  					arry[ arry.length ] = parsed[indx];
  				}
  				return arry;
  			}
  		},
  		getOptions: function( table, column, onlyAvail ) {
  			table = $( table )[0];
  			var rowIndex, tbodyIndex, len, row, cache, indx, child, childLen,
  				c = table.config,
  				wo = c.widgetOptions,
  				arry = [];
  			for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
  				cache = c.cache[tbodyIndex];
  				len = c.cache[tbodyIndex].normalized.length;
  				// loop through the rows
  				for ( rowIndex = 0; rowIndex < len; rowIndex++ ) {
  					// get cached row from cache.row ( old ) or row data object
  					// ( new; last item in normalized array )
  					row = cache.row ?
  						cache.row[ rowIndex ] :
  						cache.normalized[ rowIndex ][ c.columns ].$row[0];
  					// check if has class filtered
  					if ( onlyAvail && row.className.match( wo.filter_filteredRow ) ) {
  						continue;
  					}
  					// get non-normalized cell content
  					if ( wo.filter_useParsedData ||
  						c.parsers[column].parsed ||
  						c.$headerIndexed[column].hasClass( 'filter-parsed' ) ) {
  						arry[ arry.length ] = '' + cache.normalized[ rowIndex ][ column ];
  						// child row parsed data
  						if ( wo.filter_childRows && wo.filter_childByColumn ) {
  							childLen = cache.normalized[ rowIndex ][ c.columns ].$row.length - 1;
  							for ( indx = 0; indx < childLen; indx++ ) {
  								arry[ arry.length ] = '' + cache.normalized[ rowIndex ][ c.columns ].child[ indx ][ column ];
  							}
  						}
  					} else {
  						// get raw cached data instead of content directly from the cells
  						arry[ arry.length ] = cache.normalized[ rowIndex ][ c.columns ].raw[ column ];
  						// child row unparsed data
  						if ( wo.filter_childRows && wo.filter_childByColumn ) {
  							childLen = cache.normalized[ rowIndex ][ c.columns ].$row.length;
  							for ( indx = 1; indx < childLen; indx++ ) {
  								child =  cache.normalized[ rowIndex ][ c.columns ].$row.eq( indx ).children().eq( column );
  								arry[ arry.length ] = '' + ts.getElementText( c, child, column );
  							}
  						}
  					}
  				}
  			}
  			return arry;
  		},
  		buildSelect: function( table, column, arry, updating, onlyAvail ) {
  			table = $( table )[0];
  			column = parseInt( column, 10 );
  			if ( !table.config.cache || $.isEmptyObject( table.config.cache ) ) {
  				return;
  			}
  
  			var indx, val, txt, t, $filters, $filter, option,
  				c = table.config,
  				wo = c.widgetOptions,
  				node = c.$headerIndexed[ column ],
  				// t.data( 'placeholder' ) won't work in jQuery older than 1.4.3
  				options = '<option value="">' +
  					( node.data( 'placeholder' ) ||
  						node.attr( 'data-placeholder' ) ||
  						wo.filter_placeholder.select || ''
  					) + '</option>',
  				// Get curent filter value
  				currentValue = c.$table
  					.find( 'thead' )
  					.find( 'select.' + tscss.filter + '[data-column="' + column + '"]' )
  					.val();
  
  			// nothing included in arry ( external source ), so get the options from
  			// filter_selectSource or column data
  			if ( typeof arry === 'undefined' || arry === '' ) {
  				arry = tsf.getOptionSource( table, column, onlyAvail );
  			}
  
  			if ( $.isArray( arry ) ) {
  				// build option list
  				for ( indx = 0; indx < arry.length; indx++ ) {
  					option = arry[ indx ];
  					if ( option.text ) {
  						// OBJECT!! add data-function-name in case the value is set in filter_functions
  						option['data-function-name'] = typeof option.value === 'undefined' ? option.text : option.value;
  
  						// support jQuery < v1.8, otherwise the below code could be shortened to
  						// options += $( '<option>', option )[ 0 ].outerHTML;
  						options += '<option';
  						for ( val in option ) {
  							if ( option.hasOwnProperty( val ) && val !== 'text' ) {
  								options += ' ' + val + '="' + option[ val ] + '"';
  							}
  						}
  						if ( !option.value ) {
  							options += ' value="' + option.text + '"';
  						}
  						options += '>' + option.text + '</option>';
  						// above code is needed in jQuery < v1.8
  
  						// make sure we don't turn an object into a string (objects without a "text" property)
  					} else if ( '' + option !== '[object Object]' ) {
  						txt = option = ( '' + option ).replace( tsfRegex.quote, '&quot;' );
  						val = txt;
  						// allow including a symbol in the selectSource array
  						// 'a-z|A through Z' so that 'a-z' becomes the option value
  						// and 'A through Z' becomes the option text
  						if ( txt.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) {
  							t = txt.split( wo.filter_selectSourceSeparator );
  							val = t[0];
  							txt = t[1];
  						}
  						// replace quotes - fixes #242 & ignore empty strings
  						// see http://stackoverflow.com/q/14990971/145346
  						options += option !== '' ?
  							'<option ' +
  								( val === txt ? '' : 'data-function-name="' + option + '" ' ) +
  								'value="' + val + '">' + txt +
  							'</option>' : '';
  					}
  				}
  				// clear arry so it doesn't get appended twice
  				arry = [];
  			}
  
  			// update all selects in the same column ( clone thead in sticky headers &
  			// any external selects ) - fixes 473
  			$filters = ( c.$filters ? c.$filters : c.$table.children( 'thead' ) )
  				.find( '.' + tscss.filter );
  			if ( wo.filter_$externalFilters ) {
  				$filters = $filters && $filters.length ?
  					$filters.add( wo.filter_$externalFilters ) :
  					wo.filter_$externalFilters;
  			}
  			$filter = $filters.filter( 'select[data-column="' + column + '"]' );
  
  			// make sure there is a select there!
  			if ( $filter.length ) {
  				$filter[ updating ? 'html' : 'append' ]( options );
  				if ( !$.isArray( arry ) ) {
  					// append options if arry is provided externally as a string or jQuery object
  					// options ( default value ) was already added
  					$filter.append( arry ).val( currentValue );
  				}
  				$filter.val( currentValue );
  			}
  		},
  		buildDefault: function( table, updating ) {
  			var columnIndex, $header, noSelect,
  				c = table.config,
  				wo = c.widgetOptions,
  				columns = c.columns;
  			// build default select dropdown
  			for ( columnIndex = 0; columnIndex < columns; columnIndex++ ) {
  				$header = c.$headerIndexed[columnIndex];
  				noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) );
  				// look for the filter-select class; build/update it if found
  				if ( ( $header.hasClass( 'filter-select' ) ||
  					ts.getColumnData( table, wo.filter_functions, columnIndex ) === true ) && noSelect ) {
  					tsf.buildSelect( table, columnIndex, '', updating, $header.hasClass( wo.filter_onlyAvail ) );
  				}
  			}
  		}
  	};
  
  	// filter regex variable
  	tsfRegex = tsf.regex;
  
  	ts.getFilters = function( table, getRaw, setFilters, skipFirst ) {
  		var i, $filters, $column, cols,
  			filters = [],
  			c = table ? $( table )[0].config : '',
  			wo = c ? c.widgetOptions : '';
  		if ( ( getRaw !== true && wo && !wo.filter_columnFilters ) ||
  			// setFilters called, but last search is exactly the same as the current
  			// fixes issue #733 & #903 where calling update causes the input values to reset
  			( $.isArray(setFilters) && tsf.equalFilters(c, setFilters, c.lastSearch) )
  		) {
  			return $( table ).data( 'lastSearch' ) || [];
  		}
  		if ( c ) {
  			if ( c.$filters ) {
  				$filters = c.$filters.find( '.' + tscss.filter );
  			}
  			if ( wo.filter_$externalFilters ) {
  				$filters = $filters && $filters.length ?
  					$filters.add( wo.filter_$externalFilters ) :
  					wo.filter_$externalFilters;
  			}
  			if ( $filters && $filters.length ) {
  				filters = setFilters || [];
  				for ( i = 0; i < c.columns + 1; i++ ) {
  					cols = ( i === c.columns ?
  						// 'all' columns can now include a range or set of columms ( data-column='0-2,4,6-7' )
  						wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector :
  						'[data-column="' + i + '"]' );
  					$column = $filters.filter( cols );
  					if ( $column.length ) {
  						// move the latest search to the first slot in the array
  						$column = tsf.getLatestSearch( $column );
  						if ( $.isArray( setFilters ) ) {
  							// skip first ( latest input ) to maintain cursor position while typing
  							if ( skipFirst && $column.length > 1 ) {
  								$column = $column.slice( 1 );
  							}
  							if ( i === c.columns ) {
  								// prevent data-column='all' from filling data-column='0,1' ( etc )
  								cols = $column.filter( wo.filter_anyColumnSelector );
  								$column = cols.length ? cols : $column;
  							}
  							$column
  								.val( setFilters[ i ] )
  								// must include a namespace here; but not c.namespace + 'filter'?
  								.trigger( 'change' + c.namespace );
  						} else {
  							filters[i] = $column.val() || '';
  							// don't change the first... it will move the cursor
  							if ( i === c.columns ) {
  								// don't update range columns from 'all' setting
  								$column
  									.slice( 1 )
  									.filter( '[data-column*="' + $column.attr( 'data-column' ) + '"]' )
  									.val( filters[ i ] );
  							} else {
  								$column
  									.slice( 1 )
  									.val( filters[ i ] );
  							}
  						}
  						// save any match input dynamically
  						if ( i === c.columns && $column.length ) {
  							wo.filter_$anyMatch = $column;
  						}
  					}
  				}
  			}
  		}
  		return filters;
  	};
  
  	ts.setFilters = function( table, filter, apply, skipFirst ) {
  		var c = table ? $( table )[0].config : '',
  			valid = ts.getFilters( table, true, filter, skipFirst );
  		// default apply to "true"
  		if ( typeof apply === 'undefined' ) {
  			apply = true;
  		}
  		if ( c && apply ) {
  			// ensure new set filters are applied, even if the search is the same
  			c.lastCombinedFilter = null;
  			c.lastSearch = [];
  			tsf.searching( c.table, filter, skipFirst );
  			c.$table.triggerHandler( 'filterFomatterUpdate' );
  		}
  		return valid.length !== 0;
  	};
  
  })( jQuery );
  
  /*! Widget: stickyHeaders - updated 1/6/2017 (v2.28.4) *//*
   * Requires tablesorter v2.8+ and jQuery 1.4.3+
   * by Rob Garrison
   */
  ;(function ($, window) {
  	'use strict';
  	var ts = $.tablesorter || {};
  
  	$.extend(ts.css, {
  		sticky    : 'tablesorter-stickyHeader', // stickyHeader
  		stickyVis : 'tablesorter-sticky-visible',
  		stickyHide: 'tablesorter-sticky-hidden',
  		stickyWrap: 'tablesorter-sticky-wrapper'
  	});
  
  	// Add a resize event to table headers
  	ts.addHeaderResizeEvent = function(table, disable, settings) {
  		table = $(table)[0]; // make sure we're using a dom element
  		if ( !table.config ) { return; }
  		var defaults = {
  				timer : 250
  			},
  			options = $.extend({}, defaults, settings),
  			c = table.config,
  			wo = c.widgetOptions,
  			checkSizes = function( triggerEvent ) {
  				var index, headers, $header, sizes, width, height,
  					len = c.$headers.length;
  				wo.resize_flag = true;
  				headers = [];
  				for ( index = 0; index < len; index++ ) {
  					$header = c.$headers.eq( index );
  					sizes = $header.data( 'savedSizes' ) || [ 0, 0 ]; // fixes #394
  					width = $header[0].offsetWidth;
  					height = $header[0].offsetHeight;
  					if ( width !== sizes[0] || height !== sizes[1] ) {
  						$header.data( 'savedSizes', [ width, height ] );
  						headers.push( $header[0] );
  					}
  				}
  				if ( headers.length && triggerEvent !== false ) {
  					c.$table.triggerHandler( 'resize', [ headers ] );
  				}
  				wo.resize_flag = false;
  			};
  		clearInterval(wo.resize_timer);
  		if (disable) {
  			wo.resize_flag = false;
  			return false;
  		}
  		checkSizes( false );
  		wo.resize_timer = setInterval(function() {
  			if (wo.resize_flag) { return; }
  			checkSizes();
  		}, options.timer);
  	};
  
  	// Sticky headers based on this awesome article:
  	// http://css-tricks.com/13465-persistent-headers/
  	// and https://github.com/jmosbech/StickyTableHeaders by Jonas Mosbech
  	// **************************
  	ts.addWidget({
  		id: 'stickyHeaders',
  		priority: 54, // sticky widget must be initialized after the filter & before pager widget!
  		options: {
  			stickyHeaders : '',       // extra class name added to the sticky header row
  			stickyHeaders_appendTo : null, // jQuery selector or object to phycially attach the sticky headers
  			stickyHeaders_attachTo : null, // jQuery selector or object to attach scroll listener to (overridden by xScroll & yScroll settings)
  			stickyHeaders_xScroll : null, // jQuery selector or object to monitor horizontal scroll position (defaults: xScroll > attachTo > window)
  			stickyHeaders_yScroll : null, // jQuery selector or object to monitor vertical scroll position (defaults: yScroll > attachTo > window)
  			stickyHeaders_offset : 0, // number or jquery selector targeting the position:fixed element
  			stickyHeaders_filteredToTop: true, // scroll table top into view after filtering
  			stickyHeaders_cloneId : '-sticky', // added to table ID, if it exists
  			stickyHeaders_addResizeEvent : true, // trigger 'resize' event on headers
  			stickyHeaders_includeCaption : true, // if false and a caption exist, it won't be included in the sticky header
  			stickyHeaders_zIndex : 2 // The zIndex of the stickyHeaders, allows the user to adjust this to their needs
  		},
  		format: function(table, c, wo) {
  			// filter widget doesn't initialize on an empty table. Fixes #449
  			if ( c.$table.hasClass('hasStickyHeaders') || ($.inArray('filter', c.widgets) >= 0 && !c.$table.hasClass('hasFilters')) ) {
  				return;
  			}
  			var index, len, $t,
  				$table = c.$table,
  				// add position: relative to attach element, hopefully it won't cause trouble.
  				$attach = $(wo.stickyHeaders_attachTo),
  				namespace = c.namespace + 'stickyheaders ',
  				// element to watch for the scroll event
  				$yScroll = $(wo.stickyHeaders_yScroll || wo.stickyHeaders_attachTo || window),
  				$xScroll = $(wo.stickyHeaders_xScroll || wo.stickyHeaders_attachTo || window),
  				$thead = $table.children('thead:first'),
  				$header = $thead.children('tr').not('.sticky-false').children(),
  				$tfoot = $table.children('tfoot'),
  				$stickyOffset = isNaN(wo.stickyHeaders_offset) ? $(wo.stickyHeaders_offset) : '',
  				stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0,
  				// is this table nested? If so, find parent sticky header wrapper (div, not table)
  				$nestedSticky = $table.parent().closest('.' + ts.css.table).hasClass('hasStickyHeaders') ?
  					$table.parent().closest('table.tablesorter')[0].config.widgetOptions.$sticky.parent() : [],
  				nestedStickyTop = $nestedSticky.length ? $nestedSticky.height() : 0,
  				// clone table, then wrap to make sticky header
  				$stickyTable = wo.$sticky = $table.clone()
  					.addClass('containsStickyHeaders ' + ts.css.sticky + ' ' + wo.stickyHeaders + ' ' + c.namespace.slice(1) + '_extra_table' )
  					.wrap('<div class="' + ts.css.stickyWrap + '">'),
  				$stickyWrap = $stickyTable.parent()
  					.addClass(ts.css.stickyHide)
  					.css({
  						position   : $attach.length ? 'absolute' : 'fixed',
  						padding    : parseInt( $stickyTable.parent().parent().css('padding-left'), 10 ),
  						top        : stickyOffset + nestedStickyTop,
  						left       : 0,
  						visibility : 'hidden',
  						zIndex     : wo.stickyHeaders_zIndex || 2
  					}),
  				$stickyThead = $stickyTable.children('thead:first'),
  				$stickyCells,
  				laststate = '',
  				spacing = 0,
  				setWidth = function($orig, $clone){
  					var index, width, border, $cell, $this,
  						$cells = $orig.filter(':visible'),
  						len = $cells.length;
  					for ( index = 0; index < len; index++ ) {
  						$cell = $clone.filter(':visible').eq(index);
  						$this = $cells.eq(index);
  						// code from https://github.com/jmosbech/StickyTableHeaders
  						if ($this.css('box-sizing') === 'border-box') {
  							width = $this.outerWidth();
  						} else {
  							if ($cell.css('border-collapse') === 'collapse') {
  								if (window.getComputedStyle) {
  									width = parseFloat( window.getComputedStyle($this[0], null).width );
  								} else {
  									// ie8 only
  									border = parseFloat( $this.css('border-width') );
  									width = $this.outerWidth() - parseFloat( $this.css('padding-left') ) - parseFloat( $this.css('padding-right') ) - border;
  								}
  							} else {
  								width = $this.width();
  							}
  						}
  						$cell.css({
  							'width': width,
  							'min-width': width,
  							'max-width': width
  						});
  					}
  				},
  				resizeHeader = function() {
  					stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0;
  					spacing = 0;
  					$stickyWrap.css({
  						left : $attach.length ? parseInt($attach.css('padding-left'), 10) || 0 :
  								$table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing,
  						width: $table.outerWidth()
  					});
  					setWidth( $table, $stickyTable );
  					setWidth( $header, $stickyCells );
  				},
  				scrollSticky = function( resizing ) {
  					if (!$table.is(':visible')) { return; } // fixes #278
  					// Detect nested tables - fixes #724
  					nestedStickyTop = $nestedSticky.length ? $nestedSticky.offset().top - $yScroll.scrollTop() + $nestedSticky.height() : 0;
  					var offset = $table.offset(),
  						yWindow = $.isWindow( $yScroll[0] ), // $.isWindow needs jQuery 1.4.3
  						xWindow = $.isWindow( $xScroll[0] ),
  						attachTop = $attach.length ?
  							( yWindow ? $yScroll.scrollTop() : $yScroll.offset().top ) :
  							$yScroll.scrollTop(),
  						captionHeight = wo.stickyHeaders_includeCaption ? 0 : $table.children( 'caption' ).height() || 0,
  						scrollTop = attachTop + stickyOffset + nestedStickyTop - captionHeight,
  						tableHeight = $table.height() - ($stickyWrap.height() + ($tfoot.height() || 0)) - captionHeight,
  						isVisible = ( scrollTop > offset.top ) && ( scrollTop < offset.top + tableHeight ) ? 'visible' : 'hidden',
  						cssSettings = { visibility : isVisible };
  					if ($attach.length) {
  						cssSettings.top = yWindow ? scrollTop - $attach.offset().top : $attach.scrollTop();
  					}
  					if (xWindow) {
  						// adjust when scrolling horizontally - fixes issue #143
  						cssSettings.left = $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing;
  					}
  					if ($nestedSticky.length) {
  						cssSettings.top = ( cssSettings.top || 0 ) + stickyOffset + nestedStickyTop;
  					}
  					$stickyWrap
  						.removeClass( ts.css.stickyVis + ' ' + ts.css.stickyHide )
  						.addClass( isVisible === 'visible' ? ts.css.stickyVis : ts.css.stickyHide )
  						.css(cssSettings);
  					if (isVisible !== laststate || resizing) {
  						// make sure the column widths match
  						resizeHeader();
  						laststate = isVisible;
  					}
  				};
  			// only add a position relative if a position isn't already defined
  			if ($attach.length && !$attach.css('position')) {
  				$attach.css('position', 'relative');
  			}
  			// fix clone ID, if it exists - fixes #271
  			if ($stickyTable.attr('id')) { $stickyTable[0].id += wo.stickyHeaders_cloneId; }
  			// clear out cloned table, except for sticky header
  			// include caption & filter row (fixes #126 & #249) - don't remove cells to get correct cell indexing
  			$stickyTable.find('thead:gt(0), tr.sticky-false').hide();
  			$stickyTable.find('tbody, tfoot').remove();
  			$stickyTable.find('caption').toggle(wo.stickyHeaders_includeCaption);
  			// issue #172 - find td/th in sticky header
  			$stickyCells = $stickyThead.children().children();
  			$stickyTable.css({ height:0, width:0, margin: 0 });
  			// remove resizable block
  			$stickyCells.find('.' + ts.css.resizer).remove();
  			// update sticky header class names to match real header after sorting
  			$table
  				.addClass('hasStickyHeaders')
  				.bind('pagerComplete' + namespace, function() {
  					resizeHeader();
  				});
  
  			ts.bindEvents(table, $stickyThead.children().children('.' + ts.css.header));
  
  			if (wo.stickyHeaders_appendTo) {
  				$(wo.stickyHeaders_appendTo).append( $stickyWrap );
  			} else {
  				// add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned.
  				$table.after( $stickyWrap );
  			}
  
  			// onRenderHeader is defined, we need to do something about it (fixes #641)
  			if (c.onRenderHeader) {
  				$t = $stickyThead.children('tr').children();
  				len = $t.length;
  				for ( index = 0; index < len; index++ ) {
  					// send second parameter
  					c.onRenderHeader.apply( $t.eq( index ), [ index, c, $stickyTable ] );
  				}
  			}
  
  			// make it sticky!
  			$xScroll.add($yScroll)
  				.unbind( ('scroll resize '.split(' ').join( namespace )).replace(/\s+/g, ' ') )
  				.bind('scroll resize '.split(' ').join( namespace ), function( event ) {
  					scrollSticky( event.type === 'resize' );
  				});
  			c.$table
  				.unbind('stickyHeadersUpdate' + namespace)
  				.bind('stickyHeadersUpdate' + namespace, function(){
  					scrollSticky( true );
  				});
  
  			if (wo.stickyHeaders_addResizeEvent) {
  				ts.addHeaderResizeEvent(table);
  			}
  
  			// look for filter widget
  			if ($table.hasClass('hasFilters') && wo.filter_columnFilters) {
  				// scroll table into view after filtering, if sticky header is active - #482
  				$table.bind('filterEnd' + namespace, function() {
  					// $(':focus') needs jQuery 1.6+
  					var $td = $(document.activeElement).closest('td'),
  						column = $td.parent().children().index($td);
  					// only scroll if sticky header is active
  					if ($stickyWrap.hasClass(ts.css.stickyVis) && wo.stickyHeaders_filteredToTop) {
  						// scroll to original table (not sticky clone)
  						window.scrollTo(0, $table.position().top);
  						// give same input/select focus; check if c.$filters exists; fixes #594
  						if (column >= 0 && c.$filters) {
  							c.$filters.eq(column).find('a, select, input').filter(':visible').focus();
  						}
  					}
  				});
  				ts.filter.bindSearch( $table, $stickyCells.find('.' + ts.css.filter) );
  				// support hideFilters
  				if (wo.filter_hideFilters) {
  					ts.filter.hideFilters(c, $stickyTable);
  				}
  			}
  
  			// resize table (Firefox)
  			if (wo.stickyHeaders_addResizeEvent) {
  				$table.bind('resize' + c.namespace + 'stickyheaders', function() {
  					resizeHeader();
  				});
  			}
  
  			$table.triggerHandler('stickyHeadersInit');
  
  		},
  		remove: function(table, c, wo) {
  			var namespace = c.namespace + 'stickyheaders ';
  			c.$table
  				.removeClass('hasStickyHeaders')
  				.unbind( ('pagerComplete resize filterEnd stickyHeadersUpdate '.split(' ').join(namespace)).replace(/\s+/g, ' ') )
  				.next('.' + ts.css.stickyWrap).remove();
  			if (wo.$sticky && wo.$sticky.length) { wo.$sticky.remove(); } // remove cloned table
  			$(window)
  				.add(wo.stickyHeaders_xScroll)
  				.add(wo.stickyHeaders_yScroll)
  				.add(wo.stickyHeaders_attachTo)
  				.unbind( ('scroll resize '.split(' ').join(namespace)).replace(/\s+/g, ' ') );
  			ts.addHeaderResizeEvent(table, true);
  		}
  	});
  
  })(jQuery, window);
  
  /*! Widget: resizable - updated 4/18/2017 (v2.28.8) */
  /*jshint browser:true, jquery:true, unused:false */
  ;(function ($, window) {
  	'use strict';
  	var ts = $.tablesorter || {};
  
  	$.extend(ts.css, {
  		resizableContainer : 'tablesorter-resizable-container',
  		resizableHandle    : 'tablesorter-resizable-handle',
  		resizableNoSelect  : 'tablesorter-disableSelection',
  		resizableStorage   : 'tablesorter-resizable'
  	});
  
  	// Add extra scroller css
  	$(function(){
  		var s = '<style>' +
  			'body.' + ts.css.resizableNoSelect + ' { -ms-user-select: none; -moz-user-select: -moz-none;' +
  				'-khtml-user-select: none; -webkit-user-select: none; user-select: none; }' +
  			'.' + ts.css.resizableContainer + ' { position: relative; height: 1px; }' +
  			// make handle z-index > than stickyHeader z-index, so the handle stays above sticky header
  			'.' + ts.css.resizableHandle + ' { position: absolute; display: inline-block; width: 8px;' +
  				'top: 1px; cursor: ew-resize; z-index: 3; user-select: none; -moz-user-select: none; }' +
  			'</style>';
  		$('head').append(s);
  	});
  
  	ts.resizable = {
  		init : function( c, wo ) {
  			if ( c.$table.hasClass( 'hasResizable' ) ) { return; }
  			c.$table.addClass( 'hasResizable' );
  
  			var noResize, $header, column, storedSizes, tmp,
  				$table = c.$table,
  				$parent = $table.parent(),
  				marginTop = parseInt( $table.css( 'margin-top' ), 10 ),
  
  			// internal variables
  			vars = wo.resizable_vars = {
  				useStorage : ts.storage && wo.resizable !== false,
  				$wrap : $parent,
  				mouseXPosition : 0,
  				$target : null,
  				$next : null,
  				overflow : $parent.css('overflow') === 'auto' ||
  					$parent.css('overflow') === 'scroll' ||
  					$parent.css('overflow-x') === 'auto' ||
  					$parent.css('overflow-x') === 'scroll',
  				storedSizes : []
  			};
  
  			// set default widths
  			ts.resizableReset( c.table, true );
  
  			// now get measurements!
  			vars.tableWidth = $table.width();
  			// attempt to autodetect
  			vars.fullWidth = Math.abs( $parent.width() - vars.tableWidth ) < 20;
  
  			/*
  			// Hacky method to determine if table width is set to 'auto'
  			// http://stackoverflow.com/a/20892048/145346
  			if ( !vars.fullWidth ) {
  				tmp = $table.width();
  				$header = $table.wrap('<span>').parent(); // temp variable
  				storedSizes = parseInt( $table.css( 'margin-left' ), 10 ) || 0;
  				$table.css( 'margin-left', storedSizes + 50 );
  				vars.tableWidth = $header.width() > tmp ? 'auto' : tmp;
  				$table.css( 'margin-left', storedSizes ? storedSizes : '' );
  				$header = null;
  				$table.unwrap('<span>');
  			}
  			*/
  
  			if ( vars.useStorage && vars.overflow ) {
  				// save table width
  				ts.storage( c.table, 'tablesorter-table-original-css-width', vars.tableWidth );
  				tmp = ts.storage( c.table, 'tablesorter-table-resized-width' ) || 'auto';
  				ts.resizable.setWidth( $table, tmp, true );
  			}
  			wo.resizable_vars.storedSizes = storedSizes = ( vars.useStorage ?
  				ts.storage( c.table, ts.css.resizableStorage ) :
  				[] ) || [];
  			ts.resizable.setWidths( c, wo, storedSizes );
  			ts.resizable.updateStoredSizes( c, wo );
  
  			wo.$resizable_container = $( '<div class="' + ts.css.resizableContainer + '">' )
  				.css({ top : marginTop })
  				.insertBefore( $table );
  			// add container
  			for ( column = 0; column < c.columns; column++ ) {
  				$header = c.$headerIndexed[ column ];
  				tmp = ts.getColumnData( c.table, c.headers, column );
  				noResize = ts.getData( $header, tmp, 'resizable' ) === 'false';
  				if ( !noResize ) {
  					$( '<div class="' + ts.css.resizableHandle + '">' )
  						.appendTo( wo.$resizable_container )
  						.attr({
  							'data-column' : column,
  							'unselectable' : 'on'
  						})
  						.data( 'header', $header )
  						.bind( 'selectstart', false );
  				}
  			}
  			ts.resizable.bindings( c, wo );
  		},
  
  		updateStoredSizes : function( c, wo ) {
  			var column, $header,
  				len = c.columns,
  				vars = wo.resizable_vars;
  			vars.storedSizes = [];
  			for ( column = 0; column < len; column++ ) {
  				$header = c.$headerIndexed[ column ];
  				vars.storedSizes[ column ] = $header.is(':visible') ? $header.width() : 0;
  			}
  		},
  
  		setWidth : function( $el, width, overflow ) {
  			// overflow tables need min & max width set as well
  			$el.css({
  				'width' : width,
  				'min-width' : overflow ? width : '',
  				'max-width' : overflow ? width : ''
  			});
  		},
  
  		setWidths : function( c, wo, storedSizes ) {
  			var column, $temp,
  				vars = wo.resizable_vars,
  				$extra = $( c.namespace + '_extra_headers' ),
  				$col = c.$table.children( 'colgroup' ).children( 'col' );
  			storedSizes = storedSizes || vars.storedSizes || [];
  			// process only if table ID or url match
  			if ( storedSizes.length ) {
  				for ( column = 0; column < c.columns; column++ ) {
  					// set saved resizable widths
  					ts.resizable.setWidth( c.$headerIndexed[ column ], storedSizes[ column ], vars.overflow );
  					if ( $extra.length ) {
  						// stickyHeaders needs to modify min & max width as well
  						$temp = $extra.eq( column ).add( $col.eq( column ) );
  						ts.resizable.setWidth( $temp, storedSizes[ column ], vars.overflow );
  					}
  				}
  				$temp = $( c.namespace + '_extra_table' );
  				if ( $temp.length && !ts.hasWidget( c.table, 'scroller' ) ) {
  					ts.resizable.setWidth( $temp, c.$table.outerWidth(), vars.overflow );
  				}
  			}
  		},
  
  		setHandlePosition : function( c, wo ) {
  			var startPosition,
  				tableHeight = c.$table.height(),
  				$handles = wo.$resizable_container.children(),
  				handleCenter = Math.floor( $handles.width() / 2 );
  
  			if ( ts.hasWidget( c.table, 'scroller' ) ) {
  				tableHeight = 0;
  				c.$table.closest( '.' + ts.css.scrollerWrap ).children().each(function(){
  					var $this = $(this);
  					// center table has a max-height set
  					tableHeight += $this.filter('[style*="height"]').length ? $this.height() : $this.children('table').height();
  				});
  			}
  
  			if ( !wo.resizable_includeFooter && c.$table.children('tfoot').length ) {
  				tableHeight -= c.$table.children('tfoot').height();
  			}
  			// subtract out table left position from resizable handles. Fixes #864
  			startPosition = c.$table.position().left;
  			$handles.each( function() {
  				var $this = $(this),
  					column = parseInt( $this.attr( 'data-column' ), 10 ),
  					columns = c.columns - 1,
  					$header = $this.data( 'header' );
  				if ( !$header ) { return; } // see #859
  				if ( !$header.is(':visible') ) {
  					$this.hide();
  				} else if ( column < columns || column === columns && wo.resizable_addLastColumn ) {
  					$this.css({
  						display: 'inline-block',
  						height : tableHeight,
  						left : $header.position().left - startPosition + $header.outerWidth() - handleCenter
  					});
  				}
  			});
  		},
  
  		// prevent text selection while dragging resize bar
  		toggleTextSelection : function( c, wo, toggle ) {
  			var namespace = c.namespace + 'tsresize';
  			wo.resizable_vars.disabled = toggle;
  			$( 'body' ).toggleClass( ts.css.resizableNoSelect, toggle );
  			if ( toggle ) {
  				$( 'body' )
  					.attr( 'unselectable', 'on' )
  					.bind( 'selectstart' + namespace, false );
  			} else {
  				$( 'body' )
  					.removeAttr( 'unselectable' )
  					.unbind( 'selectstart' + namespace );
  			}
  		},
  
  		bindings : function( c, wo ) {
  			var namespace = c.namespace + 'tsresize';
  			wo.$resizable_container.children().bind( 'mousedown', function( event ) {
  				// save header cell and mouse position
  				var column,
  					vars = wo.resizable_vars,
  					$extras = $( c.namespace + '_extra_headers' ),
  					$header = $( event.target ).data( 'header' );
  
  				column = parseInt( $header.attr( 'data-column' ), 10 );
  				vars.$target = $header = $header.add( $extras.filter('[data-column="' + column + '"]') );
  				vars.target = column;
  
  				// if table is not as wide as it's parent, then resize the table
  				vars.$next = event.shiftKey || wo.resizable_targetLast ?
  					$header.parent().children().not( '.resizable-false' ).filter( ':last' ) :
  					$header.nextAll( ':not(.resizable-false)' ).eq( 0 );
  
  				column = parseInt( vars.$next.attr( 'data-column' ), 10 );
  				vars.$next = vars.$next.add( $extras.filter('[data-column="' + column + '"]') );
  				vars.next = column;
  
  				vars.mouseXPosition = event.pageX;
  				ts.resizable.updateStoredSizes( c, wo );
  				ts.resizable.toggleTextSelection(c, wo, true );
  			});
  
  			$( document )
  				.bind( 'mousemove' + namespace, function( event ) {
  					var vars = wo.resizable_vars;
  					// ignore mousemove if no mousedown
  					if ( !vars.disabled || vars.mouseXPosition === 0 || !vars.$target ) { return; }
  					if ( wo.resizable_throttle ) {
  						clearTimeout( vars.timer );
  						vars.timer = setTimeout( function() {
  							ts.resizable.mouseMove( c, wo, event );
  						}, isNaN( wo.resizable_throttle ) ? 5 : wo.resizable_throttle );
  					} else {
  						ts.resizable.mouseMove( c, wo, event );
  					}
  				})
  				.bind( 'mouseup' + namespace, function() {
  					if (!wo.resizable_vars.disabled) { return; }
  					ts.resizable.toggleTextSelection( c, wo, false );
  					ts.resizable.stopResize( c, wo );
  					ts.resizable.setHandlePosition( c, wo );
  				});
  
  			// resizeEnd event triggered by scroller widget
  			$( window ).bind( 'resize' + namespace + ' resizeEnd' + namespace, function() {
  				ts.resizable.setHandlePosition( c, wo );
  			});
  
  			// right click to reset columns to default widths
  			c.$table
  				.bind( 'columnUpdate pagerComplete resizableUpdate '.split( ' ' ).join( namespace + ' ' ), function() {
  					ts.resizable.setHandlePosition( c, wo );
  				})
  				.bind( 'resizableReset' + namespace, function() {
  					ts.resizableReset( c.table );
  				})
  				.find( 'thead:first' )
  				.add( $( c.namespace + '_extra_table' ).find( 'thead:first' ) )
  				.bind( 'contextmenu' + namespace, function() {
  					// $.isEmptyObject() needs jQuery 1.4+; allow right click if already reset
  					var allowClick = wo.resizable_vars.storedSizes.length === 0;
  					ts.resizableReset( c.table );
  					ts.resizable.setHandlePosition( c, wo );
  					wo.resizable_vars.storedSizes = [];
  					return allowClick;
  				});
  
  		},
  
  		mouseMove : function( c, wo, event ) {
  			if ( wo.resizable_vars.mouseXPosition === 0 || !wo.resizable_vars.$target ) { return; }
  			// resize columns
  			var column,
  				total = 0,
  				vars = wo.resizable_vars,
  				$next = vars.$next,
  				tar = vars.storedSizes[ vars.target ],
  				leftEdge = event.pageX - vars.mouseXPosition;
  			if ( vars.overflow ) {
  				if ( tar + leftEdge > 0 ) {
  					vars.storedSizes[ vars.target ] += leftEdge;
  					ts.resizable.setWidth( vars.$target, vars.storedSizes[ vars.target ], true );
  					// update the entire table width
  					for ( column = 0; column < c.columns; column++ ) {
  						total += vars.storedSizes[ column ];
  					}
  					ts.resizable.setWidth( c.$table.add( $( c.namespace + '_extra_table' ) ), total );
  				}
  				if ( !$next.length ) {
  					// if expanding right-most column, scroll the wrapper
  					vars.$wrap[0].scrollLeft = c.$table.width();
  				}
  			} else if ( vars.fullWidth ) {
  				vars.storedSizes[ vars.target ] += leftEdge;
  				vars.storedSizes[ vars.next ] -= leftEdge;
  				ts.resizable.setWidths( c, wo );
  			} else {
  				vars.storedSizes[ vars.target ] += leftEdge;
  				ts.resizable.setWidths( c, wo );
  			}
  			vars.mouseXPosition = event.pageX;
  			// dynamically update sticky header widths
  			c.$table.triggerHandler('stickyHeadersUpdate');
  		},
  
  		stopResize : function( c, wo ) {
  			var vars = wo.resizable_vars;
  			ts.resizable.updateStoredSizes( c, wo );
  			if ( vars.useStorage ) {
  				// save all column widths
  				ts.storage( c.table, ts.css.resizableStorage, vars.storedSizes );
  				ts.storage( c.table, 'tablesorter-table-resized-width', c.$table.width() );
  			}
  			vars.mouseXPosition = 0;
  			vars.$target = vars.$next = null;
  			// will update stickyHeaders, just in case, see #912
  			c.$table.triggerHandler('stickyHeadersUpdate');
  		}
  	};
  
  	// this widget saves the column widths if
  	// $.tablesorter.storage function is included
  	// **************************
  	ts.addWidget({
  		id: 'resizable',
  		priority: 40,
  		options: {
  			resizable : true, // save column widths to storage
  			resizable_addLastColumn : false,
  			resizable_includeFooter: true,
  			resizable_widths : [],
  			resizable_throttle : false, // set to true (5ms) or any number 0-10 range
  			resizable_targetLast : false
  		},
  		init: function(table, thisWidget, c, wo) {
  			ts.resizable.init( c, wo );
  		},
  		format: function( table, c, wo ) {
  			ts.resizable.setHandlePosition( c, wo );
  		},
  		remove: function( table, c, wo, refreshing ) {
  			if (wo.$resizable_container) {
  				var namespace = c.namespace + 'tsresize';
  				c.$table.add( $( c.namespace + '_extra_table' ) )
  					.removeClass('hasResizable')
  					.children( 'thead' )
  					.unbind( 'contextmenu' + namespace );
  
  				wo.$resizable_container.remove();
  				ts.resizable.toggleTextSelection( c, wo, false );
  				ts.resizableReset( table, refreshing );
  				$( document ).unbind( 'mousemove' + namespace + ' mouseup' + namespace );
  			}
  		}
  	});
  
  	ts.resizableReset = function( table, refreshing ) {
  		$( table ).each(function(){
  			var index, $t,
  				c = this.config,
  				wo = c && c.widgetOptions,
  				vars = wo.resizable_vars;
  			if ( table && c && c.$headerIndexed.length ) {
  				// restore the initial table width
  				if ( vars.overflow && vars.tableWidth ) {
  					ts.resizable.setWidth( c.$table, vars.tableWidth, true );
  					if ( vars.useStorage ) {
  						ts.storage( table, 'tablesorter-table-resized-width', 'auto' );
  					}
  				}
  				for ( index = 0; index < c.columns; index++ ) {
  					$t = c.$headerIndexed[ index ];
  					if ( wo.resizable_widths && wo.resizable_widths[ index ] ) {
  						ts.resizable.setWidth( $t, wo.resizable_widths[ index ], vars.overflow );
  					} else if ( !$t.hasClass( 'resizable-false' ) ) {
  						// don't clear the width of any column that is not resizable
  						ts.resizable.setWidth( $t, '', vars.overflow );
  					}
  				}
  
  				// reset stickyHeader widths
  				c.$table.triggerHandler( 'stickyHeadersUpdate' );
  				if ( ts.storage && !refreshing ) {
  					ts.storage( this, ts.css.resizableStorage, {} );
  				}
  			}
  		});
  	};
  
  })( jQuery, window );
  
  /*! Widget: saveSort - updated 10/31/2015 (v2.24.0) *//*
  * Requires tablesorter v2.16+
  * by Rob Garrison
  */
  ;(function ($) {
  	'use strict';
  	var ts = $.tablesorter || {};
  
  	// this widget saves the last sort only if the
  	// saveSort widget option is true AND the
  	// $.tablesorter.storage function is included
  	// **************************
  	ts.addWidget({
  		id: 'saveSort',
  		priority: 20,
  		options: {
  			saveSort : true
  		},
  		init: function(table, thisWidget, c, wo) {
  			// run widget format before all other widgets are applied to the table
  			thisWidget.format(table, c, wo, true);
  		},
  		format: function(table, c, wo, init) {
  			var stored, time,
  				$table = c.$table,
  				saveSort = wo.saveSort !== false, // make saveSort active/inactive; default to true
  				sortList = { 'sortList' : c.sortList };
  			if (c.debug) {
  				time = new Date();
  			}
  			if ($table.hasClass('hasSaveSort')) {
  				if (saveSort && table.hasInitialized && ts.storage) {
  					ts.storage( table, 'tablesorter-savesort', sortList );
  					if (c.debug) {
  						console.log('saveSort widget: Saving last sort: ' + c.sortList + ts.benchmark(time));
  					}
  				}
  			} else {
  				// set table sort on initial run of the widget
  				$table.addClass('hasSaveSort');
  				sortList = '';
  				// get data
  				if (ts.storage) {
  					stored = ts.storage( table, 'tablesorter-savesort' );
  					sortList = (stored && stored.hasOwnProperty('sortList') && $.isArray(stored.sortList)) ? stored.sortList : '';
  					if (c.debug) {
  						console.log('saveSort: Last sort loaded: "' + sortList + '"' + ts.benchmark(time));
  					}
  					$table.bind('saveSortReset', function(event) {
  						event.stopPropagation();
  						ts.storage( table, 'tablesorter-savesort', '' );
  					});
  				}
  				// init is true when widget init is run, this will run this widget before all other widgets have initialized
  				// this method allows using this widget in the original tablesorter plugin; but then it will run all widgets twice.
  				if (init && sortList && sortList.length > 0) {
  					c.sortList = sortList;
  				} else if (table.hasInitialized && sortList && sortList.length > 0) {
  					// update sort change
  					ts.sortOn( c, sortList );
  				}
  			}
  		},
  		remove: function(table, c) {
  			c.$table.removeClass('hasSaveSort');
  			// clear storage
  			if (ts.storage) { ts.storage( table, 'tablesorter-savesort', '' ); }
  		}
  	});
  
  })(jQuery);
  
  return jQuery.tablesorter;
  }));
  
  
  
  /*
  Turbolinks 5.0.0
  Copyright © 2016 Basecamp, LLC
   */
  
  (function(){(function(){(function(){this.Turbolinks={supported:function(){return null!=window.history.pushState&&null!=window.requestAnimationFrame}(),visit:function(e,r){return t.controller.visit(e,r)},clearCache:function(){return t.controller.clearCache()}}}).call(this)}).call(this);var t=this.Turbolinks;(function(){(function(){var e,r;t.copyObject=function(t){var e,r,n;r={};for(e in t)n=t[e],r[e]=n;return r},t.closest=function(t,r){return e.call(t,r)},e=function(){var t,e;return t=document.documentElement,null!=(e=t.closest)?e:function(t){var e;for(e=this;e;){if(e.nodeType===Node.ELEMENT_NODE&&r.call(e,t))return e;e=e.parentNode}}}(),t.defer=function(t){return setTimeout(t,1)},t.dispatch=function(t,e){var r,n,o,i,s;return i=null!=e?e:{},s=i.target,r=i.cancelable,n=i.data,o=document.createEvent("Events"),o.initEvent(t,!0,r===!0),o.data=null!=n?n:{},(null!=s?s:document).dispatchEvent(o),o},t.match=function(t,e){return r.call(t,e)},r=function(){var t,e,r,n;return t=document.documentElement,null!=(e=null!=(r=null!=(n=t.matchesSelector)?n:t.webkitMatchesSelector)?r:t.msMatchesSelector)?e:t.mozMatchesSelector}(),t.uuid=function(){var t,e,r;for(r="",t=e=1;36>=e;t=++e)r+=9===t||14===t||19===t||24===t?"-":15===t?"4":20===t?(Math.floor(4*Math.random())+8).toString(16):Math.floor(15*Math.random()).toString(16);return r}}).call(this),function(){t.Location=function(){function t(t){var e,r;null==t&&(t=""),r=document.createElement("a"),r.href=t.toString(),this.absoluteURL=r.href,e=r.hash.length,2>e?this.requestURL=this.absoluteURL:(this.requestURL=this.absoluteURL.slice(0,-e),this.anchor=r.hash.slice(1))}var e,r,n,o;return t.wrap=function(t){return t instanceof this?t:new this(t)},t.prototype.getOrigin=function(){return this.absoluteURL.split("/",3).join("/")},t.prototype.getPath=function(){var t,e;return null!=(t=null!=(e=this.absoluteURL.match(/\/\/[^\/]*(\/[^?;]*)/))?e[1]:void 0)?t:"/"},t.prototype.getPathComponents=function(){return this.getPath().split("/").slice(1)},t.prototype.getLastPathComponent=function(){return this.getPathComponents().slice(-1)[0]},t.prototype.getExtension=function(){var t,e;return null!=(t=null!=(e=this.getLastPathComponent().match(/\.[^.]*$/))?e[0]:void 0)?t:""},t.prototype.isHTML=function(){return this.getExtension().match(/^(?:|\.(?:htm|html|xhtml))$/)},t.prototype.isPrefixedBy=function(t){var e;return e=r(t),this.isEqualTo(t)||o(this.absoluteURL,e)},t.prototype.isEqualTo=function(t){return this.absoluteURL===(null!=t?t.absoluteURL:void 0)},t.prototype.toCacheKey=function(){return this.requestURL},t.prototype.toJSON=function(){return this.absoluteURL},t.prototype.toString=function(){return this.absoluteURL},t.prototype.valueOf=function(){return this.absoluteURL},r=function(t){return e(t.getOrigin()+t.getPath())},e=function(t){return n(t,"/")?t:t+"/"},o=function(t,e){return t.slice(0,e.length)===e},n=function(t,e){return t.slice(-e.length)===e},t}()}.call(this),function(){var e=function(t,e){return function(){return t.apply(e,arguments)}};t.HttpRequest=function(){function r(r,n,o){this.delegate=r,this.requestCanceled=e(this.requestCanceled,this),this.requestTimedOut=e(this.requestTimedOut,this),this.requestFailed=e(this.requestFailed,this),this.requestLoaded=e(this.requestLoaded,this),this.requestProgressed=e(this.requestProgressed,this),this.url=t.Location.wrap(n).requestURL,this.referrer=t.Location.wrap(o).absoluteURL,this.createXHR()}return r.NETWORK_FAILURE=0,r.TIMEOUT_FAILURE=-1,r.timeout=60,r.prototype.send=function(){var t;return this.xhr&&!this.sent?(this.notifyApplicationBeforeRequestStart(),this.setProgress(0),this.xhr.send(),this.sent=!0,"function"==typeof(t=this.delegate).requestStarted?t.requestStarted():void 0):void 0},r.prototype.cancel=function(){return this.xhr&&this.sent?this.xhr.abort():void 0},r.prototype.requestProgressed=function(t){return t.lengthComputable?this.setProgress(t.loaded/t.total):void 0},r.prototype.requestLoaded=function(){return this.endRequest(function(t){return function(){var e;return 200<=(e=t.xhr.status)&&300>e?t.delegate.requestCompletedWithResponse(t.xhr.responseText,t.xhr.getResponseHeader("Turbolinks-Location")):(t.failed=!0,t.delegate.requestFailedWithStatusCode(t.xhr.status,t.xhr.responseText))}}(this))},r.prototype.requestFailed=function(){return this.endRequest(function(t){return function(){return t.failed=!0,t.delegate.requestFailedWithStatusCode(t.constructor.NETWORK_FAILURE)}}(this))},r.prototype.requestTimedOut=function(){return this.endRequest(function(t){return function(){return t.failed=!0,t.delegate.requestFailedWithStatusCode(t.constructor.TIMEOUT_FAILURE)}}(this))},r.prototype.requestCanceled=function(){return this.endRequest()},r.prototype.notifyApplicationBeforeRequestStart=function(){return t.dispatch("turbolinks:request-start",{data:{url:this.url,xhr:this.xhr}})},r.prototype.notifyApplicationAfterRequestEnd=function(){return t.dispatch("turbolinks:request-end",{data:{url:this.url,xhr:this.xhr}})},r.prototype.createXHR=function(){return this.xhr=new XMLHttpRequest,this.xhr.open("GET",this.url,!0),this.xhr.timeout=1e3*this.constructor.timeout,this.xhr.setRequestHeader("Accept","text/html, application/xhtml+xml"),this.xhr.setRequestHeader("Turbolinks-Referrer",this.referrer),this.xhr.onprogress=this.requestProgressed,this.xhr.onload=this.requestLoaded,this.xhr.onerror=this.requestFailed,this.xhr.ontimeout=this.requestTimedOut,this.xhr.onabort=this.requestCanceled},r.prototype.endRequest=function(t){return this.xhr?(this.notifyApplicationAfterRequestEnd(),null!=t&&t.call(this),this.destroy()):void 0},r.prototype.setProgress=function(t){var e;return this.progress=t,"function"==typeof(e=this.delegate).requestProgressed?e.requestProgressed(this.progress):void 0},r.prototype.destroy=function(){var t;return this.setProgress(1),"function"==typeof(t=this.delegate).requestFinished&&t.requestFinished(),this.delegate=null,this.xhr=null},r}()}.call(this),function(){var e=function(t,e){return function(){return t.apply(e,arguments)}};t.ProgressBar=function(){function t(){this.trickle=e(this.trickle,this),this.stylesheetElement=this.createStylesheetElement(),this.progressElement=this.createProgressElement()}var r;return r=300,t.defaultCSS=".turbolinks-progress-bar {\n  position: fixed;\n  display: block;\n  top: 0;\n  left: 0;\n  height: 3px;\n  background: #0076ff;\n  z-index: 9999;\n  transition: width "+r+"ms ease-out, opacity "+r/2+"ms "+r/2+"ms ease-in;\n  transform: translate3d(0, 0, 0);\n}",t.prototype.show=function(){return this.visible?void 0:(this.visible=!0,this.installStylesheetElement(),this.installProgressElement(),this.startTrickling())},t.prototype.hide=function(){return this.visible&&!this.hiding?(this.hiding=!0,this.fadeProgressElement(function(t){return function(){return t.uninstallProgressElement(),t.stopTrickling(),t.visible=!1,t.hiding=!1}}(this))):void 0},t.prototype.setValue=function(t){return this.value=t,this.refresh()},t.prototype.installStylesheetElement=function(){return document.head.insertBefore(this.stylesheetElement,document.head.firstChild)},t.prototype.installProgressElement=function(){return this.progressElement.style.width=0,this.progressElement.style.opacity=1,document.documentElement.insertBefore(this.progressElement,document.body),this.refresh()},t.prototype.fadeProgressElement=function(t){return this.progressElement.style.opacity=0,setTimeout(t,1.5*r)},t.prototype.uninstallProgressElement=function(){return this.progressElement.parentNode?document.documentElement.removeChild(this.progressElement):void 0},t.prototype.startTrickling=function(){return null!=this.trickleInterval?this.trickleInterval:this.trickleInterval=setInterval(this.trickle,r)},t.prototype.stopTrickling=function(){return clearInterval(this.trickleInterval),this.trickleInterval=null},t.prototype.trickle=function(){return this.setValue(this.value+Math.random()/100)},t.prototype.refresh=function(){return requestAnimationFrame(function(t){return function(){return t.progressElement.style.width=10+90*t.value+"%"}}(this))},t.prototype.createStylesheetElement=function(){var t;return t=document.createElement("style"),t.type="text/css",t.textContent=this.constructor.defaultCSS,t},t.prototype.createProgressElement=function(){var t;return t=document.createElement("div"),t.className="turbolinks-progress-bar",t},t}()}.call(this),function(){var e=function(t,e){return function(){return t.apply(e,arguments)}};t.BrowserAdapter=function(){function r(r){this.controller=r,this.showProgressBar=e(this.showProgressBar,this),this.progressBar=new t.ProgressBar}var n,o,i,s;return s=t.HttpRequest,n=s.NETWORK_FAILURE,i=s.TIMEOUT_FAILURE,o=500,r.prototype.visitProposedToLocationWithAction=function(t,e){return this.controller.startVisitToLocationWithAction(t,e)},r.prototype.visitStarted=function(t){return t.issueRequest(),t.changeHistory(),t.loadCachedSnapshot()},r.prototype.visitRequestStarted=function(t){return this.progressBar.setValue(0),t.hasCachedSnapshot()||"restore"!==t.action?this.showProgressBarAfterDelay():this.showProgressBar()},r.prototype.visitRequestProgressed=function(t){return this.progressBar.setValue(t.progress)},r.prototype.visitRequestCompleted=function(t){return t.loadResponse()},r.prototype.visitRequestFailedWithStatusCode=function(t,e){switch(e){case n:case i:return this.reload();default:return t.loadResponse()}},r.prototype.visitRequestFinished=function(t){return this.hideProgressBar()},r.prototype.visitCompleted=function(t){return t.followRedirect()},r.prototype.pageInvalidated=function(){return this.reload()},r.prototype.showProgressBarAfterDelay=function(){return this.progressBarTimeout=setTimeout(this.showProgressBar,o)},r.prototype.showProgressBar=function(){return this.progressBar.show()},r.prototype.hideProgressBar=function(){return this.progressBar.hide(),clearTimeout(this.progressBarTimeout)},r.prototype.reload=function(){return window.location.reload()},r}()}.call(this),function(){var e,r=function(t,e){return function(){return t.apply(e,arguments)}};e=!1,addEventListener("load",function(){return t.defer(function(){return e=!0})},!1),t.History=function(){function n(t){this.delegate=t,this.onPopState=r(this.onPopState,this)}return n.prototype.start=function(){return this.started?void 0:(addEventListener("popstate",this.onPopState,!1),this.started=!0)},n.prototype.stop=function(){return this.started?(removeEventListener("popstate",this.onPopState,!1),this.started=!1):void 0},n.prototype.push=function(e,r){return e=t.Location.wrap(e),this.update("push",e,r)},n.prototype.replace=function(e,r){return e=t.Location.wrap(e),this.update("replace",e,r)},n.prototype.onPopState=function(e){var r,n,o,i;return this.shouldHandlePopState()&&(i=null!=(n=e.state)?n.turbolinks:void 0)?(r=t.Location.wrap(window.location),o=i.restorationIdentifier,this.delegate.historyPoppedToLocationWithRestorationIdentifier(r,o)):void 0},n.prototype.shouldHandlePopState=function(){return e===!0},n.prototype.update=function(t,e,r){var n;return n={turbolinks:{restorationIdentifier:r}},history[t+"State"](n,null,e)},n}()}.call(this),function(){t.Snapshot=function(){function e(t){var e,r;r=t.head,e=t.body,this.head=null!=r?r:document.createElement("head"),this.body=null!=e?e:document.createElement("body")}return e.wrap=function(t){return t instanceof this?t:this.fromHTML(t)},e.fromHTML=function(t){var e;return e=document.createElement("html"),e.innerHTML=t,this.fromElement(e)},e.fromElement=function(t){return new this({head:t.querySelector("head"),body:t.querySelector("body")})},e.prototype.clone=function(){return new e({head:this.head.cloneNode(!0),body:this.body.cloneNode(!0)})},e.prototype.getRootLocation=function(){var e,r;return r=null!=(e=this.getSetting("root"))?e:"/",new t.Location(r)},e.prototype.getCacheControlValue=function(){return this.getSetting("cache-control")},e.prototype.hasAnchor=function(t){try{return null!=this.body.querySelector("[id='"+t+"']")}catch(e){}},e.prototype.isPreviewable=function(){return"no-preview"!==this.getCacheControlValue()},e.prototype.isCacheable=function(){return"no-cache"!==this.getCacheControlValue()},e.prototype.getSetting=function(t){var e,r;return r=this.head.querySelectorAll("meta[name='turbolinks-"+t+"']"),e=r[r.length-1],null!=e?e.getAttribute("content"):void 0},e}()}.call(this),function(){var e=[].slice;t.Renderer=function(){function t(){}var r;return t.render=function(){var t,r,n,o;return n=arguments[0],r=arguments[1],t=3<=arguments.length?e.call(arguments,2):[],o=function(t,e,r){r.prototype=t.prototype;var n=new r,o=t.apply(n,e);return Object(o)===o?o:n}(this,t,function(){}),o.delegate=n,o.render(r),o},t.prototype.renderView=function(t){return this.delegate.viewWillRender(this.newBody),t(),this.delegate.viewRendered(this.newBody)},t.prototype.invalidateView=function(){return this.delegate.viewInvalidated()},t.prototype.createScriptElement=function(t){var e;return"false"===t.getAttribute("data-turbolinks-eval")?t:(e=document.createElement("script"),e.textContent=t.textContent,r(e,t),e)},r=function(t,e){var r,n,o,i,s,a,u;for(i=e.attributes,a=[],r=0,n=i.length;n>r;r++)s=i[r],o=s.name,u=s.value,a.push(t.setAttribute(o,u));return a},t}()}.call(this),function(){t.HeadDetails=function(){function t(t){var e,r,i,s,a,u,c;for(this.element=t,this.elements={},c=this.element.childNodes,s=0,u=c.length;u>s;s++)i=c[s],i.nodeType===Node.ELEMENT_NODE&&(a=i.outerHTML,r=null!=(e=this.elements)[a]?e[a]:e[a]={type:o(i),tracked:n(i),elements:[]},r.elements.push(i))}var e,r,n,o;return t.prototype.hasElementWithKey=function(t){return t in this.elements},t.prototype.getTrackedElementSignature=function(){var t,e;return function(){var r,n;r=this.elements,n=[];for(t in r)e=r[t].tracked,e&&n.push(t);return n}.call(this).join("")},t.prototype.getScriptElementsNotInDetails=function(t){return this.getElementsMatchingTypeNotInDetails("script",t)},t.prototype.getStylesheetElementsNotInDetails=function(t){return this.getElementsMatchingTypeNotInDetails("stylesheet",t)},t.prototype.getElementsMatchingTypeNotInDetails=function(t,e){var r,n,o,i,s,a;o=this.elements,s=[];for(n in o)i=o[n],a=i.type,r=i.elements,a!==t||e.hasElementWithKey(n)||s.push(r[0]);return s},t.prototype.getProvisionalElements=function(){var t,e,r,n,o,i,s;r=[],n=this.elements;for(e in n)o=n[e],s=o.type,i=o.tracked,t=o.elements,null!=s||i?t.length>1&&r.push.apply(r,t.slice(1)):r.push.apply(r,t);return r},o=function(t){return e(t)?"script":r(t)?"stylesheet":void 0},n=function(t){return"reload"===t.getAttribute("data-turbolinks-track")},e=function(t){var e;return e=t.tagName.toLowerCase(),"script"===e},r=function(t){var e;return e=t.tagName.toLowerCase(),"style"===e||"link"===e&&"stylesheet"===t.getAttribute("rel")},t}()}.call(this),function(){var e=function(t,e){function n(){this.constructor=t}for(var o in e)r.call(e,o)&&(t[o]=e[o]);return n.prototype=e.prototype,t.prototype=new n,t.__super__=e.prototype,t},r={}.hasOwnProperty;t.SnapshotRenderer=function(r){function n(e,r){this.currentSnapshot=e,this.newSnapshot=r,this.currentHeadDetails=new t.HeadDetails(this.currentSnapshot.head),this.newHeadDetails=new t.HeadDetails(this.newSnapshot.head),this.newBody=this.newSnapshot.body}return e(n,r),n.prototype.render=function(t){return this.trackedElementsAreIdentical()?(this.mergeHead(),this.renderView(function(e){return function(){return e.replaceBody(),e.focusFirstAutofocusableElement(),t()}}(this))):this.invalidateView()},n.prototype.mergeHead=function(){return this.copyNewHeadStylesheetElements(),this.copyNewHeadScriptElements(),this.removeCurrentHeadProvisionalElements(),this.copyNewHeadProvisionalElements()},n.prototype.replaceBody=function(){return this.activateBodyScriptElements(),this.importBodyPermanentElements(),this.assignNewBody()},n.prototype.trackedElementsAreIdentical=function(){return this.currentHeadDetails.getTrackedElementSignature()===this.newHeadDetails.getTrackedElementSignature()},n.prototype.copyNewHeadStylesheetElements=function(){var t,e,r,n,o;for(n=this.getNewHeadStylesheetElements(),o=[],e=0,r=n.length;r>e;e++)t=n[e],o.push(document.head.appendChild(t));return o},n.prototype.copyNewHeadScriptElements=function(){var t,e,r,n,o;for(n=this.getNewHeadScriptElements(),o=[],e=0,r=n.length;r>e;e++)t=n[e],o.push(document.head.appendChild(this.createScriptElement(t)));return o},n.prototype.removeCurrentHeadProvisionalElements=function(){var t,e,r,n,o;for(n=this.getCurrentHeadProvisionalElements(),o=[],e=0,r=n.length;r>e;e++)t=n[e],o.push(document.head.removeChild(t));return o},n.prototype.copyNewHeadProvisionalElements=function(){var t,e,r,n,o;for(n=this.getNewHeadProvisionalElements(),o=[],e=0,r=n.length;r>e;e++)t=n[e],o.push(document.head.appendChild(t));return o},n.prototype.importBodyPermanentElements=function(){var t,e,r,n,o,i;for(n=this.getNewBodyPermanentElements(),i=[],e=0,r=n.length;r>e;e++)o=n[e],(t=this.findCurrentBodyPermanentElement(o))?i.push(o.parentNode.replaceChild(t,o)):i.push(void 0);return i},n.prototype.activateBodyScriptElements=function(){var t,e,r,n,o,i;for(n=this.getNewBodyScriptElements(),i=[],e=0,r=n.length;r>e;e++)o=n[e],t=this.createScriptElement(o),i.push(o.parentNode.replaceChild(t,o));return i},n.prototype.assignNewBody=function(){return document.body=this.newBody},n.prototype.focusFirstAutofocusableElement=function(){var t;return null!=(t=this.findFirstAutofocusableElement())?t.focus():void 0},n.prototype.getNewHeadStylesheetElements=function(){return this.newHeadDetails.getStylesheetElementsNotInDetails(this.currentHeadDetails)},n.prototype.getNewHeadScriptElements=function(){return this.newHeadDetails.getScriptElementsNotInDetails(this.currentHeadDetails)},n.prototype.getCurrentHeadProvisionalElements=function(){return this.currentHeadDetails.getProvisionalElements()},n.prototype.getNewHeadProvisionalElements=function(){return this.newHeadDetails.getProvisionalElements()},n.prototype.getNewBodyPermanentElements=function(){return this.newBody.querySelectorAll("[id][data-turbolinks-permanent]")},n.prototype.findCurrentBodyPermanentElement=function(t){return document.body.querySelector("#"+t.id+"[data-turbolinks-permanent]")},n.prototype.getNewBodyScriptElements=function(){return this.newBody.querySelectorAll("script")},n.prototype.findFirstAutofocusableElement=function(){return document.body.querySelector("[autofocus]")},n}(t.Renderer)}.call(this),function(){var e=function(t,e){function n(){this.constructor=t}for(var o in e)r.call(e,o)&&(t[o]=e[o]);return n.prototype=e.prototype,t.prototype=new n,t.__super__=e.prototype,t},r={}.hasOwnProperty;t.ErrorRenderer=function(t){function r(t){this.html=t}return e(r,t),r.prototype.render=function(t){return this.renderView(function(e){return function(){return e.replaceDocumentHTML(),e.activateBodyScriptElements(),t()}}(this))},r.prototype.replaceDocumentHTML=function(){return document.documentElement.innerHTML=this.html},r.prototype.activateBodyScriptElements=function(){var t,e,r,n,o,i;for(n=this.getScriptElements(),i=[],e=0,r=n.length;r>e;e++)o=n[e],t=this.createScriptElement(o),i.push(o.parentNode.replaceChild(t,o));return i},r.prototype.getScriptElements=function(){return document.documentElement.querySelectorAll("script")},r}(t.Renderer)}.call(this),function(){t.View=function(){function e(t){this.delegate=t,this.element=document.documentElement}return e.prototype.getRootLocation=function(){return this.getSnapshot().getRootLocation()},e.prototype.getSnapshot=function(){return t.Snapshot.fromElement(this.element)},e.prototype.render=function(t,e){var r,n,o;return o=t.snapshot,r=t.error,n=t.isPreview,this.markAsPreview(n),null!=o?this.renderSnapshot(o,e):this.renderError(r,e)},e.prototype.markAsPreview=function(t){return t?this.element.setAttribute("data-turbolinks-preview",""):this.element.removeAttribute("data-turbolinks-preview")},e.prototype.renderSnapshot=function(e,r){return t.SnapshotRenderer.render(this.delegate,r,this.getSnapshot(),t.Snapshot.wrap(e))},e.prototype.renderError=function(e,r){return t.ErrorRenderer.render(this.delegate,r,e)},e}()}.call(this),function(){var e=function(t,e){return function(){return t.apply(e,arguments)}};t.ScrollManager=function(){function t(t){this.delegate=t,this.onScroll=e(this.onScroll,this)}return t.prototype.start=function(){return this.started?void 0:(addEventListener("scroll",this.onScroll,!1),this.onScroll(),this.started=!0)},t.prototype.stop=function(){return this.started?(removeEventListener("scroll",this.onScroll,!1),this.started=!1):void 0},t.prototype.scrollToElement=function(t){return t.scrollIntoView()},t.prototype.scrollToPosition=function(t){var e,r;return e=t.x,r=t.y,window.scrollTo(e,r)},t.prototype.onScroll=function(t){return this.updatePosition({x:window.pageXOffset,y:window.pageYOffset})},t.prototype.updatePosition=function(t){var e;return this.position=t,null!=(e=this.delegate)?e.scrollPositionChanged(this.position):void 0},t}()}.call(this),function(){t.SnapshotCache=function(){function e(t){this.size=t,this.keys=[],this.snapshots={}}var r;return e.prototype.has=function(t){var e;return e=r(t),e in this.snapshots},e.prototype.get=function(t){var e;if(this.has(t))return e=this.read(t),this.touch(t),e},e.prototype.put=function(t,e){return this.write(t,e),this.touch(t),e},e.prototype.read=function(t){var e;return e=r(t),this.snapshots[e]},e.prototype.write=function(t,e){var n;return n=r(t),this.snapshots[n]=e},e.prototype.touch=function(t){var e,n;return n=r(t),e=this.keys.indexOf(n),e>-1&&this.keys.splice(e,1),this.keys.unshift(n),this.trim()},e.prototype.trim=function(){var t,e,r,n,o;for(n=this.keys.splice(this.size),o=[],t=0,r=n.length;r>t;t++)e=n[t],o.push(delete this.snapshots[e]);return o},r=function(e){return t.Location.wrap(e).toCacheKey()},e}()}.call(this),function(){var e=function(t,e){return function(){return t.apply(e,arguments)}};t.Visit=function(){function r(r,n,o){this.controller=r,this.action=o,this.performScroll=e(this.performScroll,this),this.identifier=t.uuid(),this.location=t.Location.wrap(n),this.adapter=this.controller.adapter,this.state="initialized",this.timingMetrics={}}var n;return r.prototype.start=function(){return"initialized"===this.state?(this.recordTimingMetric("visitStart"),this.state="started",this.adapter.visitStarted(this)):void 0},r.prototype.cancel=function(){var t;return"started"===this.state?(null!=(t=this.request)&&t.cancel(),this.cancelRender(),this.state="canceled"):void 0},r.prototype.complete=function(){var t;return"started"===this.state?(this.recordTimingMetric("visitEnd"),this.state="completed","function"==typeof(t=this.adapter).visitCompleted&&t.visitCompleted(this),this.controller.visitCompleted(this)):void 0},r.prototype.fail=function(){var t;return"started"===this.state?(this.state="failed","function"==typeof(t=this.adapter).visitFailed?t.visitFailed(this):void 0):void 0},r.prototype.changeHistory=function(){var t,e;return this.historyChanged?void 0:(t=this.location.isEqualTo(this.referrer)?"replace":this.action,e=n(t),this.controller[e](this.location,this.restorationIdentifier),this.historyChanged=!0)},r.prototype.issueRequest=function(){return this.shouldIssueRequest()&&null==this.request?(this.progress=0,this.request=new t.HttpRequest(this,this.location,this.referrer),this.request.send()):void 0},r.prototype.getCachedSnapshot=function(){var t;return!(t=this.controller.getCachedSnapshotForLocation(this.location))||null!=this.location.anchor&&!t.hasAnchor(this.location.anchor)||"restore"!==this.action&&!t.isPreviewable()?void 0:t},r.prototype.hasCachedSnapshot=function(){return null!=this.getCachedSnapshot()},r.prototype.loadCachedSnapshot=function(){var t,e;return(e=this.getCachedSnapshot())?(t=this.shouldIssueRequest(),this.render(function(){var r;return this.cacheSnapshot(),this.controller.render({snapshot:e,isPreview:t},this.performScroll),"function"==typeof(r=this.adapter).visitRendered&&r.visitRendered(this),t?void 0:this.complete()})):void 0},r.prototype.loadResponse=function(){return null!=this.response?this.render(function(){var t,e;return this.cacheSnapshot(),this.request.failed?(this.controller.render({error:this.response},this.performScroll),"function"==typeof(t=this.adapter).visitRendered&&t.visitRendered(this),this.fail()):(this.controller.render({snapshot:this.response},this.performScroll),"function"==typeof(e=this.adapter).visitRendered&&e.visitRendered(this),this.complete())}):void 0},r.prototype.followRedirect=function(){return this.redirectedToLocation&&!this.followedRedirect?(this.location=this.redirectedToLocation,this.controller.replaceHistoryWithLocationAndRestorationIdentifier(this.redirectedToLocation,this.restorationIdentifier),this.followedRedirect=!0):void 0},r.prototype.requestStarted=function(){var t;return this.recordTimingMetric("requestStart"),"function"==typeof(t=this.adapter).visitRequestStarted?t.visitRequestStarted(this):void 0},r.prototype.requestProgressed=function(t){var e;return this.progress=t,"function"==typeof(e=this.adapter).visitRequestProgressed?e.visitRequestProgressed(this):void 0},r.prototype.requestCompletedWithResponse=function(e,r){return this.response=e,null!=r&&(this.redirectedToLocation=t.Location.wrap(r)),this.adapter.visitRequestCompleted(this)},r.prototype.requestFailedWithStatusCode=function(t,e){return this.response=e,this.adapter.visitRequestFailedWithStatusCode(this,t)},r.prototype.requestFinished=function(){var t;return this.recordTimingMetric("requestEnd"),"function"==typeof(t=this.adapter).visitRequestFinished?t.visitRequestFinished(this):void 0},r.prototype.performScroll=function(){return this.scrolled?void 0:("restore"===this.action?this.scrollToRestoredPosition()||this.scrollToTop():this.scrollToAnchor()||this.scrollToTop(),this.scrolled=!0)},r.prototype.scrollToRestoredPosition=function(){var t,e;return t=null!=(e=this.restorationData)?e.scrollPosition:void 0,null!=t?(this.controller.scrollToPosition(t),!0):void 0},r.prototype.scrollToAnchor=function(){return null!=this.location.anchor?(this.controller.scrollToAnchor(this.location.anchor),!0):void 0},r.prototype.scrollToTop=function(){return this.controller.scrollToPosition({x:0,y:0})},r.prototype.recordTimingMetric=function(t){var e;return null!=(e=this.timingMetrics)[t]?e[t]:e[t]=(new Date).getTime()},r.prototype.getTimingMetrics=function(){return t.copyObject(this.timingMetrics)},n=function(t){switch(t){case"replace":return"replaceHistoryWithLocationAndRestorationIdentifier";case"advance":case"restore":return"pushHistoryWithLocationAndRestorationIdentifier"}},r.prototype.shouldIssueRequest=function(){return"restore"===this.action?!this.hasCachedSnapshot():!0},r.prototype.cacheSnapshot=function(){return this.snapshotCached?void 0:(this.controller.cacheSnapshot(),this.snapshotCached=!0)},r.prototype.render=function(t){return this.cancelRender(),this.frame=requestAnimationFrame(function(e){return function(){return e.frame=null,t.call(e)}}(this))},r.prototype.cancelRender=function(){return this.frame?cancelAnimationFrame(this.frame):void 0},r}()}.call(this),function(){var e=function(t,e){return function(){return t.apply(e,arguments)}};t.Controller=function(){function r(){this.clickBubbled=e(this.clickBubbled,this),this.clickCaptured=e(this.clickCaptured,this),this.pageLoaded=e(this.pageLoaded,this),this.history=new t.History(this),this.view=new t.View(this),this.scrollManager=new t.ScrollManager(this),this.restorationData={},this.clearCache()}return r.prototype.start=function(){return t.supported&&!this.started?(addEventListener("click",this.clickCaptured,!0),addEventListener("DOMContentLoaded",this.pageLoaded,!1),this.scrollManager.start(),this.startHistory(),this.started=!0,this.enabled=!0):void 0},r.prototype.disable=function(){return this.enabled=!1},r.prototype.stop=function(){return this.started?(removeEventListener("click",this.clickCaptured,!0),removeEventListener("DOMContentLoaded",this.pageLoaded,!1),this.scrollManager.stop(),this.stopHistory(),this.started=!1):void 0},r.prototype.clearCache=function(){return this.cache=new t.SnapshotCache(10)},r.prototype.visit=function(e,r){var n,o;return null==r&&(r={}),e=t.Location.wrap(e),this.applicationAllowsVisitingLocation(e)?this.locationIsVisitable(e)?(n=null!=(o=r.action)?o:"advance",this.adapter.visitProposedToLocationWithAction(e,n)):window.location=e:void 0},r.prototype.startVisitToLocationWithAction=function(e,r,n){var o;return t.supported?(o=this.getRestorationDataForIdentifier(n),this.startVisit(e,r,{restorationData:o})):window.location=e},r.prototype.startHistory=function(){return this.location=t.Location.wrap(window.location),this.restorationIdentifier=t.uuid(),this.history.start(),this.history.replace(this.location,this.restorationIdentifier)},r.prototype.stopHistory=function(){return this.history.stop()},r.prototype.pushHistoryWithLocationAndRestorationIdentifier=function(e,r){return this.restorationIdentifier=r,this.location=t.Location.wrap(e),this.history.push(this.location,this.restorationIdentifier)},r.prototype.replaceHistoryWithLocationAndRestorationIdentifier=function(e,r){return this.restorationIdentifier=r,this.location=t.Location.wrap(e),this.history.replace(this.location,this.restorationIdentifier)},r.prototype.historyPoppedToLocationWithRestorationIdentifier=function(e,r){var n;return this.restorationIdentifier=r,this.enabled?(n=this.getRestorationDataForIdentifier(this.restorationIdentifier),this.startVisit(e,"restore",{restorationIdentifier:this.restorationIdentifier,restorationData:n,historyChanged:!0}),this.location=t.Location.wrap(e)):this.adapter.pageInvalidated()},r.prototype.getCachedSnapshotForLocation=function(t){var e;return e=this.cache.get(t),e?e.clone():void 0},r.prototype.shouldCacheSnapshot=function(){return this.view.getSnapshot().isCacheable()},r.prototype.cacheSnapshot=function(){var t;return this.shouldCacheSnapshot()?(this.notifyApplicationBeforeCachingSnapshot(),t=this.view.getSnapshot(),this.cache.put(this.lastRenderedLocation,t.clone())):void 0},r.prototype.scrollToAnchor=function(t){var e;return(e=document.getElementById(t))?this.scrollToElement(e):this.scrollToPosition({x:0,y:0})},r.prototype.scrollToElement=function(t){return this.scrollManager.scrollToElement(t)},r.prototype.scrollToPosition=function(t){return this.scrollManager.scrollToPosition(t)},r.prototype.scrollPositionChanged=function(t){var e;return e=this.getCurrentRestorationData(),e.scrollPosition=t},r.prototype.render=function(t,e){return this.view.render(t,e)},r.prototype.viewInvalidated=function(){return this.adapter.pageInvalidated()},r.prototype.viewWillRender=function(t){return this.notifyApplicationBeforeRender(t)},r.prototype.viewRendered=function(){return this.lastRenderedLocation=this.currentVisit.location,this.notifyApplicationAfterRender()},r.prototype.pageLoaded=function(){return this.lastRenderedLocation=this.location,this.notifyApplicationAfterPageLoad()},r.prototype.clickCaptured=function(){return removeEventListener("click",this.clickBubbled,!1),addEventListener("click",this.clickBubbled,!1)},r.prototype.clickBubbled=function(t){var e,r,n;return this.enabled&&this.clickEventIsSignificant(t)&&(r=this.getVisitableLinkForNode(t.target))&&(n=this.getVisitableLocationForLink(r))&&this.applicationAllowsFollowingLinkToLocation(r,n)?(t.preventDefault(),e=this.getActionForLink(r),this.visit(n,{action:e})):void 0},r.prototype.applicationAllowsFollowingLinkToLocation=function(t,e){var r;return r=this.notifyApplicationAfterClickingLinkToLocation(t,e),!r.defaultPrevented},r.prototype.applicationAllowsVisitingLocation=function(t){var e;return e=this.notifyApplicationBeforeVisitingLocation(t),!e.defaultPrevented},r.prototype.notifyApplicationAfterClickingLinkToLocation=function(e,r){return t.dispatch("turbolinks:click",{target:e,data:{url:r.absoluteURL},cancelable:!0})},r.prototype.notifyApplicationBeforeVisitingLocation=function(e){return t.dispatch("turbolinks:before-visit",{data:{url:e.absoluteURL},cancelable:!0})},r.prototype.notifyApplicationAfterVisitingLocation=function(e){return t.dispatch("turbolinks:visit",{data:{url:e.absoluteURL}})},r.prototype.notifyApplicationBeforeCachingSnapshot=function(){return t.dispatch("turbolinks:before-cache")},r.prototype.notifyApplicationBeforeRender=function(e){return t.dispatch("turbolinks:before-render",{data:{newBody:e}})},r.prototype.notifyApplicationAfterRender=function(){return t.dispatch("turbolinks:render")},r.prototype.notifyApplicationAfterPageLoad=function(e){return null==e&&(e={}),t.dispatch("turbolinks:load",{data:{url:this.location.absoluteURL,timing:e}})},r.prototype.startVisit=function(t,e,r){var n;return null!=(n=this.currentVisit)&&n.cancel(),this.currentVisit=this.createVisit(t,e,r),this.currentVisit.start(),this.notifyApplicationAfterVisitingLocation(t)},r.prototype.createVisit=function(e,r,n){
  var o,i,s,a,u;return i=null!=n?n:{},a=i.restorationIdentifier,s=i.restorationData,o=i.historyChanged,u=new t.Visit(this,e,r),u.restorationIdentifier=null!=a?a:t.uuid(),u.restorationData=t.copyObject(s),u.historyChanged=o,u.referrer=this.location,u},r.prototype.visitCompleted=function(t){return this.notifyApplicationAfterPageLoad(t.getTimingMetrics())},r.prototype.clickEventIsSignificant=function(t){return!(t.defaultPrevented||t.target.isContentEditable||t.which>1||t.altKey||t.ctrlKey||t.metaKey||t.shiftKey)},r.prototype.getVisitableLinkForNode=function(e){return this.nodeIsVisitable(e)?t.closest(e,"a[href]:not([target])"):void 0},r.prototype.getVisitableLocationForLink=function(e){var r;return r=new t.Location(e.getAttribute("href")),this.locationIsVisitable(r)?r:void 0},r.prototype.getActionForLink=function(t){var e;return null!=(e=t.getAttribute("data-turbolinks-action"))?e:"advance"},r.prototype.nodeIsVisitable=function(e){var r;return(r=t.closest(e,"[data-turbolinks]"))?"false"!==r.getAttribute("data-turbolinks"):!0},r.prototype.locationIsVisitable=function(t){return t.isPrefixedBy(this.view.getRootLocation())&&t.isHTML()},r.prototype.getCurrentRestorationData=function(){return this.getRestorationDataForIdentifier(this.restorationIdentifier)},r.prototype.getRestorationDataForIdentifier=function(t){var e;return null!=(e=this.restorationData)[t]?e[t]:e[t]={}},r}()}.call(this),function(){var e,r,n;t.start=function(){return r()?(null==t.controller&&(t.controller=e()),t.controller.start()):void 0},r=function(){return null==window.Turbolinks&&(window.Turbolinks=t),n()},e=function(){var e;return e=new t.Controller,e.adapter=new t.BrowserAdapter(e),e},n=function(){return window.Turbolinks===t},n()&&t.start()}.call(this)}).call(this),"object"==typeof module&&module.exports?module.exports=t:"function"==typeof define&&define.amd&&define(t)}).call(this);
  (function() {
    (function() {
      (function() {
        var slice = [].slice;
  
        this.ActionCable = {
          INTERNAL: {
            "message_types": {
              "welcome": "welcome",
              "ping": "ping",
              "confirmation": "confirm_subscription",
              "rejection": "reject_subscription"
            },
            "default_mount_path": "/cable",
            "protocols": ["actioncable-v1-json", "actioncable-unsupported"]
          },
          createConsumer: function(url) {
            var ref;
            if (url == null) {
              url = (ref = this.getConfig("url")) != null ? ref : this.INTERNAL.default_mount_path;
            }
            return new ActionCable.Consumer(this.createWebSocketURL(url));
          },
          getConfig: function(name) {
            var element;
            element = document.head.querySelector("meta[name='action-cable-" + name + "']");
            return element != null ? element.getAttribute("content") : void 0;
          },
          createWebSocketURL: function(url) {
            var a;
            if (url && !/^wss?:/i.test(url)) {
              a = document.createElement("a");
              a.href = url;
              a.href = a.href;
              a.protocol = a.protocol.replace("http", "ws");
              return a.href;
            } else {
              return url;
            }
          },
          startDebugging: function() {
            return this.debugging = true;
          },
          stopDebugging: function() {
            return this.debugging = null;
          },
          log: function() {
            var messages;
            messages = 1 <= arguments.length ? slice.call(arguments, 0) : [];
            if (this.debugging) {
              messages.push(Date.now());
              return console.log.apply(console, ["[ActionCable]"].concat(slice.call(messages)));
            }
          }
        };
  
      }).call(this);
    }).call(this);
  
    var ActionCable = this.ActionCable;
  
    (function() {
      (function() {
        var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
  
        ActionCable.ConnectionMonitor = (function() {
          var clamp, now, secondsSince;
  
          ConnectionMonitor.pollInterval = {
            min: 3,
            max: 30
          };
  
          ConnectionMonitor.staleThreshold = 6;
  
          function ConnectionMonitor(connection) {
            this.connection = connection;
            this.visibilityDidChange = bind(this.visibilityDidChange, this);
            this.reconnectAttempts = 0;
          }
  
          ConnectionMonitor.prototype.start = function() {
            if (!this.isRunning()) {
              this.startedAt = now();
              delete this.stoppedAt;
              this.startPolling();
              document.addEventListener("visibilitychange", this.visibilityDidChange);
              return ActionCable.log("ConnectionMonitor started. pollInterval = " + (this.getPollInterval()) + " ms");
            }
          };
  
          ConnectionMonitor.prototype.stop = function() {
            if (this.isRunning()) {
              this.stoppedAt = now();
              this.stopPolling();
              document.removeEventListener("visibilitychange", this.visibilityDidChange);
              return ActionCable.log("ConnectionMonitor stopped");
            }
          };
  
          ConnectionMonitor.prototype.isRunning = function() {
            return (this.startedAt != null) && (this.stoppedAt == null);
          };
  
          ConnectionMonitor.prototype.recordPing = function() {
            return this.pingedAt = now();
          };
  
          ConnectionMonitor.prototype.recordConnect = function() {
            this.reconnectAttempts = 0;
            this.recordPing();
            delete this.disconnectedAt;
            return ActionCable.log("ConnectionMonitor recorded connect");
          };
  
          ConnectionMonitor.prototype.recordDisconnect = function() {
            this.disconnectedAt = now();
            return ActionCable.log("ConnectionMonitor recorded disconnect");
          };
  
          ConnectionMonitor.prototype.startPolling = function() {
            this.stopPolling();
            return this.poll();
          };
  
          ConnectionMonitor.prototype.stopPolling = function() {
            return clearTimeout(this.pollTimeout);
          };
  
          ConnectionMonitor.prototype.poll = function() {
            return this.pollTimeout = setTimeout((function(_this) {
              return function() {
                _this.reconnectIfStale();
                return _this.poll();
              };
            })(this), this.getPollInterval());
          };
  
          ConnectionMonitor.prototype.getPollInterval = function() {
            var interval, max, min, ref;
            ref = this.constructor.pollInterval, min = ref.min, max = ref.max;
            interval = 5 * Math.log(this.reconnectAttempts + 1);
            return Math.round(clamp(interval, min, max) * 1000);
          };
  
          ConnectionMonitor.prototype.reconnectIfStale = function() {
            if (this.connectionIsStale()) {
              ActionCable.log("ConnectionMonitor detected stale connection. reconnectAttempts = " + this.reconnectAttempts + ", pollInterval = " + (this.getPollInterval()) + " ms, time disconnected = " + (secondsSince(this.disconnectedAt)) + " s, stale threshold = " + this.constructor.staleThreshold + " s");
              this.reconnectAttempts++;
              if (this.disconnectedRecently()) {
                return ActionCable.log("ConnectionMonitor skipping reopening recent disconnect");
              } else {
                ActionCable.log("ConnectionMonitor reopening");
                return this.connection.reopen();
              }
            }
          };
  
          ConnectionMonitor.prototype.connectionIsStale = function() {
            var ref;
            return secondsSince((ref = this.pingedAt) != null ? ref : this.startedAt) > this.constructor.staleThreshold;
          };
  
          ConnectionMonitor.prototype.disconnectedRecently = function() {
            return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
          };
  
          ConnectionMonitor.prototype.visibilityDidChange = function() {
            if (document.visibilityState === "visible") {
              return setTimeout((function(_this) {
                return function() {
                  if (_this.connectionIsStale() || !_this.connection.isOpen()) {
                    ActionCable.log("ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = " + document.visibilityState);
                    return _this.connection.reopen();
                  }
                };
              })(this), 200);
            }
          };
  
          now = function() {
            return new Date().getTime();
          };
  
          secondsSince = function(time) {
            return (now() - time) / 1000;
          };
  
          clamp = function(number, min, max) {
            return Math.max(min, Math.min(max, number));
          };
  
          return ConnectionMonitor;
  
        })();
  
      }).call(this);
      (function() {
        var i, message_types, protocols, ref, supportedProtocols, unsupportedProtocol,
          slice = [].slice,
          bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
          indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
  
        ref = ActionCable.INTERNAL, message_types = ref.message_types, protocols = ref.protocols;
  
        supportedProtocols = 2 <= protocols.length ? slice.call(protocols, 0, i = protocols.length - 1) : (i = 0, []), unsupportedProtocol = protocols[i++];
  
        ActionCable.Connection = (function() {
          Connection.reopenDelay = 500;
  
          function Connection(consumer) {
            this.consumer = consumer;
            this.open = bind(this.open, this);
            this.subscriptions = this.consumer.subscriptions;
            this.monitor = new ActionCable.ConnectionMonitor(this);
            this.disconnected = true;
          }
  
          Connection.prototype.send = function(data) {
            if (this.isOpen()) {
              this.webSocket.send(JSON.stringify(data));
              return true;
            } else {
              return false;
            }
          };
  
          Connection.prototype.open = function() {
            if (this.isActive()) {
              ActionCable.log("Attempted to open WebSocket, but existing socket is " + (this.getState()));
              throw new Error("Existing connection must be closed before opening");
            } else {
              ActionCable.log("Opening WebSocket, current state is " + (this.getState()) + ", subprotocols: " + protocols);
              if (this.webSocket != null) {
                this.uninstallEventHandlers();
              }
              this.webSocket = new WebSocket(this.consumer.url, protocols);
              this.installEventHandlers();
              this.monitor.start();
              return true;
            }
          };
  
          Connection.prototype.close = function(arg) {
            var allowReconnect, ref1;
            allowReconnect = (arg != null ? arg : {
              allowReconnect: true
            }).allowReconnect;
            if (!allowReconnect) {
              this.monitor.stop();
            }
            if (this.isActive()) {
              return (ref1 = this.webSocket) != null ? ref1.close() : void 0;
            }
          };
  
          Connection.prototype.reopen = function() {
            var error;
            ActionCable.log("Reopening WebSocket, current state is " + (this.getState()));
            if (this.isActive()) {
              try {
                return this.close();
              } catch (error1) {
                error = error1;
                return ActionCable.log("Failed to reopen WebSocket", error);
              } finally {
                ActionCable.log("Reopening WebSocket in " + this.constructor.reopenDelay + "ms");
                setTimeout(this.open, this.constructor.reopenDelay);
              }
            } else {
              return this.open();
            }
          };
  
          Connection.prototype.getProtocol = function() {
            var ref1;
            return (ref1 = this.webSocket) != null ? ref1.protocol : void 0;
          };
  
          Connection.prototype.isOpen = function() {
            return this.isState("open");
          };
  
          Connection.prototype.isActive = function() {
            return this.isState("open", "connecting");
          };
  
          Connection.prototype.isProtocolSupported = function() {
            var ref1;
            return ref1 = this.getProtocol(), indexOf.call(supportedProtocols, ref1) >= 0;
          };
  
          Connection.prototype.isState = function() {
            var ref1, states;
            states = 1 <= arguments.length ? slice.call(arguments, 0) : [];
            return ref1 = this.getState(), indexOf.call(states, ref1) >= 0;
          };
  
          Connection.prototype.getState = function() {
            var ref1, state, value;
            for (state in WebSocket) {
              value = WebSocket[state];
              if (value === ((ref1 = this.webSocket) != null ? ref1.readyState : void 0)) {
                return state.toLowerCase();
              }
            }
            return null;
          };
  
          Connection.prototype.installEventHandlers = function() {
            var eventName, handler;
            for (eventName in this.events) {
              handler = this.events[eventName].bind(this);
              this.webSocket["on" + eventName] = handler;
            }
          };
  
          Connection.prototype.uninstallEventHandlers = function() {
            var eventName;
            for (eventName in this.events) {
              this.webSocket["on" + eventName] = function() {};
            }
          };
  
          Connection.prototype.events = {
            message: function(event) {
              var identifier, message, ref1, type;
              if (!this.isProtocolSupported()) {
                return;
              }
              ref1 = JSON.parse(event.data), identifier = ref1.identifier, message = ref1.message, type = ref1.type;
              switch (type) {
                case message_types.welcome:
                  this.monitor.recordConnect();
                  return this.subscriptions.reload();
                case message_types.ping:
                  return this.monitor.recordPing();
                case message_types.confirmation:
                  return this.subscriptions.notify(identifier, "connected");
                case message_types.rejection:
                  return this.subscriptions.reject(identifier);
                default:
                  return this.subscriptions.notify(identifier, "received", message);
              }
            },
            open: function() {
              ActionCable.log("WebSocket onopen event, using '" + (this.getProtocol()) + "' subprotocol");
              this.disconnected = false;
              if (!this.isProtocolSupported()) {
                ActionCable.log("Protocol is unsupported. Stopping monitor and disconnecting.");
                return this.close({
                  allowReconnect: false
                });
              }
            },
            close: function(event) {
              ActionCable.log("WebSocket onclose event");
              if (this.disconnected) {
                return;
              }
              this.disconnected = true;
              this.monitor.recordDisconnect();
              return this.subscriptions.notifyAll("disconnected", {
                willAttemptReconnect: this.monitor.isRunning()
              });
            },
            error: function() {
              return ActionCable.log("WebSocket onerror event");
            }
          };
  
          return Connection;
  
        })();
  
      }).call(this);
      (function() {
        var slice = [].slice;
  
        ActionCable.Subscriptions = (function() {
          function Subscriptions(consumer) {
            this.consumer = consumer;
            this.subscriptions = [];
          }
  
          Subscriptions.prototype.create = function(channelName, mixin) {
            var channel, params, subscription;
            channel = channelName;
            params = typeof channel === "object" ? channel : {
              channel: channel
            };
            subscription = new ActionCable.Subscription(this.consumer, params, mixin);
            return this.add(subscription);
          };
  
          Subscriptions.prototype.add = function(subscription) {
            this.subscriptions.push(subscription);
            this.consumer.ensureActiveConnection();
            this.notify(subscription, "initialized");
            this.sendCommand(subscription, "subscribe");
            return subscription;
          };
  
          Subscriptions.prototype.remove = function(subscription) {
            this.forget(subscription);
            if (!this.findAll(subscription.identifier).length) {
              this.sendCommand(subscription, "unsubscribe");
            }
            return subscription;
          };
  
          Subscriptions.prototype.reject = function(identifier) {
            var i, len, ref, results, subscription;
            ref = this.findAll(identifier);
            results = [];
            for (i = 0, len = ref.length; i < len; i++) {
              subscription = ref[i];
              this.forget(subscription);
              this.notify(subscription, "rejected");
              results.push(subscription);
            }
            return results;
          };
  
          Subscriptions.prototype.forget = function(subscription) {
            var s;
            this.subscriptions = (function() {
              var i, len, ref, results;
              ref = this.subscriptions;
              results = [];
              for (i = 0, len = ref.length; i < len; i++) {
                s = ref[i];
                if (s !== subscription) {
                  results.push(s);
                }
              }
              return results;
            }).call(this);
            return subscription;
          };
  
          Subscriptions.prototype.findAll = function(identifier) {
            var i, len, ref, results, s;
            ref = this.subscriptions;
            results = [];
            for (i = 0, len = ref.length; i < len; i++) {
              s = ref[i];
              if (s.identifier === identifier) {
                results.push(s);
              }
            }
            return results;
          };
  
          Subscriptions.prototype.reload = function() {
            var i, len, ref, results, subscription;
            ref = this.subscriptions;
            results = [];
            for (i = 0, len = ref.length; i < len; i++) {
              subscription = ref[i];
              results.push(this.sendCommand(subscription, "subscribe"));
            }
            return results;
          };
  
          Subscriptions.prototype.notifyAll = function() {
            var args, callbackName, i, len, ref, results, subscription;
            callbackName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
            ref = this.subscriptions;
            results = [];
            for (i = 0, len = ref.length; i < len; i++) {
              subscription = ref[i];
              results.push(this.notify.apply(this, [subscription, callbackName].concat(slice.call(args))));
            }
            return results;
          };
  
          Subscriptions.prototype.notify = function() {
            var args, callbackName, i, len, results, subscription, subscriptions;
            subscription = arguments[0], callbackName = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : [];
            if (typeof subscription === "string") {
              subscriptions = this.findAll(subscription);
            } else {
              subscriptions = [subscription];
            }
            results = [];
            for (i = 0, len = subscriptions.length; i < len; i++) {
              subscription = subscriptions[i];
              results.push(typeof subscription[callbackName] === "function" ? subscription[callbackName].apply(subscription, args) : void 0);
            }
            return results;
          };
  
          Subscriptions.prototype.sendCommand = function(subscription, command) {
            var identifier;
            identifier = subscription.identifier;
            return this.consumer.send({
              command: command,
              identifier: identifier
            });
          };
  
          return Subscriptions;
  
        })();
  
      }).call(this);
      (function() {
        ActionCable.Subscription = (function() {
          var extend;
  
          function Subscription(consumer, params, mixin) {
            this.consumer = consumer;
            if (params == null) {
              params = {};
            }
            this.identifier = JSON.stringify(params);
            extend(this, mixin);
          }
  
          Subscription.prototype.perform = function(action, data) {
            if (data == null) {
              data = {};
            }
            data.action = action;
            return this.send(data);
          };
  
          Subscription.prototype.send = function(data) {
            return this.consumer.send({
              command: "message",
              identifier: this.identifier,
              data: JSON.stringify(data)
            });
          };
  
          Subscription.prototype.unsubscribe = function() {
            return this.consumer.subscriptions.remove(this);
          };
  
          extend = function(object, properties) {
            var key, value;
            if (properties != null) {
              for (key in properties) {
                value = properties[key];
                object[key] = value;
              }
            }
            return object;
          };
  
          return Subscription;
  
        })();
  
      }).call(this);
      (function() {
        ActionCable.Consumer = (function() {
          function Consumer(url) {
            this.url = url;
            this.subscriptions = new ActionCable.Subscriptions(this);
            this.connection = new ActionCable.Connection(this);
          }
  
          Consumer.prototype.send = function(data) {
            return this.connection.send(data);
          };
  
          Consumer.prototype.connect = function() {
            return this.connection.open();
          };
  
          Consumer.prototype.disconnect = function() {
            return this.connection.close({
              allowReconnect: false
            });
          };
  
          Consumer.prototype.ensureActiveConnection = function() {
            if (!this.connection.isActive()) {
              return this.connection.open();
            }
          };
  
          return Consumer;
  
        })();
  
      }).call(this);
    }).call(this);
  
    if (typeof module === "object" && module.exports) {
      module.exports = ActionCable;
    } else if (typeof define === "function" && define.amd) {
      define(ActionCable);
    }
  }).call(this);
  // Action Cable provides the framework to deal with WebSockets in Rails.
  // You can generate new channels where WebSocket features live using the rails generate channel command.
  //
  
  
  
  
  (function() {
    this.App || (this.App = {});
  
    App.cable = ActionCable.createConsumer();
  
  }).call(this);
  var tablesort = function() {
    $(".tablesorter").tablesorter();
  };
  
  var flashfade = function() {
    $("#flashBox").click(function() {
      $(this).fadeOut(500, function() {
        $(this).hide();
      });
    });
  };
  
  var flashhide = function() {
    setTimeout(function() {
      $('#flashBox').fadeOut(500, function() {
        $(this).hide();
      });
    }, 3500);
  };
  
  
  $(document).on('turbolinks:load', flashfade);
  $(document).on('turbolinks:load', flashhide);
  $(document).on('turbolinks:load', tablesort);
  
  // This is a manifest file that'll be compiled into application.js, which will include all the files
  // listed below.
  //
  // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
  // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
  //
  // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
  // compiled file. JavaScript code in this file should be added after the last require_* statement.
  //
  // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
  // about supported directives.
  //
  
  
  
  
  
  ;
  