/*!
 * jQuery JavaScript Library v1.12.4
 * http://jquery.com/
 *
 * Includes Sizzle.js
 * http://sizzlejs.com/
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 *
 * Date: 2016-05-20T17:17Z
 */

(function( global, factory ) {

	if ( typeof module === "object" && typeof module.exports === "object" ) {
		// For CommonJS and CommonJS-like environments where a proper `window`
		// is present, execute the factory and get jQuery.
		// For environments that do not 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 ) {

// Support: Firefox 18+
// Can't be in strict mode, several libs including ASP.NET trace
// the stack via arguments.caller.callee and Firefox dies if
// you try to trace through "use strict" call chains. (#13335)
//"use strict";
var deletedIds = [];

var document = window.document;

var slice = deletedIds.slice;

var concat = deletedIds.concat;

var push = deletedIds.push;

var indexOf = deletedIds.indexOf;

var class2type = {};

var toString = class2type.toString;

var hasOwn = class2type.hasOwnProperty;

var support = {};



var
	version = "1.12.4",

	// 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.1, IE<9
	// Make sure we trim BOM and NBSP
	rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,

	// Matches dashed string for camelizing
	rmsPrefix = /^-ms-/,
	rdashAlpha = /-([\da-z])/gi,

	// Used by jQuery.camelCase as callback to replace()
	fcamelCase = function( all, letter ) {
		return letter.toUpperCase();
	};

jQuery.fn = jQuery.prototype = {

	// The current version of jQuery being used
	jquery: version,

	constructor: jQuery,

	// Start with an empty selector
	selector: "",

	// The default length of a jQuery object is 0
	length: 0,

	toArray: function() {
		return slice.call( this );
	},

	// Get the Nth element in the matched element set OR
	// Get the whole matched element set as a clean array
	get: function( num ) {
		return num != null ?

			// Return just the one element from the set
			( num < 0 ? this[ num + this.length ] : this[ num ] ) :

			// Return all the elements in a clean array
			slice.call( this );
	},

	// Take an array of elements and push it onto the stack
	// (returning the new matched element set)
	pushStack: function( elems ) {

		// Build a new jQuery matched element set
		var ret = jQuery.merge( this.constructor(), elems );

		// Add the old object onto the stack (as a reference)
		ret.prevObject = this;
		ret.context = this.context;

		// Return the newly-formed element set
		return ret;
	},

	// Execute a callback for every element in the matched set.
	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: deletedIds.sort,
	splice: deletedIds.splice
};

jQuery.extend = jQuery.fn.extend = function() {
	var src, copyIsArray, copy, name, options, clone,
		target = arguments[ 0 ] || {},
		i = 1,
		length = arguments.length,
		deep = false;

	// Handle a deep copy situation
	if ( typeof target === "boolean" ) {
		deep = target;

		// skip the boolean and the target
		target = arguments[ i ] || {};
		i++;
	}

	// Handle case when target is a string or something (possible in deep copy)
	if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
		target = {};
	}

	// extend jQuery itself if only one argument is passed
	if ( i === length ) {
		target = this;
		i--;
	}

	for ( ; i < length; i++ ) {

		// Only deal with non-null/undefined values
		if ( ( options = arguments[ i ] ) != null ) {

			// Extend the base object
			for ( name in options ) {
				src = target[ name ];
				copy = options[ name ];

				// Prevent never-ending loop
				if ( target === copy ) {
					continue;
				}

				// Recurse if we're merging plain objects or arrays
				if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
					( copyIsArray = jQuery.isArray( copy ) ) ) ) {

					if ( copyIsArray ) {
						copyIsArray = false;
						clone = src && jQuery.isArray( src ) ? src : [];

					} else {
						clone = src && jQuery.isPlainObject( src ) ? src : {};
					}

					// Never move original objects, clone them
					target[ name ] = jQuery.extend( deep, clone, copy );

				// Don't bring in undefined values
				} else if ( copy !== undefined ) {
					target[ name ] = copy;
				}
			}
		}
	}

	// Return the modified object
	return target;
};

jQuery.extend( {

	// Unique for each copy of jQuery on the page
	expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),

	// Assume jQuery is ready without the ready module
	isReady: true,

	error: function( msg ) {
		throw new Error( msg );
	},

	noop: function() {},

	// See test/unit/core.js for details concerning isFunction.
	// Since version 1.3, DOM methods and functions like alert
	// aren't supported. They return false on IE (#2968).
	isFunction: function( obj ) {
		return jQuery.type( obj ) === "function";
	},

	isArray: Array.isArray || function( obj ) {
		return jQuery.type( obj ) === "array";
	},

	isWindow: function( obj ) {
		/* jshint eqeqeq: false */
		return obj != null && obj == obj.window;
	},

	isNumeric: function( obj ) {

		// parseFloat NaNs numeric-cast false positives (null|true|false|"")
		// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
		// subtraction forces infinities to NaN
		// adding 1 corrects loss of precision from parseFloat (#15100)
		var realStringObj = obj && obj.toString();
		return !jQuery.isArray( obj ) && ( realStringObj - parseFloat( realStringObj ) + 1 ) >= 0;
	},

	isEmptyObject: function( obj ) {
		var name;
		for ( name in obj ) {
			return false;
		}
		return true;
	},

	isPlainObject: function( obj ) {
		var key;

		// Must be an Object.
		// Because of IE, we also have to check the presence of the constructor property.
		// Make sure that DOM nodes and window objects don't pass through, as well
		if ( !obj || jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
			return false;
		}

		try {

			// Not own constructor property must be Object
			if ( obj.constructor &&
				!hasOwn.call( obj, "constructor" ) &&
				!hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) {
				return false;
			}
		} catch ( e ) {

			// IE8,9 Will throw exceptions on certain host objects #9897
			return false;
		}

		// Support: IE<9
		// Handle iteration over inherited properties before own properties.
		if ( !support.ownFirst ) {
			for ( key in obj ) {
				return hasOwn.call( obj, key );
			}
		}

		// Own properties are enumerated firstly, so to speed up,
		// if last one is own, then all properties are own.
		for ( key in obj ) {}

		return key === undefined || hasOwn.call( obj, key );
	},

	type: function( obj ) {
		if ( obj == null ) {
			return obj + "";
		}
		return typeof obj === "object" || typeof obj === "function" ?
			class2type[ toString.call( obj ) ] || "object" :
			typeof obj;
	},

	// Workarounds based on findings by Jim Driscoll
	// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
	globalEval: function( data ) {
		if ( data && jQuery.trim( data ) ) {

			// We use execScript on Internet Explorer
			// We use an anonymous function so that context is window
			// rather than jQuery in Firefox
			( window.execScript || function( data ) {
				window[ "eval" ].call( window, data ); // jscs:ignore requireDotNotation
			} )( data );
		}
	},

	// Convert dashed to camelCase; used by the css and data modules
	// Microsoft forgot to hump their vendor prefix (#9572)
	camelCase: function( string ) {
		return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
	},

	nodeName: function( elem, name ) {
		return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
	},

	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.1, IE<9
	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 ) {
		var len;

		if ( arr ) {
			if ( indexOf ) {
				return indexOf.call( arr, elem, i );
			}

			len = arr.length;
			i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;

			for ( ; i < len; i++ ) {

				// Skip accessing in sparse arrays
				if ( i in arr && arr[ i ] === elem ) {
					return i;
				}
			}
		}

		return -1;
	},

	merge: function( first, second ) {
		var len = +second.length,
			j = 0,
			i = first.length;

		while ( j < len ) {
			first[ i++ ] = second[ j++ ];
		}

		// Support: IE<9
		// Workaround casting of .length to NaN on otherwise arraylike objects (e.g., NodeLists)
		if ( len !== len ) {
			while ( second[ j ] !== undefined ) {
				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 args, proxy, tmp;

		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: function() {
		return +( new Date() );
	},

	// jQuery.support is not used in Core but other projects attach their
	// properties to it so it needs to exist.
	support: support
} );

// JSHint would error on this code due to the Symbol not being defined in ES5.
// Defining this global in .jshintrc would create a danger of using the global
// unguarded in another place, it seems safer to just disable JSHint for these
// three lines.
/* jshint ignore: start */
if ( typeof Symbol === "function" ) {
	jQuery.fn[ Symbol.iterator ] = deletedIds[ Symbol.iterator ];
}
/* jshint ignore: end */

// 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: iOS 8.2 (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.2.1
 * http://sizzlejs.com/
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 *
 * Date: 2015-10-17
 */
(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;
	},

	// General-purpose constants
	MAX_NEGATIVE = 1 << 31,

	// Instance methods
	hasOwn = ({}).hasOwnProperty,
	arr = [],
	pop = arr.pop,
	push_native = arr.push,
	push = arr.push,
	slice = arr.slice,
	// Use a stripped-down indexOf as it's faster than native
	// http://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-]|[^\\x00-\\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 = /[+~]/,
	rescape = /'|\\/g,

	// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
	runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
	funescape = function( _, escaped, escapedWhitespace ) {
		var high = "0x" + escaped - 0x10000;
		// NaN means non-codepoint
		// Support: Firefox<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 );
	},

	// Used for iframes
	// See setDocument()
	// Removing the function wrapper causes a "Permission Denied"
	// error in IE
	unloadHandler = function() {
		setDocument();
	};

// 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, nidselect, 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( rescape, "\\$&" );
					} else {
						context.setAttribute( "id", (nid = expando) );
					}

					// Prefix every selector in the list
					groups = tokenize( selector );
					i = groups.length;
					nidselect = ridentifier.test( nid ) ? "#" + nid : "[id='" + nid + "']";
					while ( i-- ) {
						groups[i] = nidselect + " " + 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 div and expects a boolean result
 */
function assert( fn ) {
	var div = document.createElement("div");

	try {
		return !!fn( div );
	} catch (e) {
		return false;
	} finally {
		// Remove from its parent by default
		if ( div.parentNode ) {
			div.parentNode.removeChild( div );
		}
		// release memory in IE
		div = null;
	}
}

/**
 * Adds the same handler for all of the specified attrs
 * @param {String} attrs Pipe-separated list of attributes
 * @param {Function} handler The method that will be applied
 */
function addHandle( attrs, handler ) {
	var arr = attrs.split("|"),
		i = 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 &&
			( ~b.sourceIndex || MAX_NEGATIVE ) -
			( ~a.sourceIndex || MAX_NEGATIVE );

	// Use IE sourceIndex if available on both nodes
	if ( diff ) {
		return diff;
	}

	// Check if b follows a
	if ( cur ) {
		while ( (cur = cur.nextSibling) ) {
			if ( cur === b ) {
				return -1;
			}
		}
	}

	return a ? 1 : -1;
}

/**
 * Returns a function to use in pseudos for input types
 * @param {String} type
 */
function createInputPseudo( type ) {
	return function( elem ) {
		var name = elem.nodeName.toLowerCase();
		return name === "input" && elem.type === type;
	};
}

/**
 * Returns a function to use in pseudos for buttons
 * @param {String} type
 */
function createButtonPseudo( type ) {
	return function( elem ) {
		var name = elem.nodeName.toLowerCase();
		return (name === "input" || name === "button") && elem.type === type;
	};
}

/**
 * Returns a function to use in pseudos for positionals
 * @param {Function} fn
 */
function createPositionalPseudo( fn ) {
	return markFunction(function( argument ) {
		argument = +argument;
		return markFunction(function( seed, matches ) {
			var j,
				matchIndexes = fn( [], seed.length, argument ),
				i = matchIndexes.length;

			// Match elements found at the specified indexes
			while ( i-- ) {
				if ( seed[ (j = matchIndexes[i]) ] ) {
					seed[j] = !(matches[j] = seed[j]);
				}
			}
		});
	});
}

/**
 * Checks a node for validity as a Sizzle context
 * @param {Element|Object=} context
 * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
 */
function testContext( context ) {
	return context && typeof context.getElementsByTagName !== "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, parent,
		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 ( (parent = document.defaultView) && parent.top !== parent ) {
		// Support: IE 11
		if ( parent.addEventListener ) {
			parent.addEventListener( "unload", unloadHandler, false );

		// Support: IE 9 - 10 only
		} else if ( parent.attachEvent ) {
			parent.attachEvent( "onunload", unloadHandler );
		}
	}

	/* Attributes
	---------------------------------------------------------------------- */

	// Support: IE<8
	// Verify that getAttribute really returns attributes and not properties
	// (excepting IE8 booleans)
	support.attributes = assert(function( div ) {
		div.className = "i";
		return !div.getAttribute("className");
	});

	/* getElement(s)By*
	---------------------------------------------------------------------- */

	// Check if getElementsByTagName("*") returns only elements
	support.getElementsByTagName = assert(function( div ) {
		div.appendChild( document.createComment("") );
		return !div.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 programatically-set names,
	// so use a roundabout getElementsByName test
	support.getById = assert(function( div ) {
		docElem.appendChild( div ).id = expando;
		return !document.getElementsByName || !document.getElementsByName( expando ).length;
	});

	// ID find and filter
	if ( support.getById ) {
		Expr.find["ID"] = function( id, context ) {
			if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
				var m = context.getElementById( id );
				return m ? [ m ] : [];
			}
		};
		Expr.filter["ID"] = function( id ) {
			var attrId = id.replace( runescape, funescape );
			return function( elem ) {
				return elem.getAttribute("id") === attrId;
			};
		};
	} else {
		// Support: IE6/7
		// getElementById is not reliable as a find shortcut
		delete Expr.find["ID"];

		Expr.filter["ID"] =  function( id ) {
			var attrId = id.replace( runescape, funescape );
			return function( elem ) {
				var node = typeof elem.getAttributeNode !== "undefined" &&
					elem.getAttributeNode("id");
				return node && node.value === attrId;
			};
		};
	}

	// 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 http://bugs.jquery.com/ticket/13378
	rbuggyQSA = [];

	if ( (support.qsa = rnative.test( document.querySelectorAll )) ) {
		// Build QSA regex
		// Regex strategy adopted from Diego Perini
		assert(function( div ) {
			// Select is set to empty string on purpose
			// This is to test IE's treatment of not explicitly
			// setting a boolean content attribute,
			// since its presence should be enough
			// http://bugs.jquery.com/ticket/12359
			docElem.appendChild( div ).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
			// http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
			if ( div.querySelectorAll("[msallowcapture^='']").length ) {
				rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
			}

			// Support: IE8
			// Boolean attributes and "value" are not treated correctly
			if ( !div.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 ( !div.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 ( !div.querySelectorAll(":checked").length ) {
				rbuggyQSA.push(":checked");
			}

			// Support: Safari 8+, iOS 8+
			// https://bugs.webkit.org/show_bug.cgi?id=136851
			// In-page `selector#id sibing-combinator selector` fails
			if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) {
				rbuggyQSA.push(".#.+[+~]");
			}
		});

		assert(function( div ) {
			// Support: Windows 8 Native Apps
			// The type and name attributes are restricted during .innerHTML assignment
			var input = document.createElement("input");
			input.setAttribute( "type", "hidden" );
			div.appendChild( input ).setAttribute( "name", "D" );

			// Support: IE8
			// Enforce case-sensitivity of name attribute
			if ( div.querySelectorAll("[name=d]").length ) {
				rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
			}

			// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
			// IE8 throws error here and will not see later tests
			if ( !div.querySelectorAll(":enabled").length ) {
				rbuggyQSA.push( ":enabled", ":disabled" );
			}

			// Opera 10-11 does not throw on post-comma invalid pseudos
			div.querySelectorAll("*,:x");
			rbuggyQSA.push(",.*:");
		});
	}

	if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
		docElem.webkitMatchesSelector ||
		docElem.mozMatchesSelector ||
		docElem.oMatchesSelector ||
		docElem.msMatchesSelector) )) ) {

		assert(function( div ) {
			// Check to see if it's possible to do matchesSelector
			// on a disconnected node (IE 9)
			support.disconnectedMatch = matches.call( div, "div" );

			// This should fail with an exception
			// Gecko does not error, returns false instead
			matches.call( div, "[s!='']:x" );
			rbuggyMatches.push( "!=", pseudos );
		});
	}

	rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
	rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );

	/* Contains
	---------------------------------------------------------------------- */
	hasCompare = rnative.test( docElem.compareDocumentPosition );

	// Element contains another
	// Purposefully 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.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": function( elem ) {
			return elem.disabled === false;
		},

		"disabled": function( elem ) {
			return elem.disabled === true;
		},

		"checked": function( elem ) {
			// In CSS3, :checked should return both checked and selected elements
			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
			var nodeName = elem.nodeName.toLowerCase();
			return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
		},

		"selected": function( elem ) {
			// Accessing this property makes selected-by-default
			// options in Safari work properly
			if ( elem.parentNode ) {
				elem.parentNode.selectedIndex;
			}

			return elem.selected === true;
		},

		// Contents
		"empty": function( elem ) {
			// http://www.w3.org/TR/selectors/#empty-pseudo
			// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
			//   but not by others (comment: 8; processing instruction: 7; etc.)
			// nodeType < 6 works because attributes (2) do not appear as children
			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
				if ( elem.nodeType < 6 ) {
					return false;
				}
			}
			return true;
		},

		"parent": function( elem ) {
			return !Expr.pseudos["empty"]( elem );
		},

		// Element/input types
		"header": function( elem ) {
			return rheader.test( elem.nodeName );
		},

		"input": function( elem ) {
			return rinputs.test( elem.nodeName );
		},

		"button": function( elem ) {
			var name = elem.nodeName.toLowerCase();
			return name === "input" && elem.type === "button" || name === "button";
		},

		"text": function( elem ) {
			var attr;
			return elem.nodeName.toLowerCase() === "input" &&
				elem.type === "text" &&

				// Support: IE<8
				// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
				( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
		},

		// Position-in-collection
		"first": createPositionalPseudo(function() {
			return [ 0 ];
		}),

		"last": createPositionalPseudo(function( matchIndexes, length ) {
			return [ length - 1 ];
		}),

		"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
			return [ argument < 0 ? argument + length : argument ];
		}),

		"even": createPositionalPseudo(function( matchIndexes, length ) {
			var i = 0;
			for ( ; i < length; i += 2 ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		}),

		"odd": createPositionalPseudo(function( matchIndexes, length ) {
			var i = 1;
			for ( ; i < length; i += 2 ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		}),

		"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
			var i = argument < 0 ? argument + length : argument;
			for ( ; --i >= 0; ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		}),

		"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
			var i = argument < 0 ? argument + length : argument;
			for ( ; ++i < length; ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		})
	}
};

Expr.pseudos["nth"] = Expr.pseudos["eq"];

// Add button/input type pseudos
for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
	Expr.pseudos[ i ] = createInputPseudo( i );
}
for ( i in { submit: true, reset: true } ) {
	Expr.pseudos[ i ] = createButtonPseudo( i );
}

// Easy API for creating new setFilters
function setFilters() {}
setFilters.prototype = Expr.filters = Expr.pseudos;
Expr.setFilters = new setFilters();

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,
		checkNonElements = base && dir === "parentNode",
		doneName = done++;

	return combinator.first ?
		// Check against closest ancestor/preceding element
		function( elem, context, xml ) {
			while ( (elem = elem[ dir ]) ) {
				if ( elem.nodeType === 1 || checkNonElements ) {
					return matcher( elem, context, xml );
				}
			}
		} :

		// Check against all ancestor/preceding elements
		function( elem, context, xml ) {
			var oldCache, 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 ( (oldCache = uniqueCache[ dir ]) &&
							oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {

							// Assign to newCache so results back-propagate to previous elements
							return (newCache[ 2 ] = oldCache[ 2 ]);
						} else {
							// Reuse newcache so results back-propagate to previous elements
							uniqueCache[ dir ] = newCache;

							// A match means we're done; a fail means we have to keep checking
							if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
								return true;
							}
						}
					}
				}
			}
		};
}

function elementMatcher( matchers ) {
	return matchers.length > 1 ?
		function( elem, context, xml ) {
			var i = matchers.length;
			while ( i-- ) {
				if ( !matchers[i]( elem, context, xml ) ) {
					return false;
				}
			}
			return true;
		} :
		matchers[0];
}

function 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" &&
				support.getById && context.nodeType === 9 && documentIsHTML &&
				Expr.relative[ tokens[1].type ] ) {

			context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
			if ( !context ) {
				return results;

			// 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( div1 ) {
	// Should return 1, but returns 4 (following)
	return div1.compareDocumentPosition( document.createElement("div") ) & 1;
});

// Support: IE<8
// Prevent attribute/property "interpolation"
// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
if ( !assert(function( div ) {
	div.innerHTML = "<a href='#'></a>";
	return div.firstChild.getAttribute("href") === "#" ;
}) ) {
	addHandle( "type|href|height|width", function( elem, name, isXML ) {
		if ( !isXML ) {
			return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
		}
	});
}

// Support: IE<9
// Use defaultValue in place of getAttribute("value")
if ( !support.attributes || !assert(function( div ) {
	div.innerHTML = "<input/>";
	div.firstChild.setAttribute( "value", "" );
	return div.firstChild.getAttribute( "value" ) === "";
}) ) {
	addHandle( "value", function( elem, name, isXML ) {
		if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
			return elem.defaultValue;
		}
	});
}

// Support: IE<9
// Use getAttributeNode to fetch booleans when getAttribute lies
if ( !assert(function( div ) {
	return div.getAttribute("disabled") == null;
}) ) {
	addHandle( booleans, function( elem, name, isXML ) {
		var val;
		if ( !isXML ) {
			return elem[ name ] === true ? name.toLowerCase() :
					(val = elem.getAttributeNode( name )) && val.specified ?
					val.value :
				null;
		}
	});
}

return Sizzle;

})( window );



jQuery.find = Sizzle;
jQuery.expr = Sizzle.selectors;
jQuery.expr[ ":" ] = jQuery.expr.pseudos;
jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;
jQuery.text = Sizzle.getText;
jQuery.isXMLDoc = Sizzle.isXML;
jQuery.contains = Sizzle.contains;



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;

var rsingleTag = ( /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/ );



var risSimple = /^.[^:#\[\.,]*$/;

// Implement the identical functionality for filter and not
function winnow( elements, qualifier, not ) {
	if ( jQuery.isFunction( qualifier ) ) {
		return jQuery.grep( elements, function( elem, i ) {
			/* jshint -W018 */
			return !!qualifier.call( elem, i, elem ) !== not;
		} );

	}

	if ( qualifier.nodeType ) {
		return jQuery.grep( elements, function( elem ) {
			return ( elem === qualifier ) !== not;
		} );

	}

	if ( typeof qualifier === "string" ) {
		if ( risSimple.test( qualifier ) ) {
			return jQuery.filter( qualifier, elements, not );
		}

		qualifier = jQuery.filter( qualifier, elements );
	}

	return jQuery.grep( elements, function( elem ) {
		return ( jQuery.inArray( elem, qualifier ) > -1 ) !== not;
	} );
}

jQuery.filter = function( expr, elems, not ) {
	var elem = elems[ 0 ];

	if ( not ) {
		expr = ":not(" + expr + ")";
	}

	return elems.length === 1 && elem.nodeType === 1 ?
		jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
		jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
			return elem.nodeType === 1;
		} ) );
};

jQuery.fn.extend( {
	find: function( selector ) {
		var i,
			ret = [],
			self = this,
			len = self.length;

		if ( typeof selector !== "string" ) {
			return this.pushStack( jQuery( selector ).filter( function() {
				for ( i = 0; i < len; i++ ) {
					if ( jQuery.contains( self[ i ], this ) ) {
						return true;
					}
				}
			} ) );
		}

		for ( i = 0; i < len; i++ ) {
			jQuery.find( selector, self[ i ], ret );
		}

		// Needed because $( selector, context ) becomes $( context ).find( selector )
		ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
		ret.selector = this.selector ? this.selector + " " + selector : selector;
		return ret;
	},
	filter: function( selector ) {
		return this.pushStack( winnow( this, selector || [], false ) );
	},
	not: function( selector ) {
		return this.pushStack( winnow( this, selector || [], true ) );
	},
	is: function( selector ) {
		return !!winnow(
			this,

			// If this is a positional/relative selector, check membership in the returned set
			// so $("p:first").is("p:last") won't return true for a doc with two "p".
			typeof selector === "string" && rneedsContext.test( selector ) ?
				jQuery( selector ) :
				selector || [],
			false
		).length;
	}
} );


// Initialize a jQuery object


// A central reference to the root jQuery(document)
var rootjQuery,

	// A simple way to check for HTML strings
	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
	// Strict HTML recognition (#11290: must start with <)
	rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,

	init = jQuery.fn.init = function( selector, context, root ) {
		var match, elem;

		// HANDLE: $(""), $(null), $(undefined), $(false)
		if ( !selector ) {
			return this;
		}

		// 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.charAt( 0 ) === "<" &&
				selector.charAt( selector.length - 1 ) === ">" &&
				selector.length >= 3 ) {

				// Assume that strings that start and end with <> are HTML and skip the regex check
				match = [ null, selector, null ];

			} else {
				match = rquickExpr.exec( selector );
			}

			// Match html or make sure no context is specified for #id
			if ( match && ( match[ 1 ] || !context ) ) {

				// HANDLE: $(html) -> $(array)
				if ( match[ 1 ] ) {
					context = context instanceof jQuery ? context[ 0 ] : context;

					// scripts is true for back-compat
					// Intentionally let the error be thrown if parseHTML is not present
					jQuery.merge( this, jQuery.parseHTML(
						match[ 1 ],
						context && context.nodeType ? context.ownerDocument || context : document,
						true
					) );

					// HANDLE: $(html, props)
					if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
						for ( match in context ) {

							// Properties of context are called as methods if possible
							if ( jQuery.isFunction( this[ match ] ) ) {
								this[ match ]( context[ match ] );

							// ...and otherwise set as attributes
							} else {
								this.attr( match, context[ match ] );
							}
						}
					}

					return this;

				// HANDLE: $(#id)
				} else {
					elem = document.getElementById( match[ 2 ] );

					// Check parentNode to catch when Blackberry 4.6 returns
					// nodes that are no longer in the document #6963
					if ( elem && elem.parentNode ) {

						// Handle the case where IE and Opera return items
						// by name instead of ID
						if ( elem.id !== match[ 2 ] ) {
							return rootjQuery.find( selector );
						}

						// Otherwise, we inject the element directly into the jQuery object
						this.length = 1;
						this[ 0 ] = elem;
					}

					this.context = document;
					this.selector = selector;
					return this;
				}

			// HANDLE: $(expr, $(...))
			} else if ( !context || context.jquery ) {
				return ( context || 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.context = this[ 0 ] = selector;
			this.length = 1;
			return this;

		// HANDLE: $(function)
		// Shortcut for document ready
		} else if ( jQuery.isFunction( selector ) ) {
			return typeof root.ready !== "undefined" ?
				root.ready( selector ) :

				// Execute immediately if ready is not present
				selector( jQuery );
		}

		if ( selector.selector !== undefined ) {
			this.selector = selector.selector;
			this.context = selector.context;
		}

		return jQuery.makeArray( selector, this );
	};

// Give the init function the jQuery prototype for later instantiation
init.prototype = jQuery.fn;

// Initialize central reference
rootjQuery = jQuery( document );


var rparentsprev = /^(?:parents|prev(?:Until|All))/,

	// methods guaranteed to produce a unique set when starting from a unique set
	guaranteedUnique = {
		children: true,
		contents: true,
		next: true,
		prev: true
	};

jQuery.fn.extend( {
	has: function( target ) {
		var i,
			targets = jQuery( target, this ),
			len = targets.length;

		return this.filter( function() {
			for ( i = 0; i < len; i++ ) {
				if ( jQuery.contains( this, targets[ i ] ) ) {
					return true;
				}
			}
		} );
	},

	closest: function( selectors, context ) {
		var cur,
			i = 0,
			l = this.length,
			matched = [],
			pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
				jQuery( selectors, context || this.context ) :
				0;

		for ( ; i < l; i++ ) {
			for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {

				// Always skip document fragments
				if ( cur.nodeType < 11 && ( pos ?
					pos.index( cur ) > -1 :

					// Don't pass non-elements to Sizzle
					cur.nodeType === 1 &&
						jQuery.find.matchesSelector( cur, selectors ) ) ) {

					matched.push( cur );
					break;
				}
			}
		}

		return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
	},

	// Determine the position of an element within
	// the matched set of elements
	index: function( elem ) {

		// No argument, return index in parent
		if ( !elem ) {
			return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
		}

		// index in selector
		if ( typeof elem === "string" ) {
			return jQuery.inArray( this[ 0 ], jQuery( elem ) );
		}

		// Locate the position of the desired element
		return jQuery.inArray(

			// If it receives a jQuery object, the first element is used
			elem.jquery ? elem[ 0 ] : elem, this );
	},

	add: function( selector, context ) {
		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 ) {
	do {
		cur = cur[ dir ];
	} while ( cur && 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 ) {
		return jQuery.nodeName( elem, "iframe" ) ?
			elem.contentDocument || elem.contentWindow.document :
			jQuery.merge( [], elem.childNodes );
	}
}, function( name, fn ) {
	jQuery.fn[ name ] = function( until, selector ) {
		var ret = jQuery.map( this, fn, until );

		if ( name.slice( -5 ) !== "Until" ) {
			selector = until;
		}

		if ( selector && typeof selector === "string" ) {
			ret = jQuery.filter( selector, ret );
		}

		if ( this.length > 1 ) {

			// Remove duplicates
			if ( !guaranteedUnique[ name ] ) {
				ret = jQuery.uniqueSort( ret );
			}

			// Reverse order for parents* and prev-derivatives
			if ( rparentsprev.test( name ) ) {
				ret = ret.reverse();
			}
		}

		return this.pushStack( ret );
	};
} );
var rnotwhite = ( /\S+/g );



// Convert String-formatted options into Object-formatted ones
function createOptions( options ) {
	var object = {};
	jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
		object[ flag ] = true;
	} );
	return object;
}

/*
 * Create a callback list using the following parameters:
 *
 *	options: an optional list of space-separated options that will change how
 *			the callback list behaves or a more traditional option object
 *
 * By default a callback list will act like an event callback list and can be
 * "fired" multiple times.
 *
 * Possible options:
 *
 *	once:			will ensure the callback list can only be fired once (like a Deferred)
 *
 *	memory:			will keep track of previous values and will call any callback added
 *					after the list has been fired right away with the latest "memorized"
 *					values (like a Deferred)
 *
 *	unique:			will ensure a callback can only be added once (no duplicate in the list)
 *
 *	stopOnFalse:	interrupt callings when a callback returns false
 *
 */
jQuery.Callbacks = function( options ) {

	// Convert options from String-formatted to Object-formatted if needed
	// (we check in cache first)
	options = typeof options === "string" ?
		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 = 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 = true;
				if ( !memory ) {
					self.disable();
				}
				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;
};


jQuery.extend( {

	Deferred: function( func ) {
		var tuples = [

				// action, add listener, listener list, final state
				[ "resolve", "done", jQuery.Callbacks( "once memory" ), "resolved" ],
				[ "reject", "fail", jQuery.Callbacks( "once memory" ), "rejected" ],
				[ "notify", "progress", jQuery.Callbacks( "memory" ) ]
			],
			state = "pending",
			promise = {
				state: function() {
					return state;
				},
				always: function() {
					deferred.done( arguments ).fail( arguments );
					return this;
				},
				then: function( /* fnDone, fnFail, fnProgress */ ) {
					var fns = arguments;
					return jQuery.Deferred( function( newDefer ) {
						jQuery.each( tuples, function( i, tuple ) {
							var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];

							// deferred[ done | fail | progress ] for forwarding actions to newDefer
							deferred[ tuple[ 1 ] ]( function() {
								var returned = fn && fn.apply( this, arguments );
								if ( returned && jQuery.isFunction( returned.promise ) ) {
									returned.promise()
										.progress( newDefer.notify )
										.done( newDefer.resolve )
										.fail( newDefer.reject );
								} else {
									newDefer[ tuple[ 0 ] + "With" ](
										this === promise ? newDefer.promise() : this,
										fn ? [ returned ] : arguments
									);
								}
							} );
						} );
						fns = null;
					} ).promise();
				},

				// Get a promise for this deferred
				// If obj is provided, the promise aspect is added to the object
				promise: function( obj ) {
					return obj != null ? jQuery.extend( obj, promise ) : promise;
				}
			},
			deferred = {};

		// Keep pipe for back-compat
		promise.pipe = promise.then;

		// Add list-specific methods
		jQuery.each( tuples, function( i, tuple ) {
			var list = tuple[ 2 ],
				stateString = tuple[ 3 ];

			// promise[ done | fail | progress ] = list.add
			promise[ tuple[ 1 ] ] = list.add;

			// Handle state
			if ( stateString ) {
				list.add( function() {

					// state = [ resolved | rejected ]
					state = stateString;

				// [ reject_list | resolve_list ].disable; progress_list.lock
				}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
			}

			// deferred[ resolve | reject | notify ]
			deferred[ tuple[ 0 ] ] = function() {
				deferred[ tuple[ 0 ] + "With" ]( this === deferred ? promise : this, arguments );
				return this;
			};
			deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
		} );

		// Make the deferred a promise
		promise.promise( deferred );

		// Call given func if any
		if ( func ) {
			func.call( deferred, deferred );
		}

		// All done!
		return deferred;
	},

	// Deferred helper
	when: function( subordinate /* , ..., subordinateN */ ) {
		var i = 0,
			resolveValues = slice.call( arguments ),
			length = resolveValues.length,

			// the count of uncompleted subordinates
			remaining = length !== 1 ||
				( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,

			// the master Deferred.
			// If resolveValues consist of only a single Deferred, just use that.
			deferred = remaining === 1 ? subordinate : jQuery.Deferred(),

			// Update function for both resolve and progress values
			updateFunc = function( i, contexts, values ) {
				return function( value ) {
					contexts[ i ] = this;
					values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
					if ( values === progressValues ) {
						deferred.notifyWith( contexts, values );

					} else if ( !( --remaining ) ) {
						deferred.resolveWith( contexts, values );
					}
				};
			},

			progressValues, progressContexts, resolveContexts;

		// add listeners to Deferred subordinates; treat others as resolved
		if ( length > 1 ) {
			progressValues = new Array( length );
			progressContexts = new Array( length );
			resolveContexts = new Array( length );
			for ( ; i < length; i++ ) {
				if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
					resolveValues[ i ].promise()
						.progress( updateFunc( i, progressContexts, progressValues ) )
						.done( updateFunc( i, resolveContexts, resolveValues ) )
						.fail( deferred.reject );
				} else {
					--remaining;
				}
			}
		}

		// if we're not waiting on anything, resolve the master
		if ( !remaining ) {
			deferred.resolveWith( resolveContexts, resolveValues );
		}

		return deferred.promise();
	}
} );


// The deferred used on DOM ready
var readyList;

jQuery.fn.ready = function( fn ) {

	// Add the callback
	jQuery.ready.promise().done( fn );

	return this;
};

jQuery.extend( {

	// Is the DOM ready to be used? Set to true once it occurs.
	isReady: false,

	// A counter to track how many items to wait for before
	// the ready event fires. See #6781
	readyWait: 1,

	// Hold (or release) the ready event
	holdReady: function( hold ) {
		if ( hold ) {
			jQuery.readyWait++;
		} else {
			jQuery.ready( true );
		}
	},

	// Handle when the DOM is ready
	ready: function( wait ) {

		// Abort if there are pending holds or we're already ready
		if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
			return;
		}

		// Remember that the DOM is ready
		jQuery.isReady = true;

		// If a normal DOM Ready event fired, decrement, and wait if need be
		if ( wait !== true && --jQuery.readyWait > 0 ) {
			return;
		}

		// If there are functions bound, to execute
		readyList.resolveWith( document, [ jQuery ] );

		// Trigger any bound ready events
		if ( jQuery.fn.triggerHandler ) {
			jQuery( document ).triggerHandler( "ready" );
			jQuery( document ).off( "ready" );
		}
	}
} );

/**
 * Clean-up method for dom ready events
 */
function detach() {
	if ( document.addEventListener ) {
		document.removeEventListener( "DOMContentLoaded", completed );
		window.removeEventListener( "load", completed );

	} else {
		document.detachEvent( "onreadystatechange", completed );
		window.detachEvent( "onload", completed );
	}
}

/**
 * The ready event handler and self cleanup method
 */
function completed() {

	// readyState === "complete" is good enough for us to call the dom ready in oldIE
	if ( document.addEventListener ||
		window.event.type === "load" ||
		document.readyState === "complete" ) {

		detach();
		jQuery.ready();
	}
}

jQuery.ready.promise = function( obj ) {
	if ( !readyList ) {

		readyList = jQuery.Deferred();

		// Catch cases where $(document).ready() is called
		// after the browser event has already occurred.
		// Support: IE6-10
		// 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 );

		// Standards-based browsers support DOMContentLoaded
		} else if ( document.addEventListener ) {

			// Use the handy event callback
			document.addEventListener( "DOMContentLoaded", completed );

			// A fallback to window.onload, that will always work
			window.addEventListener( "load", completed );

		// If IE event model is used
		} else {

			// Ensure firing before onload, maybe late but safe also for iframes
			document.attachEvent( "onreadystatechange", completed );

			// A fallback to window.onload, that will always work
			window.attachEvent( "onload", completed );

			// If IE and not a frame
			// continually check to see if the document is ready
			var top = false;

			try {
				top = window.frameElement == null && document.documentElement;
			} catch ( e ) {}

			if ( top && top.doScroll ) {
				( function doScrollCheck() {
					if ( !jQuery.isReady ) {

						try {

							// Use the trick by Diego Perini
							// http://javascript.nwbox.com/IEContentLoaded/
							top.doScroll( "left" );
						} catch ( e ) {
							return window.setTimeout( doScrollCheck, 50 );
						}

						// detach all dom ready events
						detach();

						// and execute any waiting functions
						jQuery.ready();
					}
				} )();
			}
		}
	}
	return readyList.promise( obj );
};

// Kick off the DOM ready check even if the user does not
jQuery.ready.promise();




// Support: IE<9
// Iteration over object's inherited properties before its own
var i;
for ( i in jQuery( support ) ) {
	break;
}
support.ownFirst = i === "0";

// Note: most support tests are defined in their respective modules.
// false until the test is run
support.inlineBlockNeedsLayout = false;

// Execute ASAP in case we need to set body.style.zoom
jQuery( function() {

	// Minified: var a,b,c,d
	var val, div, body, container;

	body = document.getElementsByTagName( "body" )[ 0 ];
	if ( !body || !body.style ) {

		// Return for frameset docs that don't have a body
		return;
	}

	// Setup
	div = document.createElement( "div" );
	container = document.createElement( "div" );
	container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px";
	body.appendChild( container ).appendChild( div );

	if ( typeof div.style.zoom !== "undefined" ) {

		// Support: IE<8
		// Check if natively block-level elements act like inline-block
		// elements when setting their display to 'inline' and giving
		// them layout
		div.style.cssText = "display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1";

		support.inlineBlockNeedsLayout = val = div.offsetWidth === 3;
		if ( val ) {

			// Prevent IE 6 from affecting layout for positioned elements #11048
			// Prevent IE from shrinking the body in IE 7 mode #12869
			// Support: IE<8
			body.style.zoom = 1;
		}
	}

	body.removeChild( container );
} );


( function() {
	var div = document.createElement( "div" );

	// Support: IE<9
	support.deleteExpando = true;
	try {
		delete div.test;
	} catch ( e ) {
		support.deleteExpando = false;
	}

	// Null elements to avoid leaks in IE.
	div = null;
} )();
var acceptData = function( elem ) {
	var noData = jQuery.noData[ ( elem.nodeName + " " ).toLowerCase() ],
		nodeType = +elem.nodeType || 1;

	// Do not set data on non-element DOM nodes because it will not be cleared (#8335).
	return nodeType !== 1 && nodeType !== 9 ?
		false :

		// Nodes accept data unless otherwise specified; rejection can be conditional
		!noData || noData !== true && elem.getAttribute( "classid" ) === noData;
};




var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
	rmultiDash = /([A-Z])/g;

function dataAttr( elem, key, data ) {

	// If nothing was found internally, try to fetch any
	// data from the HTML5 data-* attribute
	if ( data === undefined && elem.nodeType === 1 ) {

		var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();

		data = elem.getAttribute( name );

		if ( typeof data === "string" ) {
			try {
				data = data === "true" ? true :
					data === "false" ? false :
					data === "null" ? null :

					// Only convert to a number if it doesn't change the string
					+data + "" === data ? +data :
					rbrace.test( data ) ? jQuery.parseJSON( data ) :
					data;
			} catch ( e ) {}

			// Make sure we set the data so it isn't changed later
			jQuery.data( elem, key, data );

		} else {
			data = undefined;
		}
	}

	return data;
}

// checks a cache object for emptiness
function isEmptyDataObject( obj ) {
	var name;
	for ( name in obj ) {

		// if the public data object is empty, the private is still empty
		if ( name === "data" && jQuery.isEmptyObject( obj[ name ] ) ) {
			continue;
		}
		if ( name !== "toJSON" ) {
			return false;
		}
	}

	return true;
}

function internalData( elem, name, data, pvt /* Internal Use Only */ ) {
	if ( !acceptData( elem ) ) {
		return;
	}

	var ret, thisCache,
		internalKey = jQuery.expando,

		// We have to handle DOM nodes and JS objects differently because IE6-7
		// can't GC object references properly across the DOM-JS boundary
		isNode = elem.nodeType,

		// Only DOM nodes need the global jQuery cache; JS object data is
		// attached directly to the object so GC can occur automatically
		cache = isNode ? jQuery.cache : elem,

		// Only defining an ID for JS objects if its cache already exists allows
		// the code to shortcut on the same path as a DOM node with no cache
		id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;

	// Avoid doing any more work than we need to when trying to get data on an
	// object that has no data at all
	if ( ( !id || !cache[ id ] || ( !pvt && !cache[ id ].data ) ) &&
		data === undefined && typeof name === "string" ) {
		return;
	}

	if ( !id ) {

		// Only DOM nodes need a new unique ID for each element since their data
		// ends up in the global cache
		if ( isNode ) {
			id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++;
		} else {
			id = internalKey;
		}
	}

	if ( !cache[ id ] ) {

		// Avoid exposing jQuery metadata on plain JS objects when the object
		// is serialized using JSON.stringify
		cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
	}

	// An object can be passed to jQuery.data instead of a key/value pair; this gets
	// shallow copied over onto the existing cache
	if ( typeof name === "object" || typeof name === "function" ) {
		if ( pvt ) {
			cache[ id ] = jQuery.extend( cache[ id ], name );
		} else {
			cache[ id ].data = jQuery.extend( cache[ id ].data, name );
		}
	}

	thisCache = cache[ id ];

	// jQuery data() is stored in a separate object inside the object's internal data
	// cache in order to avoid key collisions between internal data and user-defined
	// data.
	if ( !pvt ) {
		if ( !thisCache.data ) {
			thisCache.data = {};
		}

		thisCache = thisCache.data;
	}

	if ( data !== undefined ) {
		thisCache[ jQuery.camelCase( name ) ] = data;
	}

	// Check for both converted-to-camel and non-converted data property names
	// If a data property was specified
	if ( typeof name === "string" ) {

		// First Try to find as-is property data
		ret = thisCache[ name ];

		// Test for null|undefined property data
		if ( ret == null ) {

			// Try to find the camelCased property
			ret = thisCache[ jQuery.camelCase( name ) ];
		}
	} else {
		ret = thisCache;
	}

	return ret;
}

function internalRemoveData( elem, name, pvt ) {
	if ( !acceptData( elem ) ) {
		return;
	}

	var thisCache, i,
		isNode = elem.nodeType,

		// See jQuery.data for more information
		cache = isNode ? jQuery.cache : elem,
		id = isNode ? elem[ jQuery.expando ] : jQuery.expando;

	// If there is already no cache entry for this object, there is no
	// purpose in continuing
	if ( !cache[ id ] ) {
		return;
	}

	if ( name ) {

		thisCache = pvt ? cache[ id ] : cache[ id ].data;

		if ( thisCache ) {

			// Support array or space separated string names for data keys
			if ( !jQuery.isArray( name ) ) {

				// try the string as a key before any manipulation
				if ( name in thisCache ) {
					name = [ name ];
				} else {

					// split the camel cased version by spaces unless a key with the spaces exists
					name = jQuery.camelCase( name );
					if ( name in thisCache ) {
						name = [ name ];
					} else {
						name = name.split( " " );
					}
				}
			} else {

				// If "name" is an array of keys...
				// When data is initially created, via ("key", "val") signature,
				// keys will be converted to camelCase.
				// Since there is no way to tell _how_ a key was added, remove
				// both plain key and camelCase key. #12786
				// This will only penalize the array argument path.
				name = name.concat( jQuery.map( name, jQuery.camelCase ) );
			}

			i = name.length;
			while ( i-- ) {
				delete thisCache[ name[ i ] ];
			}

			// If there is no data left in the cache, we want to continue
			// and let the cache object itself get destroyed
			if ( pvt ? !isEmptyDataObject( thisCache ) : !jQuery.isEmptyObject( thisCache ) ) {
				return;
			}
		}
	}

	// See jQuery.data for more information
	if ( !pvt ) {
		delete cache[ id ].data;

		// Don't destroy the parent cache unless the internal data object
		// had been the only thing left in it
		if ( !isEmptyDataObject( cache[ id ] ) ) {
			return;
		}
	}

	// Destroy the cache
	if ( isNode ) {
		jQuery.cleanData( [ elem ], true );

	// Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
	/* jshint eqeqeq: false */
	} else if ( support.deleteExpando || cache != cache.window ) {
		/* jshint eqeqeq: true */
		delete cache[ id ];

	// When all else fails, undefined
	} else {
		cache[ id ] = undefined;
	}
}

jQuery.extend( {
	cache: {},

	// The following elements (space-suffixed to avoid Object.prototype collisions)
	// throw uncatchable exceptions if you attempt to set expando properties
	noData: {
		"applet ": true,
		"embed ": true,

		// ...but Flash objects (which have this classid) *can* handle expandos
		"object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
	},

	hasData: function( elem ) {
		elem = elem.nodeType ? jQuery.cache[ elem[ jQuery.expando ] ] : elem[ jQuery.expando ];
		return !!elem && !isEmptyDataObject( elem );
	},

	data: function( elem, name, data ) {
		return internalData( elem, name, data );
	},

	removeData: function( elem, name ) {
		return internalRemoveData( elem, name );
	},

	// For internal use only.
	_data: function( elem, name, data ) {
		return internalData( elem, name, data, true );
	},

	_removeData: function( elem, name ) {
		return internalRemoveData( elem, name, true );
	}
} );

jQuery.fn.extend( {
	data: function( key, value ) {
		var i, name, data,
			elem = this[ 0 ],
			attrs = elem && elem.attributes;

		// Special expections of .data basically thwart jQuery.access,
		// so implement the relevant behavior ourselves

		// Gets all values
		if ( key === undefined ) {
			if ( this.length ) {
				data = jQuery.data( elem );

				if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
					i = attrs.length;
					while ( i-- ) {

						// Support: IE11+
						// 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 ] );
							}
						}
					}
					jQuery._data( elem, "parsedAttrs", true );
				}
			}

			return data;
		}

		// Sets multiple values
		if ( typeof key === "object" ) {
			return this.each( function() {
				jQuery.data( this, key );
			} );
		}

		return arguments.length > 1 ?

			// Sets one value
			this.each( function() {
				jQuery.data( this, key, value );
			} ) :

			// Gets one value
			// Try to fetch any internally stored data first
			elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined;
	},

	removeData: function( key ) {
		return this.each( function() {
			jQuery.removeData( this, key );
		} );
	}
} );


jQuery.extend( {
	queue: function( elem, type, data ) {
		var queue;

		if ( elem ) {
			type = ( type || "fx" ) + "queue";
			queue = jQuery._data( elem, type );

			// Speed up dequeue by getting out quickly if this is just a lookup
			if ( data ) {
				if ( !queue || jQuery.isArray( data ) ) {
					queue = jQuery._data( elem, type, jQuery.makeArray( data ) );
				} else {
					queue.push( data );
				}
			}
			return queue || [];
		}
	},

	dequeue: function( elem, type ) {
		type = type || "fx";

		var queue = jQuery.queue( elem, type ),
			startLength = queue.length,
			fn = queue.shift(),
			hooks = jQuery._queueHooks( elem, type ),
			next = function() {
				jQuery.dequeue( elem, type );
			};

		// If the fx queue is dequeued, always remove the progress sentinel
		if ( fn === "inprogress" ) {
			fn = queue.shift();
			startLength--;
		}

		if ( fn ) {

			// Add a progress sentinel to prevent the fx queue from being
			// automatically dequeued
			if ( type === "fx" ) {
				queue.unshift( "inprogress" );
			}

			// clear up the last queue stop function
			delete hooks.stop;
			fn.call( elem, next, hooks );
		}

		if ( !startLength && hooks ) {
			hooks.empty.fire();
		}
	},

	// not intended for public consumption - generates a queueHooks object,
	// or returns the current one
	_queueHooks: function( elem, type ) {
		var key = type + "queueHooks";
		return jQuery._data( elem, key ) || jQuery._data( elem, key, {
			empty: jQuery.Callbacks( "once memory" ).add( function() {
				jQuery._removeData( elem, type + "queue" );
				jQuery._removeData( elem, 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 = jQuery._data( elements[ i ], type + "queueHooks" );
			if ( tmp && tmp.empty ) {
				count++;
				tmp.empty.add( resolve );
			}
		}
		resolve();
		return defer.promise( obj );
	}
} );


( function() {
	var shrinkWrapBlocksVal;

	support.shrinkWrapBlocks = function() {
		if ( shrinkWrapBlocksVal != null ) {
			return shrinkWrapBlocksVal;
		}

		// Will be changed later if needed.
		shrinkWrapBlocksVal = false;

		// Minified: var b,c,d
		var div, body, container;

		body = document.getElementsByTagName( "body" )[ 0 ];
		if ( !body || !body.style ) {

			// Test fired too early or in an unsupported environment, exit.
			return;
		}

		// Setup
		div = document.createElement( "div" );
		container = document.createElement( "div" );
		container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px";
		body.appendChild( container ).appendChild( div );

		// Support: IE6
		// Check if elements with layout shrink-wrap their children
		if ( typeof div.style.zoom !== "undefined" ) {

			// Reset CSS: box-sizing; display; margin; border
			div.style.cssText =

				// Support: Firefox<29, Android 2.3
				// Vendor-prefix box-sizing
				"-webkit-box-sizing:content-box;-moz-box-sizing:content-box;" +
				"box-sizing:content-box;display:block;margin:0;border:0;" +
				"padding:1px;width:1px;zoom:1";
			div.appendChild( document.createElement( "div" ) ).style.width = "5px";
			shrinkWrapBlocksVal = div.offsetWidth !== 3;
		}

		body.removeChild( container );

		return shrinkWrapBlocksVal;
	};

} )();
var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;

var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );


var cssExpand = [ "Top", "Right", "Bottom", "Left" ];

var isHidden = function( elem, el ) {

		// isHidden might be called from jQuery#filter function;
		// in that case, element will be second argument
		elem = el || elem;
		return jQuery.css( elem, "display" ) === "none" ||
			!jQuery.contains( elem.ownerDocument, elem );
	};



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;
}


// 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,
		length = 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 < length; i++ ) {
				fn(
					elems[ i ],
					key,
					raw ? value : value.call( elems[ i ], i, fn( elems[ i ], key ) )
				);
			}
		}
	}

	return chainable ?
		elems :

		// Gets
		bulk ?
			fn.call( elems ) :
			length ? fn( elems[ 0 ], key ) : emptyGet;
};
var rcheckableType = ( /^(?:checkbox|radio)$/i );

var rtagName = ( /<([\w:-]+)/ );

var rscriptType = ( /^$|\/(?:java|ecma)script/i );

var rleadingWhitespace = ( /^\s+/ );

var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|" +
		"details|dialog|figcaption|figure|footer|header|hgroup|main|" +
		"mark|meter|nav|output|picture|progress|section|summary|template|time|video";



function createSafeFragment( document ) {
	var list = nodeNames.split( "|" ),
		safeFrag = document.createDocumentFragment();

	if ( safeFrag.createElement ) {
		while ( list.length ) {
			safeFrag.createElement(
				list.pop()
			);
		}
	}
	return safeFrag;
}


( function() {
	var div = document.createElement( "div" ),
		fragment = document.createDocumentFragment(),
		input = document.createElement( "input" );

	// Setup
	div.innerHTML = "  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";

	// IE strips leading whitespace when .innerHTML is used
	support.leadingWhitespace = div.firstChild.nodeType === 3;

	// Make sure that tbody elements aren't automatically inserted
	// IE will insert them into empty tables
	support.tbody = !div.getElementsByTagName( "tbody" ).length;

	// Make sure that link elements get serialized correctly by innerHTML
	// This requires a wrapper element in IE
	support.htmlSerialize = !!div.getElementsByTagName( "link" ).length;

	// Makes sure cloning an html5 element does not cause problems
	// Where outerHTML is undefined, this still works
	support.html5Clone =
		document.createElement( "nav" ).cloneNode( true ).outerHTML !== "<:nav></:nav>";

	// Check if a disconnected checkbox will retain its checked
	// value of true after appended to the DOM (IE6/7)
	input.type = "checkbox";
	input.checked = true;
	fragment.appendChild( input );
	support.appendChecked = input.checked;

	// Make sure textarea (and checkbox) defaultValue is properly cloned
	// Support: IE6-IE11+
	div.innerHTML = "<textarea>x</textarea>";
	support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;

	// #11217 - WebKit loses check when the name is after the checked attribute
	fragment.appendChild( div );

	// Support: Windows Web Apps (WWA)
	// `name` and `type` must use .setAttribute for WWA (#14901)
	input = document.createElement( "input" );
	input.setAttribute( "type", "radio" );
	input.setAttribute( "checked", "checked" );
	input.setAttribute( "name", "t" );

	div.appendChild( input );

	// Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3
	// old WebKit doesn't clone checked state correctly in fragments
	support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;

	// Support: IE<9
	// Cloned elements keep attachEvent handlers, we use addEventListener on IE9+
	support.noCloneEvent = !!div.addEventListener;

	// Support: IE<9
	// Since attributes and properties are the same in IE,
	// cleanData must set properties to undefined rather than use removeAttribute
	div[ jQuery.expando ] = 1;
	support.attributes = !div.getAttribute( jQuery.expando );
} )();


// We have to close these tags to support XHTML (#13200)
var wrapMap = {
	option: [ 1, "<select multiple='multiple'>", "</select>" ],
	legend: [ 1, "<fieldset>", "</fieldset>" ],
	area: [ 1, "<map>", "</map>" ],

	// Support: IE8
	param: [ 1, "<object>", "</object>" ],
	thead: [ 1, "<table>", "</table>" ],
	tr: [ 2, "<table><tbody>", "</tbody></table>" ],
	col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
	td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],

	// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,
	// unless wrapped in a div with non-breaking characters in front of it.
	_default: support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X<div>", "</div>" ]
};

// Support: IE8-IE9
wrapMap.optgroup = wrapMap.option;

wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
wrapMap.th = wrapMap.td;


function getAll( context, tag ) {
	var elems, elem,
		i = 0,
		found = typeof context.getElementsByTagName !== "undefined" ?
			context.getElementsByTagName( tag || "*" ) :
			typeof context.querySelectorAll !== "undefined" ?
				context.querySelectorAll( tag || "*" ) :
				undefined;

	if ( !found ) {
		for ( found = [], elems = context.childNodes || context;
			( elem = elems[ i ] ) != null;
			i++
		) {
			if ( !tag || jQuery.nodeName( elem, tag ) ) {
				found.push( elem );
			} else {
				jQuery.merge( found, getAll( elem, tag ) );
			}
		}
	}

	return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
		jQuery.merge( [ context ], found ) :
		found;
}


// Mark scripts as having already been evaluated
function setGlobalEval( elems, refElements ) {
	var elem,
		i = 0;
	for ( ; ( elem = elems[ i ] ) != null; i++ ) {
		jQuery._data(
			elem,
			"globalEval",
			!refElements || jQuery._data( refElements[ i ], "globalEval" )
		);
	}
}


var rhtml = /<|&#?\w+;/,
	rtbody = /<tbody/i;

function fixDefaultChecked( elem ) {
	if ( rcheckableType.test( elem.type ) ) {
		elem.defaultChecked = elem.checked;
	}
}

function buildFragment( elems, context, scripts, selection, ignored ) {
	var j, elem, contains,
		tmp, tag, tbody, wrap,
		l = elems.length,

		// Ensure a safe fragment
		safe = createSafeFragment( context ),

		nodes = [],
		i = 0;

	for ( ; i < l; i++ ) {
		elem = elems[ i ];

		if ( elem || elem === 0 ) {

			// Add nodes directly
			if ( jQuery.type( elem ) === "object" ) {
				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 || safe.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;
				}

				// Manually add leading whitespace removed by IE
				if ( !support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
					nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[ 0 ] ) );
				}

				// Remove IE's autoinserted <tbody> from table fragments
				if ( !support.tbody ) {

					// String was a <table>, *may* have spurious <tbody>
					elem = tag === "table" && !rtbody.test( elem ) ?
						tmp.firstChild :

						// String was a bare <thead> or <tfoot>
						wrap[ 1 ] === "<table>" && !rtbody.test( elem ) ?
							tmp :
							0;

					j = elem && elem.childNodes.length;
					while ( j-- ) {
						if ( jQuery.nodeName( ( tbody = elem.childNodes[ j ] ), "tbody" ) &&
							!tbody.childNodes.length ) {

							elem.removeChild( tbody );
						}
					}
				}

				jQuery.merge( nodes, tmp.childNodes );

				// Fix #12392 for WebKit and IE > 9
				tmp.textContent = "";

				// Fix #12392 for oldIE
				while ( tmp.firstChild ) {
					tmp.removeChild( tmp.firstChild );
				}

				// Remember the top-level container for proper cleanup
				tmp = safe.lastChild;
			}
		}
	}

	// Fix #11356: Clear elements from fragment
	if ( tmp ) {
		safe.removeChild( tmp );
	}

	// Reset defaultChecked for any radios and checkboxes
	// about to be appended to the DOM in IE 6/7 (#8060)
	if ( !support.appendChecked ) {
		jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked );
	}

	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( safe.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 );
				}
			}
		}
	}

	tmp = null;

	return safe;
}


( function() {
	var i, eventName,
		div = document.createElement( "div" );

	// Support: IE<9 (lack submit/change bubble), Firefox (lack focus(in | out) events)
	for ( i in { submit: true, change: true, focusin: true } ) {
		eventName = "on" + i;

		if ( !( support[ i ] = eventName in window ) ) {

			// Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP)
			div.setAttribute( eventName, "t" );
			support[ i ] = div.attributes[ eventName ].expando === false;
		}
	}

	// Null elements to avoid leaks in IE.
	div = null;
} )();


var rformElems = /^(?:input|select|textarea)$/i,
	rkeyEvent = /^key/,
	rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
	rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
	rtypenamespace = /^([^.]*)(?:\.(.+)|)/;

function returnTrue() {
	return true;
}

function returnFalse() {
	return false;
}

// Support: IE9
// 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 tmp, events, t, handleObjIn,
			special, eventHandle, handleObj,
			handlers, type, namespaces, origType,
			elemData = jQuery._data( elem );

		// Don't attach events to noData or text/comment nodes (but allow plain objects)
		if ( !elemData ) {
			return;
		}

		// Caller can pass in an object of custom data in lieu of the handler
		if ( handler.handler ) {
			handleObjIn = handler;
			handler = handleObjIn.handler;
			selector = handleObjIn.selector;
		}

		// Make sure that the handler has a unique ID, used to find/remove it later
		if ( !handler.guid ) {
			handler.guid = jQuery.guid++;
		}

		// Init the element's event structure and main handler, if this is the first
		if ( !( events = elemData.events ) ) {
			events = elemData.events = {};
		}
		if ( !( eventHandle = elemData.handle ) ) {
			eventHandle = elemData.handle = function( e ) {

				// Discard the second event of a jQuery.event.trigger() and
				// when an event is called after a page has unloaded
				return typeof jQuery !== "undefined" &&
					( !e || jQuery.event.triggered !== e.type ) ?
					jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
					undefined;
			};

			// Add elem as a property of the handle fn to prevent a memory leak
			// with IE non-native events
			eventHandle.elem = elem;
		}

		// Handle multiple events separated by a space
		types = ( types || "" ).match( rnotwhite ) || [ "" ];
		t = types.length;
		while ( t-- ) {
			tmp = rtypenamespace.exec( types[ t ] ) || [];
			type = origType = tmp[ 1 ];
			namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();

			// There *must* be a type, no attaching namespace-only handlers
			if ( !type ) {
				continue;
			}

			// If event changes its type, use the special event handlers for the changed type
			special = jQuery.event.special[ type ] || {};

			// If selector defined, determine special event api type, otherwise given type
			type = ( selector ? special.delegateType : special.bindType ) || type;

			// Update special based on newly reset type
			special = jQuery.event.special[ type ] || {};

			// handleObj is passed to all event handlers
			handleObj = jQuery.extend( {
				type: type,
				origType: origType,
				data: data,
				handler: handler,
				guid: handler.guid,
				selector: selector,
				needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
				namespace: namespaces.join( "." )
			}, handleObjIn );

			// Init the event handler queue if we're the first
			if ( !( handlers = events[ type ] ) ) {
				handlers = events[ type ] = [];
				handlers.delegateCount = 0;

				// Only use addEventListener/attachEvent if the special events handler returns false
				if ( !special.setup ||
					special.setup.call( elem, data, namespaces, eventHandle ) === false ) {

					// Bind the global event handler to the element
					if ( elem.addEventListener ) {
						elem.addEventListener( type, eventHandle, false );

					} else if ( elem.attachEvent ) {
						elem.attachEvent( "on" + type, eventHandle );
					}
				}
			}

			if ( special.add ) {
				special.add.call( elem, handleObj );

				if ( !handleObj.handler.guid ) {
					handleObj.handler.guid = handler.guid;
				}
			}

			// Add to the element's handler list, delegates in front
			if ( selector ) {
				handlers.splice( handlers.delegateCount++, 0, handleObj );
			} else {
				handlers.push( handleObj );
			}

			// Keep track of which events have ever been used, for event optimization
			jQuery.event.global[ type ] = true;
		}

		// Nullify elem to prevent memory leaks in IE
		elem = null;
	},

	// Detach an event or set of events from an element
	remove: function( elem, types, handler, selector, mappedTypes ) {
		var j, handleObj, tmp,
			origCount, t, events,
			special, handlers, type,
			namespaces, origType,
			elemData = jQuery.hasData( elem ) && jQuery._data( elem );

		if ( !elemData || !( events = elemData.events ) ) {
			return;
		}

		// Once for each type.namespace in types; type may be omitted
		types = ( types || "" ).match( rnotwhite ) || [ "" ];
		t = types.length;
		while ( t-- ) {
			tmp = rtypenamespace.exec( types[ t ] ) || [];
			type = origType = tmp[ 1 ];
			namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();

			// Unbind all events (on this namespace, if provided) for the element
			if ( !type ) {
				for ( type in events ) {
					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
				}
				continue;
			}

			special = jQuery.event.special[ type ] || {};
			type = ( selector ? special.delegateType : special.bindType ) || type;
			handlers = events[ type ] || [];
			tmp = tmp[ 2 ] &&
				new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );

			// Remove matching events
			origCount = j = handlers.length;
			while ( j-- ) {
				handleObj = handlers[ j ];

				if ( ( mappedTypes || origType === handleObj.origType ) &&
					( !handler || handler.guid === handleObj.guid ) &&
					( !tmp || tmp.test( handleObj.namespace ) ) &&
					( !selector || selector === handleObj.selector ||
						selector === "**" && handleObj.selector ) ) {
					handlers.splice( j, 1 );

					if ( handleObj.selector ) {
						handlers.delegateCount--;
					}
					if ( special.remove ) {
						special.remove.call( elem, handleObj );
					}
				}
			}

			// Remove generic event handler if we removed something and no more handlers exist
			// (avoids potential for endless recursion during removal of special event handlers)
			if ( origCount && !handlers.length ) {
				if ( !special.teardown ||
					special.teardown.call( elem, namespaces, elemData.handle ) === false ) {

					jQuery.removeEvent( elem, type, elemData.handle );
				}

				delete events[ type ];
			}
		}

		// Remove the expando if it's no longer used
		if ( jQuery.isEmptyObject( events ) ) {
			delete elemData.handle;

			// removeData also checks for emptiness and clears the expando if empty
			// so use it instead of delete
			jQuery._removeData( elem, "events" );
		}
	},

	trigger: function( event, data, elem, onlyHandlers ) {
		var handle, ontype, cur,
			bubbleType, special, tmp, i,
			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 = ( jQuery._data( cur, "events" ) || {} )[ event.type ] &&
				jQuery._data( 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 name as the event.
				// Can't use an .isFunction() check here because IE6/7 fails that test.
				// Don't do default actions on window, that's where global variables be (#6170)
				if ( ontype && 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;
					try {
						elem[ type ]();
					} catch ( e ) {

						// IE<9 dies on focus/blur to hidden element (#1486,#12518)
						// only reproducible on winXP IE8 native, not IE9 in IE8 mode
					}
					jQuery.event.triggered = undefined;

					if ( tmp ) {
						elem[ ontype ] = tmp;
					}
				}
			}
		}

		return event.result;
	},

	dispatch: function( event ) {

		// Make a writable jQuery.Event from the native event object
		event = jQuery.event.fix( event );

		var i, j, ret, matched, handleObj,
			handlerQueue = [],
			args = slice.call( arguments ),
			handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
			special = jQuery.event.special[ event.type ] || {};

		// Use the fix-ed jQuery.Event rather than the (read-only) native event
		args[ 0 ] = event;
		event.delegateTarget = this;

		// Call the preDispatch hook for the mapped type, and let it bail if desired
		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
			return;
		}

		// Determine handlers
		handlerQueue = jQuery.event.handlers.call( this, event, handlers );

		// Run delegates first; they may want to stop propagation beneath us
		i = 0;
		while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
			event.currentTarget = matched.elem;

			j = 0;
			while ( ( handleObj = matched.handlers[ j++ ] ) &&
				!event.isImmediatePropagationStopped() ) {

				// Triggered event must either 1) have no namespace, or 2) have namespace(s)
				// a subset or equal to those in the bound event (both can have no namespace).
				if ( !event.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, matches, sel, handleObj,
			handlerQueue = [],
			delegateCount = handlers.delegateCount,
			cur = event.target;

		// Support (at least): Chrome, IE9
		// Find delegate handlers
		// Black-hole SVG <use> instance trees (#13180)
		//
		// Support: Firefox<=42+
		// Avoid non-left-click in FF but don't block IE radio events (#3861, gh-2343)
		if ( delegateCount && cur.nodeType &&
			( event.type !== "click" || isNaN( event.button ) || event.button < 1 ) ) {

			/* jshint eqeqeq: false */
			for ( ; cur != this; cur = cur.parentNode || this ) {
				/* jshint eqeqeq: true */

				// Don't check non-elements (#13208)
				// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
				if ( cur.nodeType === 1 && ( cur.disabled !== true || event.type !== "click" ) ) {
					matches = [];
					for ( i = 0; i < delegateCount; i++ ) {
						handleObj = handlers[ i ];

						// Don't conflict with Object.prototype properties (#13203)
						sel = handleObj.selector + " ";

						if ( matches[ sel ] === undefined ) {
							matches[ sel ] = handleObj.needsContext ?
								jQuery( sel, this ).index( cur ) > -1 :
								jQuery.find( sel, this, null, [ cur ] ).length;
						}
						if ( matches[ sel ] ) {
							matches.push( handleObj );
						}
					}
					if ( matches.length ) {
						handlerQueue.push( { elem: cur, handlers: matches } );
					}
				}
			}
		}

		// Add the remaining (directly-bound) handlers
		if ( delegateCount < handlers.length ) {
			handlerQueue.push( { elem: this, handlers: handlers.slice( delegateCount ) } );
		}

		return handlerQueue;
	},

	fix: function( event ) {
		if ( event[ jQuery.expando ] ) {
			return event;
		}

		// Create a writable copy of the event object and normalize some properties
		var i, prop, copy,
			type = event.type,
			originalEvent = event,
			fixHook = this.fixHooks[ type ];

		if ( !fixHook ) {
			this.fixHooks[ type ] = fixHook =
				rmouseEvent.test( type ) ? this.mouseHooks :
				rkeyEvent.test( type ) ? this.keyHooks :
				{};
		}
		copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;

		event = new jQuery.Event( originalEvent );

		i = copy.length;
		while ( i-- ) {
			prop = copy[ i ];
			event[ prop ] = originalEvent[ prop ];
		}

		// Support: IE<9
		// Fix target property (#1925)
		if ( !event.target ) {
			event.target = originalEvent.srcElement || document;
		}

		// Support: Safari 6-8+
		// Target should not be a text node (#504, #13143)
		if ( event.target.nodeType === 3 ) {
			event.target = event.target.parentNode;
		}

		// Support: IE<9
		// For mouse/key events, metaKey==false if it's undefined (#3368, #11328)
		event.metaKey = !!event.metaKey;

		return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
	},

	// Includes some event props shared by KeyEvent and MouseEvent
	props: ( "altKey bubbles cancelable ctrlKey currentTarget detail eventPhase " +
		"metaKey relatedTarget shiftKey target timeStamp view which" ).split( " " ),

	fixHooks: {},

	keyHooks: {
		props: "char charCode key keyCode".split( " " ),
		filter: function( event, original ) {

			// Add which for key events
			if ( event.which == null ) {
				event.which = original.charCode != null ? original.charCode : original.keyCode;
			}

			return event;
		}
	},

	mouseHooks: {
		props: ( "button buttons clientX clientY fromElement offsetX offsetY " +
			"pageX pageY screenX screenY toElement" ).split( " " ),
		filter: function( event, original ) {
			var body, eventDoc, doc,
				button = original.button,
				fromElement = original.fromElement;

			// Calculate pageX/Y if missing and clientX/Y available
			if ( event.pageX == null && original.clientX != null ) {
				eventDoc = event.target.ownerDocument || document;
				doc = eventDoc.documentElement;
				body = eventDoc.body;

				event.pageX = original.clientX +
					( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
					( doc && doc.clientLeft || body && body.clientLeft || 0 );
				event.pageY = original.clientY +
					( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) -
					( doc && doc.clientTop  || body && body.clientTop  || 0 );
			}

			// Add relatedTarget, if necessary
			if ( !event.relatedTarget && fromElement ) {
				event.relatedTarget = fromElement === event.target ?
					original.toElement :
					fromElement;
			}

			// Add which for click: 1 === left; 2 === middle; 3 === right
			// Note: button is not normalized, so don't use it
			if ( !event.which && button !== undefined ) {
				event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
			}

			return event;
		}
	},

	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 ) {
					try {
						this.focus();
						return false;
					} catch ( e ) {

						// Support: IE<9
						// If we error on focus to hidden element (#1486, #12518),
						// let .trigger() run the handlers
					}
				}
			},
			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 ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) {
					this.click();
					return false;
				}
			},

			// For cross-browser consistency, don't fire native .click() on links
			_default: function( event ) {
				return jQuery.nodeName( event.target, "a" );
			}
		},

		beforeunload: {
			postDispatch: function( event ) {

				// Support: Firefox 20+
				// Firefox doesn't alert if the returnValue field is not set.
				if ( event.result !== undefined && event.originalEvent ) {
					event.originalEvent.returnValue = event.result;
				}
			}
		}
	},

	// Piggyback on a donor event to simulate a different one
	simulate: function( type, elem, event ) {
		var e = jQuery.extend(
			new jQuery.Event(),
			event,
			{
				type: type,
				isSimulated: true

				// Previously, `originalEvent: {}` was set here, so stopPropagation call
				// would not be triggered on donor event, since in our own
				// jQuery.event.stopPropagation function we had a check for existence of
				// originalEvent.stopPropagation method, so, consequently it would be a noop.
				//
				// Guard for simulated events was moved to jQuery.event.stopPropagation function
				// since `originalEvent` should point to the original event for the
				// constancy with other events and for more focused logic
			}
		);

		jQuery.event.trigger( e, null, elem );

		if ( e.isDefaultPrevented() ) {
			event.preventDefault();
		}
	}
};

jQuery.removeEvent = document.removeEventListener ?
	function( elem, type, handle ) {

		// This "if" is needed for plain objects
		if ( elem.removeEventListener ) {
			elem.removeEventListener( type, handle );
		}
	} :
	function( elem, type, handle ) {
		var name = "on" + type;

		if ( elem.detachEvent ) {

			// #8545, #7054, preventing memory leaks for custom events in IE6-8
			// detachEvent needed property on element, by name of that event,
			// to properly expose it to GC
			if ( typeof elem[ name ] === "undefined" ) {
				elem[ name ] = null;
			}

			elem.detachEvent( name, 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: IE < 9, Android < 4.0
				src.returnValue === false ?
			returnTrue :
			returnFalse;

	// Event type
	} else {
		this.type = src;
	}

	// Put explicitly provided properties onto the event object
	if ( props ) {
		jQuery.extend( this, props );
	}

	// Create a timestamp if incoming event doesn't have one
	this.timeStamp = src && src.timeStamp || jQuery.now();

	// Mark it as fixed
	this[ jQuery.expando ] = true;
};

// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
jQuery.Event.prototype = {
	constructor: jQuery.Event,
	isDefaultPrevented: returnFalse,
	isPropagationStopped: returnFalse,
	isImmediatePropagationStopped: returnFalse,

	preventDefault: function() {
		var e = this.originalEvent;

		this.isDefaultPrevented = returnTrue;
		if ( !e ) {
			return;
		}

		// If preventDefault exists, run it on the original event
		if ( e.preventDefault ) {
			e.preventDefault();

		// Support: IE
		// Otherwise set the returnValue property of the original event to false
		} else {
			e.returnValue = false;
		}
	},
	stopPropagation: function() {
		var e = this.originalEvent;

		this.isPropagationStopped = returnTrue;

		if ( !e || this.isSimulated ) {
			return;
		}

		// If stopPropagation exists, run it on the original event
		if ( e.stopPropagation ) {
			e.stopPropagation();
		}

		// Support: IE
		// Set the cancelBubble property of the original event to true
		e.cancelBubble = true;
	},
	stopImmediatePropagation: function() {
		var e = this.originalEvent;

		this.isImmediatePropagationStopped = returnTrue;

		if ( e && e.stopImmediatePropagation ) {
			e.stopImmediatePropagation();
		}

		this.stopPropagation();
	}
};

// 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://code.google.com/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;
		}
	};
} );

// IE submit delegation
if ( !support.submit ) {

	jQuery.event.special.submit = {
		setup: function() {

			// Only need this for delegated form submit events
			if ( jQuery.nodeName( this, "form" ) ) {
				return false;
			}

			// Lazy-add a submit handler when a descendant form may potentially be submitted
			jQuery.event.add( this, "click._submit keypress._submit", function( e ) {

				// Node name check avoids a VML-related crash in IE (#9807)
				var elem = e.target,
					form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ?

						// Support: IE <=8
						// We use jQuery.prop instead of elem.form
						// to allow fixing the IE8 delegated submit issue (gh-2332)
						// by 3rd party polyfills/workarounds.
						jQuery.prop( elem, "form" ) :
						undefined;

				if ( form && !jQuery._data( form, "submit" ) ) {
					jQuery.event.add( form, "submit._submit", function( event ) {
						event._submitBubble = true;
					} );
					jQuery._data( form, "submit", true );
				}
			} );

			// return undefined since we don't need an event listener
		},

		postDispatch: function( event ) {

			// If form was submitted by the user, bubble the event up the tree
			if ( event._submitBubble ) {
				delete event._submitBubble;
				if ( this.parentNode && !event.isTrigger ) {
					jQuery.event.simulate( "submit", this.parentNode, event );
				}
			}
		},

		teardown: function() {

			// Only need this for delegated form submit events
			if ( jQuery.nodeName( this, "form" ) ) {
				return false;
			}

			// Remove delegated handlers; cleanData eventually reaps submit handlers attached above
			jQuery.event.remove( this, "._submit" );
		}
	};
}

// IE change delegation and checkbox/radio fix
if ( !support.change ) {

	jQuery.event.special.change = {

		setup: function() {

			if ( rformElems.test( this.nodeName ) ) {

				// IE doesn't fire change on a check/radio until blur; trigger it on click
				// after a propertychange. Eat the blur-change in special.change.handle.
				// This still fires onchange a second time for check/radio after blur.
				if ( this.type === "checkbox" || this.type === "radio" ) {
					jQuery.event.add( this, "propertychange._change", function( event ) {
						if ( event.originalEvent.propertyName === "checked" ) {
							this._justChanged = true;
						}
					} );
					jQuery.event.add( this, "click._change", function( event ) {
						if ( this._justChanged && !event.isTrigger ) {
							this._justChanged = false;
						}

						// Allow triggered, simulated change events (#11500)
						jQuery.event.simulate( "change", this, event );
					} );
				}
				return false;
			}

			// Delegated event; lazy-add a change handler on descendant inputs
			jQuery.event.add( this, "beforeactivate._change", function( e ) {
				var elem = e.target;

				if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "change" ) ) {
					jQuery.event.add( elem, "change._change", function( event ) {
						if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
							jQuery.event.simulate( "change", this.parentNode, event );
						}
					} );
					jQuery._data( elem, "change", true );
				}
			} );
		},

		handle: function( event ) {
			var elem = event.target;

			// Swallow native change events from checkbox/radio, we already triggered them above
			if ( this !== elem || event.isSimulated || event.isTrigger ||
				( elem.type !== "radio" && elem.type !== "checkbox" ) ) {

				return event.handleObj.handler.apply( this, arguments );
			}
		},

		teardown: function() {
			jQuery.event.remove( this, "._change" );

			return !rformElems.test( this.nodeName );
		}
	};
}

// Support: Firefox
// Firefox doesn't have focus(in | out) events
// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
//
// Support: Chrome, Safari
// 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://code.google.com/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 = jQuery._data( doc, fix );

				if ( !attaches ) {
					doc.addEventListener( orig, handler, true );
				}
				jQuery._data( doc, fix, ( attaches || 0 ) + 1 );
			},
			teardown: function() {
				var doc = this.ownerDocument || this,
					attaches = jQuery._data( doc, fix ) - 1;

				if ( !attaches ) {
					doc.removeEventListener( orig, handler, true );
					jQuery._removeData( doc, fix );
				} else {
					jQuery._data( doc, fix, attaches );
				}
			}
		};
	} );
}

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 );
		} );
	},

	trigger: function( type, data ) {
		return this.each( function() {
			jQuery.event.trigger( type, data, this );
		} );
	},
	triggerHandler: function( type, data ) {
		var elem = this[ 0 ];
		if ( elem ) {
			return jQuery.event.trigger( type, data, elem, true );
		}
	}
} );


var rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
	rnoshimcache = new RegExp( "<(?:" + nodeNames + ")[\\s/>]", "i" ),
	rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,

	// Support: IE 10-11, Edge 10240+
	// 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,
	safeFragment = createSafeFragment( document ),
	fragmentDiv = safeFragment.appendChild( document.createElement( "div" ) );

// Support: IE<8
// Manipulating tables requires a tbody
function manipulationTarget( elem, content ) {
	return jQuery.nodeName( elem, "table" ) &&
		jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ?

		elem.getElementsByTagName( "tbody" )[ 0 ] ||
			elem.appendChild( elem.ownerDocument.createElement( "tbody" ) ) :
		elem;
}

// Replace/restore the type attribute of script elements for safe DOM manipulation
function disableScript( elem ) {
	elem.type = ( jQuery.find.attr( elem, "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 ) {
	if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
		return;
	}

	var type, i, l,
		oldData = jQuery._data( src ),
		curData = jQuery._data( dest, oldData ),
		events = oldData.events;

	if ( events ) {
		delete curData.handle;
		curData.events = {};

		for ( type in events ) {
			for ( i = 0, l = events[ type ].length; i < l; i++ ) {
				jQuery.event.add( dest, type, events[ type ][ i ] );
			}
		}
	}

	// make the cloned public data object a copy from the original
	if ( curData.data ) {
		curData.data = jQuery.extend( {}, curData.data );
	}
}

function fixCloneNodeIssues( src, dest ) {
	var nodeName, e, data;

	// We do not need to do anything for non-Elements
	if ( dest.nodeType !== 1 ) {
		return;
	}

	nodeName = dest.nodeName.toLowerCase();

	// IE6-8 copies events bound via attachEvent when using cloneNode.
	if ( !support.noCloneEvent && dest[ jQuery.expando ] ) {
		data = jQuery._data( dest );

		for ( e in data.events ) {
			jQuery.removeEvent( dest, e, data.handle );
		}

		// Event data gets referenced instead of copied if the expando gets copied too
		dest.removeAttribute( jQuery.expando );
	}

	// IE blanks contents when cloning scripts, and tries to evaluate newly-set text
	if ( nodeName === "script" && dest.text !== src.text ) {
		disableScript( dest ).text = src.text;
		restoreScript( dest );

	// IE6-10 improperly clones children of object elements using classid.
	// IE10 throws NoModificationAllowedError if parent is null, #12132.
	} else if ( nodeName === "object" ) {
		if ( dest.parentNode ) {
			dest.outerHTML = src.outerHTML;
		}

		// This path appears unavoidable for IE9. When cloning an object
		// element in IE9, the outerHTML strategy above is not sufficient.
		// If the src has innerHTML and the destination does not,
		// copy the src.innerHTML into the dest.innerHTML. #10324
		if ( support.html5Clone && ( src.innerHTML && !jQuery.trim( dest.innerHTML ) ) ) {
			dest.innerHTML = src.innerHTML;
		}

	} else if ( nodeName === "input" && rcheckableType.test( src.type ) ) {

		// IE6-8 fails to persist the checked state of a cloned checkbox
		// or radio button. Worse, IE6-7 fail to give the cloned element
		// a checked appearance if the defaultChecked value isn't also set

		dest.defaultChecked = dest.checked = src.checked;

		// IE6-7 get confused and end up setting the value of a cloned
		// checkbox/radio button to an empty string instead of "on"
		if ( dest.value !== src.value ) {
			dest.value = src.value;
		}

	// IE6-8 fails to return the selected option to the default selected
	// state when cloning options
	} else if ( nodeName === "option" ) {
		dest.defaultSelected = dest.selected = src.defaultSelected;

	// IE6-8 fails to set the defaultValue to the correct value when
	// cloning other types of input fields
	} else if ( nodeName === "input" || nodeName === "textarea" ) {
		dest.defaultValue = src.defaultValue;
	}
}

function domManip( collection, args, callback, ignored ) {

	// Flatten any nested arrays
	args = concat.apply( [], args );

	var first, node, hasScripts,
		scripts, doc, fragment,
		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.1, PhantomJS<2
						// 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 || "" ) &&
						!jQuery._data( node, "globalEval" ) &&
						jQuery.contains( doc, node ) ) {

						if ( node.src ) {

							// Optional AJAX dependency, but won't run scripts if not present
							if ( jQuery._evalUrl ) {
								jQuery._evalUrl( node.src );
							}
						} else {
							jQuery.globalEval(
								( node.text || node.textContent || node.innerHTML || "" )
									.replace( rcleanScript, "" )
							);
						}
					}
				}
			}

			// Fix #11809: Avoid leaking memory
			fragment = first = null;
		}
	}

	return collection;
}

function remove( elem, selector, keepData ) {
	var node,
		elems = selector ? jQuery.filter( selector, elem ) : elem,
		i = 0;

	for ( ; ( node = elems[ 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 destElements, node, clone, i, srcElements,
			inPage = jQuery.contains( elem.ownerDocument, elem );

		if ( support.html5Clone || jQuery.isXMLDoc( elem ) ||
			!rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {

			clone = elem.cloneNode( true );

		// IE<=8 does not properly clone detached, unknown element nodes
		} else {
			fragmentDiv.innerHTML = elem.outerHTML;
			fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
		}

		if ( ( !support.noCloneEvent || !support.noCloneChecked ) &&
				( elem.nodeType === 1 || elem.nodeType === 11 ) && !jQuery.isXMLDoc( elem ) ) {

			// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
			destElements = getAll( clone );
			srcElements = getAll( elem );

			// Fix all IE cloning issues
			for ( i = 0; ( node = srcElements[ i ] ) != null; ++i ) {

				// Ensure that the destination node is not null; Fixes #9587
				if ( destElements[ i ] ) {
					fixCloneNodeIssues( node, 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; ( node = srcElements[ i ] ) != null; i++ ) {
					cloneCopyEvent( node, destElements[ i ] );
				}
			} else {
				cloneCopyEvent( elem, clone );
			}
		}

		// Preserve script evaluation history
		destElements = getAll( clone, "script" );
		if ( destElements.length > 0 ) {
			setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
		}

		destElements = srcElements = node = null;

		// Return the cloned set
		return clone;
	},

	cleanData: function( elems, /* internal */ forceAcceptData ) {
		var elem, type, id, data,
			i = 0,
			internalKey = jQuery.expando,
			cache = jQuery.cache,
			attributes = support.attributes,
			special = jQuery.event.special;

		for ( ; ( elem = elems[ i ] ) != null; i++ ) {
			if ( forceAcceptData || acceptData( elem ) ) {

				id = elem[ internalKey ];
				data = id && cache[ id ];

				if ( data ) {
					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 );
							}
						}
					}

					// Remove cache only if it was not already removed by jQuery.event.remove
					if ( cache[ id ] ) {

						delete cache[ id ];

						// Support: IE<9
						// IE does not allow us to delete expando properties from nodes
						// IE creates expando attributes along with the property
						// IE does not have a removeAttribute function on Document nodes
						if ( !attributes && typeof elem.removeAttribute !== "undefined" ) {
							elem.removeAttribute( internalKey );

						// Webkit & Blink performance suffers when deleting properties
						// from DOM nodes, so set to undefined instead
						// https://code.google.com/p/chromium/issues/detail?id=378607
						} else {
							elem[ internalKey ] = undefined;
						}

						deletedIds.push( id );
					}
				}
			}
		}
	}
} );

jQuery.fn.extend( {

	// Keep domManip exposed until 3.0 (gh-2225)
	domManip: domManip,

	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().append(
					( this[ 0 ] && this[ 0 ].ownerDocument || document ).createTextNode( 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++ ) {

			// Remove element nodes and prevent memory leaks
			if ( elem.nodeType === 1 ) {
				jQuery.cleanData( getAll( elem, false ) );
			}

			// Remove any remaining nodes
			while ( elem.firstChild ) {
				elem.removeChild( elem.firstChild );
			}

			// If this is a select, ensure that it displays empty (#12336)
			// Support: IE<9
			if ( elem.options && jQuery.nodeName( elem, "select" ) ) {
				elem.options.length = 0;
			}
		}

		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 ) {
				return elem.nodeType === 1 ?
					elem.innerHTML.replace( rinlinejQuery, "" ) :
					undefined;
			}

			// See if we can take a shortcut and just use innerHTML
			if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
				( support.htmlSerialize || !rnoshimcache.test( value )  ) &&
				( support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
				!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {

				value = jQuery.htmlPrefilter( value );

				try {
					for ( ; i < l; i++ ) {

						// Remove element nodes and prevent memory leaks
						elem = this[ i ] || {};
						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,
			i = 0,
			ret = [],
			insert = jQuery( selector ),
			last = insert.length - 1;

		for ( ; i <= last; i++ ) {
			elems = i === last ? this : this.clone( true );
			jQuery( insert[ i ] )[ original ]( elems );

			// Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get()
			push.apply( ret, elems.get() );
		}

		return this.pushStack( ret );
	};
} );


var iframe,
	elemdisplay = {

		// Support: Firefox
		// We have to pre-define these values for FF (#10227)
		HTML: "block",
		BODY: "block"
	};

/**
 * Retrieve the actual display of a element
 * @param {String} name nodeName of the element
 * @param {Object} doc Document object
 */

// Called only from within defaultDisplay
function actualDisplay( name, doc ) {
	var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),

		display = jQuery.css( elem[ 0 ], "display" );

	// We don't have any data stored on the element,
	// so use "detach" method as fast way to get rid of the element
	elem.detach();

	return display;
}

/**
 * Try to determine the default display value of an element
 * @param {String} nodeName
 */
function defaultDisplay( nodeName ) {
	var doc = document,
		display = elemdisplay[ nodeName ];

	if ( !display ) {
		display = actualDisplay( nodeName, doc );

		// If the simple way fails, read from inside an iframe
		if ( display === "none" || !display ) {

			// Use the already-created iframe if possible
			iframe = ( iframe || jQuery( "<iframe frameborder='0' width='0' height='0'/>" ) )
				.appendTo( doc.documentElement );

			// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
			doc = ( iframe[ 0 ].contentWindow || iframe[ 0 ].contentDocument ).document;

			// Support: IE
			doc.write();
			doc.close();

			display = actualDisplay( nodeName, doc );
			iframe.detach();
		}

		// Store the correct default display
		elemdisplay[ nodeName ] = display;
	}

	return display;
}
var rmargin = ( /^margin/ );

var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );

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;
};


var documentElement = document.documentElement;



( function() {
	var pixelPositionVal, pixelMarginRightVal, boxSizingReliableVal,
		reliableHiddenOffsetsVal, reliableMarginRightVal, reliableMarginLeftVal,
		container = document.createElement( "div" ),
		div = document.createElement( "div" );

	// Finish early in limited (non-browser) environments
	if ( !div.style ) {
		return;
	}

	div.style.cssText = "float:left;opacity:.5";

	// Support: IE<9
	// Make sure that element opacity exists (as opposed to filter)
	support.opacity = div.style.opacity === "0.5";

	// Verify style float existence
	// (IE uses styleFloat instead of cssFloat)
	support.cssFloat = !!div.style.cssFloat;

	div.style.backgroundClip = "content-box";
	div.cloneNode( true ).style.backgroundClip = "";
	support.clearCloneStyle = div.style.backgroundClip === "content-box";

	container = document.createElement( "div" );
	container.style.cssText = "border:0;width:8px;height:0;top:0;left:-9999px;" +
		"padding:0;margin-top:1px;position:absolute";
	div.innerHTML = "";
	container.appendChild( div );

	// Support: Firefox<29, Android 2.3
	// Vendor-prefix box-sizing
	support.boxSizing = div.style.boxSizing === "" || div.style.MozBoxSizing === "" ||
		div.style.WebkitBoxSizing === "";

	jQuery.extend( support, {
		reliableHiddenOffsets: function() {
			if ( pixelPositionVal == null ) {
				computeStyleTests();
			}
			return reliableHiddenOffsetsVal;
		},

		boxSizingReliable: function() {

			// We're checking for pixelPositionVal here instead of boxSizingReliableVal
			// since that compresses better and they're computed together anyway.
			if ( pixelPositionVal == null ) {
				computeStyleTests();
			}
			return boxSizingReliableVal;
		},

		pixelMarginRight: function() {

			// Support: Android 4.0-4.3
			if ( pixelPositionVal == null ) {
				computeStyleTests();
			}
			return pixelMarginRightVal;
		},

		pixelPosition: function() {
			if ( pixelPositionVal == null ) {
				computeStyleTests();
			}
			return pixelPositionVal;
		},

		reliableMarginRight: function() {

			// Support: Android 2.3
			if ( pixelPositionVal == null ) {
				computeStyleTests();
			}
			return reliableMarginRightVal;
		},

		reliableMarginLeft: function() {

			// Support: IE <=8 only, Android 4.0 - 4.3 only, Firefox <=3 - 37
			if ( pixelPositionVal == null ) {
				computeStyleTests();
			}
			return reliableMarginLeftVal;
		}
	} );

	function computeStyleTests() {
		var contents, divStyle,
			documentElement = document.documentElement;

		// Setup
		documentElement.appendChild( container );

		div.style.cssText =

			// Support: Android 2.3
			// Vendor-prefix box-sizing
			"-webkit-box-sizing:border-box;box-sizing:border-box;" +
			"position:relative;display:block;" +
			"margin:auto;border:1px;padding:1px;" +
			"top:1%;width:50%";

		// Support: IE<9
		// Assume reasonable values in the absence of getComputedStyle
		pixelPositionVal = boxSizingReliableVal = reliableMarginLeftVal = false;
		pixelMarginRightVal = reliableMarginRightVal = true;

		// Check for getComputedStyle so that this code is not run in IE<9.
		if ( window.getComputedStyle ) {
			divStyle = window.getComputedStyle( div );
			pixelPositionVal = ( divStyle || {} ).top !== "1%";
			reliableMarginLeftVal = ( divStyle || {} ).marginLeft === "2px";
			boxSizingReliableVal = ( divStyle || { width: "4px" } ).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" } ).marginRight === "4px";

			// Support: Android 2.3 only
			// Div with explicit width and no margin-right incorrectly
			// gets computed margin-right based on width of container (#3333)
			// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
			contents = div.appendChild( document.createElement( "div" ) );

			// Reset CSS: box-sizing; display; margin; border; padding
			contents.style.cssText = div.style.cssText =

				// Support: Android 2.3
				// Vendor-prefix box-sizing
				"-webkit-box-sizing:content-box;-moz-box-sizing:content-box;" +
				"box-sizing:content-box;display:block;margin:0;border:0;padding:0";
			contents.style.marginRight = contents.style.width = "0";
			div.style.width = "1px";

			reliableMarginRightVal =
				!parseFloat( ( window.getComputedStyle( contents ) || {} ).marginRight );

			div.removeChild( contents );
		}

		// Support: IE6-8
		// First check that getClientRects works as expected
		// Check if table cells still have offsetWidth/Height when they are set
		// to display:none and there are still other visible table cells in a
		// table row; if so, offsetWidth/Height are not reliable for use when
		// determining if an element has been hidden directly using
		// display:none (it is still safe to use offsets if a parent element is
		// hidden; don safety goggles and see bug #4512 for more information).
		div.style.display = "none";
		reliableHiddenOffsetsVal = div.getClientRects().length === 0;
		if ( reliableHiddenOffsetsVal ) {
			div.style.display = "";
			div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>";
			div.childNodes[ 0 ].style.borderCollapse = "separate";
			contents = div.getElementsByTagName( "td" );
			contents[ 0 ].style.cssText = "margin:0;border:0;padding:0;display:none";
			reliableHiddenOffsetsVal = contents[ 0 ].offsetHeight === 0;
			if ( reliableHiddenOffsetsVal ) {
				contents[ 0 ].style.display = "";
				contents[ 1 ].style.display = "none";
				reliableHiddenOffsetsVal = contents[ 0 ].offsetHeight === 0;
			}
		}

		// Teardown
		documentElement.removeChild( container );
	}

} )();


var getStyles, curCSS,
	rposition = /^(top|right|bottom|left)$/;

if ( window.getComputedStyle ) {
	getStyles = function( elem ) {

		// Support: IE<=11+, 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 );
	};

	curCSS = function( elem, name, computed ) {
		var width, minWidth, maxWidth, ret,
			style = elem.style;

		computed = computed || getStyles( elem );

		// getPropertyValue is only needed for .css('filter') in IE9, see #12537
		ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined;

		// Support: Opera 12.1x only
		// Fall back to style even without computed
		// computed is undefined for elems on document fragments
		if ( ( ret === "" || ret === undefined ) && !jQuery.contains( elem.ownerDocument, elem ) ) {
			ret = jQuery.style( elem, name );
		}

		if ( computed ) {

			// A tribute to the "awesome hack by Dean Edwards"
			// Chrome < 17 and Safari 5.0 uses "computed value"
			// instead of "used value" for margin-right
			// Safari 5.1.7 (at least) returns percentage for a larger set of values,
			// but width seems to be reliably pixels
			// this is against the CSSOM draft spec:
			// http://dev.w3.org/csswg/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;
			}
		}

		// Support: IE
		// IE returns zIndex value as an integer.
		return ret === undefined ?
			ret :
			ret + "";
	};
} else if ( documentElement.currentStyle ) {
	getStyles = function( elem ) {
		return elem.currentStyle;
	};

	curCSS = function( elem, name, computed ) {
		var left, rs, rsLeft, ret,
			style = elem.style;

		computed = computed || getStyles( elem );
		ret = computed ? computed[ name ] : undefined;

		// Avoid setting ret to empty string here
		// so we don't default to auto
		if ( ret == null && style && style[ name ] ) {
			ret = style[ name ];
		}

		// From the awesome hack by Dean Edwards
		// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291

		// If we're not dealing with a regular pixel number
		// but a number that has a weird ending, we need to convert it to pixels
		// but not position css attributes, as those are
		// proportional to the parent element instead
		// and we can't measure the parent instead because it
		// might trigger a "stacking dolls" problem
		if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {

			// Remember the original values
			left = style.left;
			rs = elem.runtimeStyle;
			rsLeft = rs && rs.left;

			// Put in the new values to get a computed value out
			if ( rsLeft ) {
				rs.left = elem.currentStyle.left;
			}
			style.left = name === "fontSize" ? "1em" : ret;
			ret = style.pixelLeft + "px";

			// Revert the changed values
			style.left = left;
			if ( rsLeft ) {
				rs.left = rsLeft;
			}
		}

		// Support: IE
		// IE returns zIndex value as an integer.
		return ret === undefined ?
			ret :
			ret + "" || "auto";
	};
}




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

		ralpha = /alpha\([^)]*\)/i,
	ropacity = /opacity\s*=\s*([^)]*)/i,

	// 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]).+)/,
	rnumsplit = new RegExp( "^(" + pnum + ")(.*)$", "i" ),

	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
	cssNormalTransform = {
		letterSpacing: "0",
		fontWeight: "400"
	},

	cssPrefixes = [ "Webkit", "O", "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.charAt( 0 ).toUpperCase() + name.slice( 1 ),
		i = cssPrefixes.length;

	while ( i-- ) {
		name = cssPrefixes[ i ] + capName;
		if ( name in emptyStyle ) {
			return name;
		}
	}
}

function showHide( elements, show ) {
	var display, elem, hidden,
		values = [],
		index = 0,
		length = elements.length;

	for ( ; index < length; index++ ) {
		elem = elements[ index ];
		if ( !elem.style ) {
			continue;
		}

		values[ index ] = jQuery._data( elem, "olddisplay" );
		display = elem.style.display;
		if ( show ) {

			// Reset the inline display of this element to learn if it is
			// being hidden by cascaded rules or not
			if ( !values[ index ] && display === "none" ) {
				elem.style.display = "";
			}

			// Set elements which have been overridden with display: none
			// in a stylesheet to whatever the default browser style is
			// for such an element
			if ( elem.style.display === "" && isHidden( elem ) ) {
				values[ index ] =
					jQuery._data( elem, "olddisplay", defaultDisplay( elem.nodeName ) );
			}
		} else {
			hidden = isHidden( elem );

			if ( display && display !== "none" || !hidden ) {
				jQuery._data(
					elem,
					"olddisplay",
					hidden ? display : jQuery.css( elem, "display" )
				);
			}
		}
	}

	// Set the display of most of the elements in a second loop
	// to avoid the constant reflow
	for ( index = 0; index < length; index++ ) {
		elem = elements[ index ];
		if ( !elem.style ) {
			continue;
		}
		if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
			elem.style.display = show ? values[ index ] || "" : "none";
		}
	}

	return elements;
}

function setPositiveNumber( elem, value, subtract ) {
	var matches = rnumsplit.exec( value );
	return matches ?

		// Guard against undefined "subtract", e.g., when used as in cssHooks
		Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
		value;
}

function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
	var i = extra === ( isBorderBox ? "border" : "content" ) ?

		// If we already have the right measurement, avoid augmentation
		4 :

		// Otherwise initialize for horizontal or vertical properties
		name === "width" ? 1 : 0,

		val = 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 offset property, which is equivalent to the border-box value
	var valueIsBorderBox = true,
		val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
		styles = getStyles( elem ),
		isBorderBox = support.boxSizing &&
			jQuery.css( elem, "boxSizing", false, styles ) === "border-box";

	// some non-html elements return undefined for offsetWidth, so check for null/undefined
	// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
	// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
	if ( val <= 0 || val == null ) {

		// Fall back to computed then uncomputed css if necessary
		val = curCSS( elem, name, styles );
		if ( val < 0 || val == null ) {
			val = elem.style[ name ];
		}

		// Computed unit is not pixels. Stop here and return.
		if ( rnumnonpx.test( val ) ) {
			return val;
		}

		// we need the 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 ] );

		// 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: {

		// normalize float css property
		"float": support.cssFloat ? "cssFloat" : "styleFloat"
	},

	// Get and set the style property on a DOM Node
	style: function( elem, name, value, extra ) {

		// Don't set styles on text and comment nodes
		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
			return;
		}

		// Make sure that we're working with the right name
		var ret, type, hooks,
			origName = jQuery.camelCase( name ),
			style = elem.style;

		name = jQuery.cssProps[ origName ] ||
			( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );

		// gets hook for the prefixed version
		// followed by the 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. See: #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" );
			}

			// Fixes #8908, it can be done more correctly by specifing setters in cssHooks,
			// but it would mean to define eight
			// (for every problematic property) identical functions
			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 ) {

				// Support: IE
				// Swallow errors from 'invalid' CSS values (#5509)
				try {
					style[ name ] = value;
				} catch ( e ) {}
			}

		} else {

			// If a hook was provided get the non-computed value from there
			if ( hooks && "get" in hooks &&
				( ret = hooks.get( elem, false, extra ) ) !== undefined ) {

				return ret;
			}

			// Otherwise just get the value from the style object
			return style[ name ];
		}
	},

	css: function( elem, name, extra, styles ) {
		var num, val, hooks,
			origName = jQuery.camelCase( name );

		// Make sure that we're working with the right name
		name = jQuery.cssProps[ origName ] ||
			( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );

		// gets hook for the prefixed version
		// followed by the unprefixed version
		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 ];
		}

		// Return, converting to number 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
				// however, it must have a current display style that would benefit from this
				return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
					elem.offsetWidth === 0 ?
						swap( elem, cssShow, function() {
							return getWidthOrHeight( elem, name, extra );
						} ) :
						getWidthOrHeight( elem, name, extra );
			}
		},

		set: function( elem, value, extra ) {
			var styles = extra && getStyles( elem );
			return setPositiveNumber( elem, value, extra ?
				augmentWidthOrHeight(
					elem,
					name,
					extra,
					support.boxSizing &&
						jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
					styles
				) : 0
			);
		}
	};
} );

if ( !support.opacity ) {
	jQuery.cssHooks.opacity = {
		get: function( elem, computed ) {

			// IE uses filters for opacity
			return ropacity.test( ( computed && elem.currentStyle ?
				elem.currentStyle.filter :
				elem.style.filter ) || "" ) ?
					( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
					computed ? "1" : "";
		},

		set: function( elem, value ) {
			var style = elem.style,
				currentStyle = elem.currentStyle,
				opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
				filter = currentStyle && currentStyle.filter || style.filter || "";

			// IE has trouble with opacity if it does not have layout
			// Force it by setting the zoom level
			style.zoom = 1;

			// if setting opacity to 1, and no other filters exist -
			// attempt to remove filter attribute #6652
			// if value === "", then remove inline opacity #12685
			if ( ( value >= 1 || value === "" ) &&
					jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
					style.removeAttribute ) {

				// Setting style.filter to null, "" & " " still leave "filter:" in the cssText
				// if "filter:" is present at all, clearType is disabled, we want to avoid this
				// style.removeAttribute is IE Only, but so apparently is this code path...
				style.removeAttribute( "filter" );

				// if there is no filter style applied in a css rule
				// or unset inline opacity, we are done
				if ( value === "" || currentStyle && !currentStyle.filter ) {
					return;
				}
			}

			// otherwise, set new filter values
			style.filter = ralpha.test( filter ) ?
				filter.replace( ralpha, opacity ) :
				filter + " " + opacity;
		}
	};
}

jQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight,
	function( elem, computed ) {
		if ( computed ) {
			return swap( elem, { "display": "inline-block" },
				curCSS, [ elem, "marginRight" ] );
		}
	}
);

jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
	function( elem, computed ) {
		if ( computed ) {
			return (
				parseFloat( curCSS( elem, "marginLeft" ) ) ||

				// Support: IE<=11+
				// Running getBoundingClientRect on a disconnected node in IE throws an error
				// Support: IE8 only
				// getClientRects() errors on disconnected elems
				( jQuery.contains( elem.ownerDocument, elem ) ?
					elem.getBoundingClientRect().left -
						swap( elem, { marginLeft: 0 }, function() {
							return elem.getBoundingClientRect().left;
						} ) :
					0
				)
			) + "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 ( jQuery.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 );
	},
	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 ( isHidden( this ) ) {
				jQuery( this ).show();
			} else {
				jQuery( this ).hide();
			}
		} );
	}
} );


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
			// so, 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 its
			// 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
// 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, timerId,
	rfxtypes = /^(?:toggle|show|hide)$/,
	rrun = /queueHooks$/;

// 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,
		attrs = { height: type },
		i = 0;

	// if we include width, step value is 1 to do all cssExpand values,
	// if we don't include width, 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 ) {
	/* jshint validthis: true */
	var prop, value, toggle, tween, hooks, oldfire, display, checkDisplay,
		anim = this,
		orig = {},
		style = elem.style,
		hidden = elem.nodeType && isHidden( elem ),
		dataShow = jQuery._data( elem, "fxshow" );

	// handle queue: false promises
	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() {

			// doing this makes sure that the complete handler will be called
			// before this completes
			anim.always( function() {
				hooks.unqueued--;
				if ( !jQuery.queue( elem, "fx" ).length ) {
					hooks.empty.fire();
				}
			} );
		} );
	}

	// height/width overflow pass
	if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {

		// Make sure that nothing sneaks out
		// Record all 3 overflow attributes because IE does not
		// change the overflow attribute when overflowX and
		// overflowY are set to the same value
		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];

		// Set display property to inline-block for height/width
		// animations on inline elements that are having width/height animated
		display = jQuery.css( elem, "display" );

		// Test default display if display is currently "none"
		checkDisplay = display === "none" ?
			jQuery._data( elem, "olddisplay" ) || defaultDisplay( elem.nodeName ) : display;

		if ( checkDisplay === "inline" && jQuery.css( elem, "float" ) === "none" ) {

			// inline-level elements accept inline-block;
			// block-level elements need to be inline with layout
			if ( !support.inlineBlockNeedsLayout || defaultDisplay( elem.nodeName ) === "inline" ) {
				style.display = "inline-block";
			} else {
				style.zoom = 1;
			}
		}
	}

	if ( opts.overflow ) {
		style.overflow = "hidden";
		if ( !support.shrinkWrapBlocks() ) {
			anim.always( function() {
				style.overflow = opts.overflow[ 0 ];
				style.overflowX = opts.overflow[ 1 ];
				style.overflowY = opts.overflow[ 2 ];
			} );
		}
	}

	// show/hide pass
	for ( prop in props ) {
		value = props[ prop ];
		if ( rfxtypes.exec( value ) ) {
			delete props[ prop ];
			toggle = toggle || value === "toggle";
			if ( value === ( hidden ? "hide" : "show" ) ) {

				// If there is dataShow left over from a stopped hide or show
				// and we are going to proceed with show, we should pretend to be hidden
				if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
					hidden = true;
				} else {
					continue;
				}
			}
			orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );

		// Any non-fx value stops us from restoring the original display value
		} else {
			display = undefined;
		}
	}

	if ( !jQuery.isEmptyObject( orig ) ) {
		if ( dataShow ) {
			if ( "hidden" in dataShow ) {
				hidden = dataShow.hidden;
			}
		} else {
			dataShow = jQuery._data( elem, "fxshow", {} );
		}

		// store state if its toggle - enables .stop().toggle() to "reverse"
		if ( toggle ) {
			dataShow.hidden = !hidden;
		}
		if ( hidden ) {
			jQuery( elem ).show();
		} else {
			anim.done( function() {
				jQuery( elem ).hide();
			} );
		}
		anim.done( function() {
			var prop;
			jQuery._removeData( elem, "fxshow" );
			for ( prop in orig ) {
				jQuery.style( elem, prop, orig[ prop ] );
			}
		} );
		for ( prop in orig ) {
			tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );

			if ( !( prop in dataShow ) ) {
				dataShow[ prop ] = tween.start;
				if ( hidden ) {
					tween.end = tween.start;
					tween.start = prop === "width" || prop === "height" ? 1 : 0;
				}
			}
		}

	// If this is a noop like .hide().hide(), restore an overwritten display value
	} else if ( ( display === "none" ? defaultDisplay( elem.nodeName ) : display ) === "inline" ) {
		style.display = display;
	}
}

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 ( jQuery.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 wont overwrite keys already present.
			// also - reusing 'index' from above 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
				// 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 ( percent < 1 && length ) {
				return remaining;
			} else {
				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 );
	}

	jQuery.fx.timer(
		jQuery.extend( tick, {
			elem: elem,
			anim: animation,
			queue: animation.opts.queue
		} )
	);

	// attach callbacks from options
	return animation.progress( animation.opts.progress )
		.done( animation.opts.done, animation.opts.complete )
		.fail( animation.opts.fail )
		.always( animation.opts.always );
}

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( rnotwhite );
		}

		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
	};

	opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
		opt.duration in jQuery.fx.speeds ?
			jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;

	// normalize opt.queue - true/undefined/null -> "fx"
	if ( opt.queue == null || opt.queue === true ) {
		opt.queue = "fx";
	}

	// Queueing
	opt.old = opt.complete;

	opt.complete = function() {
		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( isHidden ).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 || jQuery._data( 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 = jQuery._data( 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 = jQuery._data( 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,
		timers = jQuery.timers,
		i = 0;

	fxNow = jQuery.now();

	for ( ; i < timers.length; i++ ) {
		timer = timers[ i ];

		// Checks the timer has not already been removed
		if ( !timer() && timers[ i ] === timer ) {
			timers.splice( i--, 1 );
		}
	}

	if ( !timers.length ) {
		jQuery.fx.stop();
	}
	fxNow = undefined;
};

jQuery.fx.timer = function( timer ) {
	jQuery.timers.push( timer );
	if ( timer() ) {
		jQuery.fx.start();
	} else {
		jQuery.timers.pop();
	}
};

jQuery.fx.interval = 13;

jQuery.fx.start = function() {
	if ( !timerId ) {
		timerId = window.setInterval( jQuery.fx.tick, jQuery.fx.interval );
	}
};

jQuery.fx.stop = function() {
	window.clearInterval( timerId );
	timerId = null;
};

jQuery.fx.speeds = {
	slow: 600,
	fast: 200,

	// Default speed
	_default: 400
};


// Based off of the plugin by Clint Helfers, with permission.
// http://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 a,
		input = document.createElement( "input" ),
		div = document.createElement( "div" ),
		select = document.createElement( "select" ),
		opt = select.appendChild( document.createElement( "option" ) );

	// Setup
	div = document.createElement( "div" );
	div.setAttribute( "className", "t" );
	div.innerHTML = "  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
	a = div.getElementsByTagName( "a" )[ 0 ];

	// Support: Windows Web Apps (WWA)
	// `type` must use .setAttribute for WWA (#14901)
	input.setAttribute( "type", "checkbox" );
	div.appendChild( input );

	a = div.getElementsByTagName( "a" )[ 0 ];

	// First batch of tests.
	a.style.cssText = "top:1px";

	// Test setAttribute on camelCase class.
	// If it works, we need attrFixes when doing get/setAttribute (ie6/7)
	support.getSetAttribute = div.className !== "t";

	// Get the style information from getAttribute
	// (IE uses .cssText instead)
	support.style = /top/.test( a.getAttribute( "style" ) );

	// Make sure that URLs aren't manipulated
	// (IE normalizes it by default)
	support.hrefNormalized = a.getAttribute( "href" ) === "/a";

	// Check the default checkbox/radio value ("" on WebKit; "on" elsewhere)
	support.checkOn = !!input.value;

	// Make sure that a selected-by-default option has a working selected property.
	// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
	support.optSelected = opt.selected;

	// Tests for enctype support on a form (#6743)
	support.enctype = !!document.createElement( "form" ).enctype;

	// Make sure that the options inside disabled selects aren't marked as disabled
	// (WebKit marks them as disabled)
	select.disabled = true;
	support.optDisabled = !opt.disabled;

	// Support: IE8 only
	// Check if we can trust getAttribute("value")
	input = document.createElement( "input" );
	input.setAttribute( "value", "" );
	support.input = input.getAttribute( "value" ) === "";

	// Check if an input maintains its value after becoming a radio
	input.value = "t";
	input.setAttribute( "type", "radio" );
	support.radioValue = input.value === "t";
} )();


var rreturn = /\r/g,
	rspaces = /[\x20\t\r\n\f]+/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;

				return typeof ret === "string" ?

					// handle most common string cases
					ret.replace( rreturn, "" ) :

					// handle cases where value is null/undef or number
					ret == null ? "" : ret;
			}

			return;
		}

		isFunction = jQuery.isFunction( value );

		return this.each( function( i ) {
			var 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 ( jQuery.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: IE10-11+
					// option.text throws exceptions (#14686, #14858)
					// Strip and collapse whitespace
					// https://html.spec.whatwg.org/#strip-and-collapse-whitespace
					jQuery.trim( jQuery.text( elem ) ).replace( rspaces, " " );
			}
		},
		select: {
			get: function( elem ) {
				var value, option,
					options = elem.options,
					index = elem.selectedIndex,
					one = elem.type === "select-one" || index < 0,
					values = one ? null : [],
					max = one ? index + 1 : options.length,
					i = index < 0 ?
						max :
						one ? index : 0;

				// Loop through all the selected options
				for ( ; i < max; i++ ) {
					option = options[ i ];

					// oldIE 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
							( support.optDisabled ?
								!option.disabled :
								option.getAttribute( "disabled" ) === null ) &&
							( !option.parentNode.disabled ||
								!jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {

						// Get the specific value for the option
						value = jQuery( option ).val();

						// We don't need an array for one selects
						if ( one ) {
							return value;
						}

						// Multi-Selects return an array
						values.push( value );
					}
				}

				return values;
			},

			set: function( elem, value ) {
				var optionSet, option,
					options = elem.options,
					values = jQuery.makeArray( value ),
					i = options.length;

				while ( i-- ) {
					option = options[ i ];

					if ( jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 ) {

						// Support: IE6
						// When new option element is added to select box we need to
						// force reflow of newly added node in order to workaround delay
						// of initialization properties
						try {
							option.selected = optionSet = true;

						} catch ( _ ) {

							// Will be executed only in IE6
							option.scrollHeight;
						}

					} else {
						option.selected = false;
					}
				}

				// Force browsers to behave consistently when non-matching value is set
				if ( !optionSet ) {
					elem.selectedIndex = -1;
				}

				return options;
			}
		}
	}
} );

// Radios and checkboxes getter/setter
jQuery.each( [ "radio", "checkbox" ], function() {
	jQuery.valHooks[ this ] = {
		set: function( elem, value ) {
			if ( jQuery.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;
		};
	}
} );




var nodeHook, boolHook,
	attrHandle = jQuery.expr.attrHandle,
	ruseDefault = /^(?:checked|selected)$/i,
	getSetAttribute = support.getSetAttribute,
	getSetInput = support.input;

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 );
		}

		// All attributes are lowercase
		// Grab necessary hook if one is defined
		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
			name = name.toLowerCase();
			hooks = jQuery.attrHooks[ name ] ||
				( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );
		}

		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" &&
					jQuery.nodeName( elem, "input" ) ) {

					// Setting the type on a radio button after the value resets the value in IE8-9
					// Reset value to default in case type is set after value during creation
					var val = elem.value;
					elem.setAttribute( "type", value );
					if ( val ) {
						elem.value = val;
					}
					return value;
				}
			}
		}
	},

	removeAttr: function( elem, value ) {
		var name, propName,
			i = 0,
			attrNames = value && value.match( rnotwhite );

		if ( attrNames && elem.nodeType === 1 ) {
			while ( ( name = attrNames[ i++ ] ) ) {
				propName = jQuery.propFix[ name ] || name;

				// Boolean attributes get special treatment (#10870)
				if ( jQuery.expr.match.bool.test( name ) ) {

					// Set corresponding property to false
					if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
						elem[ propName ] = false;

					// Support: IE<9
					// Also clear defaultChecked/defaultSelected (if appropriate)
					} else {
						elem[ jQuery.camelCase( "default-" + name ) ] =
							elem[ propName ] = false;
					}

				// See #9699 for explanation of this approach (setting first, then removal)
				} else {
					jQuery.attr( elem, name, "" );
				}

				elem.removeAttribute( getSetAttribute ? name : propName );
			}
		}
	}
} );

// 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 if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {

			// IE<8 needs the *property* name
			elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name );

		} else {

			// Support: IE<9
			// Use defaultChecked and defaultSelected for oldIE
			elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true;
		}
		return name;
	}
};

jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
	var getter = attrHandle[ name ] || jQuery.find.attr;

	if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
		attrHandle[ name ] = function( elem, name, isXML ) {
			var ret, handle;
			if ( !isXML ) {

				// Avoid an infinite loop by temporarily removing this function from the getter
				handle = attrHandle[ name ];
				attrHandle[ name ] = ret;
				ret = getter( elem, name, isXML ) != null ?
					name.toLowerCase() :
					null;
				attrHandle[ name ] = handle;
			}
			return ret;
		};
	} else {
		attrHandle[ name ] = function( elem, name, isXML ) {
			if ( !isXML ) {
				return elem[ jQuery.camelCase( "default-" + name ) ] ?
					name.toLowerCase() :
					null;
			}
		};
	}
} );

// fix oldIE attroperties
if ( !getSetInput || !getSetAttribute ) {
	jQuery.attrHooks.value = {
		set: function( elem, value, name ) {
			if ( jQuery.nodeName( elem, "input" ) ) {

				// Does not return so that setAttribute is also used
				elem.defaultValue = value;
			} else {

				// Use nodeHook if defined (#1954); otherwise setAttribute is fine
				return nodeHook && nodeHook.set( elem, value, name );
			}
		}
	};
}

// IE6/7 do not support getting/setting some attributes with get/setAttribute
if ( !getSetAttribute ) {

	// Use this for any attribute in IE6/7
	// This fixes almost every IE6/7 issue
	nodeHook = {
		set: function( elem, value, name ) {

			// Set the existing or create a new attribute node
			var ret = elem.getAttributeNode( name );
			if ( !ret ) {
				elem.setAttributeNode(
					( ret = elem.ownerDocument.createAttribute( name ) )
				);
			}

			ret.value = value += "";

			// Break association with cloned elements by also using setAttribute (#9646)
			if ( name === "value" || value === elem.getAttribute( name ) ) {
				return value;
			}
		}
	};

	// Some attributes are constructed with empty-string values when not defined
	attrHandle.id = attrHandle.name = attrHandle.coords =
		function( elem, name, isXML ) {
			var ret;
			if ( !isXML ) {
				return ( ret = elem.getAttributeNode( name ) ) && ret.value !== "" ?
					ret.value :
					null;
			}
		};

	// Fixing value retrieval on a button requires this module
	jQuery.valHooks.button = {
		get: function( elem, name ) {
			var ret = elem.getAttributeNode( name );
			if ( ret && ret.specified ) {
				return ret.value;
			}
		},
		set: nodeHook.set
	};

	// Set contenteditable to false on removals(#10429)
	// Setting to empty string throws an error as an invalid value
	jQuery.attrHooks.contenteditable = {
		set: function( elem, value, name ) {
			nodeHook.set( elem, value === "" ? false : value, name );
		}
	};

	// Set width and height to auto instead of 0 on empty string( Bug #8150 )
	// This is for removals
	jQuery.each( [ "width", "height" ], function( i, name ) {
		jQuery.attrHooks[ name ] = {
			set: function( elem, value ) {
				if ( value === "" ) {
					elem.setAttribute( name, "auto" );
					return value;
				}
			}
		};
	} );
}

if ( !support.style ) {
	jQuery.attrHooks.style = {
		get: function( elem ) {

			// Return undefined in the case of empty string
			// Note: IE uppercases css property names, but if we were to .toLowerCase()
			// .cssText, that would destroy case sensitivity in URL's, like in "background"
			return elem.style.cssText || undefined;
		},
		set: function( elem, value ) {
			return ( elem.style.cssText = value + "" );
		}
	};
}




var rfocusable = /^(?:input|select|textarea|button|object)$/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 ) {
		name = jQuery.propFix[ name ] || name;
		return this.each( function() {

			// try/catch handles cases where IE balks (such as removing a property on window)
			try {
				this[ name ] = undefined;
				delete this[ name ];
			} catch ( e ) {}
		} );
	}
} );

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 ) {

				// elem.tabIndex doesn't always return the
				// correct value when it hasn't been explicitly set
				// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
				// Use proper attribute retrieval(#12072)
				var tabindex = jQuery.find.attr( elem, "tabindex" );

				return tabindex ?
					parseInt( tabindex, 10 ) :
					rfocusable.test( elem.nodeName ) ||
						rclickable.test( elem.nodeName ) && elem.href ?
							0 :
							-1;
			}
		}
	},

	propFix: {
		"for": "htmlFor",
		"class": "className"
	}
} );

// Some attributes require a special call on IE
// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
if ( !support.hrefNormalized ) {

	// href/src property should get the full normalized URL (#10299/#12915)
	jQuery.each( [ "href", "src" ], function( i, name ) {
		jQuery.propHooks[ name ] = {
			get: function( elem ) {
				return elem.getAttribute( name, 4 );
			}
		};
	} );
}

// Support: Safari, IE9+
// 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
if ( !support.optSelected ) {
	jQuery.propHooks.selected = {
		get: function( elem ) {
			var parent = elem.parentNode;

			if ( parent ) {
				parent.selectedIndex;

				// Make sure that it also works with optgroups, see #5701
				if ( parent.parentNode ) {
					parent.parentNode.selectedIndex;
				}
			}
			return null;
		},
		set: function( elem ) {
			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;
} );

// IE6/7 call enctype encoding
if ( !support.enctype ) {
	jQuery.propFix.enctype = "encoding";
}




var rclass = /[\t\r\n\f]/g;

function getClass( elem ) {
	return jQuery.attr( elem, "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( rnotwhite ) || [];

			while ( ( elem = this[ i++ ] ) ) {
				curValue = getClass( elem );
				cur = elem.nodeType === 1 &&
					( " " + curValue + " " ).replace( rclass, " " );

				if ( cur ) {
					j = 0;
					while ( ( clazz = classes[ j++ ] ) ) {
						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
							cur += clazz + " ";
						}
					}

					// only assign if different to avoid unneeded rendering.
					finalValue = jQuery.trim( cur );
					if ( curValue !== finalValue ) {
						jQuery.attr( elem, "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( rnotwhite ) || [];

			while ( ( elem = this[ i++ ] ) ) {
				curValue = getClass( elem );

				// This expression is here for better compressibility (see addClass)
				cur = elem.nodeType === 1 &&
					( " " + curValue + " " ).replace( rclass, " " );

				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 = jQuery.trim( cur );
					if ( curValue !== finalValue ) {
						jQuery.attr( elem, "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( rnotwhite ) || [];

				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
					jQuery._data( 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.
				jQuery.attr( this, "class",
					className || value === false ?
					"" :
					jQuery._data( this, "__className__" ) || ""
				);
			}
		} );
	},

	hasClass: function( selector ) {
		var className, elem,
			i = 0;

		className = " " + selector + " ";
		while ( ( elem = this[ i++ ] ) ) {
			if ( elem.nodeType === 1 &&
				( " " + getClass( elem ) + " " ).replace( rclass, " " )
					.indexOf( className ) > -1
			) {
				return true;
			}
		}

		return false;
	}
} );




// Return jQuery for attributes-only inclusion


jQuery.each( ( "blur focus focusin focusout load resize scroll unload click dblclick " +
	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
	"change select submit keydown keypress keyup error contextmenu" ).split( " " ),
	function( i, name ) {

	// Handle event binding
	jQuery.fn[ name ] = function( data, fn ) {
		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 );
	}
} );


var location = window.location;

var nonce = jQuery.now();

var rquery = ( /\?/ );



var rvalidtokens = /(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;

jQuery.parseJSON = function( data ) {

	// Attempt to parse using the native JSON parser first
	if ( window.JSON && window.JSON.parse ) {

		// Support: Android 2.3
		// Workaround failure to string-cast null input
		return window.JSON.parse( data + "" );
	}

	var requireNonComma,
		depth = null,
		str = jQuery.trim( data + "" );

	// Guard against invalid (and possibly dangerous) input by ensuring that nothing remains
	// after removing valid tokens
	return str && !jQuery.trim( str.replace( rvalidtokens, function( token, comma, open, close ) {

		// Force termination if we see a misplaced comma
		if ( requireNonComma && comma ) {
			depth = 0;
		}

		// Perform no more replacements after returning to outermost depth
		if ( depth === 0 ) {
			return token;
		}

		// Commas must not follow "[", "{", or ","
		requireNonComma = open || comma;

		// Determine new depth
		// array/object open ("[" or "{"): depth += true - false (increment)
		// array/object close ("]" or "}"): depth += false - true (decrement)
		// other cases ("," or primitive): depth += true - true (numeric cast)
		depth += !close - !open;

		// Remove this token
		return "";
	} ) ) ?
		( Function( "return " + str ) )() :
		jQuery.error( "Invalid JSON: " + data );
};


// Cross-browser xml parsing
jQuery.parseXML = function( data ) {
	var xml, tmp;
	if ( !data || typeof data !== "string" ) {
		return null;
	}
	try {
		if ( window.DOMParser ) { // Standard
			tmp = new window.DOMParser();
			xml = tmp.parseFromString( data, "text/xml" );
		} else { // IE
			xml = new window.ActiveXObject( "Microsoft.XMLDOM" );
			xml.async = "false";
			xml.loadXML( data );
		}
	} catch ( e ) {
		xml = undefined;
	}
	if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
		jQuery.error( "Invalid XML: " + data );
	}
	return xml;
};


var
	rhash = /#.*$/,
	rts = /([?&])_=[^&]*/,

	// IE leaves an \r character at EOL
	rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg,

	// #7653, #8125, #8152: local protocol detection
	rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
	rnoContent = /^(?:GET|HEAD)$/,
	rprotocol = /^\/\//,
	rurl = /^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,

	/* 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( "*" ),

	// Document location
	ajaxLocation = location.href,

	// Segment location into parts
	ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];

// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
function addToPrefiltersOrTransports( structure ) {

	// dataTypeExpression is optional and defaults to "*"
	return function( dataTypeExpression, func ) {

		if ( typeof dataTypeExpression !== "string" ) {
			func = dataTypeExpression;
			dataTypeExpression = "*";
		}

		var dataType,
			i = 0,
			dataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || [];

		if ( jQuery.isFunction( func ) ) {

			// For each dataType in the dataTypeExpression
			while ( ( dataType = dataTypes[ i++ ] ) ) {

				// Prepend if requested
				if ( dataType.charAt( 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 deep, key,
		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 firstDataType, ct, finalDataType, type,
		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" ] ) { // jscs:ignore requireDotNotation
						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: ajaxLocation,
		type: "GET",
		isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
		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": jQuery.parseJSON,

			// Parse text as xml
			"text xml": jQuery.parseXML
		},

		// For options that shouldn't be deep extended:
		// you can add your own custom options here if
		// and when you create one that shouldn't be
		// deep extended (see ajaxExtend)
		flatOptions: {
			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

			// Cross-domain detection vars
			parts,

			// Loop variable
			i,

			// URL without anti-cache param
			cacheURL,

			// Response headers as string
			responseHeadersString,

			// timeout handle
			timeoutTimer,

			// To know if global events are to be dispatched
			fireGlobals,

			transport,

			// Response headers
			responseHeaders,

			// 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 = {},

			// The jqXHR state
			state = 0,

			// Default abort message
			strAbort = "canceled",

			// Fake xhr
			jqXHR = {
				readyState: 0,

				// Builds headers hashtable if needed
				getResponseHeader: function( key ) {
					var match;
					if ( state === 2 ) {
						if ( !responseHeaders ) {
							responseHeaders = {};
							while ( ( match = rheaders.exec( responseHeadersString ) ) ) {
								responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ];
							}
						}
						match = responseHeaders[ key.toLowerCase() ];
					}
					return match == null ? null : match;
				},

				// Raw string
				getAllResponseHeaders: function() {
					return state === 2 ? responseHeadersString : null;
				},

				// Caches the header
				setRequestHeader: function( name, value ) {
					var lname = name.toLowerCase();
					if ( !state ) {
						name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
						requestHeaders[ name ] = value;
					}
					return this;
				},

				// Overrides response content-type header
				overrideMimeType: function( type ) {
					if ( !state ) {
						s.mimeType = type;
					}
					return this;
				},

				// Status-dependent callbacks
				statusCode: function( map ) {
					var code;
					if ( map ) {
						if ( state < 2 ) {
							for ( code in map ) {

								// Lazy-add the new callback in a way that preserves old ones
								statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
							}
						} else {

							// Execute the appropriate callbacks
							jqXHR.always( map[ jqXHR.status ] );
						}
					}
					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 ).complete = completeDeferred.add;
		jqXHR.success = jqXHR.done;
		jqXHR.error = jqXHR.fail;

		// Remove hash character (#7531: and string promotion)
		// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
		// 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 || ajaxLocation ) + "" )
			.replace( rhash, "" )
			.replace( rprotocol, ajaxLocParts[ 1 ] + "//" );

		// Alias method option to type as per ticket #12004
		s.type = options.method || options.type || s.method || s.type;

		// Extract dataTypes list
		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( rnotwhite ) || [ "" ];

		// A cross-domain request is in order when we have a protocol:host:port mismatch
		if ( s.crossDomain == null ) {
			parts = rurl.exec( s.url.toLowerCase() );
			s.crossDomain = !!( parts &&
				( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
					( parts[ 3 ] || ( parts[ 1 ] === "http:" ? "80" : "443" ) ) !==
						( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? "80" : "443" ) ) )
			);
		}

		// Convert data if not already a string
		if ( s.data && s.processData && typeof s.data !== "string" ) {
			s.data = jQuery.param( s.data, s.traditional );
		}

		// Apply prefilters
		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );

		// If request was aborted inside a prefilter, stop there
		if ( state === 2 ) {
			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
		cacheURL = s.url;

		// More options handling for requests with no content
		if ( !s.hasContent ) {

			// If data is available, append data to url
			if ( s.data ) {
				cacheURL = ( s.url += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data );

				// #9682: remove data so that it's not used in an eventual retry
				delete s.data;
			}

			// Add anti-cache in url if needed
			if ( s.cache === false ) {
				s.url = rts.test( cacheURL ) ?

					// If there is already a '_' parameter, set its value
					cacheURL.replace( rts, "$1_=" + nonce++ ) :

					// Otherwise add one to the end
					cacheURL + ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + nonce++;
			}
		}

		// 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 || state === 2 ) ) {

			// Abort if not done already and return
			return jqXHR.abort();
		}

		// aborting is no longer a cancellation
		strAbort = "abort";

		// Install callbacks on deferreds
		for ( i in { success: 1, error: 1, complete: 1 } ) {
			jqXHR[ i ]( s[ i ] );
		}

		// Get transport
		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );

		// If no transport, we auto-abort
		if ( !transport ) {
			done( -1, "No Transport" );
		} else {
			jqXHR.readyState = 1;

			// Send global event
			if ( fireGlobals ) {
				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
			}

			// If request was aborted inside ajaxSend, stop there
			if ( state === 2 ) {
				return jqXHR;
			}

			// Timeout
			if ( s.async && s.timeout > 0 ) {
				timeoutTimer = window.setTimeout( function() {
					jqXHR.abort( "timeout" );
				}, s.timeout );
			}

			try {
				state = 1;
				transport.send( requestHeaders, done );
			} catch ( e ) {

				// Propagate exception as error if not done
				if ( state < 2 ) {
					done( -1, e );

				// Simply rethrow otherwise
				} else {
					throw e;
				}
			}
		}

		// Callback for when everything is done
		function done( status, nativeStatusText, responses, headers ) {
			var isSuccess, success, error, response, modified,
				statusText = nativeStatusText;

			// Called once
			if ( state === 2 ) {
				return;
			}

			// State is "done" now
			state = 2;

			// 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 {

				// We extract error from statusText
				// then normalize statusText and status 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 ) {
		if ( jQuery.isFunction( html ) ) {
			return this.each( function( i ) {
				jQuery( this ).wrapAll( html.call( this, i ) );
			} );
		}

		if ( this[ 0 ] ) {

			// The elements to wrap the target around
			var wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );

			if ( this[ 0 ].parentNode ) {
				wrap.insertBefore( this[ 0 ] );
			}

			wrap.map( function() {
				var elem = this;

				while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
					elem = elem.firstChild;
				}

				return elem;
			} ).append( this );
		}

		return this;
	},

	wrapInner: function( html ) {
		if ( jQuery.isFunction( html ) ) {
			return this.each( function( i ) {
				jQuery( this ).wrapInner( html.call( this, i ) );
			} );
		}

		return this.each( function() {
			var self = jQuery( this ),
				contents = self.contents();

			if ( contents.length ) {
				contents.wrapAll( html );

			} else {
				self.append( html );
			}
		} );
	},

	wrap: function( html ) {
		var isFunction = jQuery.isFunction( html );

		return this.each( function( i ) {
			jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html );
		} );
	},

	unwrap: function() {
		return this.parent().each( function() {
			if ( !jQuery.nodeName( this, "body" ) ) {
				jQuery( this ).replaceWith( this.childNodes );
			}
		} ).end();
	}
} );


function getDisplay( elem ) {
	return elem.style && elem.style.display || jQuery.css( elem, "display" );
}

function filterHidden( elem ) {

	// Disconnected elements are considered hidden
	if ( !jQuery.contains( elem.ownerDocument || document, elem ) ) {
		return true;
	}
	while ( elem && elem.nodeType === 1 ) {
		if ( getDisplay( elem ) === "none" || elem.type === "hidden" ) {
			return true;
		}
		elem = elem.parentNode;
	}
	return false;
}

jQuery.expr.filters.hidden = function( elem ) {

	// Support: Opera <= 12.12
	// Opera reports offsetWidths and offsetHeights less than zero on some elements
	return support.reliableHiddenOffsets() ?
		( elem.offsetWidth <= 0 && elem.offsetHeight <= 0 &&
			!elem.getClientRects().length ) :
			filterHidden( elem );
};

jQuery.expr.filters.visible = function( elem ) {
	return !jQuery.expr.filters.hidden( elem );
};




var r20 = /%20/g,
	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 ( jQuery.isArray( obj ) ) {

		// Serialize array item.
		jQuery.each( obj, function( i, v ) {
			if ( traditional || rbracket.test( prefix ) ) {

				// Treat each array item as a scalar.
				add( prefix, v );

			} else {

				// 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, value ) {

			// If value is a function, invoke it and return its value
			value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
			s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
		};

	// Set traditional to true for jQuery <= 1.3.2 behavior.
	if ( traditional === undefined ) {
		traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
	}

	// If an array was passed in, assume that it is an array of form elements.
	if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {

		// Serialize the form elements
		jQuery.each( a, function() {
			add( this.name, this.value );
		} );

	} else {

		// If traditional, encode the "old" way (the way 1.3.2 or older
		// did it), otherwise encode params recursively.
		for ( prefix in a ) {
			buildParams( prefix, a[ prefix ], traditional, add );
		}
	}

	// Return the resulting serialization
	return s.join( "&" ).replace( r20, "+" );
};

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();

			return val == null ?
				null :
				jQuery.isArray( val ) ?
					jQuery.map( val, function( val ) {
						return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
					} ) :
					{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
		} ).get();
	}
} );


// Create the request object
// (This is still attached to ajaxSettings for backward compatibility)
jQuery.ajaxSettings.xhr = window.ActiveXObject !== undefined ?

	// Support: IE6-IE8
	function() {

		// XHR cannot access local files, always use ActiveX for that case
		if ( this.isLocal ) {
			return createActiveXHR();
		}

		// Support: IE 9-11
		// IE seems to error on cross-domain PATCH requests when ActiveX XHR
		// is used. In IE 9+ always use the native XHR.
		// Note: this condition won't catch Edge as it doesn't define
		// document.documentMode but it also doesn't support ActiveX so it won't
		// reach this code.
		if ( document.documentMode > 8 ) {
			return createStandardXHR();
		}

		// Support: IE<9
		// oldIE XHR does not support non-RFC2616 methods (#13240)
		// See http://msdn.microsoft.com/en-us/library/ie/ms536648(v=vs.85).aspx
		// and http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9
		// Although this check for six methods instead of eight
		// since IE also does not support "trace" and "connect"
		return /^(get|post|head|put|delete|options)$/i.test( this.type ) &&
			createStandardXHR() || createActiveXHR();
	} :

	// For all other browsers, use the standard XMLHttpRequest object
	createStandardXHR;

var xhrId = 0,
	xhrCallbacks = {},
	xhrSupported = jQuery.ajaxSettings.xhr();

// Support: IE<10
// Open requests must be manually aborted on unload (#5280)
// See https://support.microsoft.com/kb/2856746 for more info
if ( window.attachEvent ) {
	window.attachEvent( "onunload", function() {
		for ( var key in xhrCallbacks ) {
			xhrCallbacks[ key ]( undefined, true );
		}
	} );
}

// Determine support properties
support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
xhrSupported = support.ajax = !!xhrSupported;

// Create transport if the browser can provide an xhr
if ( xhrSupported ) {

	jQuery.ajaxTransport( function( options ) {

		// Cross domain only allowed if supported through XMLHttpRequest
		if ( !options.crossDomain || support.cors ) {

			var callback;

			return {
				send: function( headers, complete ) {
					var i,
						xhr = options.xhr(),
						id = ++xhrId;

					// Open the socket
					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 ) {

						// Support: IE<9
						// IE's ActiveXObject throws a 'Type Mismatch' exception when setting
						// request header to a null-value.
						//
						// To keep consistent with other XHR implementations, cast the value
						// to string and ignore `undefined`.
						if ( headers[ i ] !== undefined ) {
							xhr.setRequestHeader( i, headers[ i ] + "" );
						}
					}

					// Do send the request
					// This may raise an exception which is actually
					// handled in jQuery.ajax (so no try/catch here)
					xhr.send( ( options.hasContent && options.data ) || null );

					// Listener
					callback = function( _, isAbort ) {
						var status, statusText, responses;

						// Was never called and is aborted or complete
						if ( callback && ( isAbort || xhr.readyState === 4 ) ) {

							// Clean up
							delete xhrCallbacks[ id ];
							callback = undefined;
							xhr.onreadystatechange = jQuery.noop;

							// Abort manually if needed
							if ( isAbort ) {
								if ( xhr.readyState !== 4 ) {
									xhr.abort();
								}
							} else {
								responses = {};
								status = xhr.status;

								// Support: IE<10
								// Accessing binary-data responseText throws an exception
								// (#11426)
								if ( typeof xhr.responseText === "string" ) {
									responses.text = xhr.responseText;
								}

								// Firefox throws an exception when accessing
								// statusText for faulty cross-domain requests
								try {
									statusText = xhr.statusText;
								} catch ( e ) {

									// We normalize with Webkit giving an empty statusText
									statusText = "";
								}

								// Filter status for non standard behaviors

								// If the request is local and we have data: assume a success
								// (success with no data won't get notified, that's the best we
								// can do given current implementations)
								if ( !status && options.isLocal && !options.crossDomain ) {
									status = responses.text ? 200 : 404;

								// IE - #1450: sometimes returns 1223 when it should be 204
								} else if ( status === 1223 ) {
									status = 204;
								}
							}
						}

						// Call complete if needed
						if ( responses ) {
							complete( status, statusText, responses, xhr.getAllResponseHeaders() );
						}
					};

					// Do send the request
					// `xhr.send` may raise an exception, but it will be
					// handled in jQuery.ajax (so no try/catch here)
					if ( !options.async ) {

						// If we're in sync mode we fire the callback
						callback();
					} else if ( xhr.readyState === 4 ) {

						// (IE6 & IE7) if it's in cache and has been
						// retrieved directly we need to fire the callback
						window.setTimeout( callback );
					} else {

						// Register the callback, but delay it in case `xhr.send` throws
						// Add to the list of active xhr callbacks
						xhr.onreadystatechange = xhrCallbacks[ id ] = callback;
					}
				},

				abort: function() {
					if ( callback ) {
						callback( undefined, true );
					}
				}
			};
		}
	} );
}

// Functions to create xhrs
function createStandardXHR() {
	try {
		return new window.XMLHttpRequest();
	} catch ( e ) {}
}

function createActiveXHR() {
	try {
		return new window.ActiveXObject( "Microsoft.XMLHTTP" );
	} catch ( e ) {}
}




// 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 global
jQuery.ajaxPrefilter( "script", function( s ) {
	if ( s.cache === undefined ) {
		s.cache = false;
	}
	if ( s.crossDomain ) {
		s.type = "GET";
		s.global = false;
	}
} );

// Bind script tag hack transport
jQuery.ajaxTransport( "script", function( s ) {

	// This transport only deals with cross domain requests
	if ( s.crossDomain ) {

		var script,
			head = document.head || jQuery( "head" )[ 0 ] || document.documentElement;

		return {

			send: function( _, callback ) {

				script = document.createElement( "script" );

				script.async = true;

				if ( s.scriptCharset ) {
					script.charset = s.scriptCharset;
				}

				script.src = s.url;

				// Attach handlers for all browsers
				script.onload = script.onreadystatechange = function( _, isAbort ) {

					if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {

						// Handle memory leak in IE
						script.onload = script.onreadystatechange = null;

						// Remove the script
						if ( script.parentNode ) {
							script.parentNode.removeChild( script );
						}

						// Dereference the script
						script = null;

						// Callback if not abort
						if ( !isAbort ) {
							callback( 200, "success" );
						}
					}
				};

				// Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending
				// Use native DOM manipulation to avoid our domManip AJAX trickery
				head.insertBefore( script, head.firstChild );
			},

			abort: function() {
				if ( script ) {
					script.onload( undefined, true );
				}
			}
		};
	}
} );




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";
	}
} );




// data: 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 ( !data || typeof data !== "string" ) {
		return null;
	}
	if ( typeof context === "boolean" ) {
		keepScripts = context;
		context = false;
	}
	context = context || document;

	var 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 );
};


// Keep a copy of the old load method
var _load = jQuery.fn.load;

/**
 * Load a url into a page
 */
jQuery.fn.load = function( url, params, callback ) {
	if ( typeof url !== "string" && _load ) {
		return _load.apply( this, arguments );
	}

	var selector, type, response,
		self = this,
		off = url.indexOf( " " );

	if ( off > -1 ) {
		selector = jQuery.trim( url.slice( off, url.length ) );
		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.filters.animated = function( elem ) {
	return jQuery.grep( jQuery.timers, function( fn ) {
		return elem === fn.elem;
	} ).length;
};





/**
 * Gets a window from an element
 */
function getWindow( elem ) {
	return jQuery.isWindow( elem ) ?
		elem :
		elem.nodeType === 9 ?
			elem.defaultView || elem.parentWindow :
			false;
}

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" ) &&
			jQuery.inArray( "auto", [ curCSSTop, curCSSLeft ] ) > -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 ) {
		if ( arguments.length ) {
			return options === undefined ?
				this :
				this.each( function( i ) {
					jQuery.offset.setOffset( this, options, i );
				} );
		}

		var docElem, win,
			box = { top: 0, left: 0 },
			elem = this[ 0 ],
			doc = elem && elem.ownerDocument;

		if ( !doc ) {
			return;
		}

		docElem = doc.documentElement;

		// Make sure it's not a disconnected DOM node
		if ( !jQuery.contains( docElem, elem ) ) {
			return box;
		}

		// If we don't have gBCR, just use 0,0 rather than error
		// BlackBerry 5, iOS 3 (original iPhone)
		if ( typeof elem.getBoundingClientRect !== "undefined" ) {
			box = elem.getBoundingClientRect();
		}
		win = getWindow( doc );
		return {
			top: box.top  + ( win.pageYOffset || docElem.scrollTop )  - ( docElem.clientTop  || 0 ),
			left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )
		};
	},

	position: function() {
		if ( !this[ 0 ] ) {
			return;
		}

		var offsetParent, offset,
			parentOffset = { top: 0, left: 0 },
			elem = this[ 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" ) {

			// we assume that getBoundingClientRect is available when computed position is fixed
			offset = elem.getBoundingClientRect();
		} else {

			// Get *real* offsetParent
			offsetParent = this.offsetParent();

			// Get correct offsets
			offset = this.offset();
			if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
				parentOffset = offsetParent.offset();
			}

			// Add offsetParent borders
			parentOffset.top  += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
			parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
		}

		// Subtract parent offsets and element margins
		// note: when an element has margin: auto the offsetLeft and marginLeft
		// are the same in Safari causing offset.left to incorrectly be 0
		return {
			top:  offset.top  - parentOffset.top - jQuery.css( elem, "marginTop", true ),
			left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
		};
	},

	offsetParent: function() {
		return this.map( function() {
			var offsetParent = this.offsetParent;

			while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) &&
				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 = /Y/.test( prop );

	jQuery.fn[ method ] = function( val ) {
		return access( this, function( elem, method, val ) {
			var win = getWindow( elem );

			if ( val === undefined ) {
				return win ? ( prop in win ) ? win[ prop ] :
					win.document.documentElement[ method ] :
					elem[ method ];
			}

			if ( win ) {
				win.scrollTo(
					!top ? val : jQuery( win ).scrollLeft(),
					top ? val : jQuery( win ).scrollTop()
				);

			} else {
				elem[ method ] = val;
			}
		}, method, val, arguments.length, null );
	};
} );

// Support: Safari<7-8+, Chrome<37-44+
// Add the top/left cssHooks using jQuery.fn.position
// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
// getComputedStyle returns percent when specified for top/left/bottom/right
// rather than make the css module depend on the offset module, we 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 ) ) {

					// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
					// isn't a whole lot we can do. See pull request at this URL for discussion:
					// https://github.com/jquery/jquery/pull/764
					return 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
					// unfortunately, this causes bug #3838 in IE6/8 only,
					// but there is currently no good, small way to fix it.
					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, null );
		};
	} );
} );


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 );
	}
} );

// The number of elements contained in the matched element set
jQuery.fn.size = function() {
	return this.length;
};

jQuery.fn.andSelf = jQuery.fn.addBack;




// 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;
}));

/*
 * JQuery zTree core v3.5.28
 * http://treejs.cn/
 *
 * Copyright (c) 2010 Hunter.z
 *
 * Licensed same as jquery - MIT License
 * http://www.opensource.org/licenses/mit-license.php
 *
 * email: hunter.z@263.net
 * Date: 2017-01-20
 */
(function ($) {
    var settings = {}, roots = {}, caches = {},
        //default consts of core
        _consts = {
            className: {
                BUTTON: "button",
                LEVEL: "level",
                ICO_LOADING: "ico_loading",
                SWITCH: "switch",
                NAME: 'node_name'
            },
            event: {
                NODECREATED: "ztree_nodeCreated",
                CLICK: "ztree_click",
                EXPAND: "ztree_expand",
                COLLAPSE: "ztree_collapse",
                ASYNC_SUCCESS: "ztree_async_success",
                ASYNC_ERROR: "ztree_async_error",
                REMOVE: "ztree_remove",
                SELECTED: "ztree_selected",
                UNSELECTED: "ztree_unselected"
            },
            id: {
                A: "_a",
                ICON: "_ico",
                SPAN: "_span",
                SWITCH: "_switch",
                UL: "_ul"
            },
            line: {
                ROOT: "root",
                ROOTS: "roots",
                CENTER: "center",
                BOTTOM: "bottom",
                NOLINE: "noline",
                LINE: "line"
            },
            folder: {
                OPEN: "open",
                CLOSE: "close",
                DOCU: "docu"
            },
            node: {
                CURSELECTED: "curSelectedNode"
            }
        },
        //default setting of core
        _setting = {
            treeId: "",
            treeObj: null,
            view: {
                addDiyDom: null,
                autoCancelSelected: true,
                dblClickExpand: true,
                expandSpeed: "fast",
                fontCss: {},
                nameIsHTML: false,
                selectedMulti: true,
                showIcon: true,
                showLine: true,
                showTitle: true,
                txtSelectedEnable: false
            },
            data: {
                key: {
                    children: "children",
                    name: "name",
                    title: "",
                    url: "url",
                    icon: "icon"
                },
                simpleData: {
                    enable: false,
                    idKey: "id",
                    pIdKey: "pId",
                    rootPId: null
                },
                keep: {
                    parent: false,
                    leaf: false
                }
            },
            async: {
                enable: false,
                contentType: "application/x-www-form-urlencoded",
                type: "post",
                dataType: "text",
                url: "",
                autoParam: [],
                otherParam: [],
                dataFilter: null
            },
            callback: {
                beforeAsync: null,
                beforeClick: null,
                beforeDblClick: null,
                beforeRightClick: null,
                beforeMouseDown: null,
                beforeMouseUp: null,
                beforeExpand: null,
                beforeCollapse: null,
                beforeRemove: null,

                onAsyncError: null,
                onAsyncSuccess: null,
                onNodeCreated: null,
                onClick: null,
                onDblClick: null,
                onRightClick: null,
                onMouseDown: null,
                onMouseUp: null,
                onExpand: null,
                onCollapse: null,
                onRemove: null
            }
        },
        //default root of core
        //zTree use root to save full data
        _initRoot = function (setting) {
            var r = data.getRoot(setting);
            if (!r) {
                r = {};
                data.setRoot(setting, r);
            }
            r[setting.data.key.children] = [];
            r.expandTriggerFlag = false;
            r.curSelectedList = [];
            r.noSelection = true;
            r.createdNodes = [];
            r.zId = 0;
            r._ver = (new Date()).getTime();
        },
        //default cache of core
        _initCache = function (setting) {
            var c = data.getCache(setting);
            if (!c) {
                c = {};
                data.setCache(setting, c);
            }
            c.nodes = [];
            c.doms = [];
        },
        //default bindEvent of core
        _bindEvent = function (setting) {
            var o = setting.treeObj,
                c = consts.event;
            o.bind(c.NODECREATED, function (event, treeId, node) {
                tools.apply(setting.callback.onNodeCreated, [event, treeId, node]);
            });

            o.bind(c.CLICK, function (event, srcEvent, treeId, node, clickFlag) {
                tools.apply(setting.callback.onClick, [srcEvent, treeId, node, clickFlag]);
            });

            o.bind(c.EXPAND, function (event, treeId, node) {
                tools.apply(setting.callback.onExpand, [event, treeId, node]);
            });

            o.bind(c.COLLAPSE, function (event, treeId, node) {
                tools.apply(setting.callback.onCollapse, [event, treeId, node]);
            });

            o.bind(c.ASYNC_SUCCESS, function (event, treeId, node, msg) {
                tools.apply(setting.callback.onAsyncSuccess, [event, treeId, node, msg]);
            });

            o.bind(c.ASYNC_ERROR, function (event, treeId, node, XMLHttpRequest, textStatus, errorThrown) {
                tools.apply(setting.callback.onAsyncError, [event, treeId, node, XMLHttpRequest, textStatus, errorThrown]);
            });

            o.bind(c.REMOVE, function (event, treeId, treeNode) {
                tools.apply(setting.callback.onRemove, [event, treeId, treeNode]);
            });

            o.bind(c.SELECTED, function (event, treeId, node) {
                tools.apply(setting.callback.onSelected, [treeId, node]);
            });
            o.bind(c.UNSELECTED, function (event, treeId, node) {
                tools.apply(setting.callback.onUnSelected, [treeId, node]);
            });
        },
        _unbindEvent = function (setting) {
            var o = setting.treeObj,
                c = consts.event;
            o.unbind(c.NODECREATED)
                .unbind(c.CLICK)
                .unbind(c.EXPAND)
                .unbind(c.COLLAPSE)
                .unbind(c.ASYNC_SUCCESS)
                .unbind(c.ASYNC_ERROR)
                .unbind(c.REMOVE)
                .unbind(c.SELECTED)
                .unbind(c.UNSELECTED);
        },
        //default event proxy of core
        _eventProxy = function (event) {
            var target = event.target,
                setting = data.getSetting(event.data.treeId),
                tId = "", node = null,
                nodeEventType = "", treeEventType = "",
                nodeEventCallback = null, treeEventCallback = null,
                tmp = null;

            if (tools.eqs(event.type, "mousedown")) {
                treeEventType = "mousedown";
            } else if (tools.eqs(event.type, "mouseup")) {
                treeEventType = "mouseup";
            } else if (tools.eqs(event.type, "contextmenu")) {
                treeEventType = "contextmenu";
            } else if (tools.eqs(event.type, "click")) {
                if (tools.eqs(target.tagName, "span") && target.getAttribute("treeNode" + consts.id.SWITCH) !== null) {
                    tId = tools.getNodeMainDom(target).id;
                    nodeEventType = "switchNode";
                } else {
                    tmp = tools.getMDom(setting, target, [{tagName: "a", attrName: "treeNode" + consts.id.A}]);
                    if (tmp) {
                        tId = tools.getNodeMainDom(tmp).id;
                        nodeEventType = "clickNode";
                    }
                }
            } else if (tools.eqs(event.type, "dblclick")) {
                treeEventType = "dblclick";
                tmp = tools.getMDom(setting, target, [{tagName: "a", attrName: "treeNode" + consts.id.A}]);
                if (tmp) {
                    tId = tools.getNodeMainDom(tmp).id;
                    nodeEventType = "switchNode";
                }
            }
            if (treeEventType.length > 0 && tId.length == 0) {
                tmp = tools.getMDom(setting, target, [{tagName: "a", attrName: "treeNode" + consts.id.A}]);
                if (tmp) {
                    tId = tools.getNodeMainDom(tmp).id;
                }
            }
            // event to node
            if (tId.length > 0) {
                node = data.getNodeCache(setting, tId);
                switch (nodeEventType) {
                    case "switchNode" :
                        if (!node.isParent) {
                            nodeEventType = "";
                        } else if (tools.eqs(event.type, "click")
                            || (tools.eqs(event.type, "dblclick") && tools.apply(setting.view.dblClickExpand, [setting.treeId, node], setting.view.dblClickExpand))) {
                            nodeEventCallback = handler.onSwitchNode;
                        } else {
                            nodeEventType = "";
                        }
                        break;
                    case "clickNode" :
                        nodeEventCallback = handler.onClickNode;
                        break;
                }
            }
            // event to zTree
            switch (treeEventType) {
                case "mousedown" :
                    treeEventCallback = handler.onZTreeMousedown;
                    break;
                case "mouseup" :
                    treeEventCallback = handler.onZTreeMouseup;
                    break;
                case "dblclick" :
                    treeEventCallback = handler.onZTreeDblclick;
                    break;
                case "contextmenu" :
                    treeEventCallback = handler.onZTreeContextmenu;
                    break;
            }
            var proxyResult = {
                stop: false,
                node: node,
                nodeEventType: nodeEventType,
                nodeEventCallback: nodeEventCallback,
                treeEventType: treeEventType,
                treeEventCallback: treeEventCallback
            };
            return proxyResult
        },
        //default init node of core
        _initNode = function (setting, level, n, parentNode, isFirstNode, isLastNode, openFlag) {
            if (!n) return;
            var r = data.getRoot(setting),
                childKey = setting.data.key.children;
            n.level = level;
            n.tId = setting.treeId + "_" + (++r.zId);
            n.parentTId = parentNode ? parentNode.tId : null;
            n.open = (typeof n.open == "string") ? tools.eqs(n.open, "true") : !!n.open;
            if (n[childKey] && n[childKey].length > 0) {
                n.isParent = true;
                n.zAsync = true;
            } else {
                n.isParent = (typeof n.isParent == "string") ? tools.eqs(n.isParent, "true") : !!n.isParent;
                n.open = (n.isParent && !setting.async.enable) ? n.open : false;
                n.zAsync = !n.isParent;
            }
            n.isFirstNode = isFirstNode;
            n.isLastNode = isLastNode;
            n.getParentNode = function () {
                return data.getNodeCache(setting, n.parentTId);
            };
            n.getPreNode = function () {
                return data.getPreNode(setting, n);
            };
            n.getNextNode = function () {
                return data.getNextNode(setting, n);
            };
            n.getIndex = function () {
                return data.getNodeIndex(setting, n);
            };
            n.getPath = function () {
                return data.getNodePath(setting, n);
            };
            n.isAjaxing = false;
            data.fixPIdKeyValue(setting, n);
        },
        _init = {
            bind: [_bindEvent],
            unbind: [_unbindEvent],
            caches: [_initCache],
            nodes: [_initNode],
            proxys: [_eventProxy],
            roots: [_initRoot],
            beforeA: [],
            afterA: [],
            innerBeforeA: [],
            innerAfterA: [],
            zTreeTools: []
        },
        //method of operate data
        data = {
            addNodeCache: function (setting, node) {
                data.getCache(setting).nodes[data.getNodeCacheId(node.tId)] = node;
            },
            getNodeCacheId: function (tId) {
                return tId.substring(tId.lastIndexOf("_") + 1);
            },
            addAfterA: function (afterA) {
                _init.afterA.push(afterA);
            },
            addBeforeA: function (beforeA) {
                _init.beforeA.push(beforeA);
            },
            addInnerAfterA: function (innerAfterA) {
                _init.innerAfterA.push(innerAfterA);
            },
            addInnerBeforeA: function (innerBeforeA) {
                _init.innerBeforeA.push(innerBeforeA);
            },
            addInitBind: function (bindEvent) {
                _init.bind.push(bindEvent);
            },
            addInitUnBind: function (unbindEvent) {
                _init.unbind.push(unbindEvent);
            },
            addInitCache: function (initCache) {
                _init.caches.push(initCache);
            },
            addInitNode: function (initNode) {
                _init.nodes.push(initNode);
            },
            addInitProxy: function (initProxy, isFirst) {
                if (!!isFirst) {
                    _init.proxys.splice(0, 0, initProxy);
                } else {
                    _init.proxys.push(initProxy);
                }
            },
            addInitRoot: function (initRoot) {
                _init.roots.push(initRoot);
            },
            addNodesData: function (setting, parentNode, index, nodes) {
                var childKey = setting.data.key.children, params;
                if (!parentNode[childKey]) {
                    parentNode[childKey] = [];
                    index = -1;
                } else if (index >= parentNode[childKey].length) {
                    index = -1;
                }

                if (parentNode[childKey].length > 0 && index === 0) {
                    parentNode[childKey][0].isFirstNode = false;
                    view.setNodeLineIcos(setting, parentNode[childKey][0]);
                } else if (parentNode[childKey].length > 0 && index < 0) {
                    parentNode[childKey][parentNode[childKey].length - 1].isLastNode = false;
                    view.setNodeLineIcos(setting, parentNode[childKey][parentNode[childKey].length - 1]);
                }
                parentNode.isParent = true;

                if (index < 0) {
                    parentNode[childKey] = parentNode[childKey].concat(nodes);
                } else {
                    params = [index, 0].concat(nodes);
                    parentNode[childKey].splice.apply(parentNode[childKey], params);
                }
            },
            addSelectedNode: function (setting, node) {
                var root = data.getRoot(setting);
                if (!data.isSelectedNode(setting, node)) {
                    root.curSelectedList.push(node);
                }
            },
            addCreatedNode: function (setting, node) {
                if (!!setting.callback.onNodeCreated || !!setting.view.addDiyDom) {
                    var root = data.getRoot(setting);
                    root.createdNodes.push(node);
                }
            },
            addZTreeTools: function (zTreeTools) {
                _init.zTreeTools.push(zTreeTools);
            },
            exSetting: function (s) {
                $.extend(true, _setting, s);
            },
            fixPIdKeyValue: function (setting, node) {
                if (setting.data.simpleData.enable) {
                    node[setting.data.simpleData.pIdKey] = node.parentTId ? node.getParentNode()[setting.data.simpleData.idKey] : setting.data.simpleData.rootPId;
                }
            },
            getAfterA: function (setting, node, array) {
                for (var i = 0, j = _init.afterA.length; i < j; i++) {
                    _init.afterA[i].apply(this, arguments);
                }
            },
            getBeforeA: function (setting, node, array) {
                for (var i = 0, j = _init.beforeA.length; i < j; i++) {
                    _init.beforeA[i].apply(this, arguments);
                }
            },
            getInnerAfterA: function (setting, node, array) {
                for (var i = 0, j = _init.innerAfterA.length; i < j; i++) {
                    _init.innerAfterA[i].apply(this, arguments);
                }
            },
            getInnerBeforeA: function (setting, node, array) {
                for (var i = 0, j = _init.innerBeforeA.length; i < j; i++) {
                    _init.innerBeforeA[i].apply(this, arguments);
                }
            },
            getCache: function (setting) {
                return caches[setting.treeId];
            },
            getNodeIndex: function (setting, node) {
                if (!node) return null;
                var childKey = setting.data.key.children,
                    p = node.parentTId ? node.getParentNode() : data.getRoot(setting);
                for (var i = 0, l = p[childKey].length - 1; i <= l; i++) {
                    if (p[childKey][i] === node) {
                        return i;
                    }
                }
                return -1;
            },
            getNextNode: function (setting, node) {
                if (!node) return null;
                var childKey = setting.data.key.children,
                    p = node.parentTId ? node.getParentNode() : data.getRoot(setting);
                for (var i = 0, l = p[childKey].length - 1; i <= l; i++) {
                    if (p[childKey][i] === node) {
                        return (i == l ? null : p[childKey][i + 1]);
                    }
                }
                return null;
            },
            getNodeByParam: function (setting, nodes, key, value) {
                if (!nodes || !key) return null;
                var childKey = setting.data.key.children;
                for (var i = 0, l = nodes.length; i < l; i++) {
                    if (nodes[i][key] == value) {
                        return nodes[i];
                    }
                    var tmp = data.getNodeByParam(setting, nodes[i][childKey], key, value);
                    if (tmp) return tmp;
                }
                return null;
            },
            getNodeCache: function (setting, tId) {
                if (!tId) return null;
                var n = caches[setting.treeId].nodes[data.getNodeCacheId(tId)];
                return n ? n : null;
            },
            getNodeName: function (setting, node) {
                var nameKey = setting.data.key.name;
                return "" + node[nameKey];
            },
            getNodePath: function (setting, node) {
                if (!node) return null;

                var path;
                if (node.parentTId) {
                    path = node.getParentNode().getPath();
                } else {
                    path = [];
                }

                if (path) {
                    path.push(node);
                }

                return path;
            },
            getNodeTitle: function (setting, node) {
                var t = setting.data.key.title === "" ? setting.data.key.name : setting.data.key.title;
                return "" + node[t];
            },
            getNodes: function (setting) {
                return data.getRoot(setting)[setting.data.key.children];
            },
            getNodesByParam: function (setting, nodes, key, value) {
                if (!nodes || !key) return [];
                var childKey = setting.data.key.children,
                    result = [];
                for (var i = 0, l = nodes.length; i < l; i++) {
                    if (nodes[i][key] == value) {
                        result.push(nodes[i]);
                    }
                    result = result.concat(data.getNodesByParam(setting, nodes[i][childKey], key, value));
                }
                return result;
            },
            getNodesByParamFuzzy: function (setting, nodes, key, value) {
                if (!nodes || !key) return [];
                var childKey = setting.data.key.children,
                    result = [];
                value = value.toLowerCase();
                for (var i = 0, l = nodes.length; i < l; i++) {
                    if (typeof nodes[i][key] == "string" && nodes[i][key].toLowerCase().indexOf(value) > -1) {
                        result.push(nodes[i]);
                    }
                    result = result.concat(data.getNodesByParamFuzzy(setting, nodes[i][childKey], key, value));
                }
                return result;
            },
            getNodesByFilter: function (setting, nodes, filter, isSingle, invokeParam) {
                if (!nodes) return (isSingle ? null : []);
                var childKey = setting.data.key.children,
                    result = isSingle ? null : [];
                for (var i = 0, l = nodes.length; i < l; i++) {
                    if (tools.apply(filter, [nodes[i], invokeParam], false)) {
                        if (isSingle) {
                            return nodes[i];
                        }
                        result.push(nodes[i]);
                    }
                    var tmpResult = data.getNodesByFilter(setting, nodes[i][childKey], filter, isSingle, invokeParam);
                    if (isSingle && !!tmpResult) {
                        return tmpResult;
                    }
                    result = isSingle ? tmpResult : result.concat(tmpResult);
                }
                return result;
            },
            getPreNode: function (setting, node) {
                if (!node) return null;
                var childKey = setting.data.key.children,
                    p = node.parentTId ? node.getParentNode() : data.getRoot(setting);
                for (var i = 0, l = p[childKey].length; i < l; i++) {
                    if (p[childKey][i] === node) {
                        return (i == 0 ? null : p[childKey][i - 1]);
                    }
                }
                return null;
            },
            getRoot: function (setting) {
                return setting ? roots[setting.treeId] : null;
            },
            getRoots: function () {
                return roots;
            },
            getSetting: function (treeId) {
                return settings[treeId];
            },
            getSettings: function () {
                return settings;
            },
            getZTreeTools: function (treeId) {
                var r = this.getRoot(this.getSetting(treeId));
                return r ? r.treeTools : null;
            },
            initCache: function (setting) {
                for (var i = 0, j = _init.caches.length; i < j; i++) {
                    _init.caches[i].apply(this, arguments);
                }
            },
            initNode: function (setting, level, node, parentNode, preNode, nextNode) {
                for (var i = 0, j = _init.nodes.length; i < j; i++) {
                    _init.nodes[i].apply(this, arguments);
                }
            },
            initRoot: function (setting) {
                for (var i = 0, j = _init.roots.length; i < j; i++) {
                    _init.roots[i].apply(this, arguments);
                }
            },
            isSelectedNode: function (setting, node) {
                var root = data.getRoot(setting);
                for (var i = 0, j = root.curSelectedList.length; i < j; i++) {
                    if (node === root.curSelectedList[i]) return true;
                }
                return false;
            },
            removeNodeCache: function (setting, node) {
                var childKey = setting.data.key.children;
                if (node[childKey]) {
                    for (var i = 0, l = node[childKey].length; i < l; i++) {
                        data.removeNodeCache(setting, node[childKey][i]);
                    }
                }
                data.getCache(setting).nodes[data.getNodeCacheId(node.tId)] = null;
            },
            removeSelectedNode: function (setting, node) {
                var root = data.getRoot(setting);
                for (var i = 0, j = root.curSelectedList.length; i < j; i++) {
                    if (node === root.curSelectedList[i] || !data.getNodeCache(setting, root.curSelectedList[i].tId)) {
                        root.curSelectedList.splice(i, 1);
                        setting.treeObj.trigger(consts.event.UNSELECTED, [setting.treeId, node]);
                        i--;
                        j--;
                    }
                }
            },
            setCache: function (setting, cache) {
                caches[setting.treeId] = cache;
            },
            setRoot: function (setting, root) {
                roots[setting.treeId] = root;
            },
            setZTreeTools: function (setting, zTreeTools) {
                for (var i = 0, j = _init.zTreeTools.length; i < j; i++) {
                    _init.zTreeTools[i].apply(this, arguments);
                }
            },
            transformToArrayFormat: function (setting, nodes) {
                if (!nodes) return [];
                var childKey = setting.data.key.children,
                    r = [];
                if (tools.isArray(nodes)) {
                    for (var i = 0, l = nodes.length; i < l; i++) {
                        r.push(nodes[i]);
                        if (nodes[i][childKey])
                            r = r.concat(data.transformToArrayFormat(setting, nodes[i][childKey]));
                    }
                } else {
                    r.push(nodes);
                    if (nodes[childKey])
                        r = r.concat(data.transformToArrayFormat(setting, nodes[childKey]));
                }
                return r;
            },
            transformTozTreeFormat: function (setting, sNodes) {
                var i, l,
                    key = setting.data.simpleData.idKey,
                    parentKey = setting.data.simpleData.pIdKey,
                    childKey = setting.data.key.children;
                if (!key || key == "" || !sNodes) return [];

                if (tools.isArray(sNodes)) {
                    var r = [];
                    var tmpMap = {};
                    for (i = 0, l = sNodes.length; i < l; i++) {
                        tmpMap[sNodes[i][key]] = sNodes[i];
                    }
                    for (i = 0, l = sNodes.length; i < l; i++) {
                        if (tmpMap[sNodes[i][parentKey]] && sNodes[i][key] != sNodes[i][parentKey]) {
                            if (!tmpMap[sNodes[i][parentKey]][childKey])
                                tmpMap[sNodes[i][parentKey]][childKey] = [];
                            tmpMap[sNodes[i][parentKey]][childKey].push(sNodes[i]);
                        } else {
                            r.push(sNodes[i]);
                        }
                    }
                    return r;
                } else {
                    return [sNodes];
                }
            }
        },
        //method of event proxy
        event = {
            bindEvent: function (setting) {
                for (var i = 0, j = _init.bind.length; i < j; i++) {
                    _init.bind[i].apply(this, arguments);
                }
            },
            unbindEvent: function (setting) {
                for (var i = 0, j = _init.unbind.length; i < j; i++) {
                    _init.unbind[i].apply(this, arguments);
                }
            },
            bindTree: function (setting) {
                var eventParam = {
                        treeId: setting.treeId
                    },
                    o = setting.treeObj;
                if (!setting.view.txtSelectedEnable) {
                    // for can't select text
                    o.bind('selectstart', handler.onSelectStart).css({
                        "-moz-user-select": "-moz-none"
                    });
                }
                o.bind('click', eventParam, event.proxy);
                o.bind('dblclick', eventParam, event.proxy);
                o.bind('mouseover', eventParam, event.proxy);
                o.bind('mouseout', eventParam, event.proxy);
                o.bind('mousedown', eventParam, event.proxy);
                o.bind('mouseup', eventParam, event.proxy);
                o.bind('contextmenu', eventParam, event.proxy);
            },
            unbindTree: function (setting) {
                var o = setting.treeObj;
                o.unbind('selectstart', handler.onSelectStart)
                    .unbind('click', event.proxy)
                    .unbind('dblclick', event.proxy)
                    .unbind('mouseover', event.proxy)
                    .unbind('mouseout', event.proxy)
                    .unbind('mousedown', event.proxy)
                    .unbind('mouseup', event.proxy)
                    .unbind('contextmenu', event.proxy);
            },
            doProxy: function (e) {
                var results = [];
                for (var i = 0, j = _init.proxys.length; i < j; i++) {
                    var proxyResult = _init.proxys[i].apply(this, arguments);
                    results.push(proxyResult);
                    if (proxyResult.stop) {
                        break;
                    }
                }
                return results;
            },
            proxy: function (e) {
                var setting = data.getSetting(e.data.treeId);
                if (!tools.uCanDo(setting, e)) return true;
                var results = event.doProxy(e),
                    r = true, x = false;
                for (var i = 0, l = results.length; i < l; i++) {
                    var proxyResult = results[i];
                    if (proxyResult.nodeEventCallback) {
                        x = true;
                        r = proxyResult.nodeEventCallback.apply(proxyResult, [e, proxyResult.node]) && r;
                    }
                    if (proxyResult.treeEventCallback) {
                        x = true;
                        r = proxyResult.treeEventCallback.apply(proxyResult, [e, proxyResult.node]) && r;
                    }
                }
                return r;
            }
        },
        //method of event handler
        handler = {
            onSwitchNode: function (event, node) {
                var setting = data.getSetting(event.data.treeId);
                if (node.open) {
                    if (tools.apply(setting.callback.beforeCollapse, [setting.treeId, node], true) == false) return true;
                    data.getRoot(setting).expandTriggerFlag = true;
                    view.switchNode(setting, node);
                } else {
                    if (tools.apply(setting.callback.beforeExpand, [setting.treeId, node], true) == false) return true;
                    data.getRoot(setting).expandTriggerFlag = true;
                    view.switchNode(setting, node);
                }
                return true;
            },
            onClickNode: function (event, node) {
                var setting = data.getSetting(event.data.treeId),
                    clickFlag = ( (setting.view.autoCancelSelected && (event.ctrlKey || event.metaKey)) && data.isSelectedNode(setting, node)) ? 0 : (setting.view.autoCancelSelected && (event.ctrlKey || event.metaKey) && setting.view.selectedMulti) ? 2 : 1;
                if (tools.apply(setting.callback.beforeClick, [setting.treeId, node, clickFlag], true) == false) return true;
                if (clickFlag === 0) {
                    view.cancelPreSelectedNode(setting, node);
                } else {
                    view.selectNode(setting, node, clickFlag === 2);
                }
                setting.treeObj.trigger(consts.event.CLICK, [event, setting.treeId, node, clickFlag]);
                return true;
            },
            onZTreeMousedown: function (event, node) {
                var setting = data.getSetting(event.data.treeId);
                if (tools.apply(setting.callback.beforeMouseDown, [setting.treeId, node], true)) {
                    tools.apply(setting.callback.onMouseDown, [event, setting.treeId, node]);
                }
                return true;
            },
            onZTreeMouseup: function (event, node) {
                var setting = data.getSetting(event.data.treeId);
                if (tools.apply(setting.callback.beforeMouseUp, [setting.treeId, node], true)) {
                    tools.apply(setting.callback.onMouseUp, [event, setting.treeId, node]);
                }
                return true;
            },
            onZTreeDblclick: function (event, node) {
                var setting = data.getSetting(event.data.treeId);
                if (tools.apply(setting.callback.beforeDblClick, [setting.treeId, node], true)) {
                    tools.apply(setting.callback.onDblClick, [event, setting.treeId, node]);
                }
                return true;
            },
            onZTreeContextmenu: function (event, node) {
                var setting = data.getSetting(event.data.treeId);
                if (tools.apply(setting.callback.beforeRightClick, [setting.treeId, node], true)) {
                    tools.apply(setting.callback.onRightClick, [event, setting.treeId, node]);
                }
                return (typeof setting.callback.onRightClick) != "function";
            },
            onSelectStart: function (e) {
                var n = e.originalEvent.srcElement.nodeName.toLowerCase();
                return (n === "input" || n === "textarea" );
            }
        },
        //method of tools for zTree
        tools = {
            apply: function (fun, param, defaultValue) {
                if ((typeof fun) == "function") {
                    return fun.apply(zt, param ? param : []);
                }
                return defaultValue;
            },
            canAsync: function (setting, node) {
                var childKey = setting.data.key.children;
                return (setting.async.enable && node && node.isParent && !(node.zAsync || (node[childKey] && node[childKey].length > 0)));
            },
            clone: function (obj) {
                if (obj === null) return null;
                var o = tools.isArray(obj) ? [] : {};
                for (var i in obj) {
                    o[i] = (obj[i] instanceof Date) ? new Date(obj[i].getTime()) : (typeof obj[i] === "object" ? tools.clone(obj[i]) : obj[i]);
                }
                return o;
            },
            eqs: function (str1, str2) {
                return str1.toLowerCase() === str2.toLowerCase();
            },
            isArray: function (arr) {
                return Object.prototype.toString.apply(arr) === "[object Array]";
            },
            isElement: function (o) {
                return (
                    typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
                        o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName === "string"
                );
            },
            $: function (node, exp, setting) {
                if (!!exp && typeof exp != "string") {
                    setting = exp;
                    exp = "";
                }
                if (typeof node == "string") {
                    return $(node, setting ? setting.treeObj.get(0).ownerDocument : null);
                } else {
                    return $("#" + node.tId + exp, setting ? setting.treeObj : null);
                }
            },
            getMDom: function (setting, curDom, targetExpr) {
                if (!curDom) return null;
                while (curDom && curDom.id !== setting.treeId) {
                    for (var i = 0, l = targetExpr.length; curDom.tagName && i < l; i++) {
                        if (tools.eqs(curDom.tagName, targetExpr[i].tagName) && curDom.getAttribute(targetExpr[i].attrName) !== null) {
                            return curDom;
                        }
                    }
                    curDom = curDom.parentNode;
                }
                return null;
            },
            getNodeMainDom: function (target) {
                return ($(target).parent("li").get(0) || $(target).parentsUntil("li").parent().get(0));
            },
            isChildOrSelf: function (dom, parentId) {
                return ( $(dom).closest("#" + parentId).length > 0 );
            },
            uCanDo: function (setting, e) {
                return true;
            }
        },
        //method of operate ztree dom
        view = {
            addNodes: function (setting, parentNode, index, newNodes, isSilent) {
                if (setting.data.keep.leaf && parentNode && !parentNode.isParent) {
                    return;
                }
                if (!tools.isArray(newNodes)) {
                    newNodes = [newNodes];
                }
                if (setting.data.simpleData.enable) {
                    newNodes = data.transformTozTreeFormat(setting, newNodes);
                }
                if (parentNode) {
                    var target_switchObj = $$(parentNode, consts.id.SWITCH, setting),
                        target_icoObj = $$(parentNode, consts.id.ICON, setting),
                        target_ulObj = $$(parentNode, consts.id.UL, setting);

                    if (!parentNode.open) {
                        view.replaceSwitchClass(parentNode, target_switchObj, consts.folder.CLOSE);
                        view.replaceIcoClass(parentNode, target_icoObj, consts.folder.CLOSE);
                        parentNode.open = false;
                        target_ulObj.css({
                            "display": "none"
                        });
                    }

                    data.addNodesData(setting, parentNode, index, newNodes);
                    view.createNodes(setting, parentNode.level + 1, newNodes, parentNode, index);
                    if (!isSilent) {
                        view.expandCollapseParentNode(setting, parentNode, true);
                    }
                } else {
                    data.addNodesData(setting, data.getRoot(setting), index, newNodes);
                    view.createNodes(setting, 0, newNodes, null, index);
                }
            },
            appendNodes: function (setting, level, nodes, parentNode, index, initFlag, openFlag) {
                if (!nodes) return [];
                var html = [],
                    childKey = setting.data.key.children;

                var tmpPNode = (parentNode) ? parentNode : data.getRoot(setting),
                    tmpPChild = tmpPNode[childKey],
                    isFirstNode, isLastNode;

                if (!tmpPChild || index >= tmpPChild.length - nodes.length) {
                    index = -1;
                }

                for (var i = 0, l = nodes.length; i < l; i++) {
                    var node = nodes[i];
                    if (initFlag) {
                        isFirstNode = ((index === 0 || tmpPChild.length == nodes.length) && (i == 0));
                        isLastNode = (index < 0 && i == (nodes.length - 1));
                        data.initNode(setting, level, node, parentNode, isFirstNode, isLastNode, openFlag);
                        data.addNodeCache(setting, node);
                    }

                    var childHtml = [];
                    if (node[childKey] && node[childKey].length > 0) {
                        //make child html first, because checkType
                        childHtml = view.appendNodes(setting, level + 1, node[childKey], node, -1, initFlag, openFlag && node.open);
                    }
                    if (openFlag) {

                        view.makeDOMNodeMainBefore(html, setting, node);
                        view.makeDOMNodeLine(html, setting, node);
                        data.getBeforeA(setting, node, html);
                        view.makeDOMNodeNameBefore(html, setting, node);
                        data.getInnerBeforeA(setting, node, html);
                        view.makeDOMNodeIcon(html, setting, node);
                        data.getInnerAfterA(setting, node, html);
                        view.makeDOMNodeNameAfter(html, setting, node);
                        data.getAfterA(setting, node, html);
                        if (node.isParent && node.open) {
                            view.makeUlHtml(setting, node, html, childHtml.join(''));
                        }
                        view.makeDOMNodeMainAfter(html, setting, node);
                        data.addCreatedNode(setting, node);
                    }
                }
                return html;
            },
            appendParentULDom: function (setting, node) {
                var html = [],
                    nObj = $$(node, setting);
                if (!nObj.get(0) && !!node.parentTId) {
                    view.appendParentULDom(setting, node.getParentNode());
                    nObj = $$(node, setting);
                }
                var ulObj = $$(node, consts.id.UL, setting);
                if (ulObj.get(0)) {
                    ulObj.remove();
                }
                var childKey = setting.data.key.children,
                    childHtml = view.appendNodes(setting, node.level + 1, node[childKey], node, -1, false, true);
                view.makeUlHtml(setting, node, html, childHtml.join(''));
                nObj.append(html.join(''));
            },
            asyncNode: function (setting, node, isSilent, callback) {
                var i, l;
                if (node && !node.isParent) {
                    tools.apply(callback);
                    return false;
                } else if (node && node.isAjaxing) {
                    return false;
                } else if (tools.apply(setting.callback.beforeAsync, [setting.treeId, node], true) == false) {
                    tools.apply(callback);
                    return false;
                }
                if (node) {
                    node.isAjaxing = true;
                    var icoObj = $$(node, consts.id.ICON, setting);
                    icoObj.attr({"style": "", "class": consts.className.BUTTON + " " + consts.className.ICO_LOADING});
                }

                var tmpParam = {};
                for (i = 0, l = setting.async.autoParam.length; node && i < l; i++) {
                    var pKey = setting.async.autoParam[i].split("="), spKey = pKey;
                    if (pKey.length > 1) {
                        spKey = pKey[1];
                        pKey = pKey[0];
                    }
                    tmpParam[spKey] = node[pKey];
                }
                if (tools.isArray(setting.async.otherParam)) {
                    for (i = 0, l = setting.async.otherParam.length; i < l; i += 2) {
                        tmpParam[setting.async.otherParam[i]] = setting.async.otherParam[i + 1];
                    }
                } else {
                    for (var p in setting.async.otherParam) {
                        tmpParam[p] = setting.async.otherParam[p];
                    }
                }

                var _tmpV = data.getRoot(setting)._ver;
                $.ajax({
                    contentType: setting.async.contentType,
                    cache: false,
                    type: setting.async.type,
                    url: tools.apply(setting.async.url, [setting.treeId, node], setting.async.url),
                    data: setting.async.contentType.indexOf('application/json') > -1 ? JSON.stringify(tmpParam) : tmpParam,
                    dataType: setting.async.dataType,
                    success: function (msg) {
                        if (_tmpV != data.getRoot(setting)._ver) {
                            return;
                        }
                        var newNodes = [];
                        try {
                            if (!msg || msg.length == 0) {
                                newNodes = [];
                            } else if (typeof msg == "string") {
                                newNodes = eval("(" + msg + ")");
                            } else {
                                newNodes = msg;
                            }
                        } catch (err) {
                            newNodes = msg;
                        }

                        if (node) {
                            node.isAjaxing = null;
                            node.zAsync = true;
                        }
                        view.setNodeLineIcos(setting, node);
                        if (newNodes && newNodes !== "") {
                            newNodes = tools.apply(setting.async.dataFilter, [setting.treeId, node, newNodes], newNodes);
                            view.addNodes(setting, node, -1, !!newNodes ? tools.clone(newNodes) : [], !!isSilent);
                        } else {
                            view.addNodes(setting, node, -1, [], !!isSilent);
                        }
                        setting.treeObj.trigger(consts.event.ASYNC_SUCCESS, [setting.treeId, node, msg]);
                        tools.apply(callback);
                    },
                    error: function (XMLHttpRequest, textStatus, errorThrown) {
                        if (_tmpV != data.getRoot(setting)._ver) {
                            return;
                        }
                        if (node) node.isAjaxing = null;
                        view.setNodeLineIcos(setting, node);
                        setting.treeObj.trigger(consts.event.ASYNC_ERROR, [setting.treeId, node, XMLHttpRequest, textStatus, errorThrown]);
                    }
                });
                return true;
            },
            cancelPreSelectedNode: function (setting, node, excludeNode) {
                var list = data.getRoot(setting).curSelectedList,
                    i, n;
                for (i = list.length - 1; i >= 0; i--) {
                    n = list[i];
                    if (node === n || (!node && (!excludeNode || excludeNode !== n))) {
                        $$(n, consts.id.A, setting).removeClass(consts.node.CURSELECTED);
                        if (node) {
                            data.removeSelectedNode(setting, node);
                            break;
                        } else {
                            list.splice(i, 1);
                            setting.treeObj.trigger(consts.event.UNSELECTED, [setting.treeId, n]);
                        }
                    }
                }
            },
            createNodeCallback: function (setting) {
                if (!!setting.callback.onNodeCreated || !!setting.view.addDiyDom) {
                    var root = data.getRoot(setting);
                    while (root.createdNodes.length > 0) {
                        var node = root.createdNodes.shift();
                        tools.apply(setting.view.addDiyDom, [setting.treeId, node]);
                        if (!!setting.callback.onNodeCreated) {
                            setting.treeObj.trigger(consts.event.NODECREATED, [setting.treeId, node]);
                        }
                    }
                }
            },
            createNodes: function (setting, level, nodes, parentNode, index) {
                if (!nodes || nodes.length == 0) return;
                var root = data.getRoot(setting),
                    childKey = setting.data.key.children,
                    openFlag = !parentNode || parentNode.open || !!$$(parentNode[childKey][0], setting).get(0);
                root.createdNodes = [];
                var zTreeHtml = view.appendNodes(setting, level, nodes, parentNode, index, true, openFlag),
                    parentObj, nextObj;

                if (!parentNode) {
                    parentObj = setting.treeObj;
                    //setting.treeObj.append(zTreeHtml.join(''));
                } else {
                    var ulObj = $$(parentNode, consts.id.UL, setting);
                    if (ulObj.get(0)) {
                        parentObj = ulObj;
                        //ulObj.append(zTreeHtml.join(''));
                    }
                }
                if (parentObj) {
                    if (index >= 0) {
                        nextObj = parentObj.children()[index];
                    }
                    if (index >= 0 && nextObj) {
                        $(nextObj).before(zTreeHtml.join(''));
                    } else {
                        parentObj.append(zTreeHtml.join(''));
                    }
                }

                view.createNodeCallback(setting);
            },
            destroy: function (setting) {
                if (!setting) return;
                data.initCache(setting);
                data.initRoot(setting);
                event.unbindTree(setting);
                event.unbindEvent(setting);
                setting.treeObj.empty();
                delete settings[setting.treeId];
            },
            expandCollapseNode: function (setting, node, expandFlag, animateFlag, callback) {
                var root = data.getRoot(setting),
                    childKey = setting.data.key.children;
                var tmpCb, _callback;
                if (!node) {
                    tools.apply(callback, []);
                    return;
                }
                if (root.expandTriggerFlag) {
                    _callback = callback;
                    tmpCb = function () {
                        if (_callback) _callback();
                        if (node.open) {
                            setting.treeObj.trigger(consts.event.EXPAND, [setting.treeId, node]);
                        } else {
                            setting.treeObj.trigger(consts.event.COLLAPSE, [setting.treeId, node]);
                        }
                    };
                    callback = tmpCb;
                    root.expandTriggerFlag = false;
                }
                if (!node.open && node.isParent && ((!$$(node, consts.id.UL, setting).get(0)) || (node[childKey] && node[childKey].length > 0 && !$$(node[childKey][0], setting).get(0)))) {
                    view.appendParentULDom(setting, node);
                    view.createNodeCallback(setting);
                }
                if (node.open == expandFlag) {
                    tools.apply(callback, []);
                    return;
                }
                var ulObj = $$(node, consts.id.UL, setting),
                    switchObj = $$(node, consts.id.SWITCH, setting),
                    icoObj = $$(node, consts.id.ICON, setting);

                if (node.isParent) {
                    node.open = !node.open;
                    if (node.iconOpen && node.iconClose) {
                        icoObj.attr("style", view.makeNodeIcoStyle(setting, node));
                    }

                    if (node.open) {
                        view.replaceSwitchClass(node, switchObj, consts.folder.OPEN);
                        view.replaceIcoClass(node, icoObj, consts.folder.OPEN);
                        if (animateFlag == false || setting.view.expandSpeed == "") {
                            ulObj.show();
                            tools.apply(callback, []);
                        } else {
                            if (node[childKey] && node[childKey].length > 0) {
                                ulObj.slideDown(setting.view.expandSpeed, callback);
                            } else {
                                ulObj.show();
                                tools.apply(callback, []);
                            }
                        }
                    } else {
                        view.replaceSwitchClass(node, switchObj, consts.folder.CLOSE);
                        view.replaceIcoClass(node, icoObj, consts.folder.CLOSE);
                        if (animateFlag == false || setting.view.expandSpeed == "" || !(node[childKey] && node[childKey].length > 0)) {
                            ulObj.hide();
                            tools.apply(callback, []);
                        } else {
                            ulObj.slideUp(setting.view.expandSpeed, callback);
                        }
                    }
                } else {
                    tools.apply(callback, []);
                }
            },
            expandCollapseParentNode: function (setting, node, expandFlag, animateFlag, callback) {
                if (!node) return;
                if (!node.parentTId) {
                    view.expandCollapseNode(setting, node, expandFlag, animateFlag, callback);
                    return;
                } else {
                    view.expandCollapseNode(setting, node, expandFlag, animateFlag);
                }
                if (node.parentTId) {
                    view.expandCollapseParentNode(setting, node.getParentNode(), expandFlag, animateFlag, callback);
                }
            },
            expandCollapseSonNode: function (setting, node, expandFlag, animateFlag, callback) {
                var root = data.getRoot(setting),
                    childKey = setting.data.key.children,
                    treeNodes = (node) ? node[childKey] : root[childKey],
                    selfAnimateSign = (node) ? false : animateFlag,
                    expandTriggerFlag = data.getRoot(setting).expandTriggerFlag;
                data.getRoot(setting).expandTriggerFlag = false;
                if (treeNodes) {
                    for (var i = 0, l = treeNodes.length; i < l; i++) {
                        if (treeNodes[i]) view.expandCollapseSonNode(setting, treeNodes[i], expandFlag, selfAnimateSign);
                    }
                }
                data.getRoot(setting).expandTriggerFlag = expandTriggerFlag;
                view.expandCollapseNode(setting, node, expandFlag, animateFlag, callback);
            },
            isSelectedNode: function (setting, node) {
                if (!node) {
                    return false;
                }
                var list = data.getRoot(setting).curSelectedList,
                    i;
                for (i = list.length - 1; i >= 0; i--) {
                    if (node === list[i]) {
                        return true;
                    }
                }
                return false;
            },
            makeDOMNodeIcon: function (html, setting, node) {
                var nameStr = data.getNodeName(setting, node),
                    name = setting.view.nameIsHTML ? nameStr : nameStr.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
                html.push("<span id='", node.tId, consts.id.ICON,
                    "' title='' treeNode", consts.id.ICON, " class='", view.makeNodeIcoClass(setting, node),
                    "' style='", view.makeNodeIcoStyle(setting, node), "'></span><span id='", node.tId, consts.id.SPAN,
                    "' class='", consts.className.NAME,
                    "'>", name, "</span>");
            },
            makeDOMNodeLine: function (html, setting, node) {
                html.push("<span id='", node.tId, consts.id.SWITCH, "' title='' class='", view.makeNodeLineClass(setting, node), "' treeNode", consts.id.SWITCH, "></span>");
            },
            makeDOMNodeMainAfter: function (html, setting, node) {
                html.push("</li>");
            },
            makeDOMNodeMainBefore: function (html, setting, node) {
                html.push("<li id='", node.tId, "' class='", consts.className.LEVEL, node.level, "' tabindex='0' hidefocus='true' treenode>");
            },
            makeDOMNodeNameAfter: function (html, setting, node) {
                html.push("</a>");
            },
            makeDOMNodeNameBefore: function (html, setting, node) {
                var title = data.getNodeTitle(setting, node),
                    url = view.makeNodeUrl(setting, node),
                    fontcss = view.makeNodeFontCss(setting, node),
                    fontStyle = [];
                for (var f in fontcss) {
                    fontStyle.push(f, ":", fontcss[f], ";");
                }
                html.push("<a id='", node.tId, consts.id.A, "' class='", consts.className.LEVEL, node.level, "' treeNode", consts.id.A, " onclick=\"", (node.click || ''),
                    "\" ", ((url != null && url.length > 0) ? "href='" + url + "'" : ""), " target='", view.makeNodeTarget(node), "' style='", fontStyle.join(''),
                    "'");
                if (tools.apply(setting.view.showTitle, [setting.treeId, node], setting.view.showTitle) && title) {
                    html.push("title='", title.replace(/'/g, "&#39;").replace(/</g, '&lt;').replace(/>/g, '&gt;'), "'");
                }
                html.push(">");
            },
            makeNodeFontCss: function (setting, node) {
                var fontCss = tools.apply(setting.view.fontCss, [setting.treeId, node], setting.view.fontCss);
                return (fontCss && ((typeof fontCss) != "function")) ? fontCss : {};
            },
            makeNodeIcoClass: function (setting, node) {
                var icoCss = ["ico"];
                if (!node.isAjaxing) {
                    icoCss[0] = (node.iconSkin ? node.iconSkin + "_" : "") + icoCss[0];
                    if (node.isParent) {
                        icoCss.push(node.open ? consts.folder.OPEN : consts.folder.CLOSE);
                    } else {
                        icoCss.push(consts.folder.DOCU);
                    }
                }
                return consts.className.BUTTON + " " + icoCss.join('_');
            },
            makeNodeIcoStyle: function (setting, node) {
                var icoStyle = [];
                if (!node.isAjaxing) {
                    var icon = (node.isParent && node.iconOpen && node.iconClose) ? (node.open ? node.iconOpen : node.iconClose) : node[setting.data.key.icon];
                    if (icon) icoStyle.push("background:url(", icon, ") 0 0 no-repeat;");
                    if (setting.view.showIcon == false || !tools.apply(setting.view.showIcon, [setting.treeId, node], true)) {
                        icoStyle.push("width:0px;height:0px;");
                    }
                }
                return icoStyle.join('');
            },
            makeNodeLineClass: function (setting, node) {
                var lineClass = [];
                if (setting.view.showLine) {
                    if (node.level == 0 && node.isFirstNode && node.isLastNode) {
                        lineClass.push(consts.line.ROOT);
                    } else if (node.level == 0 && node.isFirstNode) {
                        lineClass.push(consts.line.ROOTS);
                    } else if (node.isLastNode) {
                        lineClass.push(consts.line.BOTTOM);
                    } else {
                        lineClass.push(consts.line.CENTER);
                    }
                } else {
                    lineClass.push(consts.line.NOLINE);
                }
                if (node.isParent) {
                    lineClass.push(node.open ? consts.folder.OPEN : consts.folder.CLOSE);
                } else {
                    lineClass.push(consts.folder.DOCU);
                }
                return view.makeNodeLineClassEx(node) + lineClass.join('_');
            },
            makeNodeLineClassEx: function (node) {
                return consts.className.BUTTON + " " + consts.className.LEVEL + node.level + " " + consts.className.SWITCH + " ";
            },
            makeNodeTarget: function (node) {
                return (node.target || "_blank");
            },
            makeNodeUrl: function (setting, node) {
                var urlKey = setting.data.key.url;
                return node[urlKey] ? node[urlKey] : null;
            },
            makeUlHtml: function (setting, node, html, content) {
                html.push("<ul id='", node.tId, consts.id.UL, "' class='", consts.className.LEVEL, node.level, " ", view.makeUlLineClass(setting, node), "' style='display:", (node.open ? "block" : "none"), "'>");
                html.push(content);
                html.push("</ul>");
            },
            makeUlLineClass: function (setting, node) {
                return ((setting.view.showLine && !node.isLastNode) ? consts.line.LINE : "");
            },
            removeChildNodes: function (setting, node) {
                if (!node) return;
                var childKey = setting.data.key.children,
                    nodes = node[childKey];
                if (!nodes) return;

                for (var i = 0, l = nodes.length; i < l; i++) {
                    data.removeNodeCache(setting, nodes[i]);
                }
                data.removeSelectedNode(setting);
                delete node[childKey];

                if (!setting.data.keep.parent) {
                    node.isParent = false;
                    node.open = false;
                    var tmp_switchObj = $$(node, consts.id.SWITCH, setting),
                        tmp_icoObj = $$(node, consts.id.ICON, setting);
                    view.replaceSwitchClass(node, tmp_switchObj, consts.folder.DOCU);
                    view.replaceIcoClass(node, tmp_icoObj, consts.folder.DOCU);
                    $$(node, consts.id.UL, setting).remove();
                } else {
                    $$(node, consts.id.UL, setting).empty();
                }
            },
            scrollIntoView: function (dom) {
                if (!dom) {
                    return;
                }
                // code src: http://jsfiddle.net/08u6cxwj/
                if (!Element.prototype.scrollIntoViewIfNeeded) {
                    Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) {
                        function withinBounds(value, min, max, extent) {
                            if (false === centerIfNeeded || max <= value + extent && value <= min + extent) {
                                return Math.min(max, Math.max(min, value));
                            } else {
                                return (min + max) / 2;
                            }
                        }

                        function makeArea(left, top, width, height) {
                            return {
                                "left": left, "top": top, "width": width, "height": height
                                , "right": left + width, "bottom": top + height
                                , "translate": function (x, y) {
                                    return makeArea(x + left, y + top, width, height);
                                }
                                , "relativeFromTo": function (lhs, rhs) {
                                    var newLeft = left, newTop = top;
                                    lhs = lhs.offsetParent;
                                    rhs = rhs.offsetParent;
                                    if (lhs === rhs) {
                                        return area;
                                    }
                                    for (; lhs; lhs = lhs.offsetParent) {
                                        newLeft += lhs.offsetLeft + lhs.clientLeft;
                                        newTop += lhs.offsetTop + lhs.clientTop;
                                    }
                                    for (; rhs; rhs = rhs.offsetParent) {
                                        newLeft -= rhs.offsetLeft + rhs.clientLeft;
                                        newTop -= rhs.offsetTop + rhs.clientTop;
                                    }
                                    return makeArea(newLeft, newTop, width, height);
                                }
                            };
                        }

                        var parent, elem = this, area = makeArea(
                            this.offsetLeft, this.offsetTop,
                            this.offsetWidth, this.offsetHeight);
                        while (tools.isElement(parent = elem.parentNode)) {
                            var clientLeft = parent.offsetLeft + parent.clientLeft;
                            var clientTop = parent.offsetTop + parent.clientTop;

                            // Make area relative to parent's client area.
                            area = area.relativeFromTo(elem, parent).translate(-clientLeft, -clientTop);

                            parent.scrollLeft = withinBounds(
                                parent.scrollLeft,
                                area.right - parent.clientWidth, area.left,
                                parent.clientWidth);

                            parent.scrollTop = withinBounds(
                                parent.scrollTop,
                                area.bottom - parent.clientHeight, area.top,
                                parent.clientHeight);

                            // Determine actual scroll amount by reading back scroll properties.
                            area = area.translate(clientLeft - parent.scrollLeft,
                                clientTop - parent.scrollTop);
                            elem = parent;
                        }
                    };
                }
                dom.scrollIntoViewIfNeeded();
            },
            setFirstNode: function (setting, parentNode) {
                var childKey = setting.data.key.children, childLength = parentNode[childKey].length;
                if (childLength > 0) {
                    parentNode[childKey][0].isFirstNode = true;
                }
            },
            setLastNode: function (setting, parentNode) {
                var childKey = setting.data.key.children, childLength = parentNode[childKey].length;
                if (childLength > 0) {
                    parentNode[childKey][childLength - 1].isLastNode = true;
                }
            },
            removeNode: function (setting, node) {
                var root = data.getRoot(setting),
                    childKey = setting.data.key.children,
                    parentNode = (node.parentTId) ? node.getParentNode() : root;

                node.isFirstNode = false;
                node.isLastNode = false;
                node.getPreNode = function () {
                    return null;
                };
                node.getNextNode = function () {
                    return null;
                };

                if (!data.getNodeCache(setting, node.tId)) {
                    return;
                }

                $$(node, setting).remove();
                data.removeNodeCache(setting, node);
                data.removeSelectedNode(setting, node);

                for (var i = 0, l = parentNode[childKey].length; i < l; i++) {
                    if (parentNode[childKey][i].tId == node.tId) {
                        parentNode[childKey].splice(i, 1);
                        break;
                    }
                }
                view.setFirstNode(setting, parentNode);
                view.setLastNode(setting, parentNode);

                var tmp_ulObj, tmp_switchObj, tmp_icoObj,
                    childLength = parentNode[childKey].length;

                //repair nodes old parent
                if (!setting.data.keep.parent && childLength == 0) {
                    //old parentNode has no child nodes
                    parentNode.isParent = false;
                    parentNode.open = false;
                    tmp_ulObj = $$(parentNode, consts.id.UL, setting);
                    tmp_switchObj = $$(parentNode, consts.id.SWITCH, setting);
                    tmp_icoObj = $$(parentNode, consts.id.ICON, setting);
                    view.replaceSwitchClass(parentNode, tmp_switchObj, consts.folder.DOCU);
                    view.replaceIcoClass(parentNode, tmp_icoObj, consts.folder.DOCU);
                    tmp_ulObj.css("display", "none");

                } else if (setting.view.showLine && childLength > 0) {
                    //old parentNode has child nodes
                    var newLast = parentNode[childKey][childLength - 1];
                    tmp_ulObj = $$(newLast, consts.id.UL, setting);
                    tmp_switchObj = $$(newLast, consts.id.SWITCH, setting);
                    tmp_icoObj = $$(newLast, consts.id.ICON, setting);
                    if (parentNode == root) {
                        if (parentNode[childKey].length == 1) {
                            //node was root, and ztree has only one root after move node
                            view.replaceSwitchClass(newLast, tmp_switchObj, consts.line.ROOT);
                        } else {
                            var tmp_first_switchObj = $$(parentNode[childKey][0], consts.id.SWITCH, setting);
                            view.replaceSwitchClass(parentNode[childKey][0], tmp_first_switchObj, consts.line.ROOTS);
                            view.replaceSwitchClass(newLast, tmp_switchObj, consts.line.BOTTOM);
                        }
                    } else {
                        view.replaceSwitchClass(newLast, tmp_switchObj, consts.line.BOTTOM);
                    }
                    tmp_ulObj.removeClass(consts.line.LINE);
                }
            },
            replaceIcoClass: function (node, obj, newName) {
                if (!obj || node.isAjaxing) return;
                var tmpName = obj.attr("class");
                if (tmpName == undefined) return;
                var tmpList = tmpName.split("_");
                switch (newName) {
                    case consts.folder.OPEN:
                    case consts.folder.CLOSE:
                    case consts.folder.DOCU:
                        tmpList[tmpList.length - 1] = newName;
                        break;
                }
                obj.attr("class", tmpList.join("_"));
            },
            replaceSwitchClass: function (node, obj, newName) {
                if (!obj) return;
                var tmpName = obj.attr("class");
                if (tmpName == undefined) return;
                var tmpList = tmpName.split("_");
                switch (newName) {
                    case consts.line.ROOT:
                    case consts.line.ROOTS:
                    case consts.line.CENTER:
                    case consts.line.BOTTOM:
                    case consts.line.NOLINE:
                        tmpList[0] = view.makeNodeLineClassEx(node) + newName;
                        break;
                    case consts.folder.OPEN:
                    case consts.folder.CLOSE:
                    case consts.folder.DOCU:
                        tmpList[1] = newName;
                        break;
                }
                obj.attr("class", tmpList.join("_"));
                if (newName !== consts.folder.DOCU) {
                    obj.removeAttr("disabled");
                } else {
                    obj.attr("disabled", "disabled");
                }
            },
            selectNode: function (setting, node, addFlag) {
                if (!addFlag) {
                    view.cancelPreSelectedNode(setting, null, node);
                }
                $$(node, consts.id.A, setting).addClass(consts.node.CURSELECTED);
                data.addSelectedNode(setting, node);
                setting.treeObj.trigger(consts.event.SELECTED, [setting.treeId, node]);
            },
            setNodeFontCss: function (setting, treeNode) {
                var aObj = $$(treeNode, consts.id.A, setting),
                    fontCss = view.makeNodeFontCss(setting, treeNode);
                if (fontCss) {
                    aObj.css(fontCss);
                }
            },
            setNodeLineIcos: function (setting, node) {
                if (!node) return;
                var switchObj = $$(node, consts.id.SWITCH, setting),
                    ulObj = $$(node, consts.id.UL, setting),
                    icoObj = $$(node, consts.id.ICON, setting),
                    ulLine = view.makeUlLineClass(setting, node);
                if (ulLine.length == 0) {
                    ulObj.removeClass(consts.line.LINE);
                } else {
                    ulObj.addClass(ulLine);
                }
                switchObj.attr("class", view.makeNodeLineClass(setting, node));
                if (node.isParent) {
                    switchObj.removeAttr("disabled");
                } else {
                    switchObj.attr("disabled", "disabled");
                }
                icoObj.removeAttr("style");
                icoObj.attr("style", view.makeNodeIcoStyle(setting, node));
                icoObj.attr("class", view.makeNodeIcoClass(setting, node));
            },
            setNodeName: function (setting, node) {
                var title = data.getNodeTitle(setting, node),
                    nObj = $$(node, consts.id.SPAN, setting);
                nObj.empty();
                if (setting.view.nameIsHTML) {
                    nObj.html(data.getNodeName(setting, node));
                } else {
                    nObj.text(data.getNodeName(setting, node));
                }
                if (tools.apply(setting.view.showTitle, [setting.treeId, node], setting.view.showTitle)) {
                    var aObj = $$(node, consts.id.A, setting);
                    aObj.attr("title", !title ? "" : title);
                }
            },
            setNodeTarget: function (setting, node) {
                var aObj = $$(node, consts.id.A, setting);
                aObj.attr("target", view.makeNodeTarget(node));
            },
            setNodeUrl: function (setting, node) {
                var aObj = $$(node, consts.id.A, setting),
                    url = view.makeNodeUrl(setting, node);
                if (url == null || url.length == 0) {
                    aObj.removeAttr("href");
                } else {
                    aObj.attr("href", url);
                }
            },
            switchNode: function (setting, node) {
                if (node.open || !tools.canAsync(setting, node)) {
                    view.expandCollapseNode(setting, node, !node.open);
                } else if (setting.async.enable) {
                    if (!view.asyncNode(setting, node)) {
                        view.expandCollapseNode(setting, node, !node.open);
                        return;
                    }
                } else if (node) {
                    view.expandCollapseNode(setting, node, !node.open);
                }
            }
        };
    // zTree defind
    $.fn.zTree = {
        consts: _consts,
        _z: {
            tools: tools,
            view: view,
            event: event,
            data: data
        },
        getZTreeObj: function (treeId) {
            var o = data.getZTreeTools(treeId);
            return o ? o : null;
        },
        destroy: function (treeId) {
            if (!!treeId && treeId.length > 0) {
                view.destroy(data.getSetting(treeId));
            } else {
                for (var s in settings) {
                    view.destroy(settings[s]);
                }
            }
        },
        init: function (obj, zSetting, zNodes) {
            var setting = tools.clone(_setting);
            $.extend(true, setting, zSetting);
            setting.treeId = obj.attr("id");
            setting.treeObj = obj;
            setting.treeObj.empty();
            settings[setting.treeId] = setting;
            //For some older browser,(e.g., ie6)
            if (typeof document.body.style.maxHeight === "undefined") {
                setting.view.expandSpeed = "";
            }
            data.initRoot(setting);
            var root = data.getRoot(setting),
                childKey = setting.data.key.children;
            zNodes = zNodes ? tools.clone(tools.isArray(zNodes) ? zNodes : [zNodes]) : [];
            if (setting.data.simpleData.enable) {
                root[childKey] = data.transformTozTreeFormat(setting, zNodes);
            } else {
                root[childKey] = zNodes;
            }

            data.initCache(setting);
            event.unbindTree(setting);
            event.bindTree(setting);
            event.unbindEvent(setting);
            event.bindEvent(setting);

            var zTreeTools = {
                setting: setting,
                addNodes: function (parentNode, index, newNodes, isSilent) {
                    if (!parentNode) parentNode = null;
                    if (parentNode && !parentNode.isParent && setting.data.keep.leaf) return null;

                    var i = parseInt(index, 10);
                    if (isNaN(i)) {
                        isSilent = !!newNodes;
                        newNodes = index;
                        index = -1;
                    } else {
                        index = i;
                    }
                    if (!newNodes) return null;


                    var xNewNodes = tools.clone(tools.isArray(newNodes) ? newNodes : [newNodes]);

                    function addCallback() {
                        view.addNodes(setting, parentNode, index, xNewNodes, (isSilent == true));
                    }

                    if (tools.canAsync(setting, parentNode)) {
                        view.asyncNode(setting, parentNode, isSilent, addCallback);
                    } else {
                        addCallback();
                    }
                    return xNewNodes;
                },
                cancelSelectedNode: function (node) {
                    view.cancelPreSelectedNode(setting, node);
                },
                destroy: function () {
                    view.destroy(setting);
                },
                expandAll: function (expandFlag) {
                    expandFlag = !!expandFlag;
                    view.expandCollapseSonNode(setting, null, expandFlag, true);
                    return expandFlag;
                },
                expandNode: function (node, expandFlag, sonSign, focus, callbackFlag) {
                    if (!node || !node.isParent) return null;
                    if (expandFlag !== true && expandFlag !== false) {
                        expandFlag = !node.open;
                    }
                    callbackFlag = !!callbackFlag;

                    if (callbackFlag && expandFlag && (tools.apply(setting.callback.beforeExpand, [setting.treeId, node], true) == false)) {
                        return null;
                    } else if (callbackFlag && !expandFlag && (tools.apply(setting.callback.beforeCollapse, [setting.treeId, node], true) == false)) {
                        return null;
                    }
                    if (expandFlag && node.parentTId) {
                        view.expandCollapseParentNode(setting, node.getParentNode(), expandFlag, false);
                    }
                    if (expandFlag === node.open && !sonSign) {
                        return null;
                    }

                    data.getRoot(setting).expandTriggerFlag = callbackFlag;
                    if (!tools.canAsync(setting, node) && sonSign) {
                        view.expandCollapseSonNode(setting, node, expandFlag, true, showNodeFocus);
                    } else {
                        node.open = !expandFlag;
                        view.switchNode(this.setting, node);
                        showNodeFocus();
                    }
                    return expandFlag;

                    function showNodeFocus() {
                        var a = $$(node, setting).get(0);
                        if (a && focus !== false) {
                            view.scrollIntoView(a);
                        }
                    }
                },
                getNodes: function () {
                    return data.getNodes(setting);
                },
                getNodeByParam: function (key, value, parentNode) {
                    if (!key) return null;
                    return data.getNodeByParam(setting, parentNode ? parentNode[setting.data.key.children] : data.getNodes(setting), key, value);
                },
                getNodeByTId: function (tId) {
                    return data.getNodeCache(setting, tId);
                },
                getNodesByParam: function (key, value, parentNode) {
                    if (!key) return null;
                    return data.getNodesByParam(setting, parentNode ? parentNode[setting.data.key.children] : data.getNodes(setting), key, value);
                },
                getNodesByParamFuzzy: function (key, value, parentNode) {
                    if (!key) return null;
                    return data.getNodesByParamFuzzy(setting, parentNode ? parentNode[setting.data.key.children] : data.getNodes(setting), key, value);
                },
                getNodesByFilter: function (filter, isSingle, parentNode, invokeParam) {
                    isSingle = !!isSingle;
                    if (!filter || (typeof filter != "function")) return (isSingle ? null : []);
                    return data.getNodesByFilter(setting, parentNode ? parentNode[setting.data.key.children] : data.getNodes(setting), filter, isSingle, invokeParam);
                },
                getNodeIndex: function (node) {
                    if (!node) return null;
                    var childKey = setting.data.key.children,
                        parentNode = (node.parentTId) ? node.getParentNode() : data.getRoot(setting);
                    for (var i = 0, l = parentNode[childKey].length; i < l; i++) {
                        if (parentNode[childKey][i] == node) return i;
                    }
                    return -1;
                },
                getSelectedNodes: function () {
                    var r = [], list = data.getRoot(setting).curSelectedList;
                    for (var i = 0, l = list.length; i < l; i++) {
                        r.push(list[i]);
                    }
                    return r;
                },
                isSelectedNode: function (node) {
                    return data.isSelectedNode(setting, node);
                },
                reAsyncChildNodes: function (parentNode, reloadType, isSilent) {
                    if (!this.setting.async.enable) return;
                    var isRoot = !parentNode;
                    if (isRoot) {
                        parentNode = data.getRoot(setting);
                    }
                    if (reloadType == "refresh") {
                        var childKey = this.setting.data.key.children;
                        for (var i = 0, l = parentNode[childKey] ? parentNode[childKey].length : 0; i < l; i++) {
                            data.removeNodeCache(setting, parentNode[childKey][i]);
                        }
                        data.removeSelectedNode(setting);
                        parentNode[childKey] = [];
                        if (isRoot) {
                            this.setting.treeObj.empty();
                        } else {
                            var ulObj = $$(parentNode, consts.id.UL, setting);
                            ulObj.empty();
                        }
                    }
                    view.asyncNode(this.setting, isRoot ? null : parentNode, !!isSilent);
                },
                refresh: function () {
                    this.setting.treeObj.empty();
                    var root = data.getRoot(setting),
                        nodes = root[setting.data.key.children]
                    data.initRoot(setting);
                    root[setting.data.key.children] = nodes
                    data.initCache(setting);
                    view.createNodes(setting, 0, root[setting.data.key.children], null, -1);
                },
                removeChildNodes: function (node) {
                    if (!node) return null;
                    var childKey = setting.data.key.children,
                        nodes = node[childKey];
                    view.removeChildNodes(setting, node);
                    return nodes ? nodes : null;
                },
                removeNode: function (node, callbackFlag) {
                    if (!node) return;
                    callbackFlag = !!callbackFlag;
                    if (callbackFlag && tools.apply(setting.callback.beforeRemove, [setting.treeId, node], true) == false) return;
                    view.removeNode(setting, node);
                    if (callbackFlag) {
                        this.setting.treeObj.trigger(consts.event.REMOVE, [setting.treeId, node]);
                    }
                },
                selectNode: function (node, addFlag, isSilent) {
                    if (!node) return;
                    if (tools.uCanDo(setting)) {
                        addFlag = setting.view.selectedMulti && addFlag;
                        if (node.parentTId) {
                            view.expandCollapseParentNode(setting, node.getParentNode(), true, false, showNodeFocus);
                        } else if (!isSilent) {
                            try {
                                $$(node, setting).focus().blur();
                            } catch (e) {
                            }
                        }
                        view.selectNode(setting, node, addFlag);
                    }

                    function showNodeFocus() {
                        if (isSilent) {
                            return;
                        }
                        var a = $$(node, setting).get(0);
                        view.scrollIntoView(a);
                    }
                },
                transformTozTreeNodes: function (simpleNodes) {
                    return data.transformTozTreeFormat(setting, simpleNodes);
                },
                transformToArray: function (nodes) {
                    return data.transformToArrayFormat(setting, nodes);
                },
                updateNode: function (node, checkTypeFlag) {
                    if (!node) return;
                    var nObj = $$(node, setting);
                    if (nObj.get(0) && tools.uCanDo(setting)) {
                        view.setNodeName(setting, node);
                        view.setNodeTarget(setting, node);
                        view.setNodeUrl(setting, node);
                        view.setNodeLineIcos(setting, node);
                        view.setNodeFontCss(setting, node);
                    }
                }
            }
            root.treeTools = zTreeTools;
            data.setZTreeTools(setting, zTreeTools);

            if (root[childKey] && root[childKey].length > 0) {
                view.createNodes(setting, 0, root[childKey], null, -1);
            } else if (setting.async.enable && setting.async.url && setting.async.url !== '') {
                view.asyncNode(setting);
            }
            return zTreeTools;
        }
    };

    var zt = $.fn.zTree,
        $$ = tools.$,
        consts = zt.consts;
})(jQuery);
/*
 * JQuery zTree excheck v3.5.28
 * http://treejs.cn/
 *
 * Copyright (c) 2010 Hunter.z
 *
 * Licensed same as jquery - MIT License
 * http://www.opensource.org/licenses/mit-license.php
 *
 * email: hunter.z@263.net
 * Date: 2017-01-20
 */
(function($){
	//default consts of excheck
	var _consts = {
		event: {
			CHECK: "ztree_check"
		},
		id: {
			CHECK: "_check"
		},
		checkbox: {
			STYLE: "checkbox",
			DEFAULT: "chk",
			DISABLED: "disable",
			FALSE: "false",
			TRUE: "true",
			FULL: "full",
			PART: "part",
			FOCUS: "focus"
		},
		radio: {
			STYLE: "radio",
			TYPE_ALL: "all",
			TYPE_LEVEL: "level"
		}
	},
	//default setting of excheck
	_setting = {
		check: {
			enable: false,
			autoCheckTrigger: false,
			chkStyle: _consts.checkbox.STYLE,
			nocheckInherit: false,
			chkDisabledInherit: false,
			radioType: _consts.radio.TYPE_LEVEL,
			chkboxType: {
				"Y": "ps",
				"N": "ps"
			}
		},
		data: {
			key: {
				checked: "checked"
			}
		},
		callback: {
			beforeCheck:null,
			onCheck:null
		}
	},
	//default root of excheck
	_initRoot = function (setting) {
		var r = data.getRoot(setting);
		r.radioCheckedList = [];
	},
	//default cache of excheck
	_initCache = function(treeId) {},
	//default bind event of excheck
	_bindEvent = function(setting) {
		var o = setting.treeObj,
		c = consts.event;
		o.bind(c.CHECK, function (event, srcEvent, treeId, node) {
			event.srcEvent = srcEvent;
			tools.apply(setting.callback.onCheck, [event, treeId, node]);
		});
	},
	_unbindEvent = function(setting) {
		var o = setting.treeObj,
		c = consts.event;
		o.unbind(c.CHECK);
	},
	//default event proxy of excheck
	_eventProxy = function(e) {
		var target = e.target,
		setting = data.getSetting(e.data.treeId),
		tId = "", node = null,
		nodeEventType = "", treeEventType = "",
		nodeEventCallback = null, treeEventCallback = null;

		if (tools.eqs(e.type, "mouseover")) {
			if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode"+ consts.id.CHECK) !== null) {
				tId = tools.getNodeMainDom(target).id;
				nodeEventType = "mouseoverCheck";
			}
		} else if (tools.eqs(e.type, "mouseout")) {
			if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode"+ consts.id.CHECK) !== null) {
				tId = tools.getNodeMainDom(target).id;
				nodeEventType = "mouseoutCheck";
			}
		} else if (tools.eqs(e.type, "click")) {
			if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode"+ consts.id.CHECK) !== null) {
				tId = tools.getNodeMainDom(target).id;
				nodeEventType = "checkNode";
			}
		}
		if (tId.length>0) {
			node = data.getNodeCache(setting, tId);
			switch (nodeEventType) {
				case "checkNode" :
					nodeEventCallback = _handler.onCheckNode;
					break;
				case "mouseoverCheck" :
					nodeEventCallback = _handler.onMouseoverCheck;
					break;
				case "mouseoutCheck" :
					nodeEventCallback = _handler.onMouseoutCheck;
					break;
			}
		}
		var proxyResult = {
			stop: nodeEventType === "checkNode",
			node: node,
			nodeEventType: nodeEventType,
			nodeEventCallback: nodeEventCallback,
			treeEventType: treeEventType,
			treeEventCallback: treeEventCallback
		};
		return proxyResult
	},
	//default init node of excheck
	_initNode = function(setting, level, n, parentNode, isFirstNode, isLastNode, openFlag) {
		if (!n) return;
		var checkedKey = setting.data.key.checked;
		if (typeof n[checkedKey] == "string") n[checkedKey] = tools.eqs(n[checkedKey], "true");
		n[checkedKey] = !!n[checkedKey];
		n.checkedOld = n[checkedKey];
		if (typeof n.nocheck == "string") n.nocheck = tools.eqs(n.nocheck, "true");
		n.nocheck = !!n.nocheck || (setting.check.nocheckInherit && parentNode && !!parentNode.nocheck);
		if (typeof n.chkDisabled == "string") n.chkDisabled = tools.eqs(n.chkDisabled, "true");
		n.chkDisabled = !!n.chkDisabled || (setting.check.chkDisabledInherit && parentNode && !!parentNode.chkDisabled);
		if (typeof n.halfCheck == "string") n.halfCheck = tools.eqs(n.halfCheck, "true");
		n.halfCheck = !!n.halfCheck;
		n.check_Child_State = -1;
		n.check_Focus = false;
		n.getCheckStatus = function() {return data.getCheckStatus(setting, n);};

		if (setting.check.chkStyle == consts.radio.STYLE && setting.check.radioType == consts.radio.TYPE_ALL && n[checkedKey] ) {
			var r = data.getRoot(setting);
			r.radioCheckedList.push(n);
		}
	},
	//add dom for check
	_beforeA = function(setting, node, html) {
		var checkedKey = setting.data.key.checked;
		if (setting.check.enable) {
			data.makeChkFlag(setting, node);
			html.push("<span ID='", node.tId, consts.id.CHECK, "' class='", view.makeChkClass(setting, node), "' treeNode", consts.id.CHECK, (node.nocheck === true?" style='display:none;'":""),"></span>");
		}
	},
	//update zTreeObj, add method of check
	_zTreeTools = function(setting, zTreeTools) {
		zTreeTools.checkNode = function(node, checked, checkTypeFlag, callbackFlag) {
			var checkedKey = this.setting.data.key.checked;
			if (node.chkDisabled === true) return;
			if (checked !== true && checked !== false) {
				checked = !node[checkedKey];
			}
			callbackFlag = !!callbackFlag;

			if (node[checkedKey] === checked && !checkTypeFlag) {
				return;
			} else if (callbackFlag && tools.apply(this.setting.callback.beforeCheck, [this.setting.treeId, node], true) == false) {
				return;
			}
			if (tools.uCanDo(this.setting) && this.setting.check.enable && node.nocheck !== true) {
				node[checkedKey] = checked;
				var checkObj = $$(node, consts.id.CHECK, this.setting);
				if (checkTypeFlag || this.setting.check.chkStyle === consts.radio.STYLE) view.checkNodeRelation(this.setting, node);
				view.setChkClass(this.setting, checkObj, node);
				view.repairParentChkClassWithSelf(this.setting, node);
				if (callbackFlag) {
					this.setting.treeObj.trigger(consts.event.CHECK, [null, this.setting.treeId, node]);
				}
			}
		}

		zTreeTools.checkAllNodes = function(checked) {
			view.repairAllChk(this.setting, !!checked);
		}

		zTreeTools.getCheckedNodes = function(checked) {
			var childKey = this.setting.data.key.children;
			checked = (checked !== false);
			return data.getTreeCheckedNodes(this.setting, data.getRoot(this.setting)[childKey], checked);
		}

		zTreeTools.getChangeCheckedNodes = function() {
			var childKey = this.setting.data.key.children;
			return data.getTreeChangeCheckedNodes(this.setting, data.getRoot(this.setting)[childKey]);
		}

		zTreeTools.setChkDisabled = function(node, disabled, inheritParent, inheritChildren) {
			disabled = !!disabled;
			inheritParent = !!inheritParent;
			inheritChildren = !!inheritChildren;
			view.repairSonChkDisabled(this.setting, node, disabled, inheritChildren);
			view.repairParentChkDisabled(this.setting, node.getParentNode(), disabled, inheritParent);
		}

		var _updateNode = zTreeTools.updateNode;
		zTreeTools.updateNode = function(node, checkTypeFlag) {
			if (_updateNode) _updateNode.apply(zTreeTools, arguments);
			if (!node || !this.setting.check.enable) return;
			var nObj = $$(node, this.setting);
			if (nObj.get(0) && tools.uCanDo(this.setting)) {
				var checkObj = $$(node, consts.id.CHECK, this.setting);
				if (checkTypeFlag == true || this.setting.check.chkStyle === consts.radio.STYLE) view.checkNodeRelation(this.setting, node);
				view.setChkClass(this.setting, checkObj, node);
				view.repairParentChkClassWithSelf(this.setting, node);
			}
		}
	},
	//method of operate data
	_data = {
		getRadioCheckedList: function(setting) {
			var checkedList = data.getRoot(setting).radioCheckedList;
			for (var i=0, j=checkedList.length; i<j; i++) {
				if(!data.getNodeCache(setting, checkedList[i].tId)) {
					checkedList.splice(i, 1);
					i--; j--;
				}
			}
			return checkedList;
		},
		getCheckStatus: function(setting, node) {
			if (!setting.check.enable || node.nocheck || node.chkDisabled) return null;
			var checkedKey = setting.data.key.checked,
			r = {
				checked: node[checkedKey],
				half: node.halfCheck ? node.halfCheck : (setting.check.chkStyle == consts.radio.STYLE ? (node.check_Child_State === 2) : (node[checkedKey] ? (node.check_Child_State > -1 && node.check_Child_State < 2) : (node.check_Child_State > 0)))
			};
			return r;
		},
		getTreeCheckedNodes: function(setting, nodes, checked, results) {
			if (!nodes) return [];
			var childKey = setting.data.key.children,
			checkedKey = setting.data.key.checked,
			onlyOne = (checked && setting.check.chkStyle == consts.radio.STYLE && setting.check.radioType == consts.radio.TYPE_ALL);
			results = !results ? [] : results;
			for (var i = 0, l = nodes.length; i < l; i++) {
				if (nodes[i].nocheck !== true && nodes[i].chkDisabled !== true && nodes[i][checkedKey] == checked) {
					results.push(nodes[i]);
					if(onlyOne) {
						break;
					}
				}
				data.getTreeCheckedNodes(setting, nodes[i][childKey], checked, results);
				if(onlyOne && results.length > 0) {
					break;
				}
			}
			return results;
		},
		getTreeChangeCheckedNodes: function(setting, nodes, results) {
			if (!nodes) return [];
			var childKey = setting.data.key.children,
			checkedKey = setting.data.key.checked;
			results = !results ? [] : results;
			for (var i = 0, l = nodes.length; i < l; i++) {
				if (nodes[i].nocheck !== true && nodes[i].chkDisabled !== true && nodes[i][checkedKey] != nodes[i].checkedOld) {
					results.push(nodes[i]);
				}
				data.getTreeChangeCheckedNodes(setting, nodes[i][childKey], results);
			}
			return results;
		},
		makeChkFlag: function(setting, node) {
			if (!node) return;
			var childKey = setting.data.key.children,
			checkedKey = setting.data.key.checked,
			chkFlag = -1;
			if (node[childKey]) {
				for (var i = 0, l = node[childKey].length; i < l; i++) {
					var cNode = node[childKey][i];
					var tmp = -1;
					if (setting.check.chkStyle == consts.radio.STYLE) {
						if (cNode.nocheck === true || cNode.chkDisabled === true) {
							tmp = cNode.check_Child_State;
						} else if (cNode.halfCheck === true) {
							tmp = 2;
						} else if (cNode[checkedKey]) {
							tmp = 2;
						} else {
							tmp = cNode.check_Child_State > 0 ? 2:0;
						}
						if (tmp == 2) {
							chkFlag = 2; break;
						} else if (tmp == 0){
							chkFlag = 0;
						}
					} else if (setting.check.chkStyle == consts.checkbox.STYLE) {
						if (cNode.nocheck === true || cNode.chkDisabled === true) {
							tmp = cNode.check_Child_State;
						} else if (cNode.halfCheck === true) {
							tmp = 1;
						} else if (cNode[checkedKey] ) {
							tmp = (cNode.check_Child_State === -1 || cNode.check_Child_State === 2) ? 2 : 1;
						} else {
							tmp = (cNode.check_Child_State > 0) ? 1 : 0;
						}
						if (tmp === 1) {
							chkFlag = 1; break;
						} else if (tmp === 2 && chkFlag > -1 && i > 0 && tmp !== chkFlag) {
							chkFlag = 1; break;
						} else if (chkFlag === 2 && tmp > -1 && tmp < 2) {
							chkFlag = 1; break;
						} else if (tmp > -1) {
							chkFlag = tmp;
						}
					}
				}
			}
			node.check_Child_State = chkFlag;
		}
	},
	//method of event proxy
	_event = {

	},
	//method of event handler
	_handler = {
		onCheckNode: function (event, node) {
			if (node.chkDisabled === true) return false;
			var setting = data.getSetting(event.data.treeId),
			checkedKey = setting.data.key.checked;
			if (tools.apply(setting.callback.beforeCheck, [setting.treeId, node], true) == false) return true;
			node[checkedKey] = !node[checkedKey];
			view.checkNodeRelation(setting, node);
			var checkObj = $$(node, consts.id.CHECK, setting);
			view.setChkClass(setting, checkObj, node);
			view.repairParentChkClassWithSelf(setting, node);
			setting.treeObj.trigger(consts.event.CHECK, [event, setting.treeId, node]);
			return true;
		},
		onMouseoverCheck: function(event, node) {
			if (node.chkDisabled === true) return false;
			var setting = data.getSetting(event.data.treeId),
			checkObj = $$(node, consts.id.CHECK, setting);
			node.check_Focus = true;
			view.setChkClass(setting, checkObj, node);
			return true;
		},
		onMouseoutCheck: function(event, node) {
			if (node.chkDisabled === true) return false;
			var setting = data.getSetting(event.data.treeId),
			checkObj = $$(node, consts.id.CHECK, setting);
			node.check_Focus = false;
			view.setChkClass(setting, checkObj, node);
			return true;
		}
	},
	//method of tools for zTree
	_tools = {

	},
	//method of operate ztree dom
	_view = {
		checkNodeRelation: function(setting, node) {
			var pNode, i, l,
			childKey = setting.data.key.children,
			checkedKey = setting.data.key.checked,
			r = consts.radio;
			if (setting.check.chkStyle == r.STYLE) {
				var checkedList = data.getRadioCheckedList(setting);
				if (node[checkedKey]) {
					if (setting.check.radioType == r.TYPE_ALL) {
						for (i = checkedList.length-1; i >= 0; i--) {
							pNode = checkedList[i];
							if (pNode[checkedKey] && pNode != node) {
								pNode[checkedKey] = false;
								checkedList.splice(i, 1);

								view.setChkClass(setting, $$(pNode, consts.id.CHECK, setting), pNode);
								if (pNode.parentTId != node.parentTId) {
									view.repairParentChkClassWithSelf(setting, pNode);
								}
							}
						}
						checkedList.push(node);
					} else {
						var parentNode = (node.parentTId) ? node.getParentNode() : data.getRoot(setting);
						for (i = 0, l = parentNode[childKey].length; i < l; i++) {
							pNode = parentNode[childKey][i];
							if (pNode[checkedKey] && pNode != node) {
								pNode[checkedKey] = false;
								view.setChkClass(setting, $$(pNode, consts.id.CHECK, setting), pNode);
							}
						}
					}
				} else if (setting.check.radioType == r.TYPE_ALL) {
					for (i = 0, l = checkedList.length; i < l; i++) {
						if (node == checkedList[i]) {
							checkedList.splice(i, 1);
							break;
						}
					}
				}

			} else {
				if (node[checkedKey] && (!node[childKey] || node[childKey].length==0 || setting.check.chkboxType.Y.indexOf("s") > -1)) {
					view.setSonNodeCheckBox(setting, node, true);
				}
				if (!node[checkedKey] && (!node[childKey] || node[childKey].length==0 || setting.check.chkboxType.N.indexOf("s") > -1)) {
					view.setSonNodeCheckBox(setting, node, false);
				}
				if (node[checkedKey] && setting.check.chkboxType.Y.indexOf("p") > -1) {
					view.setParentNodeCheckBox(setting, node, true);
				}
				if (!node[checkedKey] && setting.check.chkboxType.N.indexOf("p") > -1) {
					view.setParentNodeCheckBox(setting, node, false);
				}
			}
		},
		makeChkClass: function(setting, node) {
			var checkedKey = setting.data.key.checked,
			c = consts.checkbox, r = consts.radio,
			fullStyle = "";
			if (node.chkDisabled === true) {
				fullStyle = c.DISABLED;
			} else if (node.halfCheck) {
				fullStyle = c.PART;
			} else if (setting.check.chkStyle == r.STYLE) {
				fullStyle = (node.check_Child_State < 1)? c.FULL:c.PART;
			} else {
				fullStyle = node[checkedKey] ? ((node.check_Child_State === 2 || node.check_Child_State === -1) ? c.FULL:c.PART) : ((node.check_Child_State < 1)? c.FULL:c.PART);
			}
			var chkName = setting.check.chkStyle + "_" + (node[checkedKey] ? c.TRUE : c.FALSE) + "_" + fullStyle;
			chkName = (node.check_Focus && node.chkDisabled !== true) ? chkName + "_" + c.FOCUS : chkName;
			return consts.className.BUTTON + " " + c.DEFAULT + " " + chkName;
		},
		repairAllChk: function(setting, checked) {
			if (setting.check.enable && setting.check.chkStyle === consts.checkbox.STYLE) {
				var checkedKey = setting.data.key.checked,
				childKey = setting.data.key.children,
				root = data.getRoot(setting);
				for (var i = 0, l = root[childKey].length; i<l ; i++) {
					var node = root[childKey][i];
					if (node.nocheck !== true && node.chkDisabled !== true) {
						node[checkedKey] = checked;
					}
					view.setSonNodeCheckBox(setting, node, checked);
				}
			}
		},
		repairChkClass: function(setting, node) {
			if (!node) return;
			data.makeChkFlag(setting, node);
			if (node.nocheck !== true) {
				var checkObj = $$(node, consts.id.CHECK, setting);
				view.setChkClass(setting, checkObj, node);
			}
		},
		repairParentChkClass: function(setting, node) {
			if (!node || !node.parentTId) return;
			var pNode = node.getParentNode();
			view.repairChkClass(setting, pNode);
			view.repairParentChkClass(setting, pNode);
		},
		repairParentChkClassWithSelf: function(setting, node) {
			if (!node) return;
			var childKey = setting.data.key.children;
			if (node[childKey] && node[childKey].length > 0) {
				view.repairParentChkClass(setting, node[childKey][0]);
			} else {
				view.repairParentChkClass(setting, node);
			}
		},
		repairSonChkDisabled: function(setting, node, chkDisabled, inherit) {
			if (!node) return;
			var childKey = setting.data.key.children;
			if (node.chkDisabled != chkDisabled) {
				node.chkDisabled = chkDisabled;
			}
			view.repairChkClass(setting, node);
			if (node[childKey] && inherit) {
				for (var i = 0, l = node[childKey].length; i < l; i++) {
					var sNode = node[childKey][i];
					view.repairSonChkDisabled(setting, sNode, chkDisabled, inherit);
				}
			}
		},
		repairParentChkDisabled: function(setting, node, chkDisabled, inherit) {
			if (!node) return;
			if (node.chkDisabled != chkDisabled && inherit) {
				node.chkDisabled = chkDisabled;
			}
			view.repairChkClass(setting, node);
			view.repairParentChkDisabled(setting, node.getParentNode(), chkDisabled, inherit);
		},
		setChkClass: function(setting, obj, node) {
			if (!obj) return;
			if (node.nocheck === true) {
				obj.hide();
			} else {
				obj.show();
			}
            obj.attr('class', view.makeChkClass(setting, node));
		},
		setParentNodeCheckBox: function(setting, node, value, srcNode) {
			var childKey = setting.data.key.children,
			checkedKey = setting.data.key.checked,
			checkObj = $$(node, consts.id.CHECK, setting);
			if (!srcNode) srcNode = node;
			data.makeChkFlag(setting, node);
			if (node.nocheck !== true && node.chkDisabled !== true) {
				node[checkedKey] = value;
				view.setChkClass(setting, checkObj, node);
				if (setting.check.autoCheckTrigger && node != srcNode) {
					setting.treeObj.trigger(consts.event.CHECK, [null, setting.treeId, node]);
				}
			}
			if (node.parentTId) {
				var pSign = true;
				if (!value) {
					var pNodes = node.getParentNode()[childKey];
					for (var i = 0, l = pNodes.length; i < l; i++) {
						if ((pNodes[i].nocheck !== true && pNodes[i].chkDisabled !== true && pNodes[i][checkedKey])
						|| ((pNodes[i].nocheck === true || pNodes[i].chkDisabled === true) && pNodes[i].check_Child_State > 0)) {
							pSign = false;
							break;
						}
					}
				}
				if (pSign) {
					view.setParentNodeCheckBox(setting, node.getParentNode(), value, srcNode);
				}
			}
		},
		setSonNodeCheckBox: function(setting, node, value, srcNode) {
			if (!node) return;
			var childKey = setting.data.key.children,
			checkedKey = setting.data.key.checked,
			checkObj = $$(node, consts.id.CHECK, setting);
			if (!srcNode) srcNode = node;

			var hasDisable = false;
			if (node[childKey]) {
				for (var i = 0, l = node[childKey].length; i < l; i++) {
					var sNode = node[childKey][i];
					view.setSonNodeCheckBox(setting, sNode, value, srcNode);
					if (sNode.chkDisabled === true) hasDisable = true;
				}
			}

			if (node != data.getRoot(setting) && node.chkDisabled !== true) {
				if (hasDisable && node.nocheck !== true) {
					data.makeChkFlag(setting, node);
				}
				if (node.nocheck !== true && node.chkDisabled !== true) {
					node[checkedKey] = value;
					if (!hasDisable) node.check_Child_State = (node[childKey] && node[childKey].length > 0) ? (value ? 2 : 0) : -1;
				} else {
					node.check_Child_State = -1;
				}
				view.setChkClass(setting, checkObj, node);
				if (setting.check.autoCheckTrigger && node != srcNode && node.nocheck !== true && node.chkDisabled !== true) {
					setting.treeObj.trigger(consts.event.CHECK, [null, setting.treeId, node]);
				}
			}

		}
	},

	_z = {
		tools: _tools,
		view: _view,
		event: _event,
		data: _data
	};
	$.extend(true, $.fn.zTree.consts, _consts);
	$.extend(true, $.fn.zTree._z, _z);

	var zt = $.fn.zTree,
	tools = zt._z.tools,
	consts = zt.consts,
	view = zt._z.view,
	data = zt._z.data,
	event = zt._z.event,
	$$ = tools.$;

	data.exSetting(_setting);
	data.addInitBind(_bindEvent);
	data.addInitUnBind(_unbindEvent);
	data.addInitCache(_initCache);
	data.addInitNode(_initNode);
	data.addInitProxy(_eventProxy, true);
	data.addInitRoot(_initRoot);
	data.addBeforeA(_beforeA);
	data.addZTreeTools(_zTreeTools);

	var _createNodes = view.createNodes;
	view.createNodes = function(setting, level, nodes, parentNode, index) {
		if (_createNodes) _createNodes.apply(view, arguments);
		if (!nodes) return;
		view.repairParentChkClassWithSelf(setting, parentNode);
	}
	var _removeNode = view.removeNode;
	view.removeNode = function(setting, node) {
		var parentNode = node.getParentNode();
		if (_removeNode) _removeNode.apply(view, arguments);
		if (!node || !parentNode) return;
		view.repairChkClass(setting, parentNode);
		view.repairParentChkClass(setting, parentNode);
	}

	var _appendNodes = view.appendNodes;
	view.appendNodes = function(setting, level, nodes, parentNode, index, initFlag, openFlag) {
		var html = "";
		if (_appendNodes) {
			html = _appendNodes.apply(view, arguments);
		}
		if (parentNode) {
			data.makeChkFlag(setting, parentNode);
		}
		return html;
	}
})(jQuery);
/*
 * JQuery zTree exedit v3.5.28
 * http://treejs.cn/
 *
 * Copyright (c) 2010 Hunter.z
 *
 * Licensed same as jquery - MIT License
 * http://www.opensource.org/licenses/mit-license.php
 *
 * email: hunter.z@263.net
 * Date: 2017-01-20
 */
(function($){
	//default consts of exedit
	var _consts = {
		event: {
			DRAG: "ztree_drag",
			DROP: "ztree_drop",
			RENAME: "ztree_rename",
			DRAGMOVE:"ztree_dragmove"
		},
		id: {
			EDIT: "_edit",
			INPUT: "_input",
			REMOVE: "_remove"
		},
		move: {
			TYPE_INNER: "inner",
			TYPE_PREV: "prev",
			TYPE_NEXT: "next"
		},
		node: {
			CURSELECTED_EDIT: "curSelectedNode_Edit",
			TMPTARGET_TREE: "tmpTargetzTree",
			TMPTARGET_NODE: "tmpTargetNode"
		}
	},
	//default setting of exedit
	_setting = {
		edit: {
			enable: false,
			editNameSelectAll: false,
			showRemoveBtn: true,
			showRenameBtn: true,
			removeTitle: "remove",
			renameTitle: "rename",
			drag: {
				autoExpandTrigger: false,
				isCopy: true,
				isMove: true,
				prev: true,
				next: true,
				inner: true,
				minMoveSize: 5,
				borderMax: 10,
				borderMin: -5,
				maxShowNodeNum: 5,
				autoOpenTime: 500
			}
		},
		view: {
			addHoverDom: null,
			removeHoverDom: null
		},
		callback: {
			beforeDrag:null,
			beforeDragOpen:null,
			beforeDrop:null,
			beforeEditName:null,
			beforeRename:null,
			onDrag:null,
			onDragMove:null,
			onDrop:null,
			onRename:null
		}
	},
	//default root of exedit
	_initRoot = function (setting) {
		var r = data.getRoot(setting), rs = data.getRoots();
		r.curEditNode = null;
		r.curEditInput = null;
		r.curHoverNode = null;
		r.dragFlag = 0;
		r.dragNodeShowBefore = [];
		r.dragMaskList = new Array();
		rs.showHoverDom = true;
	},
	//default cache of exedit
	_initCache = function(treeId) {},
	//default bind event of exedit
	_bindEvent = function(setting) {
		var o = setting.treeObj;
		var c = consts.event;
		o.bind(c.RENAME, function (event, treeId, treeNode, isCancel) {
			tools.apply(setting.callback.onRename, [event, treeId, treeNode, isCancel]);
		});

		o.bind(c.DRAG, function (event, srcEvent, treeId, treeNodes) {
			tools.apply(setting.callback.onDrag, [srcEvent, treeId, treeNodes]);
		});

		o.bind(c.DRAGMOVE,function(event, srcEvent, treeId, treeNodes){
			tools.apply(setting.callback.onDragMove,[srcEvent, treeId, treeNodes]);
		});

		o.bind(c.DROP, function (event, srcEvent, treeId, treeNodes, targetNode, moveType, isCopy) {
			tools.apply(setting.callback.onDrop, [srcEvent, treeId, treeNodes, targetNode, moveType, isCopy]);
		});
	},
	_unbindEvent = function(setting) {
		var o = setting.treeObj;
		var c = consts.event;
		o.unbind(c.RENAME);
		o.unbind(c.DRAG);
		o.unbind(c.DRAGMOVE);
		o.unbind(c.DROP);
	},
	//default event proxy of exedit
	_eventProxy = function(e) {
		var target = e.target,
		setting = data.getSetting(e.data.treeId),
		relatedTarget = e.relatedTarget,
		tId = "", node = null,
		nodeEventType = "", treeEventType = "",
		nodeEventCallback = null, treeEventCallback = null,
		tmp = null;

		if (tools.eqs(e.type, "mouseover")) {
			tmp = tools.getMDom(setting, target, [{tagName:"a", attrName:"treeNode"+consts.id.A}]);
			if (tmp) {
				tId = tools.getNodeMainDom(tmp).id;
				nodeEventType = "hoverOverNode";
			}
		} else if (tools.eqs(e.type, "mouseout")) {
			tmp = tools.getMDom(setting, relatedTarget, [{tagName:"a", attrName:"treeNode"+consts.id.A}]);
			if (!tmp) {
				tId = "remove";
				nodeEventType = "hoverOutNode";
			}
		} else if (tools.eqs(e.type, "mousedown")) {
			tmp = tools.getMDom(setting, target, [{tagName:"a", attrName:"treeNode"+consts.id.A}]);
			if (tmp) {
				tId = tools.getNodeMainDom(tmp).id;
				nodeEventType = "mousedownNode";
			}
		}
		if (tId.length>0) {
			node = data.getNodeCache(setting, tId);
			switch (nodeEventType) {
				case "mousedownNode" :
					nodeEventCallback = _handler.onMousedownNode;
					break;
				case "hoverOverNode" :
					nodeEventCallback = _handler.onHoverOverNode;
					break;
				case "hoverOutNode" :
					nodeEventCallback = _handler.onHoverOutNode;
					break;
			}
		}
		var proxyResult = {
			stop: false,
			node: node,
			nodeEventType: nodeEventType,
			nodeEventCallback: nodeEventCallback,
			treeEventType: treeEventType,
			treeEventCallback: treeEventCallback
		};
		return proxyResult
	},
	//default init node of exedit
	_initNode = function(setting, level, n, parentNode, isFirstNode, isLastNode, openFlag) {
		if (!n) return;
		n.isHover = false;
		n.editNameFlag = false;
	},
	//update zTreeObj, add method of edit
	_zTreeTools = function(setting, zTreeTools) {
		zTreeTools.cancelEditName = function(newName) {
			var root = data.getRoot(this.setting);
			if (!root.curEditNode) return;
			view.cancelCurEditNode(this.setting, newName?newName:null, true);
		}
		zTreeTools.copyNode = function(targetNode, node, moveType, isSilent) {
			if (!node) return null;
			if (targetNode && !targetNode.isParent && this.setting.data.keep.leaf && moveType === consts.move.TYPE_INNER) return null;
			var _this = this,
				newNode = tools.clone(node);
			if (!targetNode) {
				targetNode = null;
				moveType = consts.move.TYPE_INNER;
			}
			if (moveType == consts.move.TYPE_INNER) {
				function copyCallback() {
					view.addNodes(_this.setting, targetNode, -1, [newNode], isSilent);
				}

				if (tools.canAsync(this.setting, targetNode)) {
					view.asyncNode(this.setting, targetNode, isSilent, copyCallback);
				} else {
					copyCallback();
				}
			} else {
				view.addNodes(this.setting, targetNode.parentNode, -1, [newNode], isSilent);
				view.moveNode(this.setting, targetNode, newNode, moveType, false, isSilent);
			}
			return newNode;
		}
		zTreeTools.editName = function(node) {
			if (!node || !node.tId || node !== data.getNodeCache(this.setting, node.tId)) return;
			if (node.parentTId) view.expandCollapseParentNode(this.setting, node.getParentNode(), true);
			view.editNode(this.setting, node)
		}
		zTreeTools.moveNode = function(targetNode, node, moveType, isSilent) {
			if (!node) return node;
			if (targetNode && !targetNode.isParent && this.setting.data.keep.leaf && moveType === consts.move.TYPE_INNER) {
				return null;
			} else if (targetNode && ((node.parentTId == targetNode.tId && moveType == consts.move.TYPE_INNER) || $$(node, this.setting).find("#" + targetNode.tId).length > 0)) {
				return null;
			} else if (!targetNode) {
				targetNode = null;
			}
			var _this = this;
			function moveCallback() {
				view.moveNode(_this.setting, targetNode, node, moveType, false, isSilent);
			}
			if (tools.canAsync(this.setting, targetNode) && moveType === consts.move.TYPE_INNER) {
				view.asyncNode(this.setting, targetNode, isSilent, moveCallback);
			} else {
				moveCallback();
			}
			return node;
		}
		zTreeTools.setEditable = function(editable) {
			this.setting.edit.enable = editable;
			return this.refresh();
		}
	},
	//method of operate data
	_data = {
		setSonNodeLevel: function(setting, parentNode, node) {
			if (!node) return;
			var childKey = setting.data.key.children;
			node.level = (parentNode)? parentNode.level + 1 : 0;
			if (!node[childKey]) return;
			for (var i = 0, l = node[childKey].length; i < l; i++) {
				if (node[childKey][i]) data.setSonNodeLevel(setting, node, node[childKey][i]);
			}
		}
	},
	//method of event proxy
	_event = {

	},
	//method of event handler
	_handler = {
		onHoverOverNode: function(event, node) {
			var setting = data.getSetting(event.data.treeId),
			root = data.getRoot(setting);
			if (root.curHoverNode != node) {
				_handler.onHoverOutNode(event);
			}
			root.curHoverNode = node;
			view.addHoverDom(setting, node);
		},
		onHoverOutNode: function(event, node) {
			var setting = data.getSetting(event.data.treeId),
			root = data.getRoot(setting);
			if (root.curHoverNode && !data.isSelectedNode(setting, root.curHoverNode)) {
				view.removeTreeDom(setting, root.curHoverNode);
				root.curHoverNode = null;
			}
		},
		onMousedownNode: function(eventMouseDown, _node) {
			var i,l,
			setting = data.getSetting(eventMouseDown.data.treeId),
			root = data.getRoot(setting), roots = data.getRoots();
			//right click can't drag & drop
			if (eventMouseDown.button == 2 || !setting.edit.enable || (!setting.edit.drag.isCopy && !setting.edit.drag.isMove)) return true;

			//input of edit node name can't drag & drop
			var target = eventMouseDown.target,
			_nodes = data.getRoot(setting).curSelectedList,
			nodes = [];
			if (!data.isSelectedNode(setting, _node)) {
				nodes = [_node];
			} else {
				for (i=0, l=_nodes.length; i<l; i++) {
					if (_nodes[i].editNameFlag && tools.eqs(target.tagName, "input") && target.getAttribute("treeNode"+consts.id.INPUT) !== null) {
						return true;
					}
					nodes.push(_nodes[i]);
					if (nodes[0].parentTId !== _nodes[i].parentTId) {
						nodes = [_node];
						break;
					}
				}
			}

			view.editNodeBlur = true;
			view.cancelCurEditNode(setting);

			var doc = $(setting.treeObj.get(0).ownerDocument),
			body = $(setting.treeObj.get(0).ownerDocument.body), curNode, tmpArrow, tmpTarget,
			isOtherTree = false,
			targetSetting = setting,
			sourceSetting = setting,
			preNode, nextNode,
			preTmpTargetNodeId = null,
			preTmpMoveType = null,
			tmpTargetNodeId = null,
			moveType = consts.move.TYPE_INNER,
			mouseDownX = eventMouseDown.clientX,
			mouseDownY = eventMouseDown.clientY,
			startTime = (new Date()).getTime();

			if (tools.uCanDo(setting)) {
				doc.bind("mousemove", _docMouseMove);
			}
			function _docMouseMove(event) {
				//avoid start drag after click node
				if (root.dragFlag == 0 && Math.abs(mouseDownX - event.clientX) < setting.edit.drag.minMoveSize
					&& Math.abs(mouseDownY - event.clientY) < setting.edit.drag.minMoveSize) {
					return true;
				}
				var i, l, tmpNode, tmpDom, tmpNodes,
				childKey = setting.data.key.children;
				body.css("cursor", "pointer");

				if (root.dragFlag == 0) {
					if (tools.apply(setting.callback.beforeDrag, [setting.treeId, nodes], true) == false) {
						_docMouseUp(event);
						return true;
					}

					for (i=0, l=nodes.length; i<l; i++) {
						if (i==0) {
							root.dragNodeShowBefore = [];
						}
						tmpNode = nodes[i];
						if (tmpNode.isParent && tmpNode.open) {
							view.expandCollapseNode(setting, tmpNode, !tmpNode.open);
							root.dragNodeShowBefore[tmpNode.tId] = true;
						} else {
							root.dragNodeShowBefore[tmpNode.tId] = false;
						}
					}

					root.dragFlag = 1;
					roots.showHoverDom = false;
					tools.showIfameMask(setting, true);

					//sort
					var isOrder = true, lastIndex = -1;
					if (nodes.length>1) {
						var pNodes = nodes[0].parentTId ? nodes[0].getParentNode()[childKey] : data.getNodes(setting);
						tmpNodes = [];
						for (i=0, l=pNodes.length; i<l; i++) {
							if (root.dragNodeShowBefore[pNodes[i].tId] !== undefined) {
								if (isOrder && lastIndex > -1 && (lastIndex+1) !== i) {
									isOrder = false;
								}
								tmpNodes.push(pNodes[i]);
								lastIndex = i;
							}
							if (nodes.length === tmpNodes.length) {
								nodes = tmpNodes;
								break;
							}
						}
					}
					if (isOrder) {
						preNode = nodes[0].getPreNode();
						nextNode = nodes[nodes.length-1].getNextNode();
					}

					//set node in selected
					curNode = $$("<ul class='zTreeDragUL'></ul>", setting);
					for (i=0, l=nodes.length; i<l; i++) {
						tmpNode = nodes[i];
						tmpNode.editNameFlag = false;
						view.selectNode(setting, tmpNode, i>0);
						view.removeTreeDom(setting, tmpNode);

						if (i > setting.edit.drag.maxShowNodeNum-1) {
							continue;
						}

						tmpDom = $$("<li id='"+ tmpNode.tId +"_tmp'></li>", setting);
						tmpDom.append($$(tmpNode, consts.id.A, setting).clone());
						tmpDom.css("padding", "0");
						tmpDom.children("#" + tmpNode.tId + consts.id.A).removeClass(consts.node.CURSELECTED);
						curNode.append(tmpDom);
						if (i == setting.edit.drag.maxShowNodeNum-1) {
							tmpDom = $$("<li id='"+ tmpNode.tId +"_moretmp'><a>  ...  </a></li>", setting);
							curNode.append(tmpDom);
						}
					}
					curNode.attr("id", nodes[0].tId + consts.id.UL + "_tmp");
					curNode.addClass(setting.treeObj.attr("class"));
					curNode.appendTo(body);

					tmpArrow = $$("<span class='tmpzTreeMove_arrow'></span>", setting);
					tmpArrow.attr("id", "zTreeMove_arrow_tmp");
					tmpArrow.appendTo(body);

					setting.treeObj.trigger(consts.event.DRAG, [event, setting.treeId, nodes]);
				}

				if (root.dragFlag == 1) {
					if (tmpTarget && tmpArrow.attr("id") == event.target.id && tmpTargetNodeId && (event.clientX + doc.scrollLeft()+2) > ($("#" + tmpTargetNodeId + consts.id.A, tmpTarget).offset().left)) {
						var xT = $("#" + tmpTargetNodeId + consts.id.A, tmpTarget);
						event.target = (xT.length > 0) ? xT.get(0) : event.target;
					} else if (tmpTarget) {
						tmpTarget.removeClass(consts.node.TMPTARGET_TREE);
						if (tmpTargetNodeId) $("#" + tmpTargetNodeId + consts.id.A, tmpTarget).removeClass(consts.node.TMPTARGET_NODE + "_" + consts.move.TYPE_PREV)
							.removeClass(consts.node.TMPTARGET_NODE + "_" + _consts.move.TYPE_NEXT).removeClass(consts.node.TMPTARGET_NODE + "_" + _consts.move.TYPE_INNER);
					}
					tmpTarget = null;
					tmpTargetNodeId = null;

					//judge drag & drop in multi ztree
					isOtherTree = false;
					targetSetting = setting;
					var settings = data.getSettings();
					for (var s in settings) {
						if (settings[s].treeId && settings[s].edit.enable && settings[s].treeId != setting.treeId
							&& (event.target.id == settings[s].treeId || $(event.target).parents("#" + settings[s].treeId).length>0)) {
							isOtherTree = true;
							targetSetting = settings[s];
						}
					}

					var docScrollTop = doc.scrollTop(),
					docScrollLeft = doc.scrollLeft(),
					treeOffset = targetSetting.treeObj.offset(),
					scrollHeight = targetSetting.treeObj.get(0).scrollHeight,
					scrollWidth = targetSetting.treeObj.get(0).scrollWidth,
					dTop = (event.clientY + docScrollTop - treeOffset.top),
					dBottom = (targetSetting.treeObj.height() + treeOffset.top - event.clientY - docScrollTop),
					dLeft = (event.clientX + docScrollLeft - treeOffset.left),
					dRight = (targetSetting.treeObj.width() + treeOffset.left - event.clientX - docScrollLeft),
					isTop = (dTop < setting.edit.drag.borderMax && dTop > setting.edit.drag.borderMin),
					isBottom = (dBottom < setting.edit.drag.borderMax && dBottom > setting.edit.drag.borderMin),
					isLeft = (dLeft < setting.edit.drag.borderMax && dLeft > setting.edit.drag.borderMin),
					isRight = (dRight < setting.edit.drag.borderMax && dRight > setting.edit.drag.borderMin),
					isTreeInner = dTop > setting.edit.drag.borderMin && dBottom > setting.edit.drag.borderMin && dLeft > setting.edit.drag.borderMin && dRight > setting.edit.drag.borderMin,
					isTreeTop = (isTop && targetSetting.treeObj.scrollTop() <= 0),
					isTreeBottom = (isBottom && (targetSetting.treeObj.scrollTop() + targetSetting.treeObj.height()+10) >= scrollHeight),
					isTreeLeft = (isLeft && targetSetting.treeObj.scrollLeft() <= 0),
					isTreeRight = (isRight && (targetSetting.treeObj.scrollLeft() + targetSetting.treeObj.width()+10) >= scrollWidth);

					if (event.target && tools.isChildOrSelf(event.target, targetSetting.treeId)) {
						//get node <li> dom
						var targetObj = event.target;
						while (targetObj && targetObj.tagName && !tools.eqs(targetObj.tagName, "li") && targetObj.id != targetSetting.treeId) {
							targetObj = targetObj.parentNode;
						}

						var canMove = true;
						//don't move to self or children of self
						for (i=0, l=nodes.length; i<l; i++) {
							tmpNode = nodes[i];
							if (targetObj.id === tmpNode.tId) {
								canMove = false;
								break;
							} else if ($$(tmpNode, setting).find("#" + targetObj.id).length > 0) {
								canMove = false;
								break;
							}
						}
						if (canMove && event.target && tools.isChildOrSelf(event.target, targetObj.id + consts.id.A)) {
							tmpTarget = $(targetObj);
							tmpTargetNodeId = targetObj.id;
						}
					}

					//the mouse must be in zTree
					tmpNode = nodes[0];
					if (isTreeInner && tools.isChildOrSelf(event.target, targetSetting.treeId)) {
						//judge mouse move in root of ztree
						if (!tmpTarget && (event.target.id == targetSetting.treeId || isTreeTop || isTreeBottom || isTreeLeft || isTreeRight) && (isOtherTree || (!isOtherTree && tmpNode.parentTId))) {
							tmpTarget = targetSetting.treeObj;
						}
						//auto scroll top
						if (isTop) {
							targetSetting.treeObj.scrollTop(targetSetting.treeObj.scrollTop()-10);
						} else if (isBottom)  {
							targetSetting.treeObj.scrollTop(targetSetting.treeObj.scrollTop()+10);
						}
						if (isLeft) {
							targetSetting.treeObj.scrollLeft(targetSetting.treeObj.scrollLeft()-10);
						} else if (isRight) {
							targetSetting.treeObj.scrollLeft(targetSetting.treeObj.scrollLeft()+10);
						}
						//auto scroll left
						if (tmpTarget && tmpTarget != targetSetting.treeObj && tmpTarget.offset().left < targetSetting.treeObj.offset().left) {
							targetSetting.treeObj.scrollLeft(targetSetting.treeObj.scrollLeft()+ tmpTarget.offset().left - targetSetting.treeObj.offset().left);
						}
					}

					curNode.css({
						"top": (event.clientY + docScrollTop + 3) + "px",
						"left": (event.clientX + docScrollLeft + 3) + "px"
					});

					var dX = 0;
					var dY = 0;
					if (tmpTarget && tmpTarget.attr("id")!=targetSetting.treeId) {
						var tmpTargetNode = tmpTargetNodeId == null ? null: data.getNodeCache(targetSetting, tmpTargetNodeId),
							isCopy = ((event.ctrlKey || event.metaKey) && setting.edit.drag.isMove && setting.edit.drag.isCopy) || (!setting.edit.drag.isMove && setting.edit.drag.isCopy),
							isPrev = !!(preNode && tmpTargetNodeId === preNode.tId),
							isNext = !!(nextNode && tmpTargetNodeId === nextNode.tId),
							isInner = (tmpNode.parentTId && tmpNode.parentTId == tmpTargetNodeId),
							canPrev = (isCopy || !isNext) && tools.apply(targetSetting.edit.drag.prev, [targetSetting.treeId, nodes, tmpTargetNode], !!targetSetting.edit.drag.prev),
							canNext = (isCopy || !isPrev) && tools.apply(targetSetting.edit.drag.next, [targetSetting.treeId, nodes, tmpTargetNode], !!targetSetting.edit.drag.next),
							canInner = (isCopy || !isInner) && !(targetSetting.data.keep.leaf && !tmpTargetNode.isParent) && tools.apply(targetSetting.edit.drag.inner, [targetSetting.treeId, nodes, tmpTargetNode], !!targetSetting.edit.drag.inner);

						function clearMove() {
							tmpTarget = null;
							tmpTargetNodeId = "";
							moveType = consts.move.TYPE_INNER;
							tmpArrow.css({
								"display":"none"
							});
							if (window.zTreeMoveTimer) {
								clearTimeout(window.zTreeMoveTimer);
								window.zTreeMoveTargetNodeTId = null
							}
						}
						if (!canPrev && !canNext && !canInner) {
							clearMove();
						} else {
							var tmpTargetA = $("#" + tmpTargetNodeId + consts.id.A, tmpTarget),
								tmpNextA = tmpTargetNode.isLastNode ? null : $("#" + tmpTargetNode.getNextNode().tId + consts.id.A, tmpTarget.next()),
								tmpTop = tmpTargetA.offset().top,
								tmpLeft = tmpTargetA.offset().left,
								prevPercent = canPrev ? (canInner ? 0.25 : (canNext ? 0.5 : 1) ) : -1,
								nextPercent = canNext ? (canInner ? 0.75 : (canPrev ? 0.5 : 0) ) : -1,
								dY_percent = (event.clientY + docScrollTop - tmpTop)/tmpTargetA.height();

							if ((prevPercent==1 || dY_percent<=prevPercent && dY_percent>=-.2) && canPrev) {
								dX = 1 - tmpArrow.width();
								dY = tmpTop - tmpArrow.height()/2;
								moveType = consts.move.TYPE_PREV;
							} else if ((nextPercent==0 || dY_percent>=nextPercent && dY_percent<=1.2) && canNext) {
								dX = 1 - tmpArrow.width();
								dY = (tmpNextA == null || (tmpTargetNode.isParent && tmpTargetNode.open)) ? (tmpTop + tmpTargetA.height() - tmpArrow.height()/2) : (tmpNextA.offset().top - tmpArrow.height()/2);
								moveType = consts.move.TYPE_NEXT;
							} else if (canInner) {
								dX = 5 - tmpArrow.width();
								dY = tmpTop;
								moveType = consts.move.TYPE_INNER;
							} else {
								clearMove();
							}

							if (tmpTarget) {
								tmpArrow.css({
									"display":"block",
									"top": dY + "px",
									"left": (tmpLeft + dX) + "px"
								});
								tmpTargetA.addClass(consts.node.TMPTARGET_NODE + "_" + moveType);

								if (preTmpTargetNodeId != tmpTargetNodeId || preTmpMoveType != moveType) {
									startTime = (new Date()).getTime();
								}
								if (tmpTargetNode && tmpTargetNode.isParent && moveType == consts.move.TYPE_INNER) {
									var startTimer = true;
									if (window.zTreeMoveTimer && window.zTreeMoveTargetNodeTId !== tmpTargetNode.tId) {
										clearTimeout(window.zTreeMoveTimer);
										window.zTreeMoveTargetNodeTId = null;
									} else if (window.zTreeMoveTimer && window.zTreeMoveTargetNodeTId === tmpTargetNode.tId) {
										startTimer = false;
									}
									if (startTimer) {
										window.zTreeMoveTimer = setTimeout(function() {
											if (moveType != consts.move.TYPE_INNER) return;
											if (tmpTargetNode && tmpTargetNode.isParent && !tmpTargetNode.open && (new Date()).getTime() - startTime > targetSetting.edit.drag.autoOpenTime
												&& tools.apply(targetSetting.callback.beforeDragOpen, [targetSetting.treeId, tmpTargetNode], true)) {
												view.switchNode(targetSetting, tmpTargetNode);
												if (targetSetting.edit.drag.autoExpandTrigger) {
													targetSetting.treeObj.trigger(consts.event.EXPAND, [targetSetting.treeId, tmpTargetNode]);
												}
											}
										}, targetSetting.edit.drag.autoOpenTime+50);
										window.zTreeMoveTargetNodeTId = tmpTargetNode.tId;
									}
								}
							}
						}
					} else {
						moveType = consts.move.TYPE_INNER;
						if (tmpTarget && tools.apply(targetSetting.edit.drag.inner, [targetSetting.treeId, nodes, null], !!targetSetting.edit.drag.inner)) {
							tmpTarget.addClass(consts.node.TMPTARGET_TREE);
						} else {
							tmpTarget = null;
						}
						tmpArrow.css({
							"display":"none"
						});
						if (window.zTreeMoveTimer) {
							clearTimeout(window.zTreeMoveTimer);
							window.zTreeMoveTargetNodeTId = null;
						}
					}
					preTmpTargetNodeId = tmpTargetNodeId;
					preTmpMoveType = moveType;

					setting.treeObj.trigger(consts.event.DRAGMOVE, [event, setting.treeId, nodes]);
				}
				return false;
			}

			doc.bind("mouseup", _docMouseUp);
			function _docMouseUp(event) {
				if (window.zTreeMoveTimer) {
					clearTimeout(window.zTreeMoveTimer);
					window.zTreeMoveTargetNodeTId = null;
				}
				preTmpTargetNodeId = null;
				preTmpMoveType = null;
				doc.unbind("mousemove", _docMouseMove);
				doc.unbind("mouseup", _docMouseUp);
				doc.unbind("selectstart", _docSelect);
				body.css("cursor", "auto");
				if (tmpTarget) {
					tmpTarget.removeClass(consts.node.TMPTARGET_TREE);
					if (tmpTargetNodeId) $("#" + tmpTargetNodeId + consts.id.A, tmpTarget).removeClass(consts.node.TMPTARGET_NODE + "_" + consts.move.TYPE_PREV)
							.removeClass(consts.node.TMPTARGET_NODE + "_" + _consts.move.TYPE_NEXT).removeClass(consts.node.TMPTARGET_NODE + "_" + _consts.move.TYPE_INNER);
				}
				tools.showIfameMask(setting, false);

				roots.showHoverDom = true;
				if (root.dragFlag == 0) return;
				root.dragFlag = 0;

				var i, l, tmpNode;
				for (i=0, l=nodes.length; i<l; i++) {
					tmpNode = nodes[i];
					if (tmpNode.isParent && root.dragNodeShowBefore[tmpNode.tId] && !tmpNode.open) {
						view.expandCollapseNode(setting, tmpNode, !tmpNode.open);
						delete root.dragNodeShowBefore[tmpNode.tId];
					}
				}

				if (curNode) curNode.remove();
				if (tmpArrow) tmpArrow.remove();

				var isCopy = ((event.ctrlKey || event.metaKey) && setting.edit.drag.isMove && setting.edit.drag.isCopy) || (!setting.edit.drag.isMove && setting.edit.drag.isCopy);
				if (!isCopy && tmpTarget && tmpTargetNodeId && nodes[0].parentTId && tmpTargetNodeId==nodes[0].parentTId && moveType == consts.move.TYPE_INNER) {
					tmpTarget = null;
				}
				if (tmpTarget) {
					var dragTargetNode = tmpTargetNodeId == null ? null: data.getNodeCache(targetSetting, tmpTargetNodeId);
					if (tools.apply(setting.callback.beforeDrop, [targetSetting.treeId, nodes, dragTargetNode, moveType, isCopy], true) == false) {
						view.selectNodes(sourceSetting, nodes);
						return;
					}
					var newNodes = isCopy ? tools.clone(nodes) : nodes;

					function dropCallback() {
						if (isOtherTree) {
							if (!isCopy) {
								for(var i=0, l=nodes.length; i<l; i++) {
									view.removeNode(setting, nodes[i]);
								}
							}
							if (moveType == consts.move.TYPE_INNER) {
								view.addNodes(targetSetting, dragTargetNode, -1, newNodes);
							} else {
								view.addNodes(targetSetting, dragTargetNode.getParentNode(), moveType == consts.move.TYPE_PREV ? dragTargetNode.getIndex() : dragTargetNode.getIndex()+1, newNodes);
							}
						} else {
							if (isCopy && moveType == consts.move.TYPE_INNER) {
								view.addNodes(targetSetting, dragTargetNode, -1, newNodes);
							} else if (isCopy) {
								view.addNodes(targetSetting, dragTargetNode.getParentNode(), moveType == consts.move.TYPE_PREV ? dragTargetNode.getIndex() : dragTargetNode.getIndex()+1, newNodes);
							} else {
								if (moveType != consts.move.TYPE_NEXT) {
									for (i=0, l=newNodes.length; i<l; i++) {
										view.moveNode(targetSetting, dragTargetNode, newNodes[i], moveType, false);
									}
								} else {
									for (i=-1, l=newNodes.length-1; i<l; l--) {
										view.moveNode(targetSetting, dragTargetNode, newNodes[l], moveType, false);
									}
								}
							}
						}
						view.selectNodes(targetSetting, newNodes);

						var a = $$(newNodes[0], setting).get(0);
						view.scrollIntoView(a);

						setting.treeObj.trigger(consts.event.DROP, [event, targetSetting.treeId, newNodes, dragTargetNode, moveType, isCopy]);
					}

					if (moveType == consts.move.TYPE_INNER && tools.canAsync(targetSetting, dragTargetNode)) {
						view.asyncNode(targetSetting, dragTargetNode, false, dropCallback);
					} else {
						dropCallback();
					}

				} else {
					view.selectNodes(sourceSetting, nodes);
					setting.treeObj.trigger(consts.event.DROP, [event, setting.treeId, nodes, null, null, null]);
				}
			}

			doc.bind("selectstart", _docSelect);
			function _docSelect() {
				return false;
			}

			//Avoid FireFox's Bug
			//If zTree Div CSS set 'overflow', so drag node outside of zTree, and event.target is error.
			if(eventMouseDown.preventDefault) {
				eventMouseDown.preventDefault();
			}
			return true;
		}
	},
	//method of tools for zTree
	_tools = {
		getAbs: function (obj) {
			var oRect = obj.getBoundingClientRect(),
			scrollTop = document.body.scrollTop+document.documentElement.scrollTop,
			scrollLeft = document.body.scrollLeft+document.documentElement.scrollLeft;
			return [oRect.left+scrollLeft,oRect.top+scrollTop];
		},
		inputFocus: function(inputObj) {
			if (inputObj.get(0)) {
				inputObj.focus();
				tools.setCursorPosition(inputObj.get(0), inputObj.val().length);
			}
		},
		inputSelect: function(inputObj) {
			if (inputObj.get(0)) {
				inputObj.focus();
				inputObj.select();
			}
		},
		setCursorPosition: function(obj, pos){
			if(obj.setSelectionRange) {
				obj.focus();
				obj.setSelectionRange(pos,pos);
			} else if (obj.createTextRange) {
				var range = obj.createTextRange();
				range.collapse(true);
				range.moveEnd('character', pos);
				range.moveStart('character', pos);
				range.select();
			}
		},
		showIfameMask: function(setting, showSign) {
			var root = data.getRoot(setting);
			//clear full mask
			while (root.dragMaskList.length > 0) {
				root.dragMaskList[0].remove();
				root.dragMaskList.shift();
			}
			if (showSign) {
				//show mask
				var iframeList = $$("iframe", setting);
				for (var i = 0, l = iframeList.length; i < l; i++) {
					var obj = iframeList.get(i),
					r = tools.getAbs(obj),
					dragMask = $$("<div id='zTreeMask_" + i + "' class='zTreeMask' style='top:" + r[1] + "px; left:" + r[0] + "px; width:" + obj.offsetWidth + "px; height:" + obj.offsetHeight + "px;'></div>", setting);
					dragMask.appendTo($$("body", setting));
					root.dragMaskList.push(dragMask);
				}
			}
		}
	},
	//method of operate ztree dom
	_view = {
		addEditBtn: function(setting, node) {
			if (node.editNameFlag || $$(node, consts.id.EDIT, setting).length > 0) {
				return;
			}
			if (!tools.apply(setting.edit.showRenameBtn, [setting.treeId, node], setting.edit.showRenameBtn)) {
				return;
			}
			var aObj = $$(node, consts.id.A, setting),
			editStr = "<span class='" + consts.className.BUTTON + " edit' id='" + node.tId + consts.id.EDIT + "' title='"+tools.apply(setting.edit.renameTitle, [setting.treeId, node], setting.edit.renameTitle)+"' treeNode"+consts.id.EDIT+" style='display:none;'></span>";
			aObj.append(editStr);

			$$(node, consts.id.EDIT, setting).bind('click',
				function() {
					if (!tools.uCanDo(setting) || tools.apply(setting.callback.beforeEditName, [setting.treeId, node], true) == false) return false;
					view.editNode(setting, node);
					return false;
				}
				).show();
		},
		addRemoveBtn: function(setting, node) {
			if (node.editNameFlag || $$(node, consts.id.REMOVE, setting).length > 0) {
				return;
			}
			if (!tools.apply(setting.edit.showRemoveBtn, [setting.treeId, node], setting.edit.showRemoveBtn)) {
				return;
			}
			var aObj = $$(node, consts.id.A, setting),
			removeStr = "<span class='" + consts.className.BUTTON + " remove' id='" + node.tId + consts.id.REMOVE + "' title='"+tools.apply(setting.edit.removeTitle, [setting.treeId, node], setting.edit.removeTitle)+"' treeNode"+consts.id.REMOVE+" style='display:none;'></span>";
			aObj.append(removeStr);

			$$(node, consts.id.REMOVE, setting).bind('click',
				function() {
					if (!tools.uCanDo(setting) || tools.apply(setting.callback.beforeRemove, [setting.treeId, node], true) == false) return false;
					view.removeNode(setting, node);
					setting.treeObj.trigger(consts.event.REMOVE, [setting.treeId, node]);
					return false;
				}
				).bind('mousedown',
				function(eventMouseDown) {
					return true;
				}
				).show();
		},
		addHoverDom: function(setting, node) {
			if (data.getRoots().showHoverDom) {
				node.isHover = true;
				if (setting.edit.enable) {
					view.addEditBtn(setting, node);
					view.addRemoveBtn(setting, node);
				}
				tools.apply(setting.view.addHoverDom, [setting.treeId, node]);
			}
		},
		cancelCurEditNode: function (setting, forceName, isCancel) {
			var root = data.getRoot(setting),
			nameKey = setting.data.key.name,
			node = root.curEditNode;

			if (node) {
				var inputObj = root.curEditInput,
				newName = forceName ? forceName:(isCancel ? node[nameKey]: inputObj.val());
				if (tools.apply(setting.callback.beforeRename, [setting.treeId, node, newName, isCancel], true) === false) {
					return false;
				}
                node[nameKey] = newName;
                var aObj = $$(node, consts.id.A, setting);
				aObj.removeClass(consts.node.CURSELECTED_EDIT);
				inputObj.unbind();
				view.setNodeName(setting, node);
				node.editNameFlag = false;
				root.curEditNode = null;
				root.curEditInput = null;
				view.selectNode(setting, node, false);
                setting.treeObj.trigger(consts.event.RENAME, [setting.treeId, node, isCancel]);
			}
			root.noSelection = true;
			return true;
		},
		editNode: function(setting, node) {
			var root = data.getRoot(setting);
			view.editNodeBlur = false;
			if (data.isSelectedNode(setting, node) && root.curEditNode == node && node.editNameFlag) {
				setTimeout(function() {tools.inputFocus(root.curEditInput);}, 0);
				return;
			}
			var nameKey = setting.data.key.name;
			node.editNameFlag = true;
			view.removeTreeDom(setting, node);
			view.cancelCurEditNode(setting);
			view.selectNode(setting, node, false);
			$$(node, consts.id.SPAN, setting).html("<input type=text class='rename' id='" + node.tId + consts.id.INPUT + "' treeNode" + consts.id.INPUT + " >");
			var inputObj = $$(node, consts.id.INPUT, setting);
			inputObj.attr("value", node[nameKey]);
			if (setting.edit.editNameSelectAll) {
				tools.inputSelect(inputObj);
			} else {
				tools.inputFocus(inputObj);
			}

			inputObj.bind('blur', function(event) {
				if (!view.editNodeBlur) {
					view.cancelCurEditNode(setting);
				}
			}).bind('keydown', function(event) {
				if (event.keyCode=="13") {
					view.editNodeBlur = true;
					view.cancelCurEditNode(setting);
				} else if (event.keyCode=="27") {
					view.cancelCurEditNode(setting, null, true);
				}
			}).bind('click', function(event) {
				return false;
			}).bind('dblclick', function(event) {
				return false;
			});

			$$(node, consts.id.A, setting).addClass(consts.node.CURSELECTED_EDIT);
			root.curEditInput = inputObj;
			root.noSelection = false;
			root.curEditNode = node;
		},
		moveNode: function(setting, targetNode, node, moveType, animateFlag, isSilent) {
			var root = data.getRoot(setting),
			childKey = setting.data.key.children;
			if (targetNode == node) return;
			if (setting.data.keep.leaf && targetNode && !targetNode.isParent && moveType == consts.move.TYPE_INNER) return;
			var oldParentNode = (node.parentTId ? node.getParentNode(): root),
			targetNodeIsRoot = (targetNode === null || targetNode == root);
			if (targetNodeIsRoot && targetNode === null) targetNode = root;
			if (targetNodeIsRoot) moveType = consts.move.TYPE_INNER;
			var targetParentNode = (targetNode.parentTId ? targetNode.getParentNode() : root);

			if (moveType != consts.move.TYPE_PREV && moveType != consts.move.TYPE_NEXT) {
				moveType = consts.move.TYPE_INNER;
			}

			if (moveType == consts.move.TYPE_INNER) {
				if (targetNodeIsRoot) {
					//parentTId of root node is null
					node.parentTId = null;
				} else {
					if (!targetNode.isParent) {
						targetNode.isParent = true;
						targetNode.open = !!targetNode.open;
						view.setNodeLineIcos(setting, targetNode);
					}
					node.parentTId = targetNode.tId;
				}
			}

			//move node Dom
			var targetObj, target_ulObj;
			if (targetNodeIsRoot) {
				targetObj = setting.treeObj;
				target_ulObj = targetObj;
			} else {
				if (!isSilent && moveType == consts.move.TYPE_INNER) {
					view.expandCollapseNode(setting, targetNode, true, false);
				} else if (!isSilent) {
					view.expandCollapseNode(setting, targetNode.getParentNode(), true, false);
				}
				targetObj = $$(targetNode, setting);
				target_ulObj = $$(targetNode, consts.id.UL, setting);
				if (!!targetObj.get(0) && !target_ulObj.get(0)) {
					var ulstr = [];
					view.makeUlHtml(setting, targetNode, ulstr, '');
					targetObj.append(ulstr.join(''));
				}
				target_ulObj = $$(targetNode, consts.id.UL, setting);
			}
			var nodeDom = $$(node, setting);
			if (!nodeDom.get(0)) {
				nodeDom = view.appendNodes(setting, node.level, [node], null, -1, false, true).join('');
			} else if (!targetObj.get(0)) {
				nodeDom.remove();
			}
			if (target_ulObj.get(0) && moveType == consts.move.TYPE_INNER) {
				target_ulObj.append(nodeDom);
			} else if (targetObj.get(0) && moveType == consts.move.TYPE_PREV) {
				targetObj.before(nodeDom);
			} else if (targetObj.get(0) && moveType == consts.move.TYPE_NEXT) {
				targetObj.after(nodeDom);
			}

			//repair the data after move
			var i,l,
			tmpSrcIndex = -1,
			tmpTargetIndex = 0,
			oldNeighbor = null,
			newNeighbor = null,
			oldLevel = node.level;
			if (node.isFirstNode) {
				tmpSrcIndex = 0;
				if (oldParentNode[childKey].length > 1 ) {
					oldNeighbor = oldParentNode[childKey][1];
					oldNeighbor.isFirstNode = true;
				}
			} else if (node.isLastNode) {
				tmpSrcIndex = oldParentNode[childKey].length -1;
				oldNeighbor = oldParentNode[childKey][tmpSrcIndex - 1];
				oldNeighbor.isLastNode = true;
			} else {
				for (i = 0, l = oldParentNode[childKey].length; i < l; i++) {
					if (oldParentNode[childKey][i].tId == node.tId) {
						tmpSrcIndex = i;
						break;
					}
				}
			}
			if (tmpSrcIndex >= 0) {
				oldParentNode[childKey].splice(tmpSrcIndex, 1);
			}
			if (moveType != consts.move.TYPE_INNER) {
				for (i = 0, l = targetParentNode[childKey].length; i < l; i++) {
					if (targetParentNode[childKey][i].tId == targetNode.tId) tmpTargetIndex = i;
				}
			}
			if (moveType == consts.move.TYPE_INNER) {
				if (!targetNode[childKey]) targetNode[childKey] = new Array();
				if (targetNode[childKey].length > 0) {
					newNeighbor = targetNode[childKey][targetNode[childKey].length - 1];
					newNeighbor.isLastNode = false;
				}
				targetNode[childKey].splice(targetNode[childKey].length, 0, node);
				node.isLastNode = true;
				node.isFirstNode = (targetNode[childKey].length == 1);
			} else if (targetNode.isFirstNode && moveType == consts.move.TYPE_PREV) {
				targetParentNode[childKey].splice(tmpTargetIndex, 0, node);
				newNeighbor = targetNode;
				newNeighbor.isFirstNode = false;
				node.parentTId = targetNode.parentTId;
				node.isFirstNode = true;
				node.isLastNode = false;

			} else if (targetNode.isLastNode && moveType == consts.move.TYPE_NEXT) {
				targetParentNode[childKey].splice(tmpTargetIndex + 1, 0, node);
				newNeighbor = targetNode;
				newNeighbor.isLastNode = false;
				node.parentTId = targetNode.parentTId;
				node.isFirstNode = false;
				node.isLastNode = true;

			} else {
				if (moveType == consts.move.TYPE_PREV) {
					targetParentNode[childKey].splice(tmpTargetIndex, 0, node);
				} else {
					targetParentNode[childKey].splice(tmpTargetIndex + 1, 0, node);
				}
				node.parentTId = targetNode.parentTId;
				node.isFirstNode = false;
				node.isLastNode = false;
			}
			data.fixPIdKeyValue(setting, node);
			data.setSonNodeLevel(setting, node.getParentNode(), node);

			//repair node what been moved
			view.setNodeLineIcos(setting, node);
			view.repairNodeLevelClass(setting, node, oldLevel)

			//repair node's old parentNode dom
			if (!setting.data.keep.parent && oldParentNode[childKey].length < 1) {
				//old parentNode has no child nodes
				oldParentNode.isParent = false;
				oldParentNode.open = false;
				var tmp_ulObj = $$(oldParentNode, consts.id.UL, setting),
				tmp_switchObj = $$(oldParentNode, consts.id.SWITCH, setting),
				tmp_icoObj = $$(oldParentNode, consts.id.ICON, setting);
				view.replaceSwitchClass(oldParentNode, tmp_switchObj, consts.folder.DOCU);
				view.replaceIcoClass(oldParentNode, tmp_icoObj, consts.folder.DOCU);
				tmp_ulObj.css("display", "none");

			} else if (oldNeighbor) {
				//old neigbor node
				view.setNodeLineIcos(setting, oldNeighbor);
			}

			//new neigbor node
			if (newNeighbor) {
				view.setNodeLineIcos(setting, newNeighbor);
			}

			//repair checkbox / radio
			if (!!setting.check && setting.check.enable && view.repairChkClass) {
				view.repairChkClass(setting, oldParentNode);
				view.repairParentChkClassWithSelf(setting, oldParentNode);
				if (oldParentNode != node.parent)
					view.repairParentChkClassWithSelf(setting, node);
			}

			//expand parents after move
			if (!isSilent) {
				view.expandCollapseParentNode(setting, node.getParentNode(), true, animateFlag);
			}
		},
		removeEditBtn: function(setting, node) {
			$$(node, consts.id.EDIT, setting).unbind().remove();
		},
		removeRemoveBtn: function(setting, node) {
			$$(node, consts.id.REMOVE, setting).unbind().remove();
		},
		removeTreeDom: function(setting, node) {
			node.isHover = false;
			view.removeEditBtn(setting, node);
			view.removeRemoveBtn(setting, node);
			tools.apply(setting.view.removeHoverDom, [setting.treeId, node]);
		},
		repairNodeLevelClass: function(setting, node, oldLevel) {
			if (oldLevel === node.level) return;
			var liObj = $$(node, setting),
			aObj = $$(node, consts.id.A, setting),
			ulObj = $$(node, consts.id.UL, setting),
			oldClass = consts.className.LEVEL + oldLevel,
			newClass = consts.className.LEVEL + node.level;
			liObj.removeClass(oldClass);
			liObj.addClass(newClass);
			aObj.removeClass(oldClass);
			aObj.addClass(newClass);
			ulObj.removeClass(oldClass);
			ulObj.addClass(newClass);
		},
		selectNodes : function(setting, nodes) {
			for (var i=0, l=nodes.length; i<l; i++) {
				view.selectNode(setting, nodes[i], i>0);
			}
		}
	},

	_z = {
		tools: _tools,
		view: _view,
		event: _event,
		data: _data
	};
	$.extend(true, $.fn.zTree.consts, _consts);
	$.extend(true, $.fn.zTree._z, _z);

	var zt = $.fn.zTree,
	tools = zt._z.tools,
	consts = zt.consts,
	view = zt._z.view,
	data = zt._z.data,
	event = zt._z.event,
	$$ = tools.$;

	data.exSetting(_setting);
	data.addInitBind(_bindEvent);
	data.addInitUnBind(_unbindEvent);
	data.addInitCache(_initCache);
	data.addInitNode(_initNode);
	data.addInitProxy(_eventProxy);
	data.addInitRoot(_initRoot);
	data.addZTreeTools(_zTreeTools);

	var _cancelPreSelectedNode = view.cancelPreSelectedNode;
	view.cancelPreSelectedNode = function (setting, node) {
		var list = data.getRoot(setting).curSelectedList;
		for (var i=0, j=list.length; i<j; i++) {
			if (!node || node === list[i]) {
				view.removeTreeDom(setting, list[i]);
				if (node) break;
			}
		}
		if (_cancelPreSelectedNode) _cancelPreSelectedNode.apply(view, arguments);
	}

	var _createNodes = view.createNodes;
	view.createNodes = function(setting, level, nodes, parentNode, index) {
		if (_createNodes) {
			_createNodes.apply(view, arguments);
		}
		if (!nodes) return;
		if (view.repairParentChkClassWithSelf) {
			view.repairParentChkClassWithSelf(setting, parentNode);
		}
	}

	var _makeNodeUrl = view.makeNodeUrl;
	view.makeNodeUrl = function(setting, node) {
		return setting.edit.enable ? null : (_makeNodeUrl.apply(view, arguments));
	}

	var _removeNode = view.removeNode;
	view.removeNode = function(setting, node) {
		var root = data.getRoot(setting);
		if (root.curEditNode === node) root.curEditNode = null;
		if (_removeNode) {
			_removeNode.apply(view, arguments);
		}
	}

	var _selectNode = view.selectNode;
	view.selectNode = function(setting, node, addFlag) {
		var root = data.getRoot(setting);
		if (data.isSelectedNode(setting, node) && root.curEditNode == node && node.editNameFlag) {
			return false;
		}
		if (_selectNode) _selectNode.apply(view, arguments);
		view.addHoverDom(setting, node);
		return true;
	}

	var _uCanDo = tools.uCanDo;
	tools.uCanDo = function(setting, e) {
		var root = data.getRoot(setting);
		if (e && (tools.eqs(e.type, "mouseover") || tools.eqs(e.type, "mouseout") || tools.eqs(e.type, "mousedown") || tools.eqs(e.type, "mouseup"))) {
			return true;
		}
		if (root.curEditNode) {
			view.editNodeBlur = false;
			root.curEditInput.focus();
		}
		return (!root.curEditNode) && (_uCanDo ? _uCanDo.apply(view, arguments) : true);
	}
})(jQuery);
'use strict';

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };

(function () {
    'use strict';

    // root of function

    var root = this,
        win = this,
        doc = win ? win.document : null,
        docElem = win ? win.document.documentElement : null,
        reIsJson = /^(["'](\\.|[^"\\\n\r])*?["']|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/,
        reMs = /^-ms-/,
        reSnakeCase = /[\-_]([\da-z])/gi,
        reCamelCase = /([A-Z])/g,
        reDot = /\./,
        reInt = /[-|+]?[\D]/gi,
        reNotNum = /\D/gi,
        reMoneySplit = new RegExp('([0-9])([0-9][0-9][0-9][,.])'),
        reAmp = /&/g,
        reEq = /=/,
        reClassNameSplit = /[ ]+/g,


    /** @namespace {Object} ax5 */
    ax5 = {},
        info = void 0,
        U = void 0,
        dom = void 0;

    /**
     * guid
     * @member {Number} ax5.guid
     */
    ax5.guid = 1;
    /**
     * ax5.guid를 구하고 증가시킵니다.
     * @method ax5.getGuid
     * @returns {Number} guid
     */
    ax5.getGuid = function () {
        return ax5.guid++;
    };

    /**
     * 상수모음
     * @namespace ax5.info
     */
    ax5.info = info = function () {
        var _arguments = arguments;

        /**
         * ax5 version
         * @member {String} ax5.info.version
         */
        var version = "1.4.117";

        /**
         * ax5 library path
         * @member {String} ax5.info.baseUrl
         */
        var baseUrl = "";

        /**
         * ax5 에러 출력메세지 사용자 재 정의
         * @member {Object} ax5.info.onerror
         * @examples
         * ```
         * ax5.info.onerror = function(){
        *  console.log(arguments);
        * }
         * ```
         */
        var onerror = function onerror() {
            console.error(U.toArray(_arguments).join(":"));
        };

        /**
         * event keyCodes
         * @member {Object} ax5.info.eventKeys
         * @example
         * ```
         * {
        * 	BACKSPACE: 8, TAB: 9,
        * 	RETURN: 13, ESC: 27, LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40, DELETE: 46,
        * 	HOME: 36, END: 35, PAGEUP: 33, PAGEDOWN: 34, INSERT: 45, SPACE: 32
        * }
         * ```
         */
        var eventKeys = {
            BACKSPACE: 8, TAB: 9,
            RETURN: 13, ESC: 27, LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40, DELETE: 46,
            HOME: 36, END: 35, PAGEUP: 33, PAGEDOWN: 34, INSERT: 45, SPACE: 32
        };

        /**
         * week names
         * @member {Object[]} weekNames
         * @member {string} weekNames[].label
         *
         * @example
         * ```
         * [
         *  {label: "SUN"},{label: "MON"},{label: "TUE"},{label: "WED"},{label: "THU"},{label: "FRI"},{label: "SAT"}
         * ]
         * console.log( weekNames[0] );
         * console.log( ax5.info.weekNames[(new Date()).getDay()].label )
         * ```
         */
        var weekNames = [{ label: "SUN" }, { label: "MON" }, { label: "TUE" }, { label: "WED" }, { label: "THU" }, { label: "FRI" }, { label: "SAT" }];

        /**
         * 사용자 브라우저 식별용 오브젝트
         * @member {Object} ax5.info.browser
         * @example
         * ```
         * console.log( ax5.info.browser );
         * //Object {name: "chrome", version: "39.0.2171.71", mobile: false}
         * ```
         */
        var browser = function (ua, mobile, browserName, match, browser, browserVersion) {
            if (!win || !win.navigator) return {};

            ua = navigator.userAgent.toLowerCase(), mobile = ua.search(/mobile/g) != -1, browserName, match, browser, browserVersion;

            if (ua.search(/iphone/g) != -1) {
                return { name: "iphone", version: 0, mobile: true };
            } else if (ua.search(/ipad/g) != -1) {
                return { name: "ipad", version: 0, mobile: true };
            } else if (ua.search(/android/g) != -1) {
                match = /(android)[ \/]([\w.]+)/.exec(ua) || [];
                browserVersion = match[2] || "0";
                return { name: "android", version: browserVersion, mobile: mobile };
            } else {
                browserName = "";
                match = /(opr)[ \/]([\w.]+)/.exec(ua) || /(chrome)[ \/]([\w.]+)/.exec(ua) || /(webkit)[ \/]([\w.]+)/.exec(ua) || /(msie) ([\w.]+)/.exec(ua) || ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || [];
                browser = match[1] || "";
                browserVersion = match[2] || "0";

                if (browser == "msie") browser = "ie";
                return {
                    name: browser,
                    version: browserVersion,
                    mobile: mobile
                };
            }
            ua = null, mobile = null, browserName = null, match = null, browser = null, browserVersion = null;
        }();

        /**
         * 브라우저 여부
         * @member {Boolean} ax5.info.isBrowser
         */
        var isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && win.document);

        /**
         * 브라우저에 따른 마우스 휠 이벤트이름
         * @member {Object} ax5.info.wheelEnm
         */
        var wheelEnm = win && /Firefox/i.test(navigator.userAgent) ? "DOMMouseScroll" : "mousewheel";

        /**
         * 첫번째 자리수 동사 - (필요한것이 없을때 : 4, 실행오류 : 5)
         * 두번째 자리수 목적어 - 문자열 0, 숫자 1, 배열 2, 오브젝트 3, 함수 4, DOM 5, 파일 6, 기타 7
         * 세번째 자리수 옵션
         * @member {Object} ax5.info.errorMsg
         */
        var errorMsg = {};

        /**
         * 현재 페이지의 Url 정보를 리턴합니다.
         * @method ax5.info.urlUtil
         * @returns {Object}
         * @example
         * ```
         * console.log( ax5.util.toJson( ax5.info.urlUtil() ) );
         * {
        *	"baseUrl": "http://ax5:2018",
        *	"href": "http://ax5:2018/samples/index.html?a=1&b=1#abc",
        *	"param": "a=1&b=1",
        *	"referrer": "",
        *	"pathname": "/samples/index.html",
        *	"hostname": "ax5",
        *	"port": "2018",
        *	"url": "http://ax5:2018/samples/index.html",
        *	"hashdata": "abc"
        * }
         * ```
         */
        function urlUtil(url, urls) {
            url = {
                href: win.location.href,
                param: win.location.search,
                referrer: doc.referrer,
                pathname: win.location.pathname,
                hostname: win.location.hostname,
                port: win.location.port
            }, urls = url.href.split(/[\?#]/);
            url.param = url.param.replace("?", "");
            url.url = urls[0];
            if (url.href.search("#") > -1) {
                url.hashdata = U.last(urls);
            }
            urls = null;
            url.baseUrl = U.left(url.href, "?").replace(url.pathname, "");
            return url;
        }

        /**
         * ax5-error-msg.js 에 정의된 ax5 error를 반환합니다.
         * @method ax5.info.getError
         * @returns {Object}
         * @example
         * ```
         * console.log( ax5.info.getError("single-uploader", "460", "upload") );
         *
         * if(!this.selectedFile){
        *      if (cfg.onEvent) {
        *      	var that = {
        *      		action: "error",
        *      		error: ax5.info.getError("single-uploader", "460", "upload")
        *      	};
        *      	cfg.onEvent.call(that, that);
        *      }
        *      return this;
        * }
         * ```
         */
        function getError(className, errorCode, methodName) {
            if (info.errorMsg && info.errorMsg[className]) {
                return {
                    className: className,
                    errorCode: errorCode,
                    methodName: methodName,
                    msg: info.errorMsg[className][errorCode]
                };
            } else {
                return { className: className, errorCode: errorCode, methodName: methodName };
            }
        }

        /**
         * 브라우져의 터치 기능 유무를 확인합니다.
         * @method ax5.info.supportTouch
         * @returns {boolean}
         * @example
         * ```
         * var chkFlag = ax5.info.supportTouch;
         */
        var supportTouch = win ? 'ontouchstart' in win || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0 : false;

        var supportFileApi = win ? win.FileReader && win.File && win.FileList && win.Blob : false;

        return {
            errorMsg: errorMsg,
            version: version,
            baseUrl: baseUrl,
            onerror: onerror,
            eventKeys: eventKeys,
            weekNames: weekNames,
            browser: browser,
            isBrowser: isBrowser,
            supportTouch: supportTouch,
            supportFileApi: supportFileApi,
            wheelEnm: wheelEnm,
            urlUtil: urlUtil,
            getError: getError
        };
    }();

    /**
     * Refer to this by {@link ax5}.
     * @namespace ax5.util
     */
    ax5['util'] = U = function () {
        var _toString = Object.prototype.toString;

        /**
         * Object나 Array의 아이템으로 사용자 함수를 호출합니다.
         * @method ax5.util.each
         * @param {Object|Array} O
         * @param {Function} _fn
         * @example
         * ```js
         * var axf = ax5.util;
         * axf.each([0,1,2], function(){
        * 	// with this
        * });
         * axf.each({a:1, b:2}, function(){
        * 	// with this
        * });
         * ```
         */
        function each(O, _fn) {
            if (isNothing(O)) return [];
            var key = void 0,
                i = 0,
                l = O.length,
                isObj = l === undefined || typeof O === "function";
            if (isObj) {
                for (key in O) {
                    if (typeof O[key] != "undefined") if (_fn.call(O[key], key, O[key]) === false) break;
                }
            } else {
                for (; i < l;) {
                    if (typeof O[i] != "undefined") if (_fn.call(O[i], i, O[i++]) === false) break;
                }
            }
            return O;
        }

        // In addition to using the http://underscorejs.org : map, reduce, reduceRight, find
        /**
         * 원본 아이템들을 이용하여 사용자 함수의 리턴값으로 이루어진 새로운 배열을 만듭니다.
         * @method ax5.util.map
         * @param {Object|Array} O
         * @param {Function} _fn
         * @returns {Array}
         * @example
         * ```js
         * var myArray = [0,1,2,3,4];
         * var myObject = {a:1, b:"2", c:{axj:"what", arrs:[0,2,"3"]},
        *    fn: function(abcdd){
        *        return abcdd;
        *    }
        * };
         *
         * var _arr = ax5.util.map( myArray,  function(index, I){
        *    return index+1;
        * });
         * console.log(_arr);
         * // [1, 2, 3, 4, 5]
         *
         * var _arr = ax5.util.map( myObject,  function(k, v){
        *    return v * 2;
        * });
         * console.log(_arr);
         * // [2, 4, NaN, NaN]
         * ```
         */
        function map(O, _fn) {
            if (isNothing(O)) return [];
            var key = void 0,
                i = 0,
                l = O.length,
                results = [],
                fnResult = void 0;
            if (isObject(O)) {
                for (key in O) {
                    if (typeof O[key] != "undefined") {
                        fnResult = undefined;
                        if ((fnResult = _fn.call(O[key], key, O[key])) === false) break;else results.push(fnResult);
                    }
                }
            } else {
                for (; i < l;) {
                    if (typeof O[i] != "undefined") {
                        fnResult = undefined;
                        if ((fnResult = _fn.call(O[i], i, O[i++])) === false) break;else results.push(fnResult);
                    }
                }
            }
            return results;
        }

        /**
         * 원본 아이템들을 이용하여 사용자 함수의 리턴값이 참인 아이템의 위치나 키값을 반환합니다.
         * @method ax5.util.search
         * @param {Object|Array} O
         * @param {Function|String|Number} _fn - 함수 또는 값
         * @returns {Number|String}
         * @example
         * ```js
         * var myArray = [0,1,2,3,4,5,6];
         * var myObject = {a:"123","b":"123",c:123};
         *
         * ax5.util.search(myArray,  function(){
        *    return this > 3;
        * });
         * // 4
         * ax5.util.search(myObject,  function(k, v){
        *    return v === 123;
        * });
         * // "c"
         * ax5.util.search([1,2,3,4], 3);
         * // 2
         * ax5.util.search([1,2], 4);
         * // -1
         * ax5.util.search(["name","value"], "value");
         * // 1
         * ax5.util.search(["name","value"], "values");
         * // -1
         * ax5.util.search({k1:"name",k2:"value"}, "value2");
         * // -1
         * ax5.util.search({k1:"name",k2:"value"}, "value");
         * // "k2"
         * ```
         */
        function search(O, _fn) {
            if (isNothing(O)) return -1;
            if (isObject(O)) {
                for (var key in O) {
                    if (typeof O[key] != "undefined" && isFunction(_fn) && _fn.call(O[key], key, O[key])) {
                        return key;
                        break;
                    } else if (O[key] == _fn) {
                        return key;
                        break;
                    }
                }
            } else {
                for (var i = 0, l = O.length; i < l; i++) {
                    if (typeof O[i] != "undefined" && isFunction(_fn) && _fn.call(O[i], i, O[i])) {
                        return i;
                        break;
                    } else if (O[i] == _fn) {
                        return i;
                        break;
                    }
                }
            }
            return -1;
        }

        /**
         * @method ax5.util.sum
         * @param {Array|Object} O
         * @param {Number} [defaultValue]
         * @param {Function} _fn
         * @returns {Number}
         * @example
         * ```js
         * var arr = [
         *  {name: "122", value: 9},
         *  {name: "122", value: 10},
         *  {name: "123", value: 11}
         * ];
         *
         * var rs = ax5.util.sum(arr, function () {
         *  if(this.name == "122") {
         *      return this.value;
         *  }
         * });
         * console.log(rs); // 19
         *
         * console.log(ax5.util.sum(arr, 10, function () {
         *   return this.value;
         * }));
         * // 40
         * ```
         */
        function sum(O, defaultValue, _fn) {
            var i = void 0,
                l = void 0,
                tokenValue = void 0;
            if (isFunction(defaultValue) && typeof _fn === "undefined") {
                _fn = defaultValue;
                defaultValue = 0;
            }
            if (typeof defaultValue === "undefined") defaultValue = 0;

            if (isArray(O)) {
                i = 0;
                l = O.length;
                for (; i < l; i++) {
                    if (typeof O[i] !== "undefined") {
                        if ((tokenValue = _fn.call(O[i], O[i])) === false) break;else if (typeof tokenValue !== "undefined") defaultValue += tokenValue;
                    }
                }
                return defaultValue;
            } else if (isObject(O)) {
                for (i in O) {
                    if (typeof O[i] != "undefined") {
                        if ((tokenValue = _fn.call(O[i], O[i])) === false) break;else if (typeof tokenValue !== "undefined") defaultValue += tokenValue;
                    }
                }
                return defaultValue;
            } else {
                console.error("argument error : ax5.util.sum - use Array or Object");
                return defaultValue;
            }
        }

        /**
         * @method ax5.util.avg
         * @param {Array|Object} O
         * @param {Number} [defaultValue]
         * @param {Function} _fn
         * @returns {Number}
         * @example
         * ```js
         * var arr = [
         *  {name: "122", value: 9},
         *  {name: "122", value: 10},
         *  {name: "123", value: 11}
         * ];
         *
         * var rs = ax5.util.avg(arr, function () {
         *      return this.value;
         * });
         *
         * console.log(rs); // 10
         * ```
         */
        function avg(O, defaultValue, _fn) {
            var i = void 0,
                l = void 0,
                tokenValue = void 0;
            if (isFunction(defaultValue) && typeof _fn === "undefined") {
                _fn = defaultValue;
                defaultValue = 0;
            }
            if (typeof defaultValue === "undefined") defaultValue = 0;

            if (isArray(O)) {
                i = 0;
                l = O.length;
                for (; i < l; i++) {
                    if (typeof O[i] !== "undefined") {
                        if ((tokenValue = _fn.call(O[i], O[i])) === false) break;else if (typeof tokenValue !== "undefined") defaultValue += tokenValue;
                    }
                }
                return defaultValue / l;
            } else if (isObject(O)) {
                l = 0;
                for (i in O) {
                    if (typeof O[i] != "undefined") {
                        if ((tokenValue = _fn.call(O[i], O[i])) === false) break;else if (typeof tokenValue !== "undefined") defaultValue += tokenValue;++l;
                    }
                }
                return defaultValue / l;
            } else {
                console.error("argument error : ax5.util.sum - use Array or Object");
                return defaultValue;
            }
        }

        /**
         * 배열의 왼쪽에서 오른쪽으로 연산을 진행하는데 수행한 결과가 왼쪽 값으로 반영되어 최종 왼쪽 값을 반환합니다.
         * @method ax5.util.reduce
         * @param {Array|Object} O
         * @param {Function} _fn
         * @returns {Alltypes}
         * @example
         * ```js
         * var aarray = [5,4,3,2,1];
         * result = ax5.util.reduce( aarray, function(p, n){
        *   return p * n;
        * });
         * console.log(result, aarray);
         * // 120 [5, 4, 3, 2, 1]
         *
         * ax5.util.reduce({a:1, b:2}, function(p, n){
        *    return parseInt(p|0) + parseInt(n);
        * });
         * // 3
         * ```
         */
        function reduce(O, _fn) {
            var i, l, tokenItem;
            if (isArray(O)) {
                i = 0, l = O.length, tokenItem = O[i];
                for (; i < l - 1;) {
                    if (typeof O[i] != "undefined") {
                        if ((tokenItem = _fn.call(root, tokenItem, O[++i])) === false) break;
                    }
                }
                return tokenItem;
            } else if (isObject(O)) {
                for (i in O) {
                    if (typeof O[i] != "undefined") {
                        if ((tokenItem = _fn.call(root, tokenItem, O[i])) === false) break;
                    }
                }
                return tokenItem;
            } else {
                console.error("argument error : ax5.util.reduce - use Array or Object");
                return null;
            }
        }

        /**
         * 배열의 오른쪽에서 왼쪽으로 연산을 진행하는데 수행한 결과가 오른쪽 값으로 반영되어 최종 오른쪽 값을 반환합니다.
         * @method ax5.util.reduceRight
         * @param {Array} O
         * @param {Function} _fn
         * @returns {Alltypes}
         * @example
         * ```js
         * var aarray = [5,4,3,2,1];
         * result = ax5.util.reduceRight( aarray, function(p, n){
        *    console.log( n );
        *    return p * n;
        * });
         * console.log(result, aarray);
         * 120 [5, 4, 3, 2, 1]
         * ```
         */
        function reduceRight(O, _fn) {
            var i = O.length - 1,
                tokenItem = O[i];
            for (; i > 0;) {
                if (typeof O[i] != "undefined") {
                    if ((tokenItem = _fn.call(root, tokenItem, O[--i])) === false) break;
                }
            }
            return tokenItem;
        }

        /**
         * 배열또는 오브젝트의 각 아이템을 인자로 하는 사용자 함수의 결과가 참인 아이템들의 배열을 반환합니다.
         * @method ax5.util.filter
         * @param {Object|Array} O
         * @param {Function} _fn
         * @returns {Array}
         * @example
         * ```js
         * var aarray = [5,4,3,2,1];
         * result = ax5.util.filter( aarray, function(){
        *    return this % 2;
        * });
         * console.log(result);
         * // [5, 3, 1]
         *
         * var filObject = {a:1, s:"string", oa:{pickup:true, name:"AXISJ"}, os:{pickup:true, name:"AX5"}};
         * result = ax5.util.filter( filObject, function(){
        * 	return this.pickup;
        * });
         * console.log( ax5.util.toJson(result) );
         * // [{"pickup": , "name": "AXISJ"}, {"pickup": , "name": "AX5"}]
         * ```
         */
        function filter(O, _fn) {
            if (isNothing(O)) return [];
            var k,
                i = 0,
                l = O.length,
                results = [],
                fnResult;
            if (isObject(O)) {
                for (k in O) {
                    if (typeof O[k] != "undefined") {
                        if (fnResult = _fn.call(O[k], k, O[k])) results.push(O[k]);
                    }
                }
            } else {
                for (; i < l;) {
                    if (typeof O[i] != "undefined") {
                        if (fnResult = _fn.call(O[i], i, O[i])) results.push(O[i]);
                        i++;
                    }
                }
            }
            return results;
        }

        /**
         * Object를 JSONString 으로 반환합니다.
         * @method ax5.util.toJson
         * @param {Object|Array} O
         * @returns {String} JSON
         * @example
         * ```js
         * var ax = ax5.util;
         * var myObject = {
        *    a:1, b:"2", c:{axj:"what", arrs:[0,2,"3"]},
        *    fn: function(abcdd){
        *        return abcdd;
        *    }
        * };
         * console.log( ax.toJson(myObject) );
         * ```
         */
        function toJson(O) {
            var jsonString = "";
            if (ax5.util.isArray(O)) {
                var i = 0,
                    l = O.length;
                jsonString += "[";
                for (; i < l; i++) {
                    if (i > 0) jsonString += ",";
                    jsonString += toJson(O[i]);
                }
                jsonString += "]";
            } else if (ax5.util.isObject(O)) {
                jsonString += "{";
                var jsonObjectBody = [];
                each(O, function (key, value) {
                    jsonObjectBody.push('"' + key + '": ' + toJson(value));
                });
                jsonString += jsonObjectBody.join(", ");
                jsonString += "}";
            } else if (ax5.util.isString(O)) {
                jsonString = '"' + O + '"';
            } else if (ax5.util.isNumber(O)) {
                jsonString = O;
            } else if (ax5.util.isUndefined(O)) {
                jsonString = "undefined";
            } else if (ax5.util.isFunction(O)) {
                jsonString = '"{Function}"';
            } else {
                jsonString = O;
            }
            return jsonString;
        }

        /**
         * 관용의 JSON Parser
         * @method ax5.util.parseJson
         * @param {String} JSONString
         * @param {Boolean} [force] - 강제 적용 여부 (json 문자열 검사를 무시하고 오브젝트 변환을 시도합니다.)
         * @returns {Object}
         * @example
         * ```
         * console.log(ax5.util.parseJson('{"a":1}'));
         * // Object {a: 1}
         * console.log(ax5.util.parseJson("{'a':1, 'b':'b'}"));
         * // Object {a: 1, b: "b"}
         * console.log(ax5.util.parseJson("{'a':1, 'b':function(){return 1;}}", true));
         * // Object {a: 1, b: function}
         * console.log(ax5.util.parseJson("{a:1}"));
         * // Object {a: 1}
         * console.log(ax5.util.parseJson("[1,2,3]"));
         * // [1, 2, 3]
         * console.log(ax5.util.parseJson("['1','2','3']"));
         * // ["1", "2", "3"]
         * console.log(ax5.util.parseJson("[{'a':'99'},'2','3']"));
         * // [Object, "2", "3"]
         * ```
         */
        function parseJson(str, force) {
            if (force || reIsJson.test(str)) {
                try {
                    return new Function('', 'return ' + str)();
                } catch (e) {
                    return { error: 500, msg: 'syntax error' };
                }
            } else {
                return { error: 500, msg: 'syntax error' };
            }
        }

        /**
         * 인자의 타입을 반환합니다.
         * @method ax5.util.getType
         * @param {Object|Array|String|Number|Element|Etc} O
         * @returns {String} window|element|object|array|function|string|number|undefined|nodelist
         * @example
         * ```js
         * var axf = ax5.util;
         * var a = 11;
         * var b = "11";
         * console.log( axf.getType(a) );
         * console.log( axf.getType(b) );
         * ```
         */
        function getType(O) {
            var typeName;
            if (O != null && O == O.window) {
                typeName = "window";
            } else if (!!(O && O.nodeType == 1)) {
                typeName = "element";
            } else if (!!(O && O.nodeType == 11)) {
                typeName = "fragment";
            } else if (O === null) {
                typeName = "null";
            } else if (typeof O === "undefined") {
                typeName = "undefined";
            } else if (_toString.call(O) == "[object Object]") {
                typeName = "object";
            } else if (_toString.call(O) == "[object Array]") {
                typeName = "array";
            } else if (_toString.call(O) == "[object String]") {
                typeName = "string";
            } else if (_toString.call(O) == "[object Number]") {
                typeName = "number";
            } else if (_toString.call(O) == "[object NodeList]") {
                typeName = "nodelist";
            } else if (typeof O === "function") {
                typeName = "function";
            }
            return typeName;
        }

        /**
         * 오브젝트가 window 인지 판단합니다.
         * @method ax5.util.isWindow
         * @param {Object} O
         * @returns {Boolean}
         */
        function isWindow(O) {
            return O != null && O == O.window;
        }

        /**
         * 오브젝트가 HTML 엘리먼트여부인지 판단합니다.
         * @method ax5.util.isElement
         * @param {Object} O
         * @returns {Boolean}
         */
        function isElement(O) {
            return !!(O && (O.nodeType == 1 || O.nodeType == 11));
        }

        /**
         * 오브젝트가 Object인지 판단합니다.
         * @method ax5.util.isObject
         * @param {Object} O
         * @returns {Boolean}
         */
        function isObject(O) {
            return _toString.call(O) == "[object Object]";
        }

        /**
         * 오브젝트가 Array인지 판단합니다.
         * @method ax5.util.isArray
         * @param {Object} O
         * @returns {Boolean}
         */
        function isArray(O) {
            return _toString.call(O) == "[object Array]";
        }

        /**
         * 오브젝트가 Function인지 판단합니다.
         * @method ax5.util.isFunction
         * @param {Object} O
         * @returns {Boolean}
         */
        function isFunction(O) {
            return typeof O === "function";
        }

        /**
         * 오브젝트가 String인지 판단합니다.
         * @method ax5.util.isString
         * @param {Object} O
         * @returns {Boolean}
         */
        function isString(O) {
            return _toString.call(O) == "[object String]";
        }

        /**
         * 오브젝트가 Number인지 판단합니다.
         * @method ax5.util.isNumber
         * @param {Object} O
         * @returns {Boolean}
         */
        function isNumber(O) {
            return _toString.call(O) == "[object Number]";
        }

        /**
         * 오브젝트가 NodeList인지 판단합니다.
         * @method ax5.util.isNodelist
         * @param {Object} O
         * @returns {Boolean}
         */
        function isNodelist(O) {
            return !!(_toString.call(O) == "[object NodeList]" || typeof O !== "undefined" && O && O[0] && O[0].nodeType == 1);
        }

        /**
         * 오브젝트가 undefined인지 판단합니다.
         * @method ax5.util.isUndefined
         * @param {Object} O
         * @returns {Boolean}
         */
        function isUndefined(O) {
            return typeof O === "undefined";
        }

        /**
         * 오브젝트가 undefined이거나 null이거나 빈값인지 판단합니다.
         * @method ax5.util.isNothing
         * @param {Object} O
         * @returns {Boolean}
         */
        function isNothing(O) {
            return typeof O === "undefined" || O === null || O === "";
        }

        /**
         * 오브젝트가 날자값인지 판단합니다.
         * @method ax5.util.isDate
         * @param {Date} O
         * @returns {Boolean}
         * @example
         * ```js
         * ax5.util.isDate('2016-09-30');
         * // false
         * ax5.util.isDate( new Date('2016-09-30') );
         * // true
         * ```
         */
        function isDate(O) {
            return O instanceof Date && !isNaN(O.valueOf());
        }

        function isDateFormat(O) {
            var result = false;
            if (!O) {} else if (O instanceof Date && !isNaN(O.valueOf())) {
                result = true;
            } else {
                if (O.length > 7) {
                    if (date(O) instanceof Date) {
                        return true;
                    }
                }
                O = O.replace(/\D/g, '');
                if (O.length > 7) {
                    var mm = O.substr(4, 2),
                        dd = O.substr(6, 2);
                    O = date(O);
                    if (O.getMonth() == mm - 1 && O.getDate() == dd) {
                        result = true;
                    }
                }
            }
            return result;
        }

        /**
         * 오브젝트의 첫번째 아이템을 반환합니다.
         * @method ax5.util.first
         * @param {Object|Array} O
         * @returns {Object}
         * @example
         * ```js
         * ax5.util.first({a:1, b:2});
         * // Object {a: 1}
         * ax5.util.first([1,2,3,4]);
         * // 1
         * ```
         */
        function first(O) {
            if (isObject(O)) {
                var keys = Object.keys(O);
                var item = {};
                item[keys[0]] = O[keys[0]];
                return item;
            } else if (isArray(O)) {
                return O[0];
            } else {
                console.error("ax5.util.object.first", "argument type error");
                return undefined;
            }
        }

        /**
         * 오브젝트의 마지막 아이템을 반환합니다.
         * @method ax5.util.last
         * @param {Object|Array} O
         * @returns {Object}
         * @example
         * ```js
         * ax5.util.last({a:1, b:2});
         * // Object {b: 2}
         * ax5.util.last([1,2,3,4]);
         * // 4
         * ```
         */
        function last(O) {
            if (isObject(O)) {
                var keys = Object.keys(O);
                var item = {};
                item[keys[keys.length - 1]] = O[keys[keys.length - 1]];
                return item;
            } else if (isArray(O)) {
                return O[O.length - 1];
            } else {
                console.error("ax5.util.object.last", "argument type error");
                return undefined;
            }
        }

        /**
         * 쿠키를 설정합니다.
         * @method ax5.util.setCookie
         * @param {String} cname - 쿠키이름
         * @param {String} cvalue - 쿠키값
         * @param {Number} [exdays] - 쿠키 유지일수
         * @param {Object} [opts] - path, domain 설정 옵션
         * @example
         * ```js
         * ax5.util.setCookie("jslib", "AX5");
         * ax5.util.setCookie("jslib", "AX5", 3);
         * ax5.util.setCookie("jslib", "AX5", 3, {path:"/", domain:".axisj.com"});
         * ```
         */
        function setCookie(cn, cv, exdays, opts) {
            var expire;
            if (typeof exdays === "number") {
                expire = new Date();
                expire.setDate(expire.getDate() + exdays);
            }
            opts = opts || {};
            return doc.cookie = [escape(cn), '=', escape(cv), expire ? "; expires=" + expire.toUTCString() : "", // use expires attribute, max-age is not supported by IE
            opts.path ? "; path=" + opts.path : "", opts.domain ? "; domain=" + opts.domain : "", opts.secure ? "; secure" : ""].join("");
        }

        /**
         * 쿠키를 가져옵니다.
         * @method ax5.util.getCookie
         * @param {String} cname
         * @returns {String} cookie value
         * @example
         * ```js
         * ax5.util.getCookie("jslib");
         * ```
         */
        function getCookie(cname) {
            var name = cname + "=";
            var ca = doc.cookie.split(';'),
                i = 0,
                l = ca.length;
            for (; i < l; i++) {
                var c = ca[i];
                while (c.charAt(0) == ' ') {
                    c = c.substring(1);
                }if (c.indexOf(name) != -1) return unescape(c.substring(name.length, c.length));
            }
            return "";
        }

        /**
         * jsonString 으로 alert 합니다.
         * @method ax5.util.alert
         * @param {Object|Array|String|Number} O
         * @returns {Object|Array|String|Number} O
         * @example ```js
         * ax5.util.alert({a:1,b:2});
         * ax5.util.alert("정말?");
         * ```
         */
        function alert(O) {
            win.alert(toJson(O));
            return O;
        }

        /**
         * 문자열의 특정 문자열까지 잘라주거나 원하는 포지션까지 잘라줍니다.
         * @method ax5.util.left
         * @param {String} str - 문자열
         * @param {String|Number} pos - 찾을 문자열 또는 포지션
         * @returns {String}
         * @example
         * ```js
         * ax5.util.left("abcd.efd", 3);
         * // abc
         * ax5.util.left("abcd.efd", ".");
         * // abcd
         * ```
         */
        function left(str, pos) {
            if (typeof str === "undefined" || typeof pos === "undefined") return "";
            if (isString(pos)) {
                return str.indexOf(pos) > -1 ? str.substr(0, str.indexOf(pos)) : "";
            } else if (isNumber(pos)) {
                return str.substr(0, pos);
            } else {
                return "";
            }
        }

        /**
         * 문자열의 특정 문자열까지 잘라주거나 원하는 포지션까지 잘라줍니다.
         * @method ax5.util.right
         * @param {String} str - 문자열
         * @param {String|Number} pos - 찾을 문자열 또는 포지션
         * @returns {String}
         * @example
         * ```js
         * ax5.util.right("abcd.efd", 3);
         * // efd
         * ax5.util.right("abcd.efd", ".");
         * // efd
         * ```
         */
        function right(str, pos) {
            if (typeof str === "undefined" || typeof pos === "undefined") return "";
            str = '' + str;
            if (isString(pos)) {
                return str.lastIndexOf(pos) > -1 ? str.substr(str.lastIndexOf(pos) + 1) : "";
            } else if (isNumber(pos)) {
                return str.substr(str.length - pos);
            } else {
                return "";
            }
        }

        /**
         * css형 문자열이나 특수문자가 포함된 문자열을 카멜케이스로 바꾸어 반환합니다.
         * @method ax5.util.camelCase
         * @param {String} str
         * @returns {String}
         * @example
         * ```js
         * ax5.util.camelCase("inner-width");
         * ax5.util.camelCase("innerWidth");
         * // innerWidth
         * ```
         */
        function camelCase(str) {
            return str.replace(reMs, "ms-").replace(reSnakeCase, function (all, letter) {
                return letter.toUpperCase();
            });
        }

        /**
         * css형 문자열이나 카멜케이스문자열을 스네이크 케이스 문자열로 바꾸어 반환합니다.
         * @method ax5.util.snakeCase
         * @param {String} str
         * @returns {String}
         * @example
         * ```js
         * ax5.util.snakeCase("innerWidth");
         * ax5.util.snakeCase("inner-Width");
         * ax5.util.snakeCase("innerWidth");
         * // inner-width
         * ```
         */
        function snakeCase(str) {
            return camelCase(str).replace(reCamelCase, function (all, letter) {
                return "-" + letter.toLowerCase();
            });
        }

        /**
         * 문자열에서 -. 을 제외한 모든 문자열을 제거하고 숫자로 반환합니다. 옵션에 따라 원하는 형식의 숫자로 변환 할 수 도 있습니다.
         * @method ax5.util.number
         * @param {String|Number} str
         * @param {Object} cond - 옵션
         * @returns {String|Number}
         * @example
         * ```js
         * var cond = {
        * 	round: {Number|Boolean} - 반올림할 자릿수,
        * 	money: {Boolean} - 통화,
        * 	abs: {Boolean} - 절대값,
        * 	byte: {Boolean} - 바이트
        * }
         *
         * console.log(ax5.util.number(123456789.678, {round:1}));
         * console.log(ax5.util.number(123456789.678, {round:1, money:true}));
         * console.log(ax5.util.number(123456789.678, {round:2, byte:true}));
         * console.log(ax5.util.number(-123456789.8888, {abs:true, round:2, money:true}));
         * console.log(ax5.util.number("A-1234~~56789.8~888PX", {abs:true, round:2, money:true}));
         *
         * //123456789.7
         * //123,456,789.7
         * //117.7MB
         * //123,456,789.89
         * //123,456,789.89
         * ```
         */
        function number(str, cond) {
            var result,
                pair = ('' + str).split(reDot),
                isMinus,
                returnValue;

            isMinus = Number(pair[0].replace(/,/g, "")) < 0 || pair[0] == "-0";
            returnValue = 0.0;
            pair[0] = pair[0].replace(reInt, "");

            if (pair[1]) {
                pair[1] = pair[1].replace(reNotNum, "");
                returnValue = Number(pair[0] + "." + pair[1]) || 0;
            } else {
                returnValue = Number(pair[0]) || 0;
            }
            result = isMinus ? -returnValue : returnValue;

            each(cond, function (k, c) {
                if (k == "round") {
                    if (isNumber(c)) {
                        if (c < 0) {
                            result = +(Math.round(result + "e-" + Math.abs(c)) + "e+" + Math.abs(c));
                        } else {
                            result = +(Math.round(result + "e+" + c) + "e-" + c);
                        }
                    } else {
                        result = Math.round(result);
                    }
                }
                if (k == "floor") {
                    result = Math.floor(result);
                }
                if (k == "ceil") {
                    result = Math.ceil(result);
                } else if (k == "money") {
                    result = function (val) {
                        var txtNumber = '' + val;
                        if (isNaN(txtNumber) || txtNumber == "") {
                            return "";
                        } else {
                            var arrNumber = txtNumber.split('.');
                            arrNumber[0] += '.';
                            do {
                                arrNumber[0] = arrNumber[0].replace(reMoneySplit, '$1,$2');
                            } while (reMoneySplit.test(arrNumber[0]));
                            if (arrNumber.length > 1) {
                                return arrNumber.join('');
                            } else {
                                return arrNumber[0].split('.')[0];
                            }
                        }
                    }(result);
                } else if (k == "abs") {
                    result = Math.abs(Number(result));
                } else if (k == "byte") {
                    result = function (val) {
                        val = Number(result);
                        var nUnit = "KB";
                        var myByte = val / 1024;
                        if (myByte / 1024 > 1) {
                            nUnit = "MB";
                            myByte = myByte / 1024;
                        }
                        if (myByte / 1024 > 1) {
                            nUnit = "GB";
                            myByte = myByte / 1024;
                        }
                        return number(myByte, { round: 1 }) + nUnit;
                    }(result);
                }
            });

            return result;
        }

        /**
         * 배열 비슷한 오브젝트를 배열로 변환해줍니다.
         * @method ax5.util.toArray
         * @param {Object|Elements|Arguments} O
         * @returns {Array}
         * @example
         * ```js
         * ax5.util.toArray(arguments);
         * //
         * ```
         */
        function toArray(O) {
            if (typeof O.length != "undefined") return Array.prototype.slice.call(O);
            return [];
        }

        /**
         * 첫번째 인자에 두번째 인자 아이템을 합쳐줍니다. concat과 같은 역할을 하지만. 인자가 Array타입이 아니어도 됩니다.
         * @method ax5.util.merge
         * @param {Array|ArrayLike} first
         * @param {Array|ArrayLike} second
         * @returns {Array} first
         * @example
         * ```
         *
         * ```
         */
        function merge(first, second) {
            var l = second.length,
                i = first.length,
                j = 0;

            if (typeof l === "number") {
                for (; j < l; j++) {
                    first[i++] = second[j];
                }
            } else {
                while (second[j] !== undefined) {
                    first[i++] = second[j++];
                }
            }

            first.length = i;

            return first;
        }

        /**
         * 오브젝트를 파라미터형식으로 또는 파라미터를 오브젝트 형식으로 변환합니다.
         * @method ax5.util.param
         * @param {Object|Array|String} O
         * @param {String} [cond] - param|object
         * @returns {Object|String}
         * @example
         * ```
         * ax5.util.param({a:1,b:'1232'}, "param");
         * ax5.util.param("a=1&b=1232", "param");
         * // "a=1&b=1232"
         * ax5.util.param("a=1&b=1232");
         * // {a: "1", b: "1232"}
         * ```
         */
        function param(O, cond) {
            var p;
            if (isString(O) && typeof cond !== "undefined" && cond == "param") {
                return O;
            } else if (isString(O) && typeof cond !== "undefined" && cond == "object" || isString(O) && typeof cond === "undefined") {
                p = {};
                each(O.split(reAmp), function () {
                    var item = this.split(reEq);
                    if (!p[item[0]]) p[item[0]] = item[1];else {
                        if (isString(p[item[0]])) p[item[0]] = [p[item[0]]];
                        p[item[0]].push(item[1]);
                    }
                });
                return p;
            } else {
                p = [];
                each(O, function (k, v) {
                    p.push(k + "=" + escape(v));
                });
                return p.join('&');
            }
        }

        function encode(s) {
            return encodeURIComponent(s);
        }

        function decode(s) {
            return decodeURIComponent(s);
        }

        function error() {
            ax5.info.onerror.apply(this, arguments);
        }

        function localDate(yy, mm, dd, hh, mi, ss) {
            var utcD, localD;
            localD = new Date();
            if (mm < 0) mm = 0;
            if (typeof hh === "undefined") hh = 12;
            if (typeof mi === "undefined") mi = 0;
            utcD = new Date(Date.UTC(yy, mm, dd || 1, hh, mi, ss || 0));

            if (mm == 0 && dd == 1 && utcD.getUTCHours() + utcD.getTimezoneOffset() / 60 < 0) {
                utcD.setUTCHours(0);
            } else {
                utcD.setUTCHours(utcD.getUTCHours() + utcD.getTimezoneOffset() / 60);
            }
            return utcD;
        }

        /**
         * 날짜 형식의 문자열이나 Date객체를 조건에 맞게 처리 한 후 원하는 return 값으로 반환합니다.
         * @method ax5.util.date
         * @param {String|Date} d
         * @param {Object} cond
         * @returns {Date|String}
         * @example
         * ```js
         * ax5.util.date('2013-01-01'); // Tue Jan 01 2013 23:59:00 GMT+0900 (KST)
         * ax5.util.date((new Date()), {add:{d:10}, return:'yyyy/MM/dd'}); // "2015/07/01"
         * ax5.util.date('1919-03-01', {add:{d:10}, return:'yyyy/MM/dd hh:mm:ss'}); // "1919/03/11 23:59:00"
         * ```
         */
        function date(d, cond) {
            var yy = void 0,
                mm = void 0,
                dd = void 0,
                hh = void 0,
                mi = void 0,
                aDateTime = void 0,
                aTimes = void 0,
                aTime = void 0,
                aDate = void 0,
                va = void 0,
                ISO_8601 = /^\d{4}(-\d\d(-\d\d(T\d\d:\d\d(:\d\d)?(\.\d+)?(([+-]\d\d:\d\d)|Z)?)?)?)?$/i,
                ISO_8601_FULL = /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?(([+-]\d\d:\d\d)|Z)?$/i;

            if (isString(d)) {
                if (d.length == 0) {
                    d = new Date();
                } else if (d.length > 15) {
                    if (ISO_8601_FULL.test(d) || ISO_8601.test(d)) {
                        d = new Date(d);
                    } else {
                        aDateTime = d.split(/ /g), aTimes, aTime, aDate = aDateTime[0].split(/\D/g), yy = aDate[0];
                        mm = parseFloat(aDate[1]);
                        dd = parseFloat(aDate[2]);
                        aTime = aDateTime[1] || "09:00";
                        aTimes = aTime.substring(0, 5).split(":");
                        hh = parseFloat(aTimes[0]);
                        mi = parseFloat(aTimes[1]);
                        if (right(aTime, 2) === "AM" || right(aTime, 2) === "PM") hh += 12;
                        d = localDate(yy, mm - 1, dd, hh, mi);
                    }
                } else if (d.length == 14) {
                    va = d.replace(/\D/g, "");
                    d = localDate(va.substr(0, 4), va.substr(4, 2) - 1, number(va.substr(6, 2)), number(va.substr(8, 2)), number(va.substr(10, 2)), number(va.substr(12, 2)));
                } else if (d.length > 7) {
                    va = d.replace(/\D/g, "");
                    d = localDate(va.substr(0, 4), va.substr(4, 2) - 1, number(va.substr(6, 2)));
                } else if (d.length > 4) {
                    va = d.replace(/\D/g, "");
                    d = localDate(va.substr(0, 4), va.substr(4, 2) - 1, 1);
                } else if (d.length > 2) {
                    va = d.replace(/\D/g, "");
                    return localDate(va.substr(0, 4), va.substr(4, 2) - 1, 1);
                } else {
                    d = new Date();
                }
            }
            if (typeof cond === "undefined" || typeof d === "undefined") {
                return d;
            } else {
                if ("add" in cond) {
                    d = function (_d, opts) {
                        var yy = void 0,
                            mm = void 0,
                            dd = void 0,
                            mxdd = void 0,
                            DyMilli = 1000 * 60 * 60 * 24;

                        if (typeof opts["d"] !== "undefined") {
                            _d.setTime(_d.getTime() + opts["d"] * DyMilli);
                        } else if (typeof opts["m"] !== "undefined") {
                            yy = _d.getFullYear();
                            mm = _d.getMonth();
                            dd = _d.getDate();
                            yy = yy + parseInt(opts["m"] / 12);
                            mm += opts["m"] % 12;
                            mxdd = daysOfMonth(yy, mm);
                            if (mxdd < dd) dd = mxdd;
                            _d = new Date(yy, mm, dd, 12);
                        } else if (typeof opts["y"] !== "undefined") {
                            _d.setTime(_d.getTime() + opts["y"] * 365 * DyMilli);
                        } else if (typeof opts["h"] !== "undefined") {
                            _d.setTime(_d.getTime() + opts["h"] * 1000 * 60 * 60);
                        }

                        return _d;
                    }(new Date(d), cond["add"]);
                }
                if ("set" in cond) {
                    d = function (_d, opts) {
                        var yy = void 0,
                            mm = void 0,
                            dd = void 0,
                            processor = {
                            "firstDayOfMonth": function firstDayOfMonth(date) {
                                yy = date.getFullYear();
                                mm = date.getMonth();
                                dd = 1;
                                return new Date(yy, mm, dd, 12);
                            },
                            "lastDayOfMonth": function lastDayOfMonth(date) {
                                yy = date.getFullYear();
                                mm = date.getMonth();
                                dd = daysOfMonth(yy, mm);
                                return new Date(yy, mm, dd, 12);
                            }
                        };
                        if (opts in processor) {
                            return processor[opts](_d);
                        } else {
                            return _d;
                        }
                    }(new Date(d), cond["set"]);
                }
                if ("return" in cond) {
                    return function () {

                        var fStr = cond["return"],
                            nY = void 0,
                            nM = void 0,
                            nD = void 0,
                            nH = void 0,
                            nMM = void 0,
                            nS = void 0,
                            nDW = void 0,
                            yre = void 0,
                            regY = void 0,
                            mre = void 0,
                            regM = void 0,
                            dre = void 0,
                            regD = void 0,
                            hre = void 0,
                            regH = void 0,
                            mire = void 0,
                            regMI = void 0,
                            sre = void 0,
                            regS = void 0,
                            dwre = void 0,
                            regDW = void 0;

                        nY = d.getUTCFullYear();
                        nM = setDigit(d.getMonth() + 1, 2);
                        nD = setDigit(d.getDate(), 2);
                        nH = setDigit(d.getHours(), 2);
                        nMM = setDigit(d.getMinutes(), 2);
                        nS = setDigit(d.getSeconds(), 2);
                        nDW = d.getDay();

                        yre = /[^y]*(yyyy)[^y]*/gi;
                        yre.exec(fStr);
                        regY = RegExp.$1;
                        mre = /[^m]*(MM)[^m]*/g;
                        mre.exec(fStr);
                        regM = RegExp.$1;
                        dre = /[^d]*(dd)[^d]*/gi;
                        dre.exec(fStr);
                        regD = RegExp.$1;
                        hre = /[^h]*(hh)[^h]*/gi;
                        hre.exec(fStr);
                        regH = RegExp.$1;
                        mire = /[^m]*(mm)[^i]*/g;
                        mire.exec(fStr);
                        regMI = RegExp.$1;
                        sre = /[^s]*(ss)[^s]*/gi;
                        sre.exec(fStr);
                        regS = RegExp.$1;
                        dwre = /[^d]*(dw)[^w]*/gi;
                        dwre.exec(fStr);
                        regDW = RegExp.$1;

                        if (regY === "yyyy") {
                            fStr = fStr.replace(regY, right(nY, regY.length));
                        }
                        if (regM === "MM") {
                            if (regM.length == 1) nM = d.getMonth() + 1;
                            fStr = fStr.replace(regM, nM);
                        }
                        if (regD === "dd") {
                            if (regD.length == 1) nD = d.getDate();
                            fStr = fStr.replace(regD, nD);
                        }
                        if (regH === "hh") {
                            fStr = fStr.replace(regH, nH);
                        }
                        if (regMI === "mm") {
                            fStr = fStr.replace(regMI, nMM);
                        }
                        if (regS === "ss") {
                            fStr = fStr.replace(regS, nS);
                        }
                        if (regDW == "dw") {
                            fStr = fStr.replace(regDW, info.weekNames[nDW].label);
                        }
                        return fStr;
                    }();
                } else {
                    return d;
                }
            }
        }

        /**
         * 인자인 날짜가 오늘부터 몇일전인지 반환합니다. 또는 인자인 날짜가 가까운 미래에 몇일 후인지 반환합니다.
         * @method ax5.util.dday
         * @param {String|Data} d
         * @param {Object} cond
         * @returns {Number}
         * @example
         * ```js
         * ax5.util.dday('2016-01-29');
         * // 1
         * ax5.util.dday('2016-01-29', {today:'2016-01-28'});
         * // 1
         * ax5.util.dday('1977-03-29', {today:'2016-01-28', age:true});
         * // 39
         * ```
         */
        function dday(d, cond) {
            var memoryDay = date(d),
                DyMilli = 1000 * 60 * 60 * 24,
                today = new Date(),
                diffnum,
                thisYearMemoryDay;

            function getDayTime(_d) {
                return Math.floor(_d.getTime() / DyMilli) * DyMilli;
            }

            if (typeof cond === "undefined") {
                diffnum = number((getDayTime(memoryDay) - getDayTime(today)) / DyMilli, { floor: true });
                return diffnum;
            } else {
                diffnum = number((getDayTime(memoryDay) - getDayTime(today)) / DyMilli, { floor: true });
                if (cond["today"]) {
                    today = date(cond.today);
                    diffnum = number((getDayTime(memoryDay) - getDayTime(today)) / DyMilli, { floor: true });
                }
                if (cond["thisYear"]) {
                    thisYearMemoryDay = new Date(today.getFullYear(), memoryDay.getMonth(), memoryDay.getDate());
                    diffnum = number((getDayTime(thisYearMemoryDay) - getDayTime(today)) / DyMilli, { floor: true });
                    if (diffnum < 0) {
                        thisYearMemoryDay = new Date(today.getFullYear() + 1, memoryDay.getMonth(), memoryDay.getDate());
                        diffnum = number((getDayTime(thisYearMemoryDay) - getDayTime(today)) / DyMilli, { floor: true });
                    }
                }
                if (cond["age"]) {
                    thisYearMemoryDay = new Date(today.getFullYear(), memoryDay.getMonth(), memoryDay.getDate());
                    diffnum = thisYearMemoryDay.getFullYear() - memoryDay.getFullYear();
                }

                return diffnum;
            }
        }

        /**
         * 인자인 날짜가 몇년 몇월의 몇번째 주차인지 반환합니다.
         * @method ax5.util.weeksOfMonth
         * @param {String|Data} d
         * @returns {Object}
         * @example
         * ```js
         * ax5.util.weeksOfMonth("2015-10-01"); // {year: 2015, month: 10, count: 1}
         * ax5.util.weeksOfMonth("2015-09-19"); // {year: 2015, month: 9, count: 3}
         * ```
         */
        function weeksOfMonth(d) {
            var myDate = date(d);
            return {
                year: myDate.getFullYear(),
                month: myDate.getMonth() + 1,
                count: parseInt(myDate.getDate() / 7 + 1)
            };
        }

        /**
         * 년월에 맞는 날자수를 반환합니다.
         * (new Date()).getMonth() 기준으로 월값을 보냅니다. "2월" 인경우 "1" 을 넘기게 됩니다.
         * @method ax5.util.daysOfMonth
         * @param {Number} y
         * @param {Number} m
         * @returns {Number}
         * @examples
         * ```js
         * ax5.util.daysOfMonth(2015, 11); // 31
         * ax5.util.daysOfMonth(2015, 1); // 28
         * ```
         */
        function daysOfMonth(y, m) {
            if (m == 3 || m == 5 || m == 8 || m == 10) {
                return 30;
            } else if (m == 1) {
                return y % 4 == 0 && y % 100 != 0 || y % 400 == 0 ? 29 : 28;
            } else {
                return 31;
            }
        }

        /**
         * 원하는 횟수 만큼 자릿수 맞춤 문자열을 포함한 문자열을 반환합니다.
         * 문자열 길이보다 작은값을 보내면 무시됩니다.
         * @method ax5.util.setDigit
         * @param {String|Number} num
         * @param {Number} length
         * @param {String} [padder=0]
         * @param {Number} [radix]
         * @returns {String}
         * @example
         * ```
         * ax5.util.setDigit(2016, 6)
         * // "002016"
         * ax5.util.setDigit(2016, 2)
         * // "2016"
         * ```
         */
        function setDigit(num, length, padder, radix) {
            var s = num.toString(radix || 10);
            return times(padder || '0', length - s.length) + s;
        }

        /**
         * 문자열을 지정된 수만큼 반복 합니다.
         * @param {String} s
         * @param {Number} count
         * @returns {string}
         * @example
         * ```
         * ax5.util.times(2016, 2)
         * //"20162016"
         * ```
         */
        function times(s, count) {
            return count < 1 ? '' : new Array(count + 1).join(s);
        }

        /**
         * 타겟엘리먼트의 부모 엘리멘트 트리에서 원하는 조건의 엘리먼트를 얻습니다.
         * @method ax5.util.findParentNode
         * @param {Element} _target - target element
         * @param {Object|Function} cond - 원하는 element를 찾을 조건
         * @returns {Element}
         * @example
         * ```
         * // cond 속성정의
         * var cond = {
        * 	tagname: {String} - 태그명 (ex. a, div, span..),
        * 	clazz: {String} - 클래스명
        * 	[, 그 외 찾고 싶은 attribute명들]
        * };
         * console.log(
         * console.log(
         *    ax5.util.findParentNode(e.target, {tagname:"a", clazz:"ax-menu-handel", "data-custom-attr":"attr_value"})
         * );
         * // cond 함수로 처리하기
         * jQuery('#id').bind("click.app_expand", function(e){
        * 	var target = ax5.util.findParentNode(e.target, function(target){
        * 		if($(target).hasClass("aside")){
        * 			return true;
        * 		}
        * 		else{
        * 			return true;
        * 		}
        * 	});
        * 	//client-aside
        * 	if(target.id !== "client-aside"){
        * 		// some action
        * 	}
        * });
         * ```
         */

        function findParentNode(_target, cond) {
            if (_target) {
                while (function () {
                    var result = true;
                    if (typeof cond === "undefined") {
                        _target = _target.parentNode ? _target.parentNode : false;
                    } else if (isFunction(cond)) {
                        result = cond(_target);
                    } else if (isObject(cond)) {
                        for (var k in cond) {
                            if (k === "tagname") {
                                if (_target.tagName.toLocaleLowerCase() != cond[k]) {
                                    result = false;
                                    break;
                                }
                            } else if (k === "clazz" || k === "class_name") {
                                if ("className" in _target) {
                                    var klasss = _target.className.split(reClassNameSplit);
                                    var hasClass = false;
                                    for (var a = 0; a < klasss.length; a++) {
                                        if (klasss[a] == cond[k]) {
                                            hasClass = true;
                                            break;
                                        }
                                    }
                                    result = hasClass;
                                } else {
                                    result = false;
                                    break;
                                }
                            } else {
                                // 그외 속성값들.
                                if (_target.getAttribute) {
                                    if (_target.getAttribute(k) != cond[k]) {
                                        result = false;
                                        break;
                                    }
                                } else {
                                    result = false;
                                    break;
                                }
                            }
                        }
                    }
                    return !result;
                }()) {
                    if (_target.parentNode && _target.parentNode.parentNode) {
                        _target = _target.parentNode;
                    } else {
                        _target = false;
                        break;
                    }
                }
            }
            return _target;
        }

        /**
         * @method ax5.util.cssNumber
         * @param {String|Number} val
         * @returns {String}
         * @example
         * ```
         * console.log(ax5.util.cssNumber("100px"))
         * console.log(ax5.util.cssNumber("100%"))
         * console.log(ax5.util.cssNumber("100"))
         * console.log(ax5.util.cssNumber(100))
         * console.log(ax5.util.cssNumber("!!100@#"))
         * ```
         */
        function cssNumber(val) {
            var re = /\D?(\d+)([a-zA-Z%]*)/i,
                found = ('' + val).match(re),
                unit = found[2] || "px";

            return found[1] + unit;
        }

        /**
         * css string 및 object 를 넘기면 object 및 string 으로 변환되어 리턴됩니다.
         * @method ax5.util.css
         * @param {Object|String} val - CSS String or CSS Object
         * @returns {String|Object}
         * @example
         * ```
         * console.log(ax5.util.css({background: "#ccc", padding: "50px", width: "100px"}));
         * //"background:#ccc;padding:50px;width:100px;"
         * console.log(ax5.util.css('width:100px;padding: 50px; background: #ccc'));
         * // object {width: "100px", padding: "50px", background: "#ccc"}
         * ```
         */
        function css(val) {
            var returns;
            if (isObject(val)) {
                returns = '';
                for (var k in val) {
                    returns += k + ':' + val[k] + ';';
                }
                return returns;
            } else if (isString(val)) {
                returns = {};
                var valSplited = val.split(/[ ]*;[ ]*/g);
                valSplited.forEach(function (v) {
                    if ((v = v.trim()) !== "") {
                        var vSplited = v.split(/[ ]*:[ ]*/g);
                        returns[vSplited[0]] = vSplited[1];
                    }
                });
                return returns;
            }
        }

        /**
         * @method ax5.util.stopEvent
         * @param {Event} e
         * @example
         * ```
         * ax5.util.stopEvent(e);
         * ```
         */
        function stopEvent(e) {
            // 이벤트 중지 구문
            if (!e) var e = window.event;

            //e.cancelBubble is supported by IE -
            // this will kill the bubbling process.
            e.cancelBubble = true;
            e.returnValue = false;

            //e.stopPropagation works only in Firefox.
            if (e.stopPropagation) e.stopPropagation();
            if (e.preventDefault) e.preventDefault();

            return false;
            // 이벤트 중지 구문 끝
        }

        /**
         * @method ax5.util.selectRange
         * @param {Element} el
         * @param {Element} offset
         * @example
         * ```
         * ax5.util.selectRange($("#select-test-0")); // selectAll
         * ax5.util.selectRange($("#select-test-0"), "selectAll"); //selectAll
         * ax5.util.selectRange($("#select-test-0"), "start"); // focus on start
         * ax5.util.selectRange($("#select-test-0"), "end"); // focus on end
         * ax5.util.selectRange($("#select-test-0"), [1, 5]); // select 1~5
         * ```
         */
        var selectRange = function () {
            var processor = {
                'textRange': {
                    'selectAll': function selectAll(el, range, offset) {},
                    'arr': function arr(el, range, offset) {
                        range.moveStart("character", offset[0]); // todo ie node select 체크필요
                        range.collapse();
                        range.moveEnd("character", offset[1]);
                    },
                    'start': function start(el, range, offset) {
                        range.moveStart("character", 0);
                        range.collapse();
                    },
                    'end': function end(el, range, offset) {
                        range.moveStart("character", range.text.length);
                        range.collapse();
                    }
                },
                'range': {
                    'selectAll': function selectAll(el, range, offset) {
                        range.selectNodeContents(el);
                    },
                    'arr': function arr(el, range, offset) {
                        if (isObject(offset[0])) {
                            range.setStart(offset[0].node, offset[0].offset);
                            range.setEnd(offset[1].node, offset[1].offset);
                        } else {
                            range.setStart(el.firstChild, offset[0]);
                            range.setEnd(el.firstChild, offset[1]);
                        }
                    },
                    'start': function start(el, range, offset) {
                        range.selectNodeContents(el);
                        range.collapse(true);
                    },
                    'end': function end(el, range, offset) {
                        range.selectNodeContents(el);
                        range.collapse(false);
                    }
                }
            };
            return function (el, offset) {
                var range, rangeType, selection;

                if (el instanceof jQuery) {
                    el = el.get(0);
                }
                if (!el) return;

                // 레인지 타입 선택
                if (doc.body.createTextRange) {
                    range = document.body.createTextRange();
                    range.moveToElementText(el);
                    rangeType = "textRange";
                } else if (window.getSelection) {
                    selection = window.getSelection();
                    range = document.createRange();
                    rangeType = "range";
                }

                // range 적용
                if (typeof offset == "undefined") {
                    processor[rangeType].selectAll.call(this, el, range, offset);
                } else if (isArray(offset)) {
                    processor[rangeType].arr.call(this, el, range, offset);
                } else {
                    for (var key in processor[rangeType]) {
                        if (offset == key) {
                            processor[rangeType][key].call(this, el, range, offset);
                            break;
                        }
                    }
                }

                // 포커스 및 셀렉트
                if (doc.body.createTextRange) {
                    range.select();
                    el.focus();
                } else if (window.getSelection) {
                    el.focus();
                    selection.removeAllRanges();
                    selection.addRange(range);
                }
            };
        }();

        /**
         * 지정한 시간을 지연시켜 함수를 실행합니다.
         * @method ax5.util.debounce
         * @param {Function} func
         * @param {Number} wait
         * @param {Object} options
         * @returns {debounced}
         * @example
         * ```js
         * var debounceFn = ax5.util.debounce(function( val ) { console.log(val); }, 300);
         * $(document.body).click(function(){
         *  debounceFn(new Date());
         * });
         * ```
         */
        // https://github.com/lodash/lodash/blob/master/debounce.js
        function debounce(func, wait, options) {
            var lastArgs = void 0,
                lastThis = void 0,
                maxWait = void 0,
                result = void 0,
                timerId = void 0,
                lastCallTime = void 0;

            var lastInvokeTime = 0;
            var leading = false;
            var maxing = false;
            var trailing = true;

            if (typeof func != 'function') {
                throw new TypeError('Expected a function');
            }
            wait = +wait || 0;
            if (isObject(options)) {
                leading = !!options.leading;
                maxing = 'maxWait' in options;
                maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait;
                trailing = 'trailing' in options ? !!options.trailing : trailing;
            }

            function invokeFunc(time) {
                var args = lastArgs;
                var thisArg = lastThis;

                lastArgs = lastThis = undefined;
                lastInvokeTime = time;
                result = func.apply(thisArg, args);
                return result;
            }

            function leadingEdge(time) {
                // Reset any `maxWait` timer.
                lastInvokeTime = time;
                // Start the timer for the trailing edge.
                timerId = setTimeout(timerExpired, wait);
                // Invoke the leading edge.
                return leading ? invokeFunc(time) : result;
            }

            function remainingWait(time) {
                var timeSinceLastCall = time - lastCallTime;
                var timeSinceLastInvoke = time - lastInvokeTime;
                var result = wait - timeSinceLastCall;

                return maxing ? Math.min(result, maxWait - timeSinceLastInvoke) : result;
            }

            function shouldInvoke(time) {
                var timeSinceLastCall = time - lastCallTime;
                var timeSinceLastInvoke = time - lastInvokeTime;

                // Either this is the first call, activity has stopped and we're at the
                // trailing edge, the system time has gone backwards and we're treating
                // it as the trailing edge, or we've hit the `maxWait` limit.
                return lastCallTime === undefined || timeSinceLastCall >= wait || timeSinceLastCall < 0 || maxing && timeSinceLastInvoke >= maxWait;
            }

            function timerExpired() {
                var time = Date.now();
                if (shouldInvoke(time)) {
                    return trailingEdge(time);
                }
                // Restart the timer.
                timerId = setTimeout(timerExpired, remainingWait(time));
            }

            function trailingEdge(time) {
                timerId = undefined;

                // Only invoke if we have `lastArgs` which means `func` has been
                // debounced at least once.
                if (trailing && lastArgs) {
                    return invokeFunc(time);
                }
                lastArgs = lastThis = undefined;
                return result;
            }

            function cancel() {
                if (timerId !== undefined) {
                    clearTimeout(timerId);
                }
                lastInvokeTime = 0;
                lastArgs = lastCallTime = lastThis = timerId = undefined;
            }

            function flush() {
                return timerId === undefined ? result : trailingEdge(Date.now());
            }

            function debounced() {
                var time = Date.now();
                var isInvoking = shouldInvoke(time);

                for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
                    args[_key] = arguments[_key];
                }

                lastArgs = args;
                lastThis = this;
                lastCallTime = time;

                if (isInvoking) {
                    if (timerId === undefined) {
                        return leadingEdge(lastCallTime);
                    }
                    if (maxing) {
                        // Handle invocations in a tight loop.
                        timerId = setTimeout(timerExpired, wait);
                        return invokeFunc(lastCallTime);
                    }
                }
                if (timerId === undefined) {
                    timerId = setTimeout(timerExpired, wait);
                }
                return result;
            }
            debounced.cancel = cancel;
            debounced.flush = flush;
            return debounced;
        }

        /**
         * @method ax5.util.throttle
         * @param func
         * @param wait
         * @param options
         * @return {debounced}
         */
        //https://github.com/lodash/lodash/blob/master/throttle.js
        function throttle(func, wait, options) {
            var leading = true;
            var trailing = true;

            if (typeof func != 'function') {
                throw new TypeError('Expected a function');
            }
            if (isObject(options)) {
                leading = 'leading' in options ? !!options.leading : leading;
                trailing = 'trailing' in options ? !!options.trailing : trailing;
            }
            return debounce(func, wait, {
                'leading': leading,
                'maxWait': wait,
                'trailing': trailing
            });
        }

        /**
         * @method ax5.util.deepCopy
         * @param {Object} obj
         * @returns {Object}
         * @example
         * ```js
         * var obj = [
         *  {name:"A", child:[{name:"a-1"}]},
         *  {name:"B", child:[{name:"b-1"}], callBack: function(){ console.log('callBack'); }}
         * ];
         * var copiedObj = ax5.util.deepCopy(obj)
         * ```
         */
        function deepCopy(obj) {
            var r, l;
            if ((typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) == 'object') {
                if (U.isArray(obj)) {
                    l = obj.length;
                    r = new Array(l);
                    for (var i = 0; i < l; i++) {
                        r[i] = deepCopy(obj[i]);
                    }
                    return r;
                } else {
                    return jQuery.extend({}, obj);
                }
            }
            return obj;
        }

        /**
         * HTML 문자열을 escape 처리합니다.
         * "&lt;" represents the < sign.
         * "&gt;" represents the > sign.
         * "&amp;" represents the & sign.
         * "&quot; represents the " mark.
         * [Character entity references](https://www.w3.org/TR/html401/charset.html#h-5.3)
         * @method ax5.util.escapeHtml
         * @param {String} s
         * @returns {string}
         * @example
         * ```
         * ax5.util.escapeHtml('HTML <span>string</span> & "escape"')
         * //"HTML &lt;span&gt;string&lt;/span&gt; &amp; &quot;escape&quot;"
         * ```
         */
        function escapeHtml(s) {
            if (_toString.call(s) != "[object String]") return s;
            if (!s) return "";
            return s.replace(/[\<\>\&\"]/gm, function (match) {
                switch (match) {
                    case "<":
                        return "&lt;";
                    case ">":
                        return "&gt;";
                    case "&":
                        return "&amp;";
                    case "\"":
                        return "&quot;";
                    default:
                        return match;
                }
            });
        }

        /**
         * HTML 문자열을 unescape 처리합니다.
         * escapeHtml를 참고하세요.
         * @method ax5.util.unescapeHtml
         * @param {String} s
         * @returns {string}
         * @example
         * ```
         * ax5.util.unescapeHtml('HTML &lt;span&gt;string&lt;/span&gt; &amp; &quot;escape&quot;')
         * //"HTML <span>string</span> & "escape""
         * ```
         */
        function unescapeHtml(s) {
            if (_toString.call(s) != "[object String]") return s;
            if (!s) return "";
            return s.replace(/(&lt;)|(&gt;)|(&amp;)|(&quot;)/gm, function (match) {
                switch (match) {
                    case "&lt;":
                        return "<";
                    case "&gt;":
                        return ">";
                    case "&amp;":
                        return "&";
                    case "&quot;":
                        return "\"";
                    default:
                        return match;
                }
            });
        }

        /**
         * @method ax5.util.string
         * @param {String} tmpl
         * @param {*} args
         * @return {ax5string}
         * @example
         * ```js
         * ax5.util.string("{0} is dead, but {1} is alive! {0} {2}").format("ASP", "ASP.NET");
         * ax5.util.string("{0} is dead, but {1} is alive! {0} {2}").format(["ASP", "ASP.NET"]);
         * ax5.util.stinrg("{0} counts").format(100);
         * ```
         */
        function string(_string) {
            return new function (_string) {
                this.value = _string;
                this.toString = function () {
                    return this.value;
                };
                /**
                 * @method ax5.util.string.format
                 * @returns {*}
                 */
                this.format = function () {
                    var args = [];
                    for (var i = 0, l = arguments.length; i < l; i++) {
                        args = args.concat(arguments[i]);
                    }
                    return this.value.replace(/{(\d+)}/g, function (match, number) {
                        return typeof args[number] != 'undefined' ? args[number] : match;
                    });
                };
                /**
                 * @method ax5.util.string.escape
                 * @returns {*}
                 */
                this.escape = function () {
                    return escapeHtml(this.value);
                };
                /**
                 * @method ax5.util.string.unescape
                 * @returns {*}
                 */
                this.unescape = function () {
                    return unescapeHtml(this.value);
                };
                /**
                 * @method ax5.util.string.encode
                 * @returns {*}
                 */
                this.encode = function () {
                    return encode(this.value);
                };
                /**
                 * @method ax5.util.string.decode
                 * @returns {*}
                 */
                this.decode = function () {
                    return decode(this.value);
                };
                /**
                 * @method ax5.util.string.left
                 * @param {String|Number} pos - 찾을 문자열 또는 포지션
                 * @returns {*}
                 */
                this.left = function (_pos) {
                    return left(this.value, _pos);
                };
                /**
                 * @method ax5.util.string.right
                 * @param {String|Number} pos - 찾을 문자열 또는 포지션
                 * @returns {*}
                 */
                this.right = function (_pos) {
                    return right(this.value, _pos);
                };
                /**
                 * @method ax5.util.string.camelCase
                 * @returns {*}
                 */
                this.camelCase = function () {
                    return camelCase(this.value);
                };
                /**
                 * @method ax5.util.string.snakeCase
                 * @returns {*}
                 */
                this.snakeCase = function () {
                    return snakeCase(this.value);
                };
            }(_string);
        }

        /**
         * @method ax5.util.color
         * @param _hexColor
         * @return {ax5color}
         * @example
         * ```js
         * ax5.util.color("#ff3300").lighten(10).getHexValue()
         * console.log(ax5.util.color("#ff3300").darken(10).getHexValue());
         * ```
         */
        function color(_hexColor) {

            var matchers = function () {

                // <http://www.w3.org/TR/css3-values/#integers>
                var CSS_INTEGER = "[-\\+]?\\d+%?";

                // <http://www.w3.org/TR/css3-values/#number-value>
                var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?";

                // Allow positive/negative integer/number.  Don't capture the either/or, just the entire outcome.
                var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")";

                // Actual matching.
                // Parentheses and commas are optional, but not required.
                // Whitespace can take the place of commas or opening paren
                var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
                var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";

                return {
                    CSS_UNIT: new RegExp(CSS_UNIT),
                    rgb: new RegExp("rgb" + PERMISSIVE_MATCH3),
                    rgba: new RegExp("rgba" + PERMISSIVE_MATCH4),
                    hsl: new RegExp("hsl" + PERMISSIVE_MATCH3),
                    hsla: new RegExp("hsla" + PERMISSIVE_MATCH4),
                    hsv: new RegExp("hsv" + PERMISSIVE_MATCH3),
                    hsva: new RegExp("hsva" + PERMISSIVE_MATCH4),
                    hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
                    hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
                    hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
                    hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/
                };
            }();

            var convertObject = function convertObject(_color) {
                var match = void 0;
                if (match = matchers.rgb.exec(_color)) {
                    return { r: match[1], g: match[2], b: match[3] };
                }
                if (match = matchers.rgba.exec(_color)) {
                    return { r: match[1], g: match[2], b: match[3], a: match[4] };
                }
                if (match = matchers.hsl.exec(_color)) {
                    return { h: match[1], s: match[2], l: match[3] };
                }
                if (match = matchers.hsla.exec(_color)) {
                    return { h: match[1], s: match[2], l: match[3], a: match[4] };
                }
                if (match = matchers.hsv.exec(_color)) {
                    return { h: match[1], s: match[2], v: match[3] };
                }
                if (match = matchers.hsva.exec(_color)) {
                    return { h: match[1], s: match[2], v: match[3], a: match[4] };
                }
                if (match = matchers.hex8.exec(_color)) {
                    return {
                        r: parseInt(match[1], 16),
                        g: parseInt(match[2], 16),
                        b: parseInt(match[3], 16),
                        a: parseInt(match[4] / 255, 16),
                        format: "hex8"
                    };
                }
                if (match = matchers.hex6.exec(_color)) {
                    return {
                        r: parseInt(match[1], 16),
                        g: parseInt(match[2], 16),
                        b: parseInt(match[3], 16),
                        format: "hex"
                    };
                }
                if (match = matchers.hex4.exec(_color)) {
                    return {
                        r: parseInt(match[1] + '' + match[1], 16),
                        g: parseInt(match[2] + '' + match[2], 16),
                        b: parseInt(match[3] + '' + match[3], 16),
                        a: parseInt(match[4] + '' + match[4], 16),
                        format: "hex8"
                    };
                }
                if (match = matchers.hex3.exec(_color)) {
                    return {
                        r: parseInt(match[1] + '' + match[1], 16),
                        g: parseInt(match[2] + '' + match[2], 16),
                        b: parseInt(match[3] + '' + match[3], 16),
                        format: "hex"
                    };
                }

                return false;
            };

            function isOnePointZero(n) {
                return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1;
            }

            function isPercentage(n) {
                return typeof n === "string" && n.indexOf('%') != -1;
            }

            function convertToPercentage(n) {
                if (n <= 1) {
                    n = n * 100 + "%";
                }

                return n;
            }

            function convertTo255(n) {
                return ax5.util.number(Math.min(255, Math.max(n, 0)), { 'round': 2 });
            }

            function convertToHex(n) {
                return setDigit(Math.round(n).toString(16), 2);
            }

            function bound01(n, max) {
                if (isOnePointZero(n)) {
                    n = "100%";
                }

                var processPercent = isPercentage(n);
                n = Math.min(max, Math.max(0, parseFloat(n)));

                // Automatically convert percentage into number
                if (processPercent) {
                    n = parseInt(n * max, 10) / 100;
                }

                // Handle floating point rounding errors
                if (Math.abs(n - max) < 0.000001) {
                    return 1;
                }

                // Convert into [0, 1] range if it isn't already
                return n % max / parseFloat(max);
            }

            function rgbToHsl(r, g, b) {
                r = bound01(r, 255);
                g = bound01(g, 255);
                b = bound01(b, 255);

                var max = Math.max(r, g, b),
                    min = Math.min(r, g, b);
                var h,
                    s,
                    l = (max + min) / 2;

                if (max == min) {
                    h = s = 0; // achromatic
                } else {
                    var d = max - min;
                    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
                    switch (max) {
                        case r:
                            h = (g - b) / d + (g < b ? 6 : 0);
                            break;
                        case g:
                            h = (b - r) / d + 2;
                            break;
                        case b:
                            h = (r - g) / d + 4;
                            break;
                    }

                    h /= 6;
                }

                return { h: h, s: s, l: l };
            }

            function hslToRgb(h, s, l) {
                var r = void 0,
                    g = void 0,
                    b = void 0;

                h = bound01(h, 360);
                s = bound01(s, 100);
                l = bound01(l, 100);

                function hue2rgb(p, q, t) {
                    if (t < 0) t += 1;
                    if (t > 1) t -= 1;
                    if (t < 1 / 6) return p + (q - p) * 6 * t;
                    if (t < 1 / 2) return q;
                    if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
                    return p;
                }

                if (s === 0) {
                    r = g = b = l; // achromatic
                } else {
                    var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
                    var p = 2 * l - q;
                    r = hue2rgb(p, q, h + 1 / 3);
                    g = hue2rgb(p, q, h);
                    b = hue2rgb(p, q, h - 1 / 3);
                }

                return { r: r * 255, g: g * 255, b: b * 255 };
            }

            return new function (_color) {
                this._originalValue = _color;
                _color = convertObject(_color);
                this.r = _color.r;
                this.g = _color.g;
                this.b = _color.b;
                this.a = _color.a || 1;
                this._format = _color.format;
                this._hex = convertToHex(this.r) + convertToHex(this.g) + convertToHex(this.b);

                this.getHexValue = function () {
                    return this._hex;
                };

                this.lighten = function (amount) {
                    amount = amount === 0 ? 0 : amount || 10;
                    var hsl = rgbToHsl(this.r, this.g, this.b),
                        rgb = {};

                    hsl.l += amount / 100;
                    hsl.l = Math.min(1, Math.max(0, hsl.l));
                    hsl.h = hsl.h * 360;

                    rgb = hslToRgb(hsl.h, convertToPercentage(hsl.s), convertToPercentage(hsl.l));

                    return color('rgba(' + convertTo255(rgb.r) + ', ' + convertTo255(rgb.g) + ', ' + convertTo255(rgb.b) + ', ' + this.a + ')');
                };

                this.darken = function (amount) {
                    amount = amount === 0 ? 0 : amount || 10;
                    var hsl = rgbToHsl(this.r, this.g, this.b),
                        rgb = {};

                    hsl.l -= amount / 100;
                    hsl.l = Math.min(1, Math.max(0, hsl.l));
                    hsl.h = hsl.h * 360;

                    rgb = hslToRgb(hsl.h, convertToPercentage(hsl.s), convertToPercentage(hsl.l));

                    return color('rgba(' + convertTo255(rgb.r) + ', ' + convertTo255(rgb.g) + ', ' + convertTo255(rgb.b) + ', ' + this.a + ')');
                };

                this.getBrightness = function () {
                    return (this.r * 299 + this.g * 587 + this.b * 114) / 1000;
                };

                this.isDark = function () {
                    return this.getBrightness() < 128;
                };

                this.isLight = function () {
                    return !this.isDark();
                };

                this.getHsl = function () {
                    var hsl = rgbToHsl(this.r, this.g, this.b);
                    hsl.l = Math.min(1, Math.max(0, hsl.l));
                    hsl.h = hsl.h * 360;
                    return {
                        h: hsl.h,
                        s: hsl.s,
                        l: hsl.l
                    };
                };
            }(_hexColor);
        }

        return {
            alert: alert,
            each: each,
            map: map,
            search: search,
            reduce: reduce,
            reduceRight: reduceRight,
            filter: filter,
            sum: sum,
            avg: avg,
            toJson: toJson,
            parseJson: parseJson,
            first: first,
            last: last,
            deepCopy: deepCopy,

            left: left,
            right: right,
            getType: getType,
            isWindow: isWindow,
            isElement: isElement,
            isObject: isObject,
            isArray: isArray,
            isFunction: isFunction,
            isString: isString,
            isNumber: isNumber,
            isNodelist: isNodelist,
            isUndefined: isUndefined,
            isNothing: isNothing,
            setCookie: setCookie,
            getCookie: getCookie,
            camelCase: camelCase,
            snakeCase: snakeCase,
            number: number,
            toArray: toArray,
            merge: merge,
            param: param,
            error: error,
            date: date,
            dday: dday,
            daysOfMonth: daysOfMonth,
            weeksOfMonth: weeksOfMonth,
            setDigit: setDigit,
            times: times,
            findParentNode: findParentNode,
            cssNumber: cssNumber,
            css: css,
            isDate: isDate,
            isDateFormat: isDateFormat,
            stopEvent: stopEvent,
            selectRange: selectRange,
            debounce: debounce,
            throttle: throttle,
            escapeHtml: escapeHtml,
            unescapeHtml: unescapeHtml,

            string: string,
            color: color
        };
    }();

    if ((typeof module === 'undefined' ? 'undefined' : _typeof(module)) === "object" && _typeof(module.exports) === "object") {
        module.exports = ax5;
    } else {
        root.ax5 = function () {
            return ax5;
        }(); // ax5.ui에 연결
    }
}).call(typeof window !== "undefined" ? window : undefined);

ax5.def = {};
ax5.info.errorMsg["ax5dialog"] = {
    "501": "Duplicate call error"
};

ax5.info.errorMsg["ax5picker"] = {
    "401": "Can not find target element",
    "402": "Can not find boundID",
    "501": "Can not find content key"
};

ax5.info.errorMsg["single-uploader"] = {
    "460": "There are no files to be uploaded.",
    "461": "There is no uploaded files."
};

ax5.info.errorMsg["ax5calendar"] = {
    "401": "Can not find target element"
};

ax5.info.errorMsg["ax5formatter"] = {
    "401": "Can not find target element",
    "402": "Can not find boundID",
    "501": "Can not find pattern"
};

ax5.info.errorMsg["ax5menu"] = {
    "501": "Can not find menu item"
};

ax5.info.errorMsg["ax5select"] = {
    "401": "Can not find target element",
    "402": "Can not find boundID",
    "501": "Can not find option"
};

ax5.info.errorMsg["ax5combobox"] = {
    "401": "Can not find target element",
    "402": "Can not find boundID",
    "501": "Can not find option"
};
// 필수 Ployfill 확장 구문
(function () {
    'use strict';

    var root = this,
        re_trim = /^\s*|\s*$/g;

    // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
    if (!Object.keys) {
        Object.keys = function () {
            var hwp = Object.prototype.hasOwnProperty,
                hdeb = !{ toString: null }.propertyIsEnumerable('toString'),
                de = ['toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'constructor'],
                del = de.length;

            return function (obj) {
                if ((typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) !== 'object' && (typeof obj !== 'function' || obj === null)) throw new TypeError('type err');
                var r = [],
                    prop,
                    i;
                for (prop in obj) {
                    if (hwp.call(obj, prop)) r.push(prop);
                }if (hdeb) {
                    for (i = 0; i < del; i++) {
                        if (hwp.call(obj, de[i])) r.push(de[i]);
                    }
                }
                return r;
            };
        }();
    }

    // ES5 15.4.4.18 Array.prototype.forEach ( callbackfn [ , thisArg ] )
    // From https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach
    if (!Array.prototype.forEach) {
        Array.prototype.forEach = function (fun /*, thisp */) {
            if (this === void 0 || this === null) {
                throw TypeError();
            }
            var t = Object(this);
            var len = t.length >>> 0;
            if (typeof fun !== "function") {
                throw TypeError();
            }
            var thisp = arguments[1],
                i;
            for (i = 0; i < len; i++) {
                if (i in t) {
                    fun.call(thisp, t[i], i, t);
                }
            }
        };
    }

    // ES5 15.3.4.5 Function.prototype.bind ( thisArg [, arg1 [, arg2, ... ]] )
    // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
    if (!Function.prototype.bind) {
        Function.prototype.bind = function (o) {
            if (typeof this !== 'function') {
                throw TypeError("function");
            }
            var slice = [].slice,
                args = slice.call(arguments, 1),
                self = this,
                bound = function bound() {
                return self.apply(this instanceof nop ? this : o, args.concat(slice.call(arguments)));
            };

            function nop() {}

            nop.prototype = self.prototype;
            bound.prototype = new nop();
            return bound;
        };
    }

    /*global document */
    /**
     * define document.querySelector & document.querySelectorAll for IE7
     *
     * A not very fast but small hack. The approach is taken from
     * http://weblogs.asp.net/bleroy/archive/2009/08/31/queryselectorall-on-old-ie-versions-something-that-doesn-t-work.aspx
     *
     */
    (function () {
        if (document.querySelectorAll || document.querySelector) {
            return;
        }
        if (!document.createStyleSheet) return;
        var style = document.createStyleSheet(),
            select = function select(selector, maxCount) {
            var all = document.all,
                l = all.length,
                i,
                resultSet = [];

            style.addRule(selector, "foo:bar");
            for (i = 0; i < l; i += 1) {
                if (all[i].currentStyle.foo === "bar") {
                    resultSet.push(all[i]);
                    if (resultSet.length > maxCount) {
                        break;
                    }
                }
            }
            style.removeRule(0);
            return resultSet;
        };

        document.querySelectorAll = function (selector) {
            return select(selector, Infinity);
        };
        document.querySelector = function (selector) {
            return select(selector, 1)[0] || null;
        };
    })();

    if (!String.prototype.trim) {
        (function () {
            String.prototype.trim = function () {
                return this.replace(re_trim, '');
            };
        })();
    }

    if (!window.JSON) {
        window.JSON = {
            parse: function parse(sJSON) {
                return new Function('', 'return ' + sJSON)();
            },
            stringify: function () {
                var r = /["]/g,
                    _f;
                return _f = function f(vContent) {
                    var result, i, j;
                    switch (result = typeof vContent === 'undefined' ? 'undefined' : _typeof(vContent)) {
                        case 'string':
                            return '"' + vContent.replace(r, '\\"') + '"';
                        case 'number':
                        case 'boolean':
                            return vContent.toString();
                        case 'undefined':
                            return 'undefined';
                        case 'function':
                            return '""';
                        case 'object':
                            if (!vContent) return 'null';
                            result = '';
                            if (vContent.splice) {
                                for (i = 0, j = vContent.length; i < j; i++) {
                                    result += ',' + _f(vContent[i]);
                                }return '[' + result.substr(1) + ']';
                            } else {
                                for (i in vContent) {
                                    if (vContent.hasOwnProperty(i) && vContent[i] !== undefined && typeof vContent[i] != 'function') result += ',"' + i + '":' + _f(vContent[i]);
                                }return '{' + result.substr(1) + '}';
                            }
                    }
                };
            }()
        };
    }

    // splice ie8 <= polyfill
    (function () {
        if (!document.documentMode || document.documentMode >= 9) return false;
        var _splice = Array.prototype.splice;
        Array.prototype.splice = function () {
            var args = Array.prototype.slice.call(arguments);
            if (typeof args[1] === "undefined") args[1] = this.length - args[0];
            return _splice.apply(this, args);
        };
    })();

    /**
     * Shim for "fixing" IE's lack of support (IE < 9) for applying slice
     * on host objects like NamedNodeMap, NodeList, and HTMLCollection
     * (technically, since host objects have been implementation-dependent,
     * at least before ES6, IE hasn't needed to work this way).
     * Also works on strings, fixes IE < 9 to allow an explicit undefined
     * for the 2nd argument (as in Firefox), and prevents errors when
     * called on other DOM objects.
     */
    (function () {
        'use strict';

        var _slice = Array.prototype.slice;

        try {
            // Can't be used with DOM elements in IE < 9
            _slice.call(document.documentElement);
        } catch (e) {
            // Fails in IE < 9
            // This will work for genuine arrays, array-like objects,
            // NamedNodeMap (attributes, entities, notations),
            // NodeList (e.g., getElementsByTagName), HTMLCollection (e.g., childNodes),
            // and will not fail on other DOM objects (as do DOM elements in IE < 9)
            Array.prototype.slice = function (begin, end) {
                // IE < 9 gets unhappy with an undefined end argument
                end = typeof end !== 'undefined' ? end : this.length;

                // For native Array objects, we use the native slice function
                if (Object.prototype.toString.call(this) === '[object Array]') {
                    return _slice.call(this, begin, end);
                }

                // For array like object we handle it ourselves.
                var i,
                    cloned = [],
                    size,
                    len = this.length;

                // Handle negative value for "begin"
                var start = begin || 0;
                start = start >= 0 ? start : Math.max(0, len + start);

                // Handle negative value for "end"
                var upTo = typeof end == 'number' ? Math.min(end, len) : len;
                if (end < 0) {
                    upTo = len + end;
                }

                // Actual expected size of the slice
                size = upTo - start;

                if (size > 0) {
                    cloned = new Array(size);
                    if (this.charAt) {
                        for (i = 0; i < size; i++) {
                            cloned[i] = this.charAt(start + i);
                        }
                    } else {
                        for (i = 0; i < size; i++) {
                            cloned[i] = this[start + i];
                        }
                    }
                }

                return cloned;
            };
        }
    })();

    // Console-polyfill. MIT license. https://github.com/paulmillr/console-polyfill
    // Make it safe to do console.log() always.
    (function (con) {
        var prop, method;
        var empty = {};
        var dummy = function dummy() {};
        var properties = 'memory'.split(',');
        var methods = ('assert,clear,count,debug,dir,dirxml,error,exception,group,' + 'groupCollapsed,groupEnd,info,log,markTimeline,profile,profiles,profileEnd,' + 'show,table,time,timeEnd,timeline,timelineEnd,timeStamp,trace,warn').split(',');
        while (prop = properties.pop()) {
            con[prop] = con[prop] || empty;
        }while (method = methods.pop()) {
            con[method] = con[method] || dummy;
        }
    })(window.console || {}); // Using `this` for web workers.


    // Modernizr style test
    if (!(window.webkitMatchMedia || window.mozMatchMedia || window.oMatchMedia || window.msMatchMedia || window.matchMedia)) {
        var root = document.getElementsByTagName('html')[0];
        root.className += ' no-matchmedia';
    }

    /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas, David Knight. Dual MIT/BSD license */
    window.matchMedia || (window.matchMedia = function () {
        "use strict";

        // For browsers that support matchMedium api such as IE 9 and webkit

        var styleMedia = window.styleMedia || window.media;

        // For those that don't support matchMedium
        if (!styleMedia) {
            var style = document.createElement('style'),
                script = document.getElementsByTagName('script')[0],
                info = null;

            style.type = 'text/css';
            style.id = 'matchmediajs-test';

            script.parentNode.insertBefore(style, script);

            // 'style.currentStyle' is used by IE <= 8 and 'window.getComputedStyle' for all other browsers
            info = 'getComputedStyle' in window && window.getComputedStyle(style, null) || style.currentStyle;

            styleMedia = {
                matchMedium: function matchMedium(media) {
                    var text = '@media ' + media + '{ #matchmediajs-test { width: 1px; } }';

                    // 'style.styleSheet' is used by IE <= 8 and 'style.textContent' for all other browsers
                    if (style.styleSheet) {
                        style.styleSheet.cssText = text;
                    } else {
                        style.textContent = text;
                    }

                    // Test if media query is true or false
                    return info.width === '1px';
                }
            };
        }

        return function (media) {
            return {
                matches: styleMedia.matchMedium(media || 'all'),
                media: media || 'all'
            };
        };
    }());

    /*! matchMedia() polyfill addListener/removeListener extension. Author & copyright (c) 2012: Scott Jehl. Dual MIT/BSD license */
    (function () {
        // Bail out for browsers that have addListener support
        if (window.matchMedia && window.matchMedia('all').addListener) {
            return false;
        }

        var localMatchMedia = window.matchMedia,
            hasMediaQueries = localMatchMedia('only all').matches,
            isListening = false,
            timeoutID = 0,
            // setTimeout for debouncing 'handleChange'
        queries = [],
            // Contains each 'mql' and associated 'listeners' if 'addListener' is used
        handleChange = function handleChange(evt) {
            // Debounce
            clearTimeout(timeoutID);

            timeoutID = setTimeout(function () {
                for (var i = 0, il = queries.length; i < il; i++) {
                    var mql = queries[i].mql,
                        listeners = queries[i].listeners || [],
                        matches = localMatchMedia(mql.media).matches;

                    // Update mql.matches value and call listeners
                    // Fire listeners only if transitioning to or from matched state
                    if (matches !== mql.matches) {
                        mql.matches = matches;

                        for (var j = 0, jl = listeners.length; j < jl; j++) {
                            listeners[j].call(window, mql);
                        }
                    }
                }
            }, 30);
        };

        window.matchMedia = function (media) {
            var mql = localMatchMedia(media),
                listeners = [],
                index = 0;

            mql.addListener = function (listener) {
                // Changes would not occur to css media type so return now (Affects IE <= 8)
                if (!hasMediaQueries) {
                    return;
                }

                // Set up 'resize' listener for browsers that support CSS3 media queries (Not for IE <= 8)
                // There should only ever be 1 resize listener running for performance
                if (!isListening) {
                    isListening = true;
                    window.addEventListener('resize', handleChange, true);
                }

                // Push object only if it has not been pushed already
                if (index === 0) {
                    index = queries.push({
                        mql: mql,
                        listeners: listeners
                    });
                }

                listeners.push(listener);
            };

            mql.removeListener = function (listener) {
                for (var i = 0, il = listeners.length; i < il; i++) {
                    if (listeners[i] === listener) {
                        listeners.splice(i, 1);
                    }
                }
            };

            return mql;
        };
    })();

    // extend innerWidth ..
    var html = document.getElementsByTagName('html')[0];
    var body = document.getElementsByTagName('body')[0];

    /*
    if (!window.innerWidth) window.innerWidth = html.clientWidth;
    if (!window.innerHeight) window.innerHeight = html.clientHeight;
    if (!window.scrollX) window.scrollX = window.pageXOffset || html.scrollLeft;
    if (!window.scrollY) window.scrollY = window.pageYOffset || html.scrollTop;
    */
}).call(window);
/**
 * Refer to this by {@link ax5}.
 * @namespace ax5.ui
 */

/**
 * @class ax5.ui.root
 * @classdesc ax5 ui class
 * @author tom@axisj.com
 * @example
 * ```
 * var myui = new ax5.ui.root();
 * ```
 */
ax5.ui = function () {

    function axUi() {
        this.config = {};
        this.name = "root";

        /**
         * 클래스의 속성 정의 메소드 속성 확장후에 내부에 init 함수를 호출합니다.
         * @method ax5.ui.root.setConfig
         * @param {Object} config - 클래스 속성값
         * @param {Boolean} [callInit=true] - init 함수 호출 여부
         * @returns {ax5.ui.axUi}
         * @example
         * ```
         * var myui = new ax5.ui.root();
         * myui.setConfig({
        * 	id:"abcd"
        * });
         * ```
         */
        this.setConfig = function (cfg, callInit) {
            jQuery.extend(true, this.config, cfg);
            if (typeof callInit == "undefined" || callInit === true) {
                this.init();
            }
            return this;
        };
        this.init = function () {
            console.log(this.config);
        };

        this.bindWindowResize = function (callBack) {
            setTimeout(function () {
                jQuery(window).resize(function () {
                    if (this.bindWindowResize__) clearTimeout(this.bindWindowResize__);
                    this.bindWindowResize__ = setTimeout(function () {
                        callBack.call(this);
                    }.bind(this), 10);
                }.bind(this));
            }.bind(this), 100);
        };

        this.stopEvent = function (e) {
            if (e.preventDefault) e.preventDefault();
            if (e.stopPropagation) e.stopPropagation();
            e.cancelBubble = true;
            return false;
        };

        this.toString = function () {
            return this.name + '@' + this.version;
        };

        // instance init
        this.main = function () {}.apply(this, arguments);
    }

    /**
     * @method ax5.ui.addClass
     * @param {Object} config
     * @param {String} config.className - name of Class
     * @param {Object} [config.classStore=ax5.ui] - 클래스가 저장될 경로
     * @param {Function} [config.superClass=ax5.ui.root]
     * @param {Function} cls - Class Function
     */
    function addClass(config, cls) {
        if (!config || !config.className) throw 'invalid call';
        var classStore = config.classStore ? config.classStore : ax5.ui;
        if (!classStore) throw 'invalid classStore';

        // make ui definition variable
        ax5.def[config.className] = {
            version: ax5.info.version
        };

        var factory = function factory(cls, arg) {
            switch (arg.length) {
                case 0:
                    return new cls();
                    break;
                case 1:
                    return new cls(arg[0]);
                    break;
                case 2:
                    return new cls(arg[0], arg[1]);
                    break;
                case 3:
                    return new cls(arg[0], arg[1], arg[2]);
                    break;
            }
        };
        var initInstance = function initInstance(name, version, instance) {
            instance.name = name;
            instance.version = version;
            instance.instanceId = ax5.getGuid();
            return instance;
        };
        var initPrototype = function initPrototype(cls) {
            var superClass = config.superClass ? config.superClass : ax5.ui.root;
            if (!ax5.util.isFunction(superClass)) throw 'invalid superClass';
            superClass.call(this); // 부모호출
            cls.prototype = new superClass(); // 상속
        };
        var wrapper = function wrapper() {
            if (!this || !(this instanceof wrapper)) throw 'invalid call';
            var instance = factory(cls, arguments);
            return initInstance(config.className, config.version || "", instance);
        };
        initPrototype.call(this, cls);
        classStore[config.className] = wrapper;
    }

    return {
        root: axUi,
        addClass: addClass
    };
}();

/*!
 * mustache.js - Logic-less {{mustache}} templates with JavaScript
 * http://github.com/janl/mustache.js
 * https://github.com/thomasJang/mustache.js -- imporove some variables
 */

(function defineMustache(global, factory) {

    factory(global.mustache = {});
})(window.ax5, function mustacheFactory(mustache) {

    var objectToString = Object.prototype.toString;
    var isArray = Array.isArray || function isArrayPolyfill(object) {
        return objectToString.call(object) === '[object Array]';
    };

    function isFunction(object) {
        return typeof object === 'function';
    }

    /**
     * More correct typeof string handling array
     * which normally returns typeof 'object'
     */
    function typeStr(obj) {
        return isArray(obj) ? 'array' : typeof obj === 'undefined' ? 'undefined' : _typeof(obj);
    }

    function escapeRegExp(string) {
        return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
    }

    /**
     * Null safe way of checking whether or not an object,
     * including its prototype, has a given property
     */
    function hasProperty(obj, propName) {
        return obj != null && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object' && propName in obj;
    }

    // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577
    // See https://github.com/janl/mustache.js/issues/189
    var regExpTest = RegExp.prototype.test;

    function testRegExp(re, string) {
        return regExpTest.call(re, string);
    }

    var nonSpaceRe = /\S/;

    function isWhitespace(string) {
        return !testRegExp(nonSpaceRe, string);
    }

    var entityMap = {
        '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '/': '&#x2F;'
    };

    function escapeHtml(string) {
        return String(string).replace(/[&<>"'\/]/g, function fromEntityMap(s) {
            return entityMap[s];
        });
    }

    var whiteRe = /\s*/;
    var spaceRe = /\s+/;
    var equalsRe = /\s*=/;
    var curlyRe = /\s*\}/;
    var tagRe = /#|\^|\/|>|\{|&|=|!/;

    /**
     * Breaks up the given `template` string into a tree of tokens. If the `tags`
     * argument is given here it must be an array with two string values: the
     * opening and closing tags used in the template (e.g. [ "<%", "%>" ]). Of
     * course, the default is to use mustaches (i.e. mustache.tags).
     *
     * A token is an array with at least 4 elements. The first element is the
     * mustache symbol that was used inside the tag, e.g. "#" or "&". If the tag
     * did not contain a symbol (i.e. {{myValue}}) this element is "name". For
     * all text that appears outside a symbol this element is "text".
     *
     * The second element of a token is its "value". For mustache tags this is
     * whatever else was inside the tag besides the opening symbol. For text tokens
     * this is the text itself.
     *
     * The third and fourth elements of the token are the start and end indices,
     * respectively, of the token in the original template.
     *
     * Tokens that are the root node of a subtree contain two more elements: 1) an
     * array of tokens in the subtree and 2) the index in the original template at
     * which the closing tag for that section begins.
     */
    function parseTemplate(template, tags) {
        if (!template) return [];

        var sections = []; // Stack to hold section tokens
        var tokens = []; // Buffer to hold the tokens
        var spaces = []; // Indices of whitespace tokens on the current line
        var hasTag = false; // Is there a {{tag}} on the current line?
        var nonSpace = false; // Is there a non-space char on the current line?

        // Strips all whitespace tokens array for the current line
        // if there was a {{#tag}} on it and otherwise only space.
        function stripSpace() {
            if (hasTag && !nonSpace) {
                while (spaces.length) {
                    delete tokens[spaces.pop()];
                }
            } else {
                spaces = [];
            }

            hasTag = false;
            nonSpace = false;
        }

        var openingTagRe, closingTagRe, closingCurlyRe;

        function compileTags(tagsToCompile) {
            if (typeof tagsToCompile === 'string') tagsToCompile = tagsToCompile.split(spaceRe, 2);

            if (!isArray(tagsToCompile) || tagsToCompile.length !== 2) throw new Error('Invalid tags: ' + tagsToCompile);

            openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + '\\s*');
            closingTagRe = new RegExp('\\s*' + escapeRegExp(tagsToCompile[1]));
            closingCurlyRe = new RegExp('\\s*' + escapeRegExp('}' + tagsToCompile[1]));
        }

        compileTags(tags || mustache.tags);

        var scanner = new Scanner(template);

        var start, type, value, chr, token, openSection;
        while (!scanner.eos()) {
            start = scanner.pos;

            // Match any text between tags.
            value = scanner.scanUntil(openingTagRe);

            if (value) {
                for (var i = 0, valueLength = value.length; i < valueLength; ++i) {
                    chr = value.charAt(i);

                    if (isWhitespace(chr)) {
                        spaces.push(tokens.length);
                    } else {
                        nonSpace = true;
                    }

                    tokens.push(['text', chr, start, start + 1]);
                    start += 1;

                    // Check for whitespace on the current line.
                    if (chr === '\n') stripSpace();
                }
            }

            // Match the opening tag.
            if (!scanner.scan(openingTagRe)) break;

            hasTag = true;

            // Get the tag type.
            type = scanner.scan(tagRe) || 'name';
            scanner.scan(whiteRe);

            // Get the tag value.
            if (type === '=') {
                value = scanner.scanUntil(equalsRe);
                scanner.scan(equalsRe);
                scanner.scanUntil(closingTagRe);
            } else if (type === '{') {
                value = scanner.scanUntil(closingCurlyRe);
                scanner.scan(curlyRe);
                scanner.scanUntil(closingTagRe);
                type = '&';
            } else {
                value = scanner.scanUntil(closingTagRe);
            }

            // Match the closing tag.
            if (!scanner.scan(closingTagRe)) throw new Error('Unclosed tag at ' + scanner.pos);

            token = [type, value, start, scanner.pos];
            tokens.push(token);

            if (type === '#' || type === '^') {
                sections.push(token);
            } else if (type === '/') {
                // Check section nesting.
                openSection = sections.pop();

                if (!openSection) throw new Error('Unopened section "' + value + '" at ' + start);

                if (openSection[1] !== value) throw new Error('Unclosed section "' + openSection[1] + '" at ' + start);
            } else if (type === 'name' || type === '{' || type === '&') {
                nonSpace = true;
            } else if (type === '=') {
                // Set the tags for the next time around.
                compileTags(value);
            }
        }

        // Make sure there are no open sections when we're done.
        openSection = sections.pop();

        if (openSection) throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos);

        return nestTokens(squashTokens(tokens));
    }

    /**
     * Combines the values of consecutive text tokens in the given `tokens` array
     * to a single token.
     */
    function squashTokens(tokens) {
        var squashedTokens = [];

        var token, lastToken;
        for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
            token = tokens[i];

            if (token) {
                if (token[0] === 'text' && lastToken && lastToken[0] === 'text') {
                    lastToken[1] += token[1];
                    lastToken[3] = token[3];
                } else {
                    squashedTokens.push(token);
                    lastToken = token;
                }
            }
        }

        return squashedTokens;
    }

    /**
     * Forms the given array of `tokens` into a nested tree structure where
     * tokens that represent a section have two additional items: 1) an array of
     * all tokens that appear in that section and 2) the index in the original
     * template that represents the end of that section.
     */
    function nestTokens(tokens) {
        var nestedTokens = [];
        var collector = nestedTokens;
        var sections = [];

        var token, section;
        for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
            token = tokens[i];

            switch (token[0]) {
                case '#':
                case '^':
                    collector.push(token);
                    sections.push(token);
                    collector = token[4] = [];
                    break;
                case '/':
                    section = sections.pop();
                    section[5] = token[2];
                    collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens;
                    break;
                default:
                    collector.push(token);
            }
        }

        return nestedTokens;
    }

    /**
     * A simple string scanner that is used by the template parser to find
     * tokens in template strings.
     */
    function Scanner(string) {
        this.string = string;
        this.tail = string;
        this.pos = 0;
    }

    /**
     * Returns `true` if the tail is empty (end of string).
     */
    Scanner.prototype.eos = function eos() {
        return this.tail === '';
    };

    /**
     * Tries to match the given regular expression at the current position.
     * Returns the matched text if it can match, the empty string otherwise.
     */
    Scanner.prototype.scan = function scan(re) {
        var match = this.tail.match(re);

        if (!match || match.index !== 0) return '';

        var string = match[0];

        this.tail = this.tail.substring(string.length);
        this.pos += string.length;

        return string;
    };

    /**
     * Skips all text until the given regular expression can be matched. Returns
     * the skipped string, which is the entire tail if no match can be made.
     */
    Scanner.prototype.scanUntil = function scanUntil(re) {
        var index = this.tail.search(re),
            match;

        switch (index) {
            case -1:
                match = this.tail;
                this.tail = '';
                break;
            case 0:
                match = '';
                break;
            default:
                match = this.tail.substring(0, index);
                this.tail = this.tail.substring(index);
        }

        this.pos += match.length;

        return match;
    };

    /**
     * Represents a rendering context by wrapping a view object and
     * maintaining a reference to the parent context.
     */
    function Context(view, parentContext) {
        this.view = view;
        this.cache = {
            '.': this.view,
            '@each': function each() {
                var returns = [];
                for (var k in this) {
                    returns.push({ '@key': k, '@value': this[k] });
                }
                return returns;
            }
        };
        this.parent = parentContext;
    }

    /**
     * Creates a new context using the given view with this context
     * as the parent.
     */
    Context.prototype.push = function push(view) {
        return new Context(view, this);
    };

    /**
     * Returns the value of the given name in this context, traversing
     * up the context hierarchy if the value is absent in this context's view.
     */
    Context.prototype.lookup = function lookup(name) {
        var cache = this.cache;

        var value;
        if (cache.hasOwnProperty(name)) {
            value = cache[name];
        } else {
            var context = this,
                names,
                index,
                lookupHit = false;

            while (context) {
                if (name.indexOf('.') > 0) {
                    value = context.view;
                    names = name.split('.');
                    index = 0;

                    /**
                     * Using the dot notion path in `name`, we descend through the
                     * nested objects.
                     *
                     * To be certain that the lookup has been successful, we have to
                     * check if the last object in the path actually has the property
                     * we are looking for. We store the result in `lookupHit`.
                     *
                     * This is specially necessary for when the value has been set to
                     * `undefined` and we want to avoid looking up parent contexts.
                     **/
                    while (value != null && index < names.length) {
                        if (index === names.length - 1) lookupHit = hasProperty(value, names[index]);

                        value = value[names[index++]];
                    }
                } else {
                    value = context.view[name];
                    lookupHit = hasProperty(context.view, name);
                }

                if (lookupHit) break;

                context = context.parent;
            }

            cache[name] = value;
        }

        if (isFunction(value)) value = value.call(this.view);

        return value;
    };

    /**
     * A Writer knows how to take a stream of tokens and render them to a
     * string, given a context. It also maintains a cache of templates to
     * avoid the need to parse the same template twice.
     */
    function Writer() {
        this.cache = {};
    }

    /**
     * Clears all cached templates in this writer.
     */
    Writer.prototype.clearCache = function clearCache() {
        this.cache = {};
    };

    /**
     * Parses and caches the given `template` and returns the array of tokens
     * that is generated from the parse.
     */
    Writer.prototype.parse = function parse(template, tags) {
        var cache = this.cache;
        var tokens = cache[template];

        if (tokens == null) tokens = cache[template] = parseTemplate(template, tags);

        return tokens;
    };

    /**
     * High-level method that is used to render the given `template` with
     * the given `view`.
     *
     * The optional `partials` argument may be an object that contains the
     * names and templates of partials that are used in the template. It may
     * also be a function that is used to load partial templates on the fly
     * that takes a single argument: the name of the partial.
     */
    Writer.prototype.render = function render(template, view, partials) {
        var tokens = this.parse(template);
        var context = view instanceof Context ? view : new Context(view);
        return this.renderTokens(tokens, context, partials, template);
    };

    /**
     * Low-level method that renders the given array of `tokens` using
     * the given `context` and `partials`.
     *
     * Note: The `originalTemplate` is only ever used to extract the portion
     * of the original template that was contained in a higher-order section.
     * If the template doesn't use higher-order sections, this argument may
     * be omitted.
     */
    Writer.prototype.renderTokens = function renderTokens(tokens, context, partials, originalTemplate) {
        var buffer = '';
        var token, symbol, value;
        for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
            value = undefined;
            token = tokens[i];
            symbol = token[0];

            if (symbol === '#') value = this.renderSection(token, context, partials, originalTemplate);else if (symbol === '^') value = this.renderInverted(token, context, partials, originalTemplate);else if (symbol === '>') value = this.renderPartial(token, context, partials, originalTemplate);else if (symbol === '&') value = this.unescapedValue(token, context);else if (symbol === 'name') value = this.escapedValue(token, context);else if (symbol === 'text') value = this.rawValue(token);

            if (value !== undefined) buffer += value;
        }

        return buffer;
    };

    Writer.prototype.renderSection = function renderSection(token, context, partials, originalTemplate) {
        var self = this;
        var buffer = '';

        var value = context.lookup(token[1]);

        // This function is used to render an arbitrary template
        // in the current context by higher-order sections.
        function subRender(template) {
            return self.render(template, context, partials);
        }

        if (!value) return;

        if (isArray(value)) {
            for (var j = 0, valueLength = value.length; j < valueLength; ++j) {
                if (value[j]) {
                    if (_typeof(value[j]) === 'object') {
                        value[j]['@i'] = j;
                        value[j]['@first'] = j === 0;
                    }

                    buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate);
                }
            }
        } else if ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' || typeof value === 'string' || typeof value === 'number') {
            buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate);
        } else if (isFunction(value)) {
            if (typeof originalTemplate !== 'string') throw new Error('Cannot use higher-order sections without the original template');

            // Extract the portion of the original template that the section contains.
            value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender);

            if (value != null) buffer += value;
        } else {
            buffer += this.renderTokens(token[4], context, partials, originalTemplate);
        }
        return buffer;
    };

    Writer.prototype.renderInverted = function renderInverted(token, context, partials, originalTemplate) {
        var value = context.lookup(token[1]);

        // Use JavaScript's definition of falsy. Include empty arrays.
        // See https://github.com/janl/mustache.js/issues/186
        if (!value || isArray(value) && value.length === 0) return this.renderTokens(token[4], context, partials, originalTemplate);
    };

    Writer.prototype.renderPartial = function renderPartial(token, context, partials) {
        if (!partials) return;

        var value = isFunction(partials) ? partials(token[1]) : partials[token[1]];
        if (value != null) return this.renderTokens(this.parse(value), context, partials, value);
    };

    Writer.prototype.unescapedValue = function unescapedValue(token, context) {
        var value = context.lookup(token[1]);
        if (value != null) return value;
    };

    Writer.prototype.escapedValue = function escapedValue(token, context) {
        var value = context.lookup(token[1]);
        if (value != null) return mustache.escape(value);
    };

    Writer.prototype.rawValue = function rawValue(token) {
        return token[1];
    };

    mustache.name = 'mustache.js';
    mustache.version = '2.1.3';
    mustache.tags = ['{{', '}}'];

    // All high-level mustache.* functions use this writer.
    var defaultWriter = new Writer();

    /**
     * Clears all cached templates in the default writer.
     */
    mustache.clearCache = function clearCache() {
        return defaultWriter.clearCache();
    };

    /**
     * Parses and caches the given template in the default writer and returns the
     * array of tokens it contains. Doing this ahead of time avoids the need to
     * parse templates on the fly as they are rendered.
     */
    mustache.parse = function parse(template, tags) {
        return defaultWriter.parse(template, tags);
    };

    /**
     * Renders the `template` with the given `view` and `partials` using the
     * default writer.
     */
    mustache.render = function render(template, view, partials) {
        if (typeof template !== 'string') {
            throw new TypeError('Invalid template! Template should be a "string" ' + 'but "' + typeStr(template) + '" was given as the first ' + 'argument for mustache#render(template, view, partials)');
        }

        return defaultWriter.render(template, view, partials);
    };

    // This is here for backwards compatibility with 0.4.x.,
    /*eslint-disable */ // eslint wants camel cased function name
    mustache.to_html = function to_html(template, view, partials, send) {
        /*eslint-enable*/

        var result = mustache.render(template, view, partials);

        if (isFunction(send)) {
            send(result);
        } else {
            return result;
        }
    };

    // Export the escaping function so that the user may override it.
    // See https://github.com/janl/mustache.js/issues/244
    mustache.escape = escapeHtml;

    // Export these mainly for testing, but also for advanced usage.
    mustache.Scanner = Scanner;
    mustache.Context = Context;
    mustache.Writer = Writer;
});
"use strict";

// ax5.ui.dialog
(function () {

    var UI = ax5.ui;
    var U = ax5.util;
    var DIALOG = void 0;

    UI.addClass({
        className: "dialog"
    }, function () {
        /**
         * @class ax5dialog
         * @classdesc
         * @author tom@axisj.com
         * @example
         * ```js
         * var dialog = new ax5.ui.dialog();
         * var mask = new ax5.ui.mask();
         * dialog.setConfig({
         *     zIndex: 5000,
         *     onStateChanged: function () {
         *         if (this.state === "open") {
         *             mask.open();
         *         }
         *         else if (this.state === "close") {
         *             mask.close();
         *         }
         *     }
         * });
         *
         * dialog.alert({
         *     theme: 'default',
         *     title: 'Alert default',
         *     msg: theme + ' color'
         * }, function () {
         *     console.log(this);
         * });
         * ```
         */
        var ax5dialog = function ax5dialog() {
            var self = this,
                cfg = void 0;

            this.instanceId = ax5.getGuid();
            this.config = {
                id: 'ax5-dialog-' + this.instanceId,
                clickEventName: "click", //(('ontouchstart' in document.documentElement) ? "touchend" : "click"),
                theme: 'default',
                width: 300,
                title: '',
                msg: '',
                lang: {
                    "ok": "ok", "cancel": "cancel"
                },
                animateTime: 150,
                autoCloseTime: 0
            };
            this.activeDialog = null;
            this.autoCloseTimer = null;
            this.queue = [];

            cfg = this.config;

            var onStateChanged = function onStateChanged(opts, that) {
                if (opts && opts.onStateChanged) {
                    opts.onStateChanged.call(that, that);
                } else if (this.onStateChanged) {
                    this.onStateChanged.call(that, that);
                }

                that = null;
                return true;
            };
            /**
             * @private ax5dialog.getContent
             * @param {String} dialogId
             * @param {Object} opts
             * @returns dialogDisplay
             */
            var getContent = function getContent(dialogId, opts) {

                var data = {
                    dialogId: dialogId,
                    title: opts.title || cfg.title || "",
                    msg: (opts.msg || cfg.msg || "").replace(/\n/g, "<br/>"),
                    input: opts.input,
                    btns: opts.btns,
                    '_crlf': function _crlf() {
                        return this.replace(/\n/g, "<br/>");
                    },
                    additionalContent: function (additionalContent) {
                        if (U.isFunction(additionalContent)) {
                            return additionalContent.call(opts);
                        } else {
                            return additionalContent;
                        }
                    }(opts.additionalContent)
                };

                try {
                    return DIALOG.tmpl.get.call(this, "dialogDisplay", data);
                } finally {
                    data = null;
                }
            };
            /**
             * @private ax5dialog.open
             * @param {Object} opts
             * @param callback
             */
            var open = function open(opts, callback) {
                var pos = {},
                    box = void 0;

                opts.id = opts.id || cfg.id;

                box = {
                    width: opts.width
                };
                jQuery(document.body).append(getContent.call(this, opts.id, opts));

                this.dialogConfig = opts;
                this.activeDialog = jQuery('#' + opts.id);
                this.activeDialog.css({ width: box.width });

                if (typeof callback === "undefined") {
                    callback = opts.callback;
                }

                // dialog 높이 구하기 - 너비가 정해지면 높이가 변경 될 것.
                opts.height = box.height = this.activeDialog.height();

                //- position 정렬
                if (typeof opts.position === "undefined" || opts.position === "center") {
                    pos.top = jQuery(window).height() / 2 - box.height / 2;
                    pos.left = jQuery(window).width() / 2 - box.width / 2;
                } else {
                    pos.left = opts.position.left || 0;
                    pos.top = opts.position.top || 0;
                }
                if (cfg.zIndex) {
                    pos["z-index"] = cfg.zIndex;
                }
                this.activeDialog.css(pos);

                // bind button event
                if (opts.dialogType === "prompt") {
                    this.activeDialog.find("[data-dialog-prompt]").get(0).focus();
                } else {
                    this.activeDialog.find("[data-dialog-btn]").get(0).focus();
                }

                this.activeDialog.find("[data-dialog-btn]").on(cfg.clickEventName, function (e) {
                    btnOnClick.call(this, e || window.event, opts, callback);
                }.bind(this));

                // bind key event
                jQuery(window).bind("keydown.ax5dialog", function (e) {
                    onKeyup.call(this, e || window.event, opts, callback);
                }.bind(this));

                jQuery(window).bind("resize.ax5dialog", function (e) {
                    align.call(this, e || window.event);
                }.bind(this));

                onStateChanged.call(this, opts, {
                    self: this,
                    state: "open"
                });

                if (opts.autoCloseTime) {
                    this.autoCloseTimer = setTimeout(function () {
                        self.close();
                    }, opts.autoCloseTime);
                }

                pos = null;
                box = null;
            };
            var align = function align(e) {
                if (!this.activeDialog) return this;
                var opts = self.dialogConfig,
                    box = {
                    width: opts.width,
                    height: opts.height
                };

                //- position 정렬
                if (typeof opts.position === "undefined" || opts.position === "center") {
                    box.top = window.innerHeight / 2 - box.height / 2;
                    box.left = window.innerWidth / 2 - box.width / 2;
                } else {
                    box.left = opts.position.left || 0;
                    box.top = opts.position.top || 0;
                }
                if (box.left < 0) box.left = 0;
                if (box.top < 0) box.top = 0;

                this.activeDialog.css(box);

                opts = null;
                box = null;

                return this;
            };
            var btnOnClick = function btnOnClick(e, opts, callback, target, k) {
                var that = void 0,
                    emptyKey = null;

                if (e.srcElement) e.target = e.srcElement;

                target = U.findParentNode(e.target, function (target) {
                    if (target.getAttribute("data-dialog-btn")) {
                        return true;
                    }
                });

                if (target) {
                    k = target.getAttribute("data-dialog-btn");

                    that = {
                        self: this,
                        key: k, value: opts.btns[k],
                        dialogId: opts.id,
                        btnTarget: target
                    };
                    if (opts.dialogType === "prompt") {
                        that.input = {};
                        for (var oi in opts.input) {
                            that.input[oi] = this.activeDialog.find('[data-dialog-prompt=' + oi + ']').val();
                            if (opts.input[oi].required && (that.input[oi] == "" || that.input[oi] == null)) {
                                emptyKey = oi;
                                break;
                            }
                        }
                    }
                    if (opts.btns[k].onClick) {
                        opts.btns[k].onClick.call(that, k);
                    } else if (opts.dialogType === "alert") {
                        if (callback) callback.call(that, k);
                        this.close({ doNotCallback: true });
                    } else if (opts.dialogType === "confirm") {
                        if (callback) callback.call(that, k);
                        this.close({ doNotCallback: true });
                    } else if (opts.dialogType === "prompt") {
                        if (k === 'ok') {
                            if (emptyKey) {
                                this.activeDialog.find('[data-dialog-prompt="' + emptyKey + '"]').get(0).focus();
                                return false;
                            }
                        }
                        if (callback) callback.call(that, k);
                        this.close({ doNotCallback: true });
                    }
                }

                that = null;
                opts = null;
                callback = null;
                target = null;
                k = null;
            };
            var onKeyup = function onKeyup(e, opts, callback, target, k) {
                var that = void 0,
                    emptyKey = null;

                if (e.keyCode == ax5.info.eventKeys.ESC) {
                    this.close();
                }
                if (opts.dialogType === "prompt") {
                    if (e.keyCode == ax5.info.eventKeys.RETURN) {
                        that = {
                            self: this,
                            key: k, value: opts.btns[k],
                            dialogId: opts.id,
                            btnTarget: target
                        };
                        that.input = {};

                        for (var oi in opts.input) {
                            that.input[oi] = this.activeDialog.find('[data-dialog-prompt=' + oi + ']').val();
                            if (opts.input[oi].required && (that.input[oi] == "" || that.input[oi] == null)) {
                                emptyKey = oi;
                                break;
                            }
                        }
                        if (emptyKey) {
                            that = null;
                            emptyKey = null;
                            return false;
                        }
                        if (callback) callback.call(that, k);
                        this.close({ doNotCallback: true });
                    }
                }

                that = null;
                emptyKey = null;
                opts = null;
                callback = null;
                target = null;
                k = null;
            };

            /**
             * Preferences of dialog UI
             * @method ax5dialog.setConfig
             * @param {Object} config - 클래스 속성값
             * @param {String} [config.theme="default"]
             * @param {Number} [config.width=300]
             * @param {String} [config.title=""]
             * @param {Number} [config.zIndex]
             * @param {Function} [config.onStateChanged] - `onStateChanged` function can be defined in setConfig method or new ax5.ui.dialog initialization method. However, you can us to define an
             * event function after initialization, if necessary
             * @param {Object} [config.lang]
             * @param {String} [config.lang.ok="ok"]
             * @param {String} [config.lang.cancel="cancel"]
             * @param {Number} [config.animateTime=150]
             * @param {Number} [config.autoCloseTime=0] - 0보다 크면 autoCloseTime 프레임후에 dialog auto close
             * @returns {ax5dialog}
             * @example
             * ```
             * var dialog = new ax5.ui.dialog();
             * dialog.setConfig({
             *      title: "app dialog title",
             *      zIndex: 5000,
             *      onStateChanged: function () {
             *          if (this.state === "open") {
             *              mask.open();
             *          }
             *          else if (this.state === "close") {
             *              mask.close();
             *          }
             *      }
             * });
             * ```
             */
            //== class body start
            this.init = function () {
                this.onStateChanged = cfg.onStateChanged;
                // this.onLoad = cfg.onLoad;
            };

            /**
             * open the dialog of alert type
             * @method ax5dialog.alert
             * @param {Object|String} config - dialog 속성을 json으로 정의하거나 msg만 전달
             * @param {String} [config.theme="default"]
             * @param {Number} [config.width=300]
             * @param {String} [config.title=""]
             * @param {Number} [config.zIndex]
             * @param {Function} [config.onStateChanged]
             * @param {Object} [config.lang]
             * @param {String} [config.lang.ok="ok"]
             * @param {String} [config.lang.cancel="cancel"]
             * @param {Number} [config.animateTime=150]
             * @param {Number} [config.autoCloseTime=0] - 0보다 크면 autoCloseTime 프레임후에 dialog auto close
             * @param {Function|String} [config.additionalContent]
             * @param {Function} [callback] - 사용자 확인 이벤트시 호출될 callback 함수
             * @returns {ax5dialog}
             * @example
             * ```
             * myDialog.alert({
             *  title: 'app title',
             *  msg: 'alert'
             * }, function(){});
             * ```
             */
            this.alert = function (opts, callback, tryCount) {
                if (U.isString(opts)) {
                    opts = {
                        title: cfg.title,
                        msg: opts
                    };
                }

                opts = jQuery.extend(true, {}, cfg, opts);
                opts.dialogType = "alert";
                opts.theme = opts.theme || cfg.theme || "";
                opts.callback = callback;

                if (typeof opts.btns === "undefined") {
                    opts.btns = {
                        ok: { label: cfg.lang["ok"], theme: opts.theme }
                    };
                }

                if (this.activeDialog) {
                    this.queue.push(opts);
                } else {
                    open.call(this, opts, callback);
                }
                return this;
            };

            /**
             * open the dialog of confirm type
             * @method ax5dialog.confirm
             * @param {Object|String} config - dialog 속성을 json으로 정의하거나 msg만 전달
             * @param {String} [config.theme="default"]
             * @param {Number} [config.width=300]
             * @param {String} [config.title=""]
             * @param {Number} [config.zIndex]
             * @param {Function} [config.onStateChanged]
             * @param {Object} [config.lang]
             * @param {String} [config.lang.ok="ok"]
             * @param {String} [config.lang.cancel="cancel"]
             * @param {Number} [config.animateTime=150]
             * @param {Number} [config.autoCloseTime=0] - 0보다 크면 autoCloseTime 프레임후에 dialog auto close
             * @param {Function|String} [config.additionalContent]
             * @param {Function} [callback] - 사용자 확인 이벤트시 호출될 callback 함수
             * @returns {ax5dialog}
             * @example
             * ```
             * myDialog.confirm({
             *      title: 'app title',
             *      msg: 'confirm',
             *      additionalContent: function () {
             *          return "<div style='border:1px solid #ccc;border-radius: 5px;background: #eee;padding: 10px;'>추가정보</div>";
             *      }
             * }, function(){});
             * ```
             */
            this.confirm = function (opts, callback, tryCount) {
                if (U.isString(opts)) {
                    opts = {
                        title: cfg.title,
                        msg: opts
                    };
                }

                opts = jQuery.extend(true, {}, cfg, opts);
                opts.dialogType = "confirm";
                opts.theme = opts.theme || cfg.theme || "";
                opts.callback = callback;

                if (typeof opts.btns === "undefined") {
                    opts.btns = {
                        ok: { label: cfg.lang["ok"], theme: opts.theme },
                        cancel: { label: cfg.lang["cancel"] }
                    };
                }

                if (this.activeDialog) {
                    this.queue.push(opts);
                } else {
                    open.call(this, opts, callback);
                }

                return this;
            };

            /**
             * open the dialog of prompt type
             * @method ax5dialog.prompt
             * @param {Object|String} config - dialog 속성을 json으로 정의하거나 msg만 전달
             * @param {String} [config.theme="default"]
             * @param {Number} [config.width=300]
             * @param {String} [config.title=""]
             * @param {Number} [config.zIndex]
             * @param {Function} [config.onStateChanged]
             * @param {Object} [config.lang]
             * @param {String} [config.lang.ok="ok"]
             * @param {String} [config.lang.cancel="cancel"]
             * @param {Number} [config.animateTime=150]
             * @param {Number} [config.autoCloseTime=0] - 0보다 크면 autoCloseTime 프레임후에 dialog auto close
             * @param {Function|String} [config.additionalContent]
             * @param {Function} [callback] - 사용자 확인 이벤트시 호출될 callback 함수
             * @returns {ax5dialog}
             * @example
             * ```
             * myDialog.prompt({
             *  title: 'app title',
             *  msg: 'alert'
             * }, function(){});
             * ```
             */
            this.prompt = function (opts, callback, tryCount) {
                if (U.isString(opts)) {
                    opts = {
                        title: cfg.title,
                        msg: opts
                    };
                }

                opts = jQuery.extend(true, {}, cfg, opts);
                opts.dialogType = "prompt";
                opts.theme = opts.theme || cfg.theme || "";
                opts.callback = callback;

                if (typeof opts.input === "undefined") {
                    opts.input = {
                        value: { label: "" }
                    };
                }
                if (typeof opts.btns === "undefined") {
                    opts.btns = {
                        ok: { label: cfg.lang["ok"], theme: opts.theme },
                        cancel: { label: cfg.lang["cancel"] }
                    };
                }

                if (this.activeDialog) {
                    this.queue.push(opts);
                } else {
                    open.call(this, opts, callback);
                }

                return this;
            };

            /**
             * close the dialog
             * @method ax5dialog.close
             * @returns {ax5dialog}
             * @example
             * ```
             * myDialog.close();
             * ```
             */
            this.close = function (_option) {
                var opts = void 0,
                    that = void 0;

                if (this.activeDialog) {
                    if (this.autoCloseTimer) clearTimeout(this.autoCloseTimer);

                    opts = self.dialogConfig;

                    this.activeDialog.addClass("destroy");
                    jQuery(window).unbind("keydown.ax5dialog");
                    jQuery(window).unbind("resize.ax5dialog");

                    setTimeout(function () {
                        if (this.activeDialog) {
                            this.activeDialog.remove();
                            this.activeDialog = null;
                        }

                        that = {
                            self: this,
                            state: "close",
                            dialogId: opts.id
                        };

                        if (opts.callback && (!_option || !_option.doNotCallback)) {
                            opts.callback.call(that);
                        }

                        if (opts && opts.onStateChanged) {
                            opts.onStateChanged.call(that, that);
                        } else if (this.onStateChanged) {
                            this.onStateChanged.call(that, that);
                        }

                        if (this.queue && this.queue.length) {
                            open.call(this, this.queue.shift());
                        }

                        opts = null;
                        that = null;
                    }.bind(this), cfg.animateTime);
                }
                return this;
            };

            // 클래스 생성자
            this.main = function () {

                UI.dialog_instance = UI.dialog_instance || [];
                UI.dialog_instance.push(this);

                if (arguments && U.isObject(arguments[0])) {
                    this.setConfig(arguments[0]);
                }
            }.apply(this, arguments);
        };
        return ax5dialog;
    }());

    DIALOG = ax5.ui.dialog;
})();

// ax5.ui.dialog.tmpl
(function () {

    var DIALOG = ax5.ui.dialog;

    var dialogDisplay = function dialogDisplay(columnKeys) {
        return " \n        <div id=\"{{dialogId}}\" data-dialog-els=\"root\" class=\"ax5-ui-dialog {{theme}}\">\n            <div class=\"ax-dialog-header\" data-dialog-els=\"header\">\n                {{{title}}}\n            </div>\n            <div class=\"ax-dialog-body\" data-dialog-els=\"body\">\n                <div class=\"ax-dialog-msg\">{{{msg}}}</div>\n                \n                {{#input}}\n                <div class=\"ax-dialog-prompt\">\n                    {{#@each}}\n                    <div class=\"form-group\">\n                    {{#@value.label}}\n                    <label>{{#_crlf}}{{{.}}}{{/_crlf}}</label>\n                    {{/@value.label}}\n                    <input type=\"{{@value.type}}\" placeholder=\"{{@value.placeholder}}\" class=\"form-control {{@value.theme}}\" data-dialog-prompt=\"{{@key}}\" style=\"width:100%;\" value=\"{{@value.value}}\" />\n                    {{#@value.help}}\n                    <p class=\"help-block\">{{#_crlf}}{{.}}{{/_crlf}}</p>\n                    {{/@value.help}}\n                    </div>\n                    {{/@each}}\n                </div>\n                {{/input}}\n                \n                <div class=\"ax-dialog-buttons\" data-dialog-els=\"buttons\">\n                    <div class=\"ax-button-wrap\">\n                    {{#btns}}\n                        {{#@each}}\n                        <button type=\"button\" data-dialog-btn=\"{{@key}}\" class=\"btn btn-{{@value.theme}}\">{{@value.label}}</button>\n                        {{/@each}}\n                    {{/btns}}\n                    </div>\n                </div>\n                \n                {{#additionalContent}}\n                <div data-dialog-els=\"additional-content\">{{{.}}}</div>\n                {{/additionalContent}}\n            </div>\n        </div>  \n        ";
    };

    DIALOG.tmpl = {
        "dialogDisplay": dialogDisplay,
        get: function get(tmplName, data, columnKeys) {
            return ax5.mustache.render(DIALOG.tmpl[tmplName].call(this, columnKeys), data);
        }
    };
})();
'use strict';

// ax5.ui.mask
(function () {

    var UI = ax5.ui;
    var U = ax5.util;
    var MASK = void 0;

    UI.addClass({
        className: "mask"
    }, function () {
        /**
         * @class ax5mask
         * @classdesc
         * @author tom@axisj.com
         * @example
         * ```js
         * var customMask = function customMask() {
         *     var cTmpl = '' +
         *         '<div class="ax-mask" id="{{maskId}}" >' +
         *         '    <div class="ax-mask-bg" style="background-color:red !important;"></div>' +
         *         '    <div class="ax-mask-content">' +
         *         '        {{{body}}}' +
         *         '    </div>' +
         *         '</div>';
         *     return cTmpl;
         * };
         * ax5.ui.mask.tmpl.customMask = customMask;
         *
         * var mask = new ax5.ui.mask();
         *
         * mask.open({
         *     templateName: 'customMask',
         *     content: 'custom MASK on target',
         *     target: $("#user-content").get(0),
         *     onClick: function(){
         *         console.log(this);
         *     }
         * });
         * ```
         */
        return function () {
            var self = this,
                cfg = void 0;

            this.instanceId = ax5.getGuid();
            this.config = {
                theme: '',
                target: jQuery(document.body).get(0),
                animateTime: 250
            };
            this.maskContent = '';
            this.status = "off";

            cfg = this.config;

            var onStateChanged = function onStateChanged(opts, that) {
                if (opts && opts.onStateChanged) {
                    opts.onStateChanged.call(that, that);
                } else if (this.onStateChanged) {
                    this.onStateChanged.call(that, that);
                }

                opts = null;
                that = null;
                return true;
            };
            var getBodyTmpl = function getBodyTmpl(data) {
                if (typeof data.templateName === "undefined") data.templateName = "defaultMask";
                return MASK.tmpl.get.call(this, data.templateName, data);
            };
            var setBody = function setBody(content) {
                this.maskContent = content;
            };

            /**
             * Preferences of Mask UI
             * @method ax5mask.setConfig
             * @param {Object} config - 클래스 속성값
             * @returns {ax5mask}
             * @example
             * ```
             * setConfig({
             *      target : {Element|AX5 nodelist}, // 마스크 처리할 대상
             *      content : {String}, // 마스크안에 들어가는 내용물
             *      onStateChanged: function(){} // 마스크 상태변경 시 호출되는 함수 this.type으로 예외처리 가능
             * }
             * ```
             */
            this.init = function () {
                // after setConfig();
                this.onStateChanged = cfg.onStateChanged;
                this.onClick = cfg.onClick;
                if (this.config.content) setBody.call(this, this.config.content);
            };

            /**
             * open mask
             * target 을 주지 않으면 기본적으로 body 에 마스크가 적용되고 원하는 타겟을 지정해서 마스크를 씌울 수 있습니다.
             * 기본 정의된 마스크 외에 사용자가 템플릿을 정의해서 마스크를 사용 가능합니다.
             * @method ax5mask.open
             * @param {Object} config
             * @param {String} config
             * @returns {ax5mask}
             * @example
             * ```js
             * my_mask.open({
             *     target: document.body,
             *     content: "<h1>Loading..</h1>",
             *     onStateChanged: function () {
             *
             *     }
             * });
             *
             * my_mask.open({
             *     target: $("#mask-target").get(0), // dom Element
             *     content: "<h1>Loading..</h1>",
             *     onStateChanged: function () {
             *
             *     }
             * });
             *
             *
             * var customMask = function customMask() {
             *     var cTmpl = '' +
             *             '<div class="ax-mask" id="{{maskId}}" >' +
             *             '    <div class="ax-mask-bg" style="background-color:red   !important;"></div>' +
             *             '    <div class="ax-mask-content">' +
             *             '        {{{body}}}' +
             *             '    </div>' +
             *             '</div>';
             *     return cTmpl;
             * };
             * ax5.ui.mask.tmpl.customMask = customMask;
             *
             * my_mask.open({
             *     target: $("#mask-target").get(0), // dom Element
             *     content: "<h1>Loading..</h1>",
             *     
             *     onStateChanged: function () {
             *
             *     }
             * });
             * ```
             */
            this.open = function (options) {

                if (this.status === "on") this.close();
                if (options && options.content) setBody.call(this, options.content);
                if (options && typeof options.templateName === "undefined") options.templateName = "defaultMask";
                self.maskConfig = jQuery.extend(true, {}, this.config, options);

                var _cfg = self.maskConfig,
                    target = _cfg.target,
                    $target = jQuery(target),
                    maskId = 'ax-mask-' + ax5.getGuid(),
                    $mask = void 0,
                    css = {},
                    that = {},
                    templateName = _cfg.templateName,
                    body = getBodyTmpl({
                    theme: _cfg.theme,
                    maskId: maskId,
                    body: this.maskContent,
                    templateName: templateName
                });

                jQuery(document.body).append(body);

                // 마스크의 타겟이 html body 가 아니라면
                if (target && target !== jQuery(document.body).get(0)) {
                    css = {
                        position: _cfg.position || "absolute",
                        left: $target.offset().left,
                        top: $target.offset().top,
                        width: $target.outerWidth(),
                        height: $target.outerHeight()
                    };

                    $target.addClass("ax-masking");

                    // 마스크의 타겟이 html body가 아닌경우 window resize 이벤트를 추적하여 엘리먼트 마스크의 CSS 속성 변경
                    jQuery(window).on("resize.ax5mask-" + this.instanceId, function (_$target) {
                        this.align();
                    }.bind(this));
                }

                if (typeof self.maskConfig.zIndex !== "undefined") {
                    css["z-index"] = self.maskConfig.zIndex;
                }

                this.$mask = $mask = jQuery("#" + maskId);
                this.$target = $target;
                this.status = "on";
                $mask.css(css);

                if (_cfg.onClick) {
                    $mask.on("click", function (e) {
                        that = {
                            self: self,
                            state: "open",
                            type: "click"
                        };
                        self.maskConfig.onClick.call(that, that);
                    });
                }

                onStateChanged.call(this, null, {
                    self: this,
                    state: "open"
                });

                options = null;
                _cfg = null;
                target = null;
                $target = null;
                maskId = null;
                $mask = null;
                css = null;
                that = null;
                templateName = null;
                body = null;

                return this;
            };

            /**
             * close mask
             * @method ax5mask.close
             * @param {Number} [_delay=0]
             * @returns {ax5mask}
             * @example
             * ```
             * my_mask.close();
             * ```
             */
            this.close = function (_delay) {
                if (this.$mask) {
                    var _close = function _close() {
                        this.status = "off";
                        this.$mask.remove();
                        this.$target.removeClass("ax-masking");

                        onStateChanged.call(this, null, {
                            self: this,
                            state: "close"
                        });

                        jQuery(window).off("resize.ax5mask-" + this.instanceId);
                    };

                    if (_delay) {
                        setTimeout(function () {
                            _close.call(this);
                        }.bind(this), _delay);
                    } else {
                        _close.call(this);
                    }
                }
                return this;
            };

            /**
             * @method ax5mask.fadeOut
             * @returns {ax5mask}
             */
            this.fadeOut = function () {
                if (this.$mask) {
                    var _close = function _close() {
                        this.status = "off";
                        this.$mask.remove();
                        this.$target.removeClass("ax-masking");

                        onStateChanged.call(this, null, {
                            self: this,
                            state: "close"
                        });

                        jQuery(window).off("resize.ax5mask-" + this.instanceId);
                    };

                    this.$mask.addClass("fade-out");
                    setTimeout(function () {
                        _close.call(this);
                    }.bind(this), cfg.animateTime);
                }
                return this;
            };

            /**
             * @method ax5mask.align
             * @returns {ax5mask}
             */
            this.align = function () {
                if (this.maskConfig && this.maskConfig.target && this.maskConfig.target !== jQuery(document.body).get(0)) {
                    try {
                        var css = {
                            position: this.maskConfig.position || "absolute",
                            left: this.$target.offset().left,
                            top: this.$target.offset().top,
                            width: this.$target.outerWidth(),
                            height: this.$target.outerHeight()
                        };
                        this.$mask.css(css);
                    } catch (e) {}
                }
                return this;
            };

            this.pullRequest = function () {
                console.log("test pullRequest01");
                console.log("test pullRequest02");
            };

            // 클래스 생성자
            this.main = function () {

                UI.mask_instance = UI.mask_instance || [];
                UI.mask_instance.push(this);

                if (arguments && U.isObject(arguments[0])) {
                    this.setConfig(arguments[0]);
                }
            }.apply(this, arguments);
        };
    }());
    MASK = ax5.ui.mask;
})();
// ax5.ui.mask.tmpl
(function () {

    var MASK = ax5.ui.mask;

    var defaultMask = function defaultMask(columnKeys) {
        return '\n            <div class="ax-mask {{theme}}" id="{{maskId}}">\n                <div class="ax-mask-bg"></div>\n                <div class="ax-mask-content">\n                    <div class="ax-mask-body">\n                    {{{body}}}\n                    </div>\n                </div>\n            </div>\n        ';
    };

    MASK.tmpl = {
        "defaultMask": defaultMask,

        get: function get(tmplName, data, columnKeys) {
            return ax5.mustache.render(MASK.tmpl[tmplName].call(this, columnKeys), data);
        }
    };
})();
"use strict";

// ax5.ui.toast
(function () {

    var UI = ax5.ui;
    var U = ax5.util;
    var TOAST;

    UI.addClass({
        className: "toast"
    }, function () {
        /**
         * @class ax5toast
         * @classdesc
         * @author tom@axisj.com
         * @example
         * ```
         * ```js
         * var toast = new ax5.ui.toast();
         * toast.setConfig({
         *     icon: '<i class="fa fa-bug"></i>',
         *     containerPosition: "bottom-right",
         *     closeIcon: '<i class="fa fa-times"></i>'
         * });
         *
         * toast.onStateChanged = function(){
         *     console.log(this);
         * };
         *
         * toast.push({
         *     icon: '<i class="fa fa-book"></i>',
         *     msg:"999999"
         * });
         *
         * toast.push({
         *     theme: theme,
         *     msg: 'toast message'
         * });
         * ```
         */
        var ax5toast = function ax5toast() {
            var self = this,
                cfg,
                toastSeq = 0,
                toastSeqClear = null;

            this.instanceId = ax5.getGuid();
            this.config = {
                clickEventName: "click", //(('ontouchstart' in document.documentElement) ? "touchstart" : "click"),
                theme: 'default',
                width: 300,
                icon: '',
                closeIcon: '',
                msg: '',
                lang: {
                    "ok": "ok", "cancel": "cancel"
                },
                displayTime: 3000,
                animateTime: 250,
                containerPosition: "bottom-left"
            };
            this.toastContainer = null;
            this.queue = [];

            cfg = this.config;

            var onStateChanged = function onStateChanged(opts, that) {
                if (opts && opts.onStateChanged) {
                    opts.onStateChanged.call(that, that);
                } else if (this.onStateChanged) {
                    this.onStateChanged.call(that, that);
                }

                opts = null;
                that = null;
                return true;
            },

            /**
             * @method ax5toast.getContent
             * @param {String} toastId
             * @param {Object} opts
             * @returns toastDisplay
             * @example
             * ```js
             * ax5toast.getContent('ax5-toast-3-1', opts);
             * ```
             */
            getContent = function getContent(toastId, opts) {

                var data = {
                    toastId: toastId,
                    theme: opts.theme,
                    icon: opts.icon,
                    msg: (opts.msg || "").replace(/\n/g, "<br/>"),
                    btns: opts.btns,
                    closeIcon: opts.closeIcon
                };

                try {
                    return TOAST.tmpl.get.call(this, "toastDisplay", data);
                } finally {
                    toastId = null;
                    data = null;
                }
            },

            /**
             * @method ax5toast.open
             * @param opts
             * @param callBack
             * @returns {ax5toast}
             */
            open = function open(opts, callBack) {
                if (toastSeqClear) clearTimeout(toastSeqClear);

                var toastBox,
                    box = {
                    width: opts.width
                };

                opts.id = 'ax5-toast-' + self.containerId + '-' + ++toastSeq;
                if (jQuery('#' + opts.id).get(0)) return this;

                if (U.left(cfg.containerPosition, '-') == 'bottom') {
                    this.toastContainer.append(getContent(opts.id, opts));
                } else {
                    this.toastContainer.prepend(getContent(opts.id, opts));
                }

                toastBox = jQuery('#' + opts.id);
                toastBox.css({ width: box.width });
                opts.toastBox = toastBox;
                this.queue.push(opts);

                onStateChanged.call(this, opts, {
                    self: this,
                    state: "open",
                    toastId: opts.id
                });

                if (opts.toastType === "push") {
                    // 자동 제거 타이머 시작
                    setTimeout(function () {
                        this.close(opts, callBack);
                    }.bind(this), cfg.displayTime);

                    toastBox.find("[data-ax-toast-btn]").on(cfg.clickEventName, function (e) {
                        btnOnClick.call(this, e || window.event, opts, toastBox, callBack);
                    }.bind(this));
                } else if (opts.toastType === "confirm") {
                    toastBox.find("[data-ax-toast-btn]").on(cfg.clickEventName, function (e) {
                        btnOnClick.call(this, e || window.event, opts, toastBox, callBack);
                    }.bind(this));
                }

                box = null;
            },
                btnOnClick = function btnOnClick(e, opts, toastBox, callBack, target, k) {
                target = U.findParentNode(e.target, function (target) {
                    if (target.getAttribute("data-ax-toast-btn")) {
                        return true;
                    }
                });

                if (target) {
                    k = target.getAttribute("data-ax-toast-btn");

                    var that = {
                        key: k, value: opts.btns ? opts.btns[k] : k,
                        toastId: opts.id,
                        btn_target: target
                    };

                    if (opts.btns && opts.btns[k].onClick) {
                        opts.btns[k].onClick.call(that, k);
                    } else if (opts.toastType === "push") {
                        if (callBack) callBack.call(that, k);
                        this.close(opts, callBack);
                    } else if (opts.toastType === "confirm") {
                        if (callBack) callBack.call(that, k);
                        this.close(opts, callBack);
                    }
                }

                e = null;
                opts = null;
                toastBox = null;
                callBack = null;
                target = null;
                k = null;
            };

            /**
             * Preferences of toast UI
             * @method ax5toast.set_config
             * @param {Object} config - 클래스 속성값
             * @returns {ax5toast}
             * @example
             * ```
             * ```
             */
            //== class body start
            this.init = function () {
                this.onStateChanged = cfg.onStateChanged;
                // after set_config();
                self.containerId = ax5.getGuid();
                var styles = [];
                if (cfg.zIndex) {
                    styles.push("z-index:" + cfg.zIndex);
                }
                jQuery(document.body).append('<div class="ax5-ui-toast-container ' + cfg.containerPosition + '" data-toast-container="' + '' + self.containerId + '" style="' + styles.join(";") + '"></div>');
                this.toastContainer = jQuery('[data-toast-container="' + self.containerId + '"]');
            };

            /**
             * @method ax5toast.push
             * @param opts
             * @param callBack
             * @returns {ax5toast}
             */
            this.push = function (opts, callBack) {
                if (!self.containerId) {
                    this.init();
                }
                if (U.isString(opts)) {
                    opts = {
                        title: cfg.title,
                        msg: opts
                    };
                }
                opts.toastType = "push";

                self.dialogConfig = {};
                jQuery.extend(true, self.dialogConfig, cfg, opts);
                opts = self.dialogConfig;

                open.call(this, opts, callBack);

                opts = null;
                callBack = null;
                return this;
            };

            /**
             * @method ax5toast.confirm
             * @param opts
             * @param callBack
             * @returns {ax5toast}
             */
            this.confirm = function (opts, callBack) {
                if (!self.containerId) {
                    this.init();
                }
                if (U.isString(opts)) {
                    opts = {
                        title: cfg.title,
                        msg: opts
                    };
                }
                opts.toastType = "confirm";

                self.dialogConfig = {};
                jQuery.extend(true, self.dialogConfig, cfg, opts);
                opts = self.dialogConfig;

                if (typeof opts.btns === "undefined") {
                    opts.btns = {
                        ok: { label: cfg.lang["ok"], theme: opts.theme }
                    };
                }
                open.call(this, opts, callBack);

                opts = null;
                callBack = null;
                return this;
            };

            /**
             * close the toast
             * @method ax5toast.close
             * @returns {ax5toast}
             * @example
             * ```
             * my_toast.close();
             * ```
             */
            this.close = function (opts, callBack) {
                if (typeof opts === "undefined") {
                    opts = U.last(this.queue);
                }

                var toastBox = opts.toastBox;
                toastBox.addClass(opts.toastType == "push" ? "removed" : "destroy");
                this.queue = U.filter(this.queue, function () {
                    return opts.id != this.id;
                });
                setTimeout(function () {
                    var that = {
                        toastId: opts.id
                    };

                    toastBox.remove();
                    if (callBack) callBack.call(that);

                    that = {
                        self: this,
                        state: "close",
                        toastId: opts.id
                    };
                    onStateChanged.call(this, opts, that);

                    // 3초후에도 아무 일이 없다면 완전히 제거
                    if (this.queue.length === 0) {
                        if (toastSeqClear) clearTimeout(toastSeqClear);
                        toastSeqClear = setTimeout(function () {
                            /// console.log("try clear seq");
                            if (this.queue.length === 0) toastSeq = 0;
                        }.bind(this), 3000);
                    }

                    that = null;
                    opts = null;
                    callBack = null;
                    toastBox = null;
                }.bind(this), cfg.animateTime);

                return this;
            };

            // 클래스 생성자
            this.main = function () {

                UI.toast_instance = UI.toast_instance || [];
                UI.toast_instance.push(this);

                if (arguments && U.isObject(arguments[0])) {
                    this.setConfig(arguments[0]);
                }
            }.apply(this, arguments);
        };
        return ax5toast;
    }());
    TOAST = ax5.ui.toast;
})();
// ax5.ui.toast.tmpl
(function () {

    var TOAST = ax5.ui.toast;

    var toastDisplay = function toastDisplay(columnKeys) {
        return "\n        <div id=\"{{toastId}}\" data-ax5-ui=\"toast\" class=\"ax5-ui-toast {{theme}}\">\n            {{#icon}}\n            <div class=\"ax-toast-icon\">{{{.}}}</div>\n            {{/icon}}\n            <div class=\"ax-toast-body\">{{{msg}}}</div>\n            {{#btns}}\n            <div class=\"ax-toast-buttons\">\n                <div class=\"ax-button-wrap\">\n                    {{#@each}}\n                    <button type=\"button\" data-ax-toast-btn=\"{{@key}}\" class=\"btn btn-{{@value.theme}}\">{{{@value.label}}}</button>\n                    {{/@each}}\n                </div>\n            </div>\n            {{/btns}}\n            {{^btns}}\n                <a class=\"ax-toast-close\" data-ax-toast-btn=\"ok\">{{{closeIcon}}}</a>\n            {{/btns}}\n            <div style=\"clear:both;\"></div>\n        </div>";
    };

    TOAST.tmpl = {
        "toastDisplay": toastDisplay,

        get: function get(tmplName, data, columnKeys) {
            return ax5.mustache.render(TOAST.tmpl[tmplName].call(this, columnKeys), data);
        }
    };
})();
"use strict";

/*
 * Copyright (c) 2016. tom@axisj.com
 * - github.com/thomasjang
 * - www.axisj.com
 */

// ax5.ui.modal
(function () {

    var UI = ax5.ui;
    var U = ax5.util;
    var MODAL = void 0;

    UI.addClass({
        className: "modal"
    }, function () {
        /**
         * @class ax5modal
         * @alias ax5.ui.modal
         * @author tom@axisj.com
         */
        return function () {
            var self = this,
                cfg = void 0,
                ENM = {
                "mousedown": ax5.info.supportTouch ? "touchstart" : "mousedown",
                "mousemove": ax5.info.supportTouch ? "touchmove" : "mousemove",
                "mouseup": ax5.info.supportTouch ? "touchend" : "mouseup"
            },
                getMousePosition = function getMousePosition(e) {
                var mouseObj = e;
                if ('changedTouches' in e && e.changedTouches) {
                    mouseObj = e.changedTouches[0];
                }
                return {
                    clientX: mouseObj.clientX,
                    clientY: mouseObj.clientY
                };
            };

            this.instanceId = ax5.getGuid();
            this.config = {
                id: 'ax5-modal-' + this.instanceId,
                position: {
                    left: "center",
                    top: "middle",
                    margin: 10
                },
                minimizePosition: "bottom-right",
                clickEventName: 'ontouchstart' in document.documentElement ? "touchstart" : "click",
                theme: 'default',
                width: 300,
                height: 400,
                closeToEsc: true,
                disableDrag: false,
                disableResize: false,
                animateTime: 250,
                iframe: false
            };
            this.activeModal = null;
            this.watingModal = false;
            this.$ = {}; // UI inside of the jQuery object store

            cfg = this.config; // extended config copy cfg

            var onStateChanged = function onStateChanged(opts, that) {
                var eventProcessor = {
                    "resize": function resize(that) {
                        if (opts && opts.onResize) {
                            opts.onResize.call(that, that);
                        } else if (this.onResize) {
                            this.onResize.call(that, that);
                        }
                    },
                    "move": function move() {}
                };
                if (that.state in eventProcessor) {
                    eventProcessor[that.state].call(this, that);
                }

                if (opts && opts.onStateChanged) {
                    opts.onStateChanged.call(that, that);
                } else if (this.onStateChanged) {
                    this.onStateChanged.call(that, that);
                }
                return true;
            },
                getContent = function getContent(modalId, opts) {
                var data = {
                    modalId: modalId,
                    theme: opts.theme,
                    header: opts.header,
                    fullScreen: opts.fullScreen ? "fullscreen" : "",
                    styles: "",
                    iframe: opts.iframe,
                    iframeLoadingMsg: opts.iframeLoadingMsg,
                    disableResize: opts.disableResize
                };

                if (opts.zIndex) {
                    data.styles += "z-index:" + opts.zIndex + ";";
                }
                if (opts.absolute) {
                    data.styles += "position:absolute;";
                }

                if (data.iframe && typeof data.iframe.param === "string") {
                    data.iframe.param = ax5.util.param(data.iframe.param);
                }

                return MODAL.tmpl.get.call(this, "content", data, {});
            },
                open = function open(opts, callback) {
                var that = void 0;
                jQuery(document.body).append(getContent.call(this, opts.id, opts));

                this.activeModal = jQuery('#' + opts.id);
                // 파트수집
                this.$ = {
                    "root": this.activeModal,
                    "header": this.activeModal.find('[data-modal-els="header"]'),
                    "body": this.activeModal.find('[data-modal-els="body"]')
                };

                if (opts.iframe) {
                    this.$["iframe-wrap"] = this.activeModal.find('[data-modal-els="iframe-wrap"]');
                    this.$["iframe"] = this.activeModal.find('[data-modal-els="iframe"]');
                    this.$["iframe-form"] = this.activeModal.find('[data-modal-els="iframe-form"]');
                    this.$["iframe-loading"] = this.activeModal.find('[data-modal-els="iframe-loading"]');
                } else {
                    this.$["body-frame"] = this.activeModal.find('[data-modal-els="body-frame"]');
                }

                //- position 정렬
                this.align();

                that = {
                    self: this,
                    id: opts.id,
                    theme: opts.theme,
                    width: opts.width,
                    height: opts.height,
                    state: "open",
                    $: this.$
                };

                if (opts.iframe) {
                    this.$["iframe-wrap"].css({ height: opts.height });
                    this.$["iframe"].css({ height: opts.height });

                    // iframe content load
                    this.$["iframe-form"].attr({ "method": opts.iframe.method });
                    this.$["iframe-form"].attr({ "target": opts.id + "-frame" });
                    this.$["iframe-form"].attr({ "action": opts.iframe.url });
                    this.$["iframe"].on("load", function () {
                        that.state = "load";
                        if (opts.iframeLoadingMsg) {
                            this.$["iframe-loading"].hide();
                        }
                        onStateChanged.call(this, opts, that);
                    }.bind(this));
                    if (!opts.iframeLoadingMsg) {
                        this.$["iframe"].show();
                    }
                    this.$["iframe-form"].submit();
                }

                if (callback) callback.call(that, that);

                if (!this.watingModal) {
                    onStateChanged.call(this, opts, that);
                }

                // bind key event
                if (opts.closeToEsc) {
                    jQuery(window).bind("keydown.ax-modal", function (e) {
                        onkeyup.call(this, e || window.event);
                    }.bind(this));
                }

                jQuery(window).bind("resize.ax-modal", function (e) {
                    this.align(null, e || window.event);
                }.bind(this));

                this.$.header.off(ENM["mousedown"]).off("dragstart").on(ENM["mousedown"], function (e) {
                    /// 이벤트 필터링 추가 : 버튼엘리먼트로 부터 발생된 이벤트이면 moveModal 시작하지 않도록 필터링
                    var isButton = U.findParentNode(e.target, function (_target) {
                        if (_target.getAttribute("data-modal-header-btn")) {
                            return true;
                        }
                    });

                    if (!opts.isFullScreen && !isButton && opts.disableDrag != true) {
                        self.mousePosition = getMousePosition(e);
                        moveModal.on.call(self);
                    }
                    if (isButton) {
                        btnOnClick.call(self, e || window.event, opts);
                    }
                }).on("dragstart", function (e) {
                    U.stopEvent(e.originalEvent);
                    return false;
                });

                this.activeModal.off(ENM["mousedown"]).off("dragstart").on(ENM["mousedown"], "[data-ax5modal-resizer]", function (e) {
                    if (opts.disableDrag || opts.isFullScreen) return false;
                    self.mousePosition = getMousePosition(e);
                    resizeModal.on.call(self, this.getAttribute("data-ax5modal-resizer"));
                }).on("dragstart", function (e) {
                    U.stopEvent(e.originalEvent);
                    return false;
                });
            },
                btnOnClick = function btnOnClick(e, opts, callback, target, k) {
                var that = void 0;
                if (e.srcElement) e.target = e.srcElement;

                target = U.findParentNode(e.target, function (target) {
                    if (target.getAttribute("data-modal-header-btn")) {
                        return true;
                    }
                });

                if (target) {
                    k = target.getAttribute("data-modal-header-btn");

                    that = {
                        self: this,
                        key: k, value: opts.header.btns[k],
                        dialogId: opts.id,
                        btnTarget: target
                    };

                    if (opts.header.btns[k].onClick) {
                        opts.header.btns[k].onClick.call(that, k);
                    }
                }

                that = null;
                opts = null;
                callback = null;
                target = null;
                k = null;
            },
                onkeyup = function onkeyup(e) {
                if (e.keyCode == ax5.info.eventKeys.ESC) {
                    this.close();
                }
            },
                alignProcessor = {
                "top-left": function topLeft() {
                    this.align({ left: "left", top: "top" });
                },
                "top-right": function topRight() {
                    this.align({ left: "right", top: "top" });
                },
                "bottom-left": function bottomLeft() {
                    this.align({ left: "left", top: "bottom" });
                },
                "bottom-right": function bottomRight() {
                    this.align({ left: "right", top: "bottom" });
                },
                "center-middle": function centerMiddle() {
                    this.align({ left: "center", top: "middle" });
                }
            },
                moveModal = {
                "on": function on() {
                    var modalZIndex = this.activeModal.css("z-index"),
                        modalOffset = this.activeModal.position(),
                        modalBox = {
                        width: this.activeModal.outerWidth(), height: this.activeModal.outerHeight()
                    },
                        windowBox = {
                        width: jQuery(window).width(),
                        height: jQuery(window).height(),
                        scrollLeft: self.modalConfig.absolute ? 0 : jQuery(document).scrollLeft(),
                        scrollTop: self.modalConfig.absolute ? 0 : jQuery(document).scrollTop()
                    },
                        getResizerPosition = function getResizerPosition(e) {
                        self.__dx = e.clientX - self.mousePosition.clientX;
                        self.__dy = e.clientY - self.mousePosition.clientY;

                        if (minX > modalOffset.left + self.__dx) {
                            self.__dx = -modalOffset.left;
                        } else if (maxX < modalOffset.left + self.__dx) {
                            self.__dx = maxX - modalOffset.left;
                        }

                        if (minY > modalOffset.top + self.__dy) {
                            self.__dy = -modalOffset.top;
                        } else if (maxY < modalOffset.top + self.__dy) {
                            self.__dy = maxY - modalOffset.top;
                        }

                        return {
                            left: modalOffset.left + self.__dx + windowBox.scrollLeft,
                            top: modalOffset.top + self.__dy + windowBox.scrollTop
                        };
                    };

                    var minX = 0,
                        maxX = windowBox.width - modalBox.width,
                        minY = 0,
                        maxY = windowBox.height - modalBox.height;

                    self.__dx = 0; // 변화량 X
                    self.__dy = 0; // 변화량 Y

                    // self.resizerBg : body 가 window보다 작을 때 문제 해결을 위한 DIV
                    self.resizerBg = jQuery('<div class="ax5modal-resizer-background" ondragstart="return false;"></div>');
                    self.resizer = jQuery('<div class="ax5modal-resizer" ondragstart="return false;"></div>');
                    self.resizerBg.css({ zIndex: modalZIndex });
                    self.resizer.css({
                        left: modalOffset.left + windowBox.scrollLeft,
                        top: modalOffset.top + windowBox.scrollTop,
                        width: modalBox.width,
                        height: modalBox.height,
                        zIndex: modalZIndex + 1
                    });

                    jQuery(document.body).append(self.resizerBg).append(self.resizer);
                    self.activeModal.addClass("draged");

                    jQuery(document.body).on(ENM["mousemove"] + ".ax5modal-move-" + this.instanceId, function (e) {
                        self.resizer.css(getResizerPosition(e));
                    }).on(ENM["mouseup"] + ".ax5modal-move-" + this.instanceId, function (e) {
                        moveModal.off.call(self);
                    }).on("mouseleave.ax5modal-move-" + this.instanceId, function (e) {
                        moveModal.off.call(self);
                    });

                    jQuery(document.body).attr('unselectable', 'on').css('user-select', 'none').on('selectstart', false);
                },
                "off": function off() {
                    var setModalPosition = function setModalPosition() {
                        var box = this.resizer.offset();
                        if (!this.modalConfig.absolute) {
                            box.left -= jQuery(document).scrollLeft();
                            box.top -= jQuery(document).scrollTop();
                        }
                        this.activeModal.css(box);
                        this.modalConfig.left = box.left;
                        this.modalConfig.top = box.top;

                        box = null;
                    };

                    this.activeModal.removeClass("draged");
                    setModalPosition.call(this);

                    this.resizer.remove();
                    this.resizer = null;
                    this.resizerBg.remove();
                    this.resizerBg = null;
                    //this.align();

                    jQuery(document.body).off(ENM["mousemove"] + ".ax5modal-move-" + this.instanceId).off(ENM["mouseup"] + ".ax5modal-move-" + this.instanceId).off("mouseleave.ax5modal-move-" + this.instanceId);

                    jQuery(document.body).removeAttr('unselectable').css('user-select', 'auto').off('selectstart');

                    onStateChanged.call(this, self.modalConfig, {
                        self: this,
                        state: "move"
                    });
                }
            },
                resizeModal = {
                "on": function on(resizerType) {
                    var modalZIndex = this.activeModal.css("z-index"),
                        modalOffset = this.activeModal.position(),
                        modalBox = {
                        width: this.activeModal.outerWidth(), height: this.activeModal.outerHeight()
                    },
                        windowBox = {
                        width: jQuery(window).width(),
                        height: jQuery(window).height(),
                        scrollLeft: this.modalConfig.absolute ? 0 : jQuery(document).scrollLeft(),
                        scrollTop: this.modalConfig.absolute ? 0 : jQuery(document).scrollTop()
                    },
                        resizerProcessor = {
                        "top": function top(e) {

                            if (minHeight > modalBox.height - self.__dy) {
                                self.__dy = modalBox.height - minHeight;
                            }

                            if (e.shiftKey) {

                                if (minHeight > modalBox.height - self.__dy * 2) {
                                    self.__dy = (modalBox.height - minHeight) / 2;
                                }

                                return {
                                    left: modalOffset.left,
                                    top: modalOffset.top + self.__dy,
                                    width: modalBox.width,
                                    height: modalBox.height - self.__dy * 2
                                };
                            } else if (e.altKey) {

                                if (minHeight > modalBox.height - self.__dy * 2) {
                                    self.__dy = (modalBox.height - minHeight) / 2;
                                }

                                return {
                                    left: modalOffset.left + self.__dy,
                                    top: modalOffset.top + self.__dy,
                                    width: modalBox.width - self.__dy * 2,
                                    height: modalBox.height - self.__dy * 2
                                };
                            } else {
                                return {
                                    left: modalOffset.left,
                                    top: modalOffset.top + self.__dy,
                                    width: modalBox.width,
                                    height: modalBox.height - self.__dy
                                };
                            }
                        },
                        "bottom": function bottom(e) {

                            if (minHeight > modalBox.height + self.__dy) {
                                self.__dy = -modalBox.height + minHeight;
                            }

                            if (e.shiftKey) {

                                if (minHeight > modalBox.height + self.__dy * 2) {
                                    self.__dy = (-modalBox.height + minHeight) / 2;
                                }

                                return {
                                    left: modalOffset.left,
                                    top: modalOffset.top - self.__dy,
                                    width: modalBox.width,
                                    height: modalBox.height + self.__dy * 2
                                };
                            } else if (e.altKey) {

                                if (minHeight > modalBox.height + self.__dy * 2) {
                                    self.__dy = (-modalBox.height + minHeight) / 2;
                                }

                                return {
                                    left: modalOffset.left - self.__dy,
                                    top: modalOffset.top - self.__dy,
                                    width: modalBox.width + self.__dy * 2,
                                    height: modalBox.height + self.__dy * 2
                                };
                            } else {
                                return {
                                    left: modalOffset.left,
                                    top: modalOffset.top,
                                    width: modalBox.width,
                                    height: modalBox.height + self.__dy
                                };
                            }
                        },
                        "left": function left(e) {

                            if (minWidth > modalBox.width - self.__dx) {
                                self.__dx = modalBox.width - minWidth;
                            }

                            if (e.shiftKey) {

                                if (minWidth > modalBox.width - self.__dx * 2) {
                                    self.__dx = (modalBox.width - minWidth) / 2;
                                }

                                return {
                                    left: modalOffset.left + self.__dx,
                                    top: modalOffset.top,
                                    width: modalBox.width - self.__dx * 2,
                                    height: modalBox.height
                                };
                            } else if (e.altKey) {

                                if (minWidth > modalBox.width - self.__dx * 2) {
                                    self.__dx = (modalBox.width - minWidth) / 2;
                                }

                                return {
                                    left: modalOffset.left + self.__dx,
                                    top: modalOffset.top + self.__dx,
                                    width: modalBox.width - self.__dx * 2,
                                    height: modalBox.height - self.__dx * 2
                                };
                            } else {
                                return {
                                    left: modalOffset.left + self.__dx,
                                    top: modalOffset.top,
                                    width: modalBox.width - self.__dx,
                                    height: modalBox.height
                                };
                            }
                        },
                        "right": function right(e) {

                            if (minWidth > modalBox.width + self.__dx) {
                                self.__dx = -modalBox.width + minWidth;
                            }

                            if (e.shiftKey) {

                                if (minWidth > modalBox.width + self.__dx * 2) {
                                    self.__dx = (-modalBox.width + minWidth) / 2;
                                }

                                return {
                                    left: modalOffset.left - self.__dx,
                                    top: modalOffset.top,
                                    width: modalBox.width + self.__dx * 2,
                                    height: modalBox.height
                                };
                            } else if (e.altKey) {

                                if (minWidth > modalBox.width + self.__dx * 2) {
                                    self.__dx = (-modalBox.width + minWidth) / 2;
                                }

                                return {
                                    left: modalOffset.left - self.__dx,
                                    top: modalOffset.top - self.__dx,
                                    width: modalBox.width + self.__dx * 2,
                                    height: modalBox.height + self.__dx * 2
                                };
                            } else {
                                return {
                                    left: modalOffset.left,
                                    top: modalOffset.top,
                                    width: modalBox.width + self.__dx,
                                    height: modalBox.height
                                };
                            }
                        },
                        "top-left": function topLeft(e) {

                            if (minWidth > modalBox.width - self.__dx) {
                                self.__dx = modalBox.width - minWidth;
                            }

                            if (minHeight > modalBox.height - self.__dy) {
                                self.__dy = modalBox.height - minHeight;
                            }

                            if (e.shiftKey || e.altKey) {

                                if (minHeight > modalBox.height - self.__dy * 2) {
                                    self.__dy = (modalBox.height - minHeight) / 2;
                                }
                                if (minWidth > modalBox.width - self.__dx * 2) {
                                    self.__dx = (modalBox.width - minWidth) / 2;
                                }

                                return {
                                    left: modalOffset.left + self.__dx,
                                    top: modalOffset.top + self.__dy,
                                    width: modalBox.width - self.__dx * 2,
                                    height: modalBox.height - self.__dy * 2
                                };
                            } else {

                                if (minHeight > modalBox.height - self.__dy * 2) {
                                    self.__dy = (modalBox.height - minHeight) / 2;
                                }
                                if (minWidth > modalBox.width - self.__dx * 2) {
                                    self.__dx = (modalBox.width - minWidth) / 2;
                                }

                                return {
                                    left: modalOffset.left + self.__dx,
                                    top: modalOffset.top + self.__dy,
                                    width: modalBox.width - self.__dx,
                                    height: modalBox.height - self.__dy
                                };
                            }
                        },
                        "top-right": function topRight(e) {

                            if (minWidth > modalBox.width + self.__dx) {
                                self.__dx = -modalBox.width + minWidth;
                            }

                            if (minHeight > modalBox.height - self.__dy) {
                                self.__dy = modalBox.height - minHeight;
                            }

                            if (e.shiftKey || e.altKey) {

                                if (minHeight > modalBox.height - self.__dy * 2) {
                                    self.__dy = (modalBox.height - minHeight) / 2;
                                }
                                if (minWidth > modalBox.width + self.__dx * 2) {
                                    self.__dx = (-modalBox.width + minWidth) / 2;
                                }

                                return {
                                    left: modalOffset.left - self.__dx,
                                    top: modalOffset.top + self.__dy,
                                    width: modalBox.width + self.__dx * 2,
                                    height: modalBox.height - self.__dy * 2
                                };
                            } else {
                                return {
                                    left: modalOffset.left,
                                    top: modalOffset.top + self.__dy,
                                    width: modalBox.width + self.__dx,
                                    height: modalBox.height - self.__dy
                                };
                            }
                        },
                        "bottom-left": function bottomLeft(e) {

                            if (minWidth > modalBox.width - self.__dx) {
                                self.__dx = modalBox.width - minWidth;
                            }

                            if (minHeight > modalBox.height + self.__dy) {
                                self.__dy = -modalBox.height + minHeight;
                            }

                            if (e.shiftKey || e.altKey) {
                                if (minWidth > modalBox.width - self.__dx * 2) {
                                    self.__dx = (modalBox.width - minWidth) / 2;
                                }
                                if (minHeight > modalBox.height + self.__dy * 2) {
                                    self.__dy = (-modalBox.height + minHeight) / 2;
                                }
                                return {
                                    left: modalOffset.left + self.__dx,
                                    top: modalOffset.top - self.__dy,
                                    width: modalBox.width - self.__dx * 2,
                                    height: modalBox.height + self.__dy * 2
                                };
                            } else {
                                return {
                                    left: modalOffset.left + self.__dx,
                                    top: modalOffset.top,
                                    width: modalBox.width - self.__dx,
                                    height: modalBox.height + self.__dy
                                };
                            }
                        },
                        "bottom-right": function bottomRight(e) {

                            if (minWidth > modalBox.width + self.__dx) {
                                self.__dx = -modalBox.width + minWidth;
                            }

                            if (minHeight > modalBox.height + self.__dy) {
                                self.__dy = -modalBox.height + minHeight;
                            }

                            if (e.shiftKey || e.altKey) {
                                if (minWidth > modalBox.width + self.__dx * 2) {
                                    self.__dx = (-modalBox.width + minWidth) / 2;
                                }
                                if (minHeight > modalBox.height + self.__dy * 2) {
                                    self.__dy = (-modalBox.height + minHeight) / 2;
                                }
                                return {
                                    left: modalOffset.left - self.__dx,
                                    top: modalOffset.top - self.__dy,
                                    width: modalBox.width + self.__dx * 2,
                                    height: modalBox.height + self.__dy * 2
                                };
                            } else {
                                return {
                                    left: modalOffset.left,
                                    top: modalOffset.top,
                                    width: modalBox.width + self.__dx,
                                    height: modalBox.height + self.__dy
                                };
                            }
                        }
                    },
                        getResizerPosition = function getResizerPosition(e) {
                        self.__dx = e.clientX - self.mousePosition.clientX;
                        self.__dy = e.clientY - self.mousePosition.clientY;

                        return resizerProcessor[resizerType](e);
                    };

                    if (!this.modalConfig.absolute) {
                        modalOffset.left += windowBox.scrollLeft;
                        modalOffset.top += windowBox.scrollTop;
                    }

                    var minWidth = 100,
                        minHeight = 100;

                    var cursorType = {
                        "top": "row-resize",
                        "bottom": "row-resize",
                        "left": "col-resize",
                        "right": "col-resize",
                        "top-left": "nwse-resize",
                        "top-right": "nesw-resize",
                        "bottom-left": "nesw-resize",
                        "bottom-right": "nwse-resize"
                    };

                    self.__dx = 0; // 변화량 X
                    self.__dy = 0; // 변화량 Y

                    // self.resizerBg : body 가 window보다 작을 때 문제 해결을 위한 DIV
                    self.resizerBg = jQuery('<div class="ax5modal-resizer-background" ondragstart="return false;"></div>');
                    self.resizer = jQuery('<div class="ax5modal-resizer" ondragstart="return false;"></div>');
                    self.resizerBg.css({
                        zIndex: modalZIndex,
                        cursor: cursorType[resizerType]
                    });
                    self.resizer.css({
                        left: modalOffset.left,
                        top: modalOffset.top,
                        width: modalBox.width,
                        height: modalBox.height,
                        zIndex: modalZIndex + 1,
                        cursor: cursorType[resizerType]
                    });
                    jQuery(document.body).append(self.resizerBg).append(self.resizer);
                    self.activeModal.addClass("draged");

                    jQuery(document.body).bind(ENM["mousemove"] + ".ax5modal-resize-" + this.instanceId, function (e) {
                        self.resizer.css(getResizerPosition(e));
                    }).bind(ENM["mouseup"] + ".ax5modal-resize-" + this.instanceId, function (e) {
                        resizeModal.off.call(self);
                    }).bind("mouseleave.ax5modal-resize-" + this.instanceId, function (e) {
                        resizeModal.off.call(self);
                    });

                    jQuery(document.body).attr('unselectable', 'on').css('user-select', 'none').bind('selectstart', false);
                },
                "off": function off() {
                    var setModalPosition = function setModalPosition() {
                        var box = this.resizer.offset();
                        jQuery.extend(box, {
                            width: this.resizer.width(),
                            height: this.resizer.height()
                        });
                        if (!this.modalConfig.absolute) {
                            box.left -= jQuery(document).scrollLeft();
                            box.top -= jQuery(document).scrollTop();
                        }
                        this.activeModal.css(box);

                        this.modalConfig.left = box.left;
                        this.modalConfig.top = box.top;
                        this.modalConfig.width = box.width;
                        this.modalConfig.height = box.height;
                        this.$["body"].css({ height: box.height - this.modalConfig.headerHeight });
                        if (this.modalConfig.iframe) {
                            this.$["iframe-wrap"].css({ height: box.height - this.modalConfig.headerHeight });
                            this.$["iframe"].css({ height: box.height - this.modalConfig.headerHeight });
                        }

                        box = null;
                    };

                    this.activeModal.removeClass("draged");
                    setModalPosition.call(this);

                    this.resizer.remove();
                    this.resizer = null;
                    this.resizerBg.remove();
                    this.resizerBg = null;

                    onStateChanged.call(this, self.modalConfig, {
                        self: this,
                        state: "resize"
                    });

                    jQuery(document.body).unbind(ENM["mousemove"] + ".ax5modal-resize-" + this.instanceId).unbind(ENM["mouseup"] + ".ax5modal-resize-" + this.instanceId).unbind("mouseleave.ax5modal-resize-" + this.instanceId);

                    jQuery(document.body).removeAttr('unselectable').css('user-select', 'auto').unbind('selectstart');
                }
            };

            /// private end

            /**
             * Preferences of modal UI
             * @method ax5modal.setConfig
             * @param {Object} config - 클래스 속성값
             * @param {Number} [config.zIndex]
             * @param {Object} [config.position]
             * @param {String} [config.position.left="center"]
             * @param {String} [config.position.top="middle"]
             * @param {Number} [config.position.margin=10]
             * @param {String} [config.minimizePosition="bottom-right"]
             * @param {Number} [config.width=300]
             * @param {Number} [config.height=400]
             * @param {Boolean} [config.closeToEsc=true]
             * @param {Boolean} [config.absolute=false]
             * @param {Boolean} [config.disableDrag=false]
             * @param {Boolean} [config.disableResize=false]
             * @param {Number} [config.animateTime=250]
             * @param {Function} [config.fullScreen]
             * @param {Function} [config.onStateChanged] - `onStateChanged` function can be defined in setConfig method or new ax5.ui.modal initialization method. However, you can us to define an event function after initialization, if necessary
             * @param {Function} [config.onResize]
             * @returns {ax5modal}
             * @example
             * ```js
             * var modal = new ax5.ui.modal({
             *     iframeLoadingMsg: '<i class="fa fa-spinner fa-5x fa-spin" aria-hidden="true"></i>',
             *     header: {
             *         title: "MODAL TITLE",
             *         btns: {
             *             minimize: {
             *                 label: '<i class="fa fa-minus-circle" aria-hidden="true"></i>', onClick: function () {
             *                     modal.minimize();
             *                 }
             *             },
             *             maximize: {
             *                 label: '<i class="fa fa-plus-circle" aria-hidden="true"></i>', onClick: function () {
             *                     modal.maximize();
             *                 }
             *             },
             *             close: {
             *                 label: '<i class="fa fa-times-circle" aria-hidden="true"></i>', onClick: function () {
             *                     modal.close();
             *                 }
             *             }
             *         }
             *     }
             * });
             *
             * modal.open({
             *     width: 800,
             *     height: 600,
             *     fullScreen: function(){
             *         return ($(window).width() < 600);
             *     },
             *     iframe: {
             *         method: "get",
             *         url: "http://chequer-app:2017/html/login.html",
             *         param: "callback=modalCallback"
             *     },
             *     onStateChanged: function(){
             *          console.log(this);
             *     },
             *     onResize: function(){
             *          console.log(this);
             *     }
             * });
             * ```
             */
            //== class body start
            this.init = function () {
                this.onStateChanged = cfg.onStateChanged;
                this.onResize = cfg.onResize;
            };

            /**
             * open the modal
             * @method ax5modal.open
             * @returns {ax5modal}
             * @example
             * ```
             * modal.open();
             * modal.open({
             *  width: 500,
             *  height: 500
             * });
             * moaal.open({}, function(){
             *  console.log(this);
             * });
             * ```
             */
            this.open = function (opts, callback, tryCount) {
                if (typeof tryCount === "undefined") tryCount = 0;
                if (!this.activeModal) {
                    opts = self.modalConfig = jQuery.extend(true, {}, cfg, opts);
                    open.call(this, opts, callback);
                    this.watingModal = false;
                } else if (tryCount < 3) {
                    // 3번까지 재 시도
                    this.watingModal = true;
                    setTimeout(function () {
                        this.open(opts, callback, tryCount + 1);
                    }.bind(this), cfg.animateTime);
                } else {
                    // 열기 시도 종료
                    this.watingModal = false;
                }
                return this;
            };

            /**
             * close the modal
             * @method ax5modal.close
             * @param _option
             * @returns {ax5modal}
             * @example
             * ```
             * my_modal.close();
             * my_modal.close({callback: function(){
             *  // on close event
             * });
             * // close 함수에 callback을 전달하면 정확한 close 타이밍을 캐치할 수 있습니다
             * ```
             */

            this.close = function (_option) {
                var opts = void 0,
                    that = void 0;

                if (this.activeModal) {
                    opts = self.modalConfig;
                    this.activeModal.addClass("destroy");
                    jQuery(window).unbind("keydown.ax-modal");
                    jQuery(window).unbind("resize.ax-modal");

                    setTimeout(function () {
                        // 프레임 제거
                        if (opts.iframe) {
                            var $iframe = this.$["iframe"]; // iframe jQuery Object
                            if ($iframe) {
                                var iframeObject = $iframe.get(0),
                                    idoc = iframeObject.contentDocument ? iframeObject.contentDocument : iframeObject.contentWindow.document;

                                try {
                                    $(idoc.body).children().each(function () {
                                        $(this).remove();
                                    });
                                } catch (e) {}
                                idoc.innerHTML = "";
                                $iframe.attr('src', 'about:blank').remove();

                                // force garbarge collection for IE only
                                window.CollectGarbage && window.CollectGarbage();
                            }
                        }

                        this.activeModal.remove();
                        this.activeModal = null;

                        // 모달 오픈 대기중이면 닫기 상태 전달 안함.
                        if (!this.watingModal) {
                            onStateChanged.call(this, opts, {
                                self: this,
                                state: "close"
                            });
                        }

                        if (_option && U.isFunction(_option.callback)) {
                            that = {
                                self: this,
                                id: opts.id,
                                theme: opts.theme,
                                width: opts.width,
                                height: opts.height,
                                state: "close",
                                $: this.$
                            };
                            _option.callback.call(that, that);
                        }
                    }.bind(this), cfg.animateTime);
                }

                this.minimized = false; // hoksi

                return this;
            };

            /**
             * @method ax5modal.minimize
             * @returns {ax5modal}
             */
            this.minimize = function () {

                return function (minimizePosition) {

                    if (this.minimized !== true) {

                        var opts = self.modalConfig;
                        if (typeof minimizePosition === "undefined") minimizePosition = cfg.minimizePosition;

                        this.minimized = true;
                        this.$.body.hide();
                        self.modalConfig.originalHeight = opts.height;
                        self.modalConfig.height = 0;
                        alignProcessor[minimizePosition].call(this);

                        onStateChanged.call(this, opts, {
                            self: this,
                            state: "minimize"
                        });
                    }

                    return this;
                };
            }();

            /**
             * @method ax5modal.restore
             * @returns {ax5modal}
             */
            this.restore = function () {
                var opts = self.modalConfig;
                if (this.minimized) {
                    this.minimized = false;
                    this.$.body.show();
                    self.modalConfig.height = self.modalConfig.originalHeight;
                    self.modalConfig.originalHeight = undefined;

                    this.align({ left: "center", top: "middle" });
                    onStateChanged.call(this, opts, {
                        self: this,
                        state: "restore"
                    });
                }
                return this;
            };

            /**
             * setCSS
             * @method ax5modal.css
             * @param {Object} css -
             * @returns {ax5modal}
             */
            this.css = function (css) {
                if (this.activeModal && !self.fullScreen) {
                    this.activeModal.css(css);
                    if (typeof css.width !== "undefined") {
                        self.modalConfig.width = css.width;
                    }
                    if (typeof css.height !== "undefined") {
                        self.modalConfig.height = css.height;
                    }

                    this.align();
                }
                return this;
            };

            /**
             * @method ax5modal.setModalConfig
             * @param _config
             * @returns {ax5.ui.ax5modal}
             */
            this.setModalConfig = function (_config) {
                self.modalConfig = jQuery.extend({}, self.modalConfig, _config);
                this.align();
                return this;
            };

            /**
             * @method ax5modal.align
             * @param position
             * @param e
             * @returns {ax5modal}
             * @example
             * ```js
             * modal.align({left:"center", top:"middle"});
             * modal.align({left:"left", top:"top", margin: 20});
             * ```
             */
            this.align = function () {

                return function (position, e) {
                    if (!this.activeModal) return this;

                    var opts = self.modalConfig,
                        box = {
                        width: opts.width,
                        height: opts.height
                    };

                    var fullScreen = opts.isFullScreen = function (_fullScreen) {
                        if (typeof _fullScreen === "undefined") {
                            return false;
                        } else if (U.isFunction(_fullScreen)) {
                            return _fullScreen();
                        }
                    }(opts.fullScreen);

                    if (fullScreen) {
                        if (opts.header) this.$.header.show();
                        if (opts.header) {
                            opts.headerHeight = this.$.header.outerHeight();
                            box.height += opts.headerHeight;
                        } else {
                            opts.headerHeight = 0;
                        }
                        box.width = jQuery(window).width();
                        box.height = opts.height;
                        box.left = 0;
                        box.top = 0;
                    } else {
                        if (opts.header) this.$.header.show();
                        if (position) {
                            jQuery.extend(true, opts.position, position);
                        }

                        if (opts.header) {
                            opts.headerHeight = this.$.header.outerHeight();
                            box.height += opts.headerHeight;
                        } else {
                            opts.headerHeight = 0;
                        }

                        //- position 정렬
                        if (opts.position.left == "left") {
                            box.left = opts.position.margin || 0;
                        } else if (opts.position.left == "right") {
                            // window.innerWidth;
                            box.left = jQuery(window).width() - box.width - (opts.position.margin || 0);
                        } else if (opts.position.left == "center") {
                            box.left = jQuery(window).width() / 2 - box.width / 2;
                        } else {
                            box.left = opts.position.left || 0;
                        }

                        if (opts.position.top == "top") {
                            box.top = opts.position.margin || 0;
                        } else if (opts.position.top == "bottom") {
                            box.top = jQuery(window).height() - box.height - (opts.position.margin || 0);
                        } else if (opts.position.top == "middle") {
                            box.top = jQuery(window).height() / 2 - box.height / 2;
                        } else {
                            box.top = opts.position.top || 0;
                        }
                        if (box.left < 0) box.left = 0;
                        if (box.top < 0) box.top = 0;
                    }

                    this.activeModal.css(box);
                    this.$["body"].css({ height: box.height - (opts.headerHeight || 0) });

                    if (opts.iframe) {
                        this.$["iframe-wrap"].css({ height: box.height - opts.headerHeight });
                        this.$["iframe"].css({ height: box.height - opts.headerHeight });
                    } else {}
                    return this;
                };
            }();

            // 클래스 생성자
            this.main = function () {

                UI.modal_instance = UI.modal_instance || [];
                UI.modal_instance.push(this);

                if (arguments && U.isObject(arguments[0])) {
                    this.setConfig(arguments[0]);
                }
            }.apply(this, arguments);
        };
    }());

    MODAL = ax5.ui.modal;
})();

// ax5.ui.modal.tmpl
(function () {
    var MODAL = ax5.ui.modal;

    var content = function content() {
        return " \n        <div id=\"{{modalId}}\" data-modal-els=\"root\" class=\"ax5modal {{theme}} {{fullscreen}}\" style=\"{{styles}}\">\n            {{#header}}\n            <div class=\"ax-modal-header\" data-modal-els=\"header\">\n                {{{title}}}\n                {{#btns}}\n                    <div class=\"ax-modal-header-addon\">\n                    {{#@each}}\n                    <button tabindex=\"-1\" data-modal-header-btn=\"{{@key}}\" class=\"{{@value.theme}}\">{{{@value.label}}}</button>\n                    {{/@each}}\n                    </div>\n                {{/btns}}\n            </div>\n            {{/header}}\n            <div class=\"ax-modal-body\" data-modal-els=\"body\">\n            {{#iframe}}\n                <div data-modal-els=\"iframe-wrap\" style=\"-webkit-overflow-scrolling: touch; overflow: auto;position: relative;\">\n                    <table data-modal-els=\"iframe-loading\" style=\"width:100%;height:100%;\"><tr><td style=\"text-align: center;vertical-align: middle\">{{{iframeLoadingMsg}}}</td></tr></table>\n                    <iframe name=\"{{modalId}}-frame\" src=\"\" width=\"100%\" height=\"100%\" frameborder=\"0\" data-modal-els=\"iframe\" style=\"position: absolute;left:0;top:0;\"></iframe>\n                </div>\n                <form name=\"{{modalId}}-form\" data-modal-els=\"iframe-form\">\n                <input type=\"hidden\" name=\"modalId\" value=\"{{modalId}}\" />\n                {{#param}}\n                {{#@each}}\n                <input type=\"hidden\" name=\"{{@key}}\" value=\"{{@value}}\" />\n                {{/@each}}\n                {{/param}}\n                </form>\n            {{/iframe}}\n            {{^iframe}}\n                <div data-modal-els=\"body-frame\" style=\"position: absolute;left:0;top:0;width:100%;height:100%;\"></div>\n            {{/iframe}}\n            </div>\n            {{^disableResize}}\n            <div data-ax5modal-resizer=\"top\"></div>\n            <div data-ax5modal-resizer=\"right\"></div>\n            <div data-ax5modal-resizer=\"bottom\"></div>\n            <div data-ax5modal-resizer=\"left\"></div>\n            <div data-ax5modal-resizer=\"top-left\"></div>\n            <div data-ax5modal-resizer=\"top-right\"></div>\n            <div data-ax5modal-resizer=\"bottom-left\"></div>\n            <div data-ax5modal-resizer=\"bottom-right\"></div>\n            {{/disableResize}}\n        </div>\n        ";
    };

    MODAL.tmpl = {
        "content": content,

        get: function get(tmplName, data, columnKeys) {
            return ax5.mustache.render(MODAL.tmpl[tmplName].call(this, columnKeys), data);
        }
    };
})();
"use strict";

// ax5.ui.calendar
(function () {

    var UI = ax5.ui;
    var U = ax5.util;
    var CALENDAR = void 0;

    UI.addClass({
        className: "calendar"
    }, function () {

        /**
         * @class ax5calendar
         * @classdesc
         * @author tom@axisj.com
         * @logs
         * 2014-06-21 tom : 시작
         * @example
         * ```js
         * ax5.info.months = ["1월","2월","3월","4월","5월","6월","7월","8월","9월","10월","11월"];
         * ax5.info.weekNames = [
         *     {label: "일"},
         *     {label: "월"},
         *     {label: "화"},
         *     {label: "수"},
         *     {label: "목"},
         *     {label: "금"},
         *     {label: "토"}
         * ];
         *
         * var myCalendar = new ax5.ui.calendar({
         *     control: {
         *         left: '≪',
         *         yearTmpl: '%s',
         *         monthTmpl: '%s',
         *         right: '≫',
         *         yearFirst: true
         *     },
         *
         *     dimensions: {
         *         itemPadding: 1,
         *         height: 200
         *     },
         *
         *         target: document.getElementById("calendar-target"),
         *         displayDate: (new Date()),
         *         mode: "day",
         *         selectMode: "day",
         *
         *         marker: (function () {
         *             var marker = {};
         *             marker[_c_date(today, {'return': 'yyyy-MM-dd', 'add': {d: -1}})] = true;
         *             marker[_c_date(today, {'return': 'yyyy-MM-dd', 'add': {d: 0}})] = true;
         *             marker[_c_date(today, {'return': 'yyyy-MM-dd', 'add': {d: 1}})] = true;
         *
        *             return marker;
         *         })(),
         *         onClick: function () {
         *             console.log(myCalendar.getSelection());
         *         },
         *         onStateChanged: function () {
         *             console.log(this);
         *         }
         *         , multipleSelect: 2
         *     });
         * ```
         */
        return function () {
            var self = this,
                cfg = void 0,
                selectableCount = 1;

            this.instanceId = ax5.getGuid();
            this.target = null;
            this.selection = [];
            this.selectionMap = {};
            this.selectableMap = {};
            this.markerMap = {};
            this.printedDay = {
                start: "", end: ""
            };
            this.config = {
                clickEventName: "click",
                theme: 'default',
                startOfWeek: 0,
                mode: 'day', // day|month|year,
                dateFormat: 'yyyy-MM-dd',
                displayDate: new Date(),
                animateTime: 100,
                dimensions: {
                    controlHeight: '40',
                    controlButtonWidth: '40',
                    colHeadHeight: '30',
                    itemPadding: 2
                },
                lang: {
                    yearHeading: "Choose the year",
                    monthHeading: "Choose the month",
                    yearTmpl: "%s",
                    months: ax5.info.months || ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'],
                    dayTmpl: "%s"
                },
                multipleSelect: false,
                selectMode: 'day',
                defaultMarkerTheme: 'holiday',
                defaultPeriodTheme: 'period'
            };

            cfg = this.config;

            var onStateChanged = function onStateChanged(opts, that) {
                if (opts && opts.onStateChanged) {
                    opts.onStateChanged.call(that, that);
                } else if (this.onStateChanged) {
                    this.onStateChanged.call(that, that);
                }

                that = null;
            };
            var getFrame = function getFrame() {
                var data = jQuery.extend(true, {}, cfg, {
                    controlCSS: {},
                    controlButtonCSS: {}
                });

                data.controlButtonCSS["height"] = data.controlCSS["height"] = U.cssNumber(cfg.dimensions.controlHeight);
                data.controlButtonCSS["line-height"] = data.controlCSS["line-height"] = U.cssNumber(cfg.dimensions.controlHeight);
                data.controlButtonCSS["width"] = U.cssNumber(cfg.dimensions.controlHeight);

                data.controlCSS = U.css(data.controlCSS);
                data.controlButtonCSS = U.css(data.controlButtonCSS);

                try {
                    return CALENDAR.tmpl.get.call(this, "frameTmpl", data);
                } finally {
                    data = null;
                }
            };
            var setDisplay = function setDisplay() {
                var myDate = U.date(cfg.displayDate),
                    yy = "",
                    mm = "",
                    yy1,
                    yy2;

                if (cfg.control) {
                    if (cfg.mode == "day" || cfg.mode == "d") {
                        yy = cfg.control.yearTmpl ? cfg.control.yearTmpl.replace('%s', myDate.getFullYear()) : myDate.getFullYear();
                        mm = cfg.control.monthTmpl ? cfg.control.monthTmpl.replace('%s', cfg.lang.months[myDate.getMonth()]) : cfg.lang.months[myDate.getMonth()];

                        this.$["control-display"].html(function () {
                            if (cfg.control.yearFirst) {
                                return '<span data-calendar-display="year">' + yy + '</span>' + '<span data-calendar-display="month">' + mm + '</span>';
                            } else {
                                return '<span data-calendar-display="month">' + mm + '</span>' + '<span data-calendar-display="year">' + yy + '</span>';
                            }
                        }());
                    } else if (cfg.mode == "month" || cfg.mode == "m") {
                        yy = cfg.control.yearTmpl ? cfg.control.yearTmpl.replace('%s', myDate.getFullYear()) : myDate.getFullYear();
                        this.$["control-display"].html('<span data-calendar-display="year">' + yy + '</span>');
                    } else if (cfg.mode == "year" || cfg.mode == "y") {
                        yy1 = cfg.control.yearTmpl ? cfg.control.yearTmpl.replace('%s', myDate.getFullYear() - 10) : myDate.getFullYear() - 10;
                        yy2 = cfg.control.yearTmpl ? cfg.control.yearTmpl.replace('%s', Number(myDate.getFullYear()) + 9) : Number(myDate.getFullYear()) + 9;
                        this.$["control-display"].html(yy1 + ' ~ ' + yy2);
                    }

                    this.$["control-display"].find('[data-calendar-display]').on(cfg.clickEventName, function (e) {
                        var target = U.findParentNode(e.target, function (target) {
                            if (target.getAttribute("data-calendar-display")) {
                                return true;
                            }
                        }),
                            mode;
                        if (target) {
                            mode = target.getAttribute("data-calendar-display");
                            this.changeMode(mode);
                        }
                        target = null;
                        mode = null;
                    }.bind(this));
                }

                myDate = null;
                yy = null;
                mm = null;
                yy1 = null;
                yy2 = null;
                return this;
            };
            var printDay = function printDay(nowDate) {
                var dotDate = U.date(nowDate),
                    monthStratDate = new Date(dotDate.getFullYear(), dotDate.getMonth(), 1, 12),
                    _today = cfg.displayDate,
                    tableStartDate = function () {
                    var day = monthStratDate.getDay();
                    if (day == 0) day = 7;
                    day -= cfg.startOfWeek;

                    try {
                        return U.date(monthStratDate, { add: { d: -day } });
                    } finally {
                        day = null;
                    }
                }(),
                    loopDate,
                    thisMonth = dotDate.getMonth(),
                    itemStyles = {},
                    i,
                    k,
                    _k,
                    frameWidth = this.$["body"].width(),
                    frameHeight = Math.floor(frameWidth * (6 / 7)),
                    // 1week = 7days, 1month = 6weeks
                data,
                    tmpl;

                if (cfg.dimensions.height) {
                    frameHeight = U.number(cfg.dimensions.height) - U.number(cfg.dimensions.colHeadHeight);
                }

                itemStyles['height'] = Math.floor(frameHeight / 6) - U.number(cfg.dimensions.itemPadding) * 2 + 'px';
                itemStyles['line-height'] = itemStyles['height'];
                itemStyles['padding'] = U.cssNumber(cfg.dimensions.itemPadding);

                data = {
                    weekNames: [].concat(ax5.info.weekNames),
                    list: []
                };

                if (cfg.startOfWeek) {
                    data.weekNames = data.weekNames.concat(data.weekNames.slice(0, cfg.startOfWeek)).splice(cfg.startOfWeek);
                }

                data.weekNames.forEach(function (n) {
                    n.colHeadHeight = U.cssNumber(cfg.dimensions.colHeadHeight);
                });

                loopDate = tableStartDate;
                i = 0;
                while (i < 6) {
                    k = 0;
                    while (k < 7) {
                        _k = (7 + (k - cfg.startOfWeek)) % 7;
                        var thisDate = '' + U.date(loopDate, { "return": cfg.dateFormat }),
                            _date = {
                            'row': i,
                            'col': k,
                            isStartOfWeek: k == 0,
                            thisDate: '' + thisDate,
                            thisDataLabel: cfg.lang.dayTmpl.replace('%s', loopDate.getDate()),
                            itemStyles: U.css(itemStyles),
                            addClass: function () {

                                var classNames = "";

                                if (cfg.selectable) {
                                    if (self.selectableMap[thisDate]) {
                                        classNames += loopDate.getMonth() == thisMonth ? " live" : "";
                                    } else {
                                        classNames += " disable";
                                    }
                                } else {
                                    if (loopDate.getMonth() == thisMonth) {
                                        if (thisDate == U.date(_today, { "return": "yyyyMMdd" })) {
                                            classNames += " focus";
                                        } else {
                                            classNames += " live";
                                        }

                                        if (loopDate.getDay() == 0) {
                                            classNames += " sunday";
                                        }
                                        if (loopDate.getDay() == 6) {
                                            classNames += " saturday";
                                        }
                                    }
                                }

                                return classNames;
                            }() + ' ' + function () {
                                return self.markerMap[thisDate] ? self.markerMap[thisDate].theme || cfg.defaultMarkerTheme : '';
                            }() + ' ' + function () {
                                return self.selectionMap[thisDate] ? "selected-day" : '';
                            }()
                        };
                        data.list.push(_date);

                        k++;
                        loopDate = U.date(loopDate, { add: { d: 1 } });

                        thisDate = null;
                        _date = null;
                    }
                    i++;
                }
                tmpl = CALENDAR.tmpl.get.call(this, "dayTmpl", data);
                this.$["body"].html(tmpl);
                this.$["body"].find('[data-calendar-item-date]').on(cfg.clickEventName, function (e) {
                    e = e || window.event;
                    onclick.call(self, e, 'date');
                    U.stopEvent(e);
                });

                this.printedDay = {
                    start: tableStartDate, end: loopDate
                };

                onStateChanged.call(this, null, {
                    self: this,
                    action: "printDay",
                    printedDay: this.printedDay
                });
                setDisplay.call(this);

                dotDate = null;
                monthStratDate = null;
                _today = null;
                tableStartDate = null;
                loopDate = null;
                thisMonth = null;
                itemStyles = null;
                i = null;
                k = null;
                frameWidth = null;
                frameHeight = null;
                data = null;
                tmpl = null;
            };
            var printMonth = function printMonth(nowDate) {
                var dotDate = U.date(nowDate),
                    nMonth = dotDate.getMonth(),
                    itemStyles = {},
                    i,
                    k,
                    m,
                    tableStartMonth,
                    frameWidth = this.$["body"].width(),
                    frameHeight = Math.floor(frameWidth * (6 / 7)),
                    data,
                    tmpl;

                if (cfg.dimensions.height) {
                    frameHeight = U.number(cfg.dimensions.height) - U.number(cfg.dimensions.colHeadHeight);
                }

                itemStyles['height'] = Math.floor(frameHeight / 4) - U.number(cfg.dimensions.itemPadding) * 2 + 'px';
                itemStyles['line-height'] = itemStyles['height'];
                itemStyles['padding'] = U.cssNumber(cfg.dimensions.itemPadding);

                data = {
                    colHeadHeight: U.cssNumber(cfg.dimensions.colHeadHeight),
                    colHeadLabel: cfg.lang.monthHeading,
                    list: []
                };

                tableStartMonth = 0;
                m = 0;
                i = 0;
                while (i < 4) {
                    k = 0;
                    while (k < 3) {
                        var _month = {
                            row: i,
                            col: k,
                            isStartOfRow: k == 0,
                            thisMonth: dotDate.getFullYear() + '-' + U.setDigit(m + 1, 2) + '-' + U.setDigit(dotDate.getDate(), 2),
                            thisMonthLabel: cfg.lang.months[m],
                            itemStyles: U.css(itemStyles),
                            addClass: function () {
                                if (cfg.selectable) {
                                    return self.selectableMap[m] ? 'live' : 'disable';
                                } else {
                                    return 'live';
                                }
                            }() + ' ' + function () {
                                return m == nMonth ? "focus" : "";
                            }() + ' ' + function () {
                                return self.markerMap[m] ? self.markerMap[m].theme || cfg.defaultMarkerTheme : '';
                            }()
                        };
                        data.list.push(_month);
                        m++;
                        k++;
                        _month = null;
                    }
                    i++;
                }
                tmpl = CALENDAR.tmpl.get.call(this, "monthTmpl", data);
                this.$["body"].html(tmpl);
                this.$["body"].find('[data-calendar-item-month]').on(cfg.clickEventName, function (e) {
                    e = e || window.event;
                    onclick.call(self, e, 'month');
                    U.stopEvent(e);
                });

                this.printedDay = {
                    start: dotDate.getFullYear() + '-' + U.setDigit(tableStartMonth + 1, 2),
                    end: dotDate.getFullYear() + '-' + U.setDigit(m, 2)
                };

                onStateChanged.call(this, null, {
                    self: this,
                    action: "printMonth",
                    printedDay: this.printedDay
                });
                setDisplay.call(this);

                dotDate = null;
                nMonth = null;
                itemStyles = null;
                i = null;
                k = null;
                m = null;
                tableStartMonth = null;
                frameWidth = null;
                frameHeight = null;
                data = null;
                tmpl = null;
            };
            var printYear = function printYear(nowDate) {
                var dotDate = U.date(nowDate),
                    nYear = dotDate.getFullYear(),
                    itemStyles = {},
                    i,
                    k,
                    y,
                    tableStartYear,
                    frameWidth = this.$["body"].width(),
                    frameHeight = Math.floor(frameWidth * (6 / 7)),
                    data,
                    tmpl;

                if (cfg.dimensions.height) {
                    frameHeight = U.number(cfg.dimensions.height) - U.number(cfg.dimensions.colHeadHeight);
                }

                itemStyles['height'] = Math.floor(frameHeight / 5) - U.number(cfg.dimensions.itemPadding) * 2 + 'px';
                itemStyles['line-height'] = itemStyles['height'];
                itemStyles['padding'] = U.cssNumber(cfg.dimensions.itemPadding);

                data = {
                    colHeadHeight: U.cssNumber(cfg.dimensions.colHeadHeight),
                    colHeadLabel: cfg.lang.yearHeading,
                    list: []
                };

                tableStartYear = nYear - 10;
                y = nYear - 10;
                i = 0;
                while (i < 5) {
                    k = 0;
                    while (k < 4) {
                        var _year = {
                            row: i,
                            col: k,
                            isStartOfRow: k == 0,
                            thisYear: y + '-' + U.setDigit(dotDate.getMonth() + 1, 2) + '-' + U.setDigit(dotDate.getDate(), 2),
                            thisYearLabel: cfg.lang.yearTmpl.replace('%s', y),
                            itemStyles: U.css(itemStyles),
                            addClass: function () {
                                if (cfg.selectable) {
                                    return self.selectableMap[y] ? 'live' : 'disable';
                                } else {
                                    return 'live';
                                }
                            }() + ' ' + function () {
                                return y == nYear ? "focus" : "";
                            }() + ' ' + function () {
                                return self.selectableMap[y] ? self.selectableMap[y].theme || cfg.defaultMarkerTheme : '';
                            }()
                        };
                        data.list.push(_year);
                        y++;
                        k++;
                        _year = null;
                    }
                    i++;
                }
                tmpl = CALENDAR.tmpl.get.call(this, "yearTmpl", data);
                this.$["body"].html(tmpl);
                this.$["body"].find('[data-calendar-item-year]').on(cfg.clickEventName, function (e) {
                    e = e || window.event;
                    onclick.call(this, e, 'year');
                    U.stopEvent(e);
                });

                this.printedDay = {
                    start: tableStartYear, end: y - 1
                };

                onStateChanged.call(this, null, {
                    self: this,
                    action: "printYear",
                    printedDay: this.printedDay
                });
                setDisplay.call(this);

                dotDate = null;
                nYear = null;
                itemStyles = null;
                i = null;
                k = null;
                y = null;
                tableStartYear = null;
                frameWidth = null;
                frameHeight = null;
                data = null;
                tmpl = null;
            };
            var onclick = function onclick(e, mode, target, value) {
                var removed, dt, selectable;

                mode = mode || "date";
                target = U.findParentNode(e.target, function (target) {
                    if (target.getAttribute("data-calendar-item-" + mode)) {
                        return true;
                    }
                });
                if (target) {
                    value = target.getAttribute("data-calendar-item-" + mode);

                    dt = U.date(value, { "return": cfg.dateFormat });
                    selectable = true;
                    selectableCount = cfg.multipleSelect ? U.isNumber(cfg.multipleSelect) ? cfg.multipleSelect : 2 : 1;

                    if (cfg.selectable) {
                        if (!self.selectableMap[dt]) selectable = false;
                    }

                    if (mode == "date") {
                        if (selectable) {

                            if (self.selection.length >= selectableCount) {
                                removed = self.selection.splice(0, self.selection.length - (selectableCount - 1));
                                removed.forEach(function (d) {
                                    self.$["body"].find('[data-calendar-item-date="' + U.date(d, { "return": cfg.dateFormat }) + '"]').removeClass("selected-day");
                                });
                            }

                            jQuery(target).addClass("selected-day");
                            self.selection.push(value);

                            if (self.onClick) {
                                self.onClick.call({
                                    self: this, date: value, target: this.target, dateElement: target
                                });
                            }
                        }
                    } else if (mode == "month") {
                        if (cfg.selectMode == "month") {
                            if (selectable) {
                                if (self.selection.length >= selectableCount) {
                                    removed = self.selection.splice(0, self.selection.length - (selectableCount - 1));
                                    removed.forEach(function (d) {
                                        self.$["body"].find('[data-calendar-item-month="' + U.date(d, { "return": 'yyyy-MM-dd' }) + '"]').removeClass("selected-month");
                                    });
                                }

                                jQuery(target).addClass("selected-month");
                                self.selection.push(value);

                                if (self.onClick) {
                                    self.onClick.call({
                                        self: this, date: value, target: this.target, dateElement: target
                                    });
                                }
                            }
                        } else {
                            self.changeMode("day", value);
                        }
                    } else if (mode == "year") {
                        if (cfg.selectMode == "year") {
                            if (selectable) {
                                if (self.selection.length >= selectableCount) {
                                    removed = self.selection.splice(0, self.selection.length - (selectableCount - 1));
                                    removed.forEach(function (d) {
                                        self.$["body"].find('[data-calendar-item-year="' + U.date(d, { "return": 'yyyy-MM-dd' }) + '"]').removeClass("selected-year");
                                    });
                                }

                                jQuery(target).addClass("selected-year");
                                self.selection.push(value);

                                if (self.onClick) {
                                    self.onClick.call({
                                        self: this, date: value, target: this.target, dateElement: target
                                    });
                                }
                            }
                        } else {
                            self.changeMode("month", value);
                        }
                    }
                }

                mode = null;
                target = null;
                value = null;
                removed = null;
                dt = null;
                selectable = null;
            };
            var move = function move(e, target, value) {
                target = U.findParentNode(e.target, function (target) {
                    if (target.getAttribute("data-calendar-move")) {
                        return true;
                    }
                });
                if (target) {
                    value = target.getAttribute("data-calendar-move");
                    if (cfg.mode == "day" || cfg.mode == "d") {
                        if (value == "left") {
                            cfg.displayDate = U.date(cfg.displayDate, { add: { m: -1 } });
                        } else {
                            cfg.displayDate = U.date(cfg.displayDate, { add: { m: 1 } });
                        }
                        printDay.call(this, cfg.displayDate);
                    } else if (cfg.mode == "month" || cfg.mode == "m") {
                        if (value == "left") {
                            cfg.displayDate = U.date(cfg.displayDate, { add: { y: -1 } });
                        } else {
                            cfg.displayDate = U.date(cfg.displayDate, { add: { y: 1 } });
                        }
                        printMonth.call(this, cfg.displayDate);
                    } else if (cfg.mode == "year" || cfg.mode == "y") {
                        if (value == "left") {
                            cfg.displayDate = U.date(cfg.displayDate, { add: { y: -10 } });
                        } else {
                            cfg.displayDate = U.date(cfg.displayDate, { add: { y: 10 } });
                        }
                        printYear.call(this, cfg.displayDate);
                    }
                }

                target = null;
                value = null;
            };
            var applyMarkerMap = function applyMarkerMap() {
                setTimeout(function () {
                    if (cfg.mode === "day" || cfg.mode === "d") {
                        for (var k in this.markerMap) {
                            this.$["body"].find('[data-calendar-item-date="' + k + '"]').addClass(this.markerMap[k].theme || cfg.defaultMarkerTheme);
                        }
                    }
                }.bind(this));
            };
            var applySelectionMap = function applySelectionMap() {
                setTimeout(function () {
                    for (var k in this.selectionMap) {
                        this.$["body"].find('[data-calendar-item-date="' + k + '"]').addClass("selected-day");
                    }
                }.bind(this));
            };
            var applyPeriodMap = function applyPeriodMap() {
                setTimeout(function () {
                    if (cfg.mode === "day" || cfg.mode === "d") {
                        for (var k in this.periodMap) {
                            if (this.periodMap[k].label) {
                                this.$["body"].find('[data-calendar-item-date="' + k + '"]').find(".addon-footer").html(this.periodMap[k].label);
                            }
                            this.$["body"].find('[data-calendar-item-date="' + k + '"]').addClass(this.periodMap[k].theme);
                        }
                    }
                }.bind(this));
            };
            var clearPeriodMap = function clearPeriodMap() {
                if (cfg.mode === "day" || cfg.mode === "d") {
                    for (var k in this.periodMap) {
                        this.$["body"].find('[data-calendar-item-date="' + k + '"]').find(".addon-footer").empty();
                        this.$["body"].find('[data-calendar-item-date="' + k + '"]').removeClass(this.periodMap[k].theme);
                    }
                }
            };

            /**
             * Preferences of calendar UI
             * @method ax5calendar.setConfig
             * @param {Object} config - 클래스 속성값
             * @param {Element|nodelist} config.target
             * @param {String} [config.mode=day|month|year]
             * @param {Function} [config.onClick}
             * @returns {ax5calendar}
             * @example
             * ```js
             * var myCalendar = new ax5.ui.calendar();
             * myCalendar.setConfig({
             *  target: $("#target"),
             *  mode: "day"
             * });
             * ```
             */
            //== class body start
            this.init = function () {
                // after setConfig();

                this.onStateChanged = cfg.onStateChanged;
                this.onClick = cfg.onClick;

                if (!cfg.target) {
                    console.log(ax5.info.getError("ax5calendar", "401", "setConfig"));
                }
                this.target = jQuery(cfg.target);

                cfg.displayDate = U.date(cfg.displayDate);
                this.target.html(getFrame.call(this));

                // 파트수집
                this.$ = {
                    "root": this.target.find('[data-calendar-els="root"]'),
                    "control": this.target.find('[data-calendar-els="control"]'),
                    "control-display": this.target.find('[data-calendar-els="control-display"]'),
                    "body": this.target.find('[data-calendar-els="body"]')
                };

                if (cfg.control) {
                    this.$["root"].on(cfg.clickEventName, '[data-calendar-move]', function (e) {
                        move.call(this, e || window.event);
                    }.bind(this));
                }

                // collect selectableMap
                if (cfg.selection) {
                    this.setSelection(cfg.selection, false);
                }
                // collect selectableMap
                if (cfg.selectable) {
                    this.setSelectable(cfg.selectable, false);
                }
                // collect markerMap
                if (cfg.marker) {
                    this.setMarker(cfg.marker, false);
                }

                setTimeout(function () {
                    if (cfg.mode === "day" || cfg.mode === "d") {
                        printDay.call(this, cfg.displayDate);
                    } else if (cfg.mode === "month" || cfg.mode === "m") {
                        printMonth.call(this, cfg.displayDate);
                    } else if (cfg.mode === "year" || cfg.mode === "y") {
                        printYear.call(this, cfg.displayDate);
                    }
                }.bind(this));
            };

            /**
             * @method ax5calendar.changeMode
             * @param {String} mode
             * @param {String} changeDate
             * @returns {ax5calendar}
             */
            this.changeMode = function (mode, changeDate) {
                if (typeof changeDate != "undefined") cfg.displayDate = changeDate;
                if (mode) cfg.mode = mode;

                this.$["body"].removeClass("fadein").addClass("fadeout");
                setTimeout(function () {
                    if (cfg.mode == "day" || cfg.mode == "d") {
                        printDay.call(this, cfg.displayDate);
                    } else if (cfg.mode == "month" || cfg.mode == "m") {
                        printMonth.call(this, cfg.displayDate);
                    } else if (cfg.mode == "year" || cfg.mode == "y") {
                        printYear.call(this, cfg.displayDate);
                    }
                    this.$["body"].removeClass("fadeout").addClass("fadein");
                }.bind(this), cfg.animateTime);

                return this;
            };

            /**
             * @method ax5calendar.setSelection
             * @param {Array} selection
             * @returns {ax5calendar}
             * @example
             * ```
             *
             * ```
             */
            this.setSelection = function () {
                self.selectionMap = {};
                var processor = {
                    'arr': function arr(v, map, count) {
                        map = {};
                        if (!U.isArray(v)) return map;
                        self.selection = v = v.splice(0, count);
                        v.forEach(function (n) {
                            if (U.isDate(n)) n = U.date(n, { 'return': cfg.dateFormat });
                            map[n] = true;
                        });
                        return map;
                    }
                };

                return function (selection, isPrint) {
                    var result = {};
                    selectableCount = cfg.multipleSelect ? U.isNumber(cfg.multipleSelect) ? cfg.multipleSelect : 2 : 1;

                    if (cfg.selection = selection) {
                        if (U.isArray(selection)) {
                            result = processor.arr(selection, {}, selectableCount);
                        } else {
                            return this;
                        }
                    }

                    this.selectionMap = jQuery.extend({}, result);
                    // 변경내용 적용하여 출력

                    if (isPrint !== false) applySelectionMap.call(this);

                    result = null;

                    return this;
                };
            }();

            /**
             * @method ax5calendar.getSelection
             */
            this.getSelection = function () {
                return this.selection;
            };

            /**
             * @method ax5calendar.setSelectable
             */
            this.setSelectable = function () {
                self.selectableMap = {};
                var processor = {
                    'arr': function arr(v, map) {
                        map = {};
                        if (!U.isArray(v)) return map;
                        v.forEach(function (n) {
                            if (U.isDate(n)) n = U.date(n, { 'return': cfg.dateFormat });
                            map[n] = true;
                        });
                        return map;
                    },
                    'obj': function obj(v, map) {
                        map = {};
                        if (U.isArray(v)) return map;
                        if (v.range) return map;
                        for (var k in v) {
                            map[k] = v[k];
                        }
                        return map;
                    },
                    'range': function range(v, map) {
                        map = {};
                        if (U.isArray(v)) return map;
                        if (!v.range) return map;

                        v.range.forEach(function (n) {
                            if (U.isDateFormat(n.from) && U.isDateFormat(n.to)) {
                                for (var d = U.date(n.from); d <= U.date(n.to); d.setDate(d.getDate() + 1)) {
                                    map[U.date(d, { "return": cfg.dateFormat })] = true;
                                }
                            } else {
                                for (var i = n.from; i <= n.to; i++) {
                                    map[i] = true;
                                }
                            }
                        });

                        return map;
                    }
                };

                return function (selectable, isPrint) {

                    var key,
                        result = {};

                    if (cfg.selectable = selectable) {
                        if (U.isArray(selectable)) {
                            result = processor.arr(selectable);
                        } else {
                            for (key in processor) {
                                if (selectable[key]) {
                                    result = processor[key](selectable);
                                    break;
                                }
                            }
                            if (Object.keys(result).length === 0) {
                                result = processor.obj(selectable);
                            }
                        }
                    }

                    this.selectableMap = result;
                    // 변경내용 적용하여 출력
                    if (isPrint !== false) this.changeMode();

                    return this;
                };
            }();

            /**
             * @method ax5calendar.setMarker
             */
            this.setMarker = function () {
                self.markerMap = {};
                var processor = {
                    'obj': function obj(v, map) {
                        map = {};
                        if (U.isArray(v)) return map;
                        if (v.range) return map;
                        for (var k in v) {
                            map[k] = v[k];
                        }

                        v = null;
                        return map;
                    },
                    'range': function range(v, map) {
                        map = {};
                        if (U.isArray(v)) return map;
                        if (!v.range) return map;

                        v.range.forEach(function (n) {
                            if (U.isDateFormat(n.from) && U.isDateFormat(n.to)) {
                                for (var d = U.date(n.from); d <= U.date(n.to); d.setDate(d.getDate() + 1)) {
                                    map[U.date(d, { "return": cfg.dateFormat })] = { theme: n.theme, label: n.label };
                                }
                            } else {
                                for (var i = n.from; i <= n.to; i++) {
                                    map[i] = { theme: n.theme, label: n.label };
                                }
                            }
                        });

                        v = null;
                        return map;
                    }
                };

                return function (marker, isApply) {

                    var key,
                        result = {};

                    if (cfg.marker = marker) {
                        for (key in processor) {
                            if (marker[key]) {
                                result = processor[key](marker);
                                break;
                            }
                        }
                        if (Object.keys(result).length === 0) {
                            result = processor.obj(marker);
                        }
                    }

                    this.markerMap = result;
                    // 변경내용 적용하여 출력
                    if (isApply !== false) applyMarkerMap.call(this);
                    return this;
                };
            }();

            /**
             * @method ax5calendar.setPeriod
             */
            this.setPeriod = function () {
                self.periodMap = {};

                var processor = {
                    'range': function range(v, map) {
                        map = {};
                        if (U.isArray(v)) return map;
                        if (!v.range) return map;

                        v.range.forEach(function (n) {
                            if (U.isDateFormat(n.from) && U.isDateFormat(n.to)) {
                                for (var d = new Date(U.date(n.from)); d <= U.date(n.to); d.setDate(d.getDate() + 1)) {
                                    if (d.getTime() == U.date(n.from).getTime()) {
                                        map[U.date(d, { "return": cfg.dateFormat })] = { theme: n.theme || cfg.defaultPeriodTheme, label: n.fromLabel };
                                    } else if (d.getTime() == U.date(n.to).getTime()) {
                                        map[U.date(d, { "return": cfg.dateFormat })] = { theme: n.theme || cfg.defaultPeriodTheme, label: n.toLabel };
                                    } else {
                                        map[U.date(d, { "return": cfg.dateFormat })] = { theme: n.theme || cfg.defaultPeriodTheme };
                                    }
                                }
                            }
                        });

                        v = null;
                        return map;
                    }
                };

                return function (period, isApply) {

                    var key,
                        result = {};

                    // 변경내용 적용하여 출력
                    if (isApply !== false) {
                        clearPeriodMap.call(this);
                    }

                    if (cfg.period = period) {
                        result = processor.range(period);
                    }

                    this.periodMap = result;

                    //console.log(this.periodMap);

                    // 변경내용 적용하여 출력
                    if (isApply !== false) {
                        applyPeriodMap.call(this);
                    }
                    return this;
                };
            }();

            // 클래스 생성자
            this.main = function () {

                UI.calendar_instance = UI.calendar_instance || [];
                UI.calendar_instance.push(this);

                if (arguments && U.isObject(arguments[0])) {
                    this.setConfig(arguments[0]);
                }
            }.apply(this, arguments);
        };
    }());
    CALENDAR = ax5.ui.calendar;
})();
// ax5.ui.calendar.tmpl
(function () {

    var CALENDAR = ax5.ui.calendar;

    var frameTmpl = function frameTmpl(columnKeys) {
        return "\n                <div class=\"ax5-ui-calendar {{theme}}\" data-calendar-els=\"root\" onselectstart=\"return false;\">\n                    {{#control}}\n                    <div class=\"calendar-control\" data-calendar-els=\"control\" style=\"{{controlCSS}}\">\n                        <a class=\"date-move-left\" data-calendar-move=\"left\" style=\"{{controlButtonCSS}}\">{{{left}}}</a>\n                        <div class=\"date-display\" data-calendar-els=\"control-display\" style=\"{{controlCSS}}\"></div>\n                        <a class=\"date-move-right\" data-calendar-move=\"right\" style=\"{{controlButtonCSS}}\">{{{right}}}</a>\n                    </div>\n                    {{/control}}\n                    <div class=\"calendar-body\" data-calendar-els=\"body\"></div>\n                </div>\n                ";
    };
    var dayTmpl = function dayTmpl(columnKeys) {
        return "\n                <table data-calendar-table=\"day\" cellpadding=\"0\" cellspacing=\"0\" style=\"width:100%;\">\n                    <thead>\n                        <tr>\n                        {{#weekNames}}\n                            <td class=\"calendar-col-{{col}}\" style=\"height: {{colHeadHeight}}\">\n                            {{label}}\n                            </td>\n                        {{/weekNames}}\n                        </tr>\n                    </thead>\n                    <tbody>\n                        <tr>\n                            {{#list}}    \n                            {{#isStartOfWeek}}\n                            {{^@first}}\n                        </tr>\n                        <tr>\n                            {{/@first}}\n                            {{/isStartOfWeek}}\n                            <td class=\"calendar-col-{{col}}\" style=\"{{itemStyles}}\">\n                                <a class=\"calendar-item-day {{addClass}}\" data-calendar-item-date=\"{{thisDate}}\">\n                                    <span class=\"addon addon-header\"></span>\n                                    {{thisDataLabel}}\n                                    <span class=\"addon addon-footer\"></span>\n                                </a>\n                            </td>\n                            {{/list}}\n                        </tr>\n                    </tbody>\n                </table>\n                ";
    };
    var monthTmpl = function monthTmpl(columnKeys) {
        return "\n                <table data-calendar-table=\"month\" cellpadding=\"0\" cellspacing=\"0\" style=\"width:100%;\">\n                    <thead>\n                        <tr>\n                            <td class=\"calendar-col-0\" colspan=\"3\" style=\"height: {{colHeadHeight}}\">\n                            {{colHeadLabel}}\n                            </td>\n                        </tr>\n                    </thead>\n                    <tbody>\n                        <tr>\n                            {{#list}}    \n                            {{#isStartOfRow}}\n                            {{^@first}}\n                        </tr>\n                        <tr>\n                            {{/@first}}\n                            {{/isStartOfRow}}\n                            <td class=\"calendar-col-{{col}}\" style=\"{{itemStyles}}\">\n                                <a class=\"calendar-item-month {{addClass}}\" data-calendar-item-month=\"{{thisMonth}}\">\n                                    <span class=\"addon\"></span>\n                                    {{thisMonthLabel}}\n                                    <span class=\"lunar\"></span>\n                                </a>\n                            </td>\n                            {{/list}}\n                        </tr>\n                    </tbody>\n                </table>\n                ";
    };
    var yearTmpl = function yearTmpl(columnKeys) {
        return "\n                <table data-calendar-table=\"year\" cellpadding=\"0\" cellspacing=\"0\" style=\"width:100%;\">\n                    <thead>\n                        <tr>\n                            <td class=\"calendar-col-0\" colspan=\"4\" style=\"height: {{colHeadHeight}}\">\n                            {{colHeadLabel}}\n                            </td>\n                        </tr>\n                    </thead>\n                    <tbody>\n                        <tr>\n                            {{#list}}    \n                            {{#isStartOfRow}}\n                            {{^@first}}\n                        </tr>\n                        <tr>\n                            {{/@first}}\n                            {{/isStartOfRow}}\n                            <td class=\"calendar-col-{{col}}\" style=\"{{itemStyles}}\">\n                                <a class=\"calendar-item-year {{addClass}}\" data-calendar-item-year=\"{{thisYear}}\">\n                                    <span class=\"addon\"></span>\n                                    {{thisYearLabel}}\n                                    <span class=\"lunar\"></span>\n                                </a>\n                            </td>\n                            {{/list}}\n                        </tr>\n                    </tbody>\n                </table>\n                ";
    };

    CALENDAR.tmpl = {
        "frameTmpl": frameTmpl,
        "dayTmpl": dayTmpl,
        "monthTmpl": monthTmpl,
        "yearTmpl": yearTmpl,

        get: function get(tmplName, data, columnKeys) {
            return ax5.mustache.render(CALENDAR.tmpl[tmplName].call(this, columnKeys), data);
        }
    };
})();
"use strict";

// ax5.ui.picker
(function () {

    var UI = ax5.ui;
    var U = ax5.util;
    var PICKER = void 0;

    UI.addClass({
        className: "picker"
    }, function () {
        /**
         * @class ax5picker
         * @classdesc
         * @author tom@axisj.com
         * @example
         * ```js
         * ax5.def.picker.date_leftArrow = '<i class="fa fa-chevron-left"></i>';
         * ax5.def.picker.date_yearTmpl = '%s';
         * ax5.def.picker.date_monthTmpl = '%s';
         * def.picker.date_rightArrow = '<i class="fa fa-chevron-right"></i>';
         *
         * var picker = new ax5.ui.picker({
         *     onStateChanged: function () {
         *         console.log(this);
         *     }
         * });
         *
         * picker.bind({
         *     target: $('[data-picker-date]'),
         *     direction: "auto",
         *     content: {
         *         type: 'date',
         *         formatter: {
         *             pattern: 'date'
         *         }
         *     }
         * });
         * ```
         */
        return function () {
            var self = this,
                cfg = void 0;

            this.instanceId = ax5.getGuid();
            this.config = {
                clickEventName: "click", //(('ontouchstart' in document.documentElement) ? "touchend" : "click"),
                theme: 'default',
                title: '',
                lang: {
                    "ok": "ok",
                    "cancel": "cancel"
                },
                animateTime: 100,
                calendar: {
                    multipleSelect: false,
                    control: {
                        left: ax5.def.picker.date_leftArrow || '&#x02190',
                        yearTmpl: ax5.def.picker.date_yearTmpl || '%s',
                        monthTmpl: ax5.def.picker.date_monthTmpl || '%s',
                        right: ax5.def.picker.date_rightArrow || '&#x02192',
                        yearFirst: true
                    }
                },
                palette: {}
            };
            this.queue = [];
            this.activePicker = null;
            this.activePickerQueueIndex = -1;
            this.openTimer = null;
            this.closeTimer = null;

            cfg = this.config;

            var onStateChanged = function onStateChanged(item, that) {
                if (item && item.onStateChanged) {
                    item.onStateChanged.call(that, that);
                } else if (this.onStateChanged) {
                    this.onStateChanged.call(that, that);
                }
                return true;
            };
            var bindPickerTarget = function () {

                var pickerEvent = {
                    'focus': function focus(queIdx, e) {
                        this.open(queIdx);
                    },
                    'click': function click(queIdx, e) {
                        this.open(queIdx);
                    }
                };

                var pickerType = {
                    '@fn': function fn(queIdx, _input) {
                        var item = this.queue[queIdx],
                            inputLength = _input.length,
                            config = {
                            inputLength: inputLength || 1
                        };

                        if (inputLength > 1) {
                            config.btns = {
                                ok: { label: cfg.lang["ok"], theme: cfg.theme }
                            };
                        }

                        this.queue[queIdx] = jQuery.extend(true, config, item);

                        config = null;
                        inputLength = null;
                    },
                    'date': function date(queIdx, _input) {
                        var item = this.queue[queIdx],
                            contentWidth = item.content ? item.content.width || 270 : 270,
                            contentMargin = item.content ? item.content.margin || 5 : 5,
                            inputLength = _input.length,
                            config = {
                            contentWidth: contentWidth * inputLength + (inputLength - 1) * contentMargin,
                            content: { width: contentWidth, margin: contentMargin },
                            inputLength: inputLength || 1
                        };

                        if (inputLength > 1 && !item.btns) {
                            config.btns = {
                                ok: { label: cfg.lang["ok"], theme: cfg.theme }
                            };
                        }

                        this.queue[queIdx] = jQuery.extend(true, config, item);

                        contentWidth = null;
                        contentMargin = null;
                        config = null;
                        inputLength = null;
                    },
                    'secure-num': function secureNum(queIdx, _input) {
                        var item = this.queue[queIdx],
                            inputLength = _input.length,
                            config = {
                            inputLength: inputLength || 1
                        };

                        this.queue[queIdx] = jQuery.extend(true, config, item);

                        config = null;
                        inputLength = null;
                    },
                    'keyboard': function keyboard(queIdx, _input) {
                        var item = this.queue[queIdx],
                            inputLength = _input.length,
                            config = {
                            inputLength: inputLength || 1
                        };

                        this.queue[queIdx] = jQuery.extend(true, config, item);

                        config = null;
                        inputLength = null;
                    },
                    'numpad': function numpad(queIdx, _input) {
                        var item = this.queue[queIdx],
                            inputLength = _input.length,
                            config = {
                            inputLength: inputLength || 1
                        };

                        this.queue[queIdx] = jQuery.extend(true, config, item);

                        config = null;
                        inputLength = null;
                    },
                    'color': function color(queIdx, _input) {
                        var item = this.queue[queIdx],
                            contentWidth = item.content ? item.content.width || 270 : 270,
                            contentMargin = item.content ? item.content.margin || 5 : 5,
                            inputLength = _input.length,
                            config = {
                            contentWidth: contentWidth * inputLength + (inputLength - 1) * contentMargin,
                            content: { width: contentWidth, margin: contentMargin },
                            inputLength: inputLength || 1
                        },
                            $colorPreview = item.$target.find('[data-ax5picker-color="preview"]');

                        if ($colorPreview.get(0)) {
                            $colorPreview.css({ "background-color": "#" + U.color(_input.val() || "#000000").getHexValue() });
                            // 컬러 피커인 경우 input의 값이 변경되면 preview를 수정
                            _input.on("change", function () {
                                $colorPreview.css({ "background-color": "#" + U.color(this.value || "#000000").getHexValue() });
                            });
                        }

                        if (inputLength > 1 && !item.btns) {
                            config.btns = {
                                ok: { label: cfg.lang["ok"], theme: cfg.theme }
                            };
                        }

                        this.queue[queIdx] = jQuery.extend(true, config, item);

                        contentWidth = null;
                        contentMargin = null;
                        config = null;
                        inputLength = null;
                    }
                };

                return function (queIdx) {
                    var item = this.queue[queIdx],
                        input = void 0;

                    if (!item.content) {
                        console.log(ax5.info.getError("ax5picker", "501", "bind"));
                        return this;
                    }

                    input = item.$target.get(0).tagName.toUpperCase() == "INPUT" ? item.$target : item.$target.find('input[type]');

                    // 함수타입
                    if (U.isFunction(item.content)) {
                        pickerType["@fn"].call(this, queIdx, input);
                    } else {
                        for (var key in pickerType) {
                            if (item.content.type == key) {
                                pickerType[key].call(this, queIdx, input);
                                break;
                            }
                        }
                    }

                    input.unbind('focus.ax5picker').unbind('click.ax5picker').bind('focus.ax5picker', pickerEvent.focus.bind(this, queIdx)).bind('click.ax5picker', pickerEvent.click.bind(this, queIdx));

                    item.$target.find('.input-group-addon').unbind('click.ax5picker').bind('click.ax5picker', pickerEvent.click.bind(this, queIdx));

                    if (item.content.formatter && ax5.ui.formatter) {
                        input.ax5formatter(item.content.formatter);
                    }

                    input = null;
                    item = null;
                    queIdx = null;
                    return this;
                };
            }();

            var alignPicker = function alignPicker(append) {
                if (!this.activePicker) return this;

                var _alignPicker = function _alignPicker(item) {
                    var $window = jQuery(window),
                        $body = jQuery(document.body);
                    var pos = {},
                        positionMargin = 12,
                        dim = {},
                        pickerDim = {},
                        pickerDirection;

                    pos = item.$target.offset();
                    dim = {
                        width: item.$target.outerWidth(),
                        height: item.$target.outerHeight()
                    };
                    pickerDim = {
                        winWidth: Math.max($window.width(), $body.width()),
                        winHeight: Math.max($window.height(), $body.height()),
                        width: this.activePicker.outerWidth(),
                        height: this.activePicker.outerHeight()
                    };

                    // picker css(width, left, top) & direction 결정

                    if (!item.direction || item.direction === "" || item.direction === "auto") {
                        // set direction
                        pickerDirection = "top";
                        if (pos.top - pickerDim.height - positionMargin < 0) {
                            pickerDirection = "top";
                        } else if (pos.top + dim.height + pickerDim.height + positionMargin > pickerDim.winHeight) {
                            pickerDirection = "bottom";
                        }
                    } else {
                        pickerDirection = item.direction;
                    }

                    if (append) {
                        this.activePicker.addClass("direction-" + pickerDirection);
                    }

                    var positionCSS = function () {
                        var css = { left: 0, top: 0 };
                        switch (pickerDirection) {
                            case "top":
                                css.left = pos.left + dim.width / 2 - pickerDim.width / 2;
                                css.top = pos.top + dim.height + positionMargin;
                                break;
                            case "bottom":
                                css.left = pos.left + dim.width / 2 - pickerDim.width / 2;
                                css.top = pos.top - pickerDim.height - positionMargin;
                                break;
                            case "left":
                                css.left = pos.left + dim.width + positionMargin;
                                css.top = pos.top - pickerDim.height / 2 + dim.height / 2;
                                break;
                            case "right":
                                css.left = pos.left - pickerDim.width - positionMargin;
                                css.top = pos.top - pickerDim.height / 2 + dim.height / 2;
                                break;
                        }
                        return css;
                    }();

                    (function () {
                        if (pickerDirection == "top" || pickerDirection == "bottom") {
                            if (positionCSS.left < 0) {
                                positionCSS.left = positionMargin;
                                this.activePickerArrow.css({ left: pos.left + dim.width / 2 - positionCSS.left });
                            } else if (positionCSS.left + pickerDim.width > pickerDim.winWidth) {
                                positionCSS.left = pickerDim.winWidth - pickerDim.width - positionMargin;
                                this.activePickerArrow.css({ left: pos.left + dim.width / 2 - positionCSS.left });
                            }
                        }
                    }).call(this);

                    this.activePicker.css(positionCSS);
                };
                var item = this.queue[this.activePickerQueueIndex];

                if (append) {
                    this.activePicker.css({ top: -999 });
                    jQuery(document.body).append(this.activePicker);
                }

                setTimeout(function () {
                    _alignPicker.call(this, item);
                }.bind(this));
            };

            var onBodyClick = function onBodyClick(e, target) {
                if (!this.activePicker) return this;

                var item = this.queue[this.activePickerQueueIndex];

                target = U.findParentNode(e.target, function (target) {
                    if (target.getAttribute("data-picker-els")) {
                        return true;
                    } else if (item.$target.get(0) == target) {
                        return true;
                    }
                });
                if (!target) {
                    this.close();
                    return this;
                }
                //console.log("i'm picker");
                return this;
            };

            var onBtnClick = function onBtnClick(e, target) {
                // console.log('btn click');
                if (e.srcElement) e.target = e.srcElement;

                target = U.findParentNode(e.target, function (target) {
                    if (target.getAttribute("data-picker-btn")) {
                        return true;
                    }
                });

                if (target) {
                    var item = this.queue[this.activePickerQueueIndex],
                        k = target.getAttribute("data-picker-btn");

                    if (item.btns && item.btns[k].onClick) {
                        var that = {
                            key: k,
                            value: item.btns[k],
                            self: this,
                            item: item
                        };
                        item.btns[k].onClick.call(that, k);
                    } else {
                        this.close();
                    }
                }
            };

            var onBodyKeyup = function onBodyKeyup(e) {
                if (e.keyCode == ax5.info.eventKeys.ESC) {
                    this.close();
                }
            };

            var getQueIdx = function getQueIdx(boundID) {
                if (!U.isString(boundID)) {
                    boundID = jQuery(boundID).data("data-axpicker-id");
                }
                if (!U.isString(boundID)) {
                    console.log(ax5.info.getError("ax5picker", "402", "getQueIdx"));
                    return;
                }
                return U.search(this.queue, function () {
                    return this.id == boundID;
                });
            };
            /// private end

            /**
             * Preferences of picker UI
             * @method ax5picker.setConfig
             * @param {Object} config - 클래스 속성값
             * @returns {ax5picker}
             * @example
             * ```
             * ```
             */
            this.init = function () {
                this.onStateChanged = cfg.onStateChanged;
            };

            /**
             * bind picker UI
             * @method ax5picker.bind
             * @param {Object} item
             * @param {Element} item.target
             * @param {String} item.direction - top|left|right|bottom|auto
             * @param {Number} item.contentWidth
             * @param {Boolean} item.disableChangeTrigger
             * @param {Function} item.onStateChanged
             * @param {Object} item.btns
             * @param {Object} item.content
             * @param {Number} item.content.width
             * @param {Number} item.content.margin
             * @param {String} item.content.type
             * @param {Object} item.content.config - binded UI config
             * @param {Object} item.content.formatter
             * @param {String} item.content.formatter.pattern
             * @returns {ax5picker}
             * @example
             * ```js
             * var picker = new ax5.ui.picker();
             * $(document.body).ready(function () {
             *   picker.bind({
             *       target: $('[data-ax5picker="basic"]'),
             *       direction: "top",
             *       content: {
             *           width: 270,
             *           margin: 10,
             *           type: 'date',
             *           config: {
             *               control: {
             *                   left: '<i class="fa fa-chevron-left"></i>',
             *                   yearTmpl: '%s',
             *                   monthTmpl: '%s',
             *                   right: '<i class="fa fa-chevron-right"></i>'
             *               },
             *               lang: {
             *                   yearTmpl: "%s년",
             *                   months: ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'],
             *                   dayTmpl: "%s"
             *               }
             *           },
             *           formatter: {
             *               pattern: 'date'
             *           }
             *       },
             *       onStateChanged: function () {
             *
             *       }
             *   });
             * });
             * ```
             */
            this.bind = function (item) {
                var pickerConfig = {},
                    queIdx = void 0;

                item = jQuery.extend(true, pickerConfig, cfg, item);

                if (!item.target) {
                    console.log(ax5.info.getError("ax5picker", "401", "bind"));
                    return this;
                }
                item.$target = jQuery(item.target);

                if (!item.$target.get(0)) {
                    console.log(ax5.info.getError("ax5picker", "401", "bind"));
                    return this;
                }

                if (!item.id) item.id = item.$target.data("data-axpicker-id");

                if (!item.id) {
                    item.id = 'ax5-picker-' + ax5.getGuid();
                    item.$target.data("data-axpicker-id", item.id);
                }
                queIdx = U.search(this.queue, function () {
                    return this.id == item.id;
                });

                if (queIdx === -1) {
                    this.queue.push(item);
                    bindPickerTarget.call(this, this.queue.length - 1);
                } else {
                    this.queue[queIdx] = jQuery.extend(true, {}, this.queue[queIdx], item);
                    bindPickerTarget.call(this, queIdx);
                }

                pickerConfig = null;
                queIdx = null;
                return this;
            };

            /**
             * @method ax5picker.setContentValue
             * @param {String} boundID
             * @param {Number} inputIndex
             * @param {String} val
             * @returns {ax5picker} this
             */
            this.setContentValue = function () {

                var multipleInputProcessor = {
                    "date": function date(_item, _inputIndex, _val) {
                        var values = [],
                            diffDay,
                            prevInputValue,
                            nextInputValue;

                        if (_item.$target.get(0).tagName.toUpperCase() !== "INPUT") {
                            _item.$target.find('input[type]').each(function () {
                                values.push(this.value);
                            });
                        }

                        if (_inputIndex == 0) {
                            if (values.length > 1 && values[1] !== "") {
                                // 값 검증
                                diffDay = ax5.util.dday(values[1], { today: values[0] });
                                if (diffDay < 0) {
                                    // 다음날짜 달력을 변경합니다.
                                    nextInputValue = _val;
                                } else {}
                            } else {
                                nextInputValue = _val;
                            }

                            if (nextInputValue) {
                                _item.pickerCalendar[1].ax5uiInstance.setSelection([nextInputValue], false).changeMode("d", nextInputValue);
                                this.setContentValue(_item.id, 1, nextInputValue);
                            }

                            return _val;
                        } else if (_inputIndex == 1) {

                            if (values.length > 1) {
                                // 값 검증
                                diffDay = ax5.util.dday(values[1], { today: values[0] });
                                if (diffDay < 0) {
                                    // 다음날짜 달력을 변경합니다.
                                    prevInputValue = values[1];
                                }
                            }

                            if (prevInputValue) {
                                _item.pickerCalendar[0].ax5uiInstance.setSelection([prevInputValue], false).changeMode("d", prevInputValue);
                                this.setContentValue(_item.id, 0, prevInputValue);
                            }

                            return _val;
                        }
                    }
                };

                return function (boundID, inputIndex, val, _option) {

                    var queIdx = U.isNumber(boundID) ? boundID : getQueIdx.call(this, boundID),
                        item = this.queue[queIdx],
                        _input = void 0;

                    if (!_option) _option = {};

                    if (item) {

                        _input = item.$target.get(0).tagName.toUpperCase() == "INPUT" ? item.$target : jQuery(item.$target.find('input[type]').get(inputIndex));
                        _input.val(val);

                        if (!item.disableChangeTrigger) {
                            _input.trigger("change");
                        } else {
                            var $colorPreview = item.$target.find('[data-ax5picker-color="preview"]');
                            if ($colorPreview.get(0)) {
                                $colorPreview.css({ "background-color": val });
                            }
                        }

                        // picker의 입력이 2개이상인 경우
                        //console.log(item.inputLength);
                        if (item.inputLength > 1) {
                            // 경우에 따라 첫번 선택에 따라 해야할 일들 처리
                            if (multipleInputProcessor[item.content.type]) {
                                val = multipleInputProcessor[item.content.type].call(this, item, inputIndex, val);
                            }
                        }

                        var that = {
                            self: self,
                            state: "changeValue",
                            item: item,
                            inputIndex: inputIndex,
                            value: val,
                            values: [val]
                        };
                        if (item.$target.get(0).tagName.toUpperCase() !== "INPUT") {
                            that.values = [];
                            item.$target.find('input[type]').each(function () {
                                that.values.push(this.value);
                            });
                        }

                        onStateChanged.call(this, item, that);

                        if (item.inputLength == 1 && !_option.doNotClose) {
                            this.close();
                        }
                    }

                    item = null;
                    boundID = null;
                    inputIndex = null;
                    val = null;
                    return this;
                };
            }();

            /**
             * @method ax5picker.getContentValue
             * @param {String} boundID
             * @param {Number} inputIndex
             * @returns {ax5picker} this
             */
            this.getContentValue = function () {
                return function (boundID, inputIndex) {
                    var queIdx = U.isNumber(boundID) ? boundID : getQueIdx.call(this, boundID),
                        item = this.queue[queIdx],
                        _input = void 0;

                    if (item) {
                        _input = item.$target.get(0).tagName.toUpperCase() == "INPUT" ? item.$target : jQuery(item.$target.find('input[type]').get(inputIndex));
                        return _input.val();
                    }

                    item = null;
                    boundID = null;
                    inputIndex = null;
                    return this;
                };
            }();

            /**
             * @method ax5picker.open
             * @param {String} boundObjectId
             * @returns {ax5picker} this
             */
            this.open = function () {

                var pickerContent = {
                    '@fn': function fn(queIdx, callback) {
                        var item = this.queue[queIdx];
                        item.content.call(item, function (html) {
                            callback(html);
                        });
                        return true;
                    },
                    'date': function date(queIdx) {
                        var item = this.queue[queIdx],
                            html = [],
                            calendarConfig = jQuery.extend({}, cfg.calendar, { displayDate: new Date() }),
                            input = item.$target.get(0).tagName.toUpperCase() == "INPUT" ? item.$target : item.$target.find('input[type]');

                        for (var i = 0; i < item.inputLength; i++) {
                            html.push('<div ' + 'style="width:' + U.cssNumber(item.content.width) + ';float:left;" ' + 'class="ax-picker-content-box" ' + 'data-calendar-target="' + i + '"></div>');
                            if (i < item.inputLength - 1) html.push('<div style="width:' + item.content.margin + 'px;float:left;height: 5px;"></div>');
                        }
                        html.push('<div style="clear:both;"></div>');
                        item.pickerContent.html(html.join(''));

                        // calendar bind
                        item.pickerCalendar = [];
                        item.pickerContent.find('[data-calendar-target]').each(function () {

                            // calendarConfig extend ~
                            var idx = this.getAttribute("data-calendar-target"),
                                dValue = input.get(idx).value,
                                d = ax5.util.date(dValue),
                                dateConvert = {
                                "year": function year(_d) {
                                    return ax5.util.date(_d, { "return": "yyyy" });
                                },
                                "month": function month(_d) {
                                    return ax5.util.date(_d, { "return": "yyyy-MM" });
                                },
                                "day": function day(_d) {
                                    return _d;
                                }
                            };

                            calendarConfig.displayDate = d;

                            if (dValue) calendarConfig.selection = [d];

                            calendarConfig = jQuery.extend(true, calendarConfig, item.content.config || {});
                            calendarConfig.target = this;
                            calendarConfig.onClick = function () {
                                self.setContentValue(item.id, idx, dateConvert[calendarConfig.selectMode || "day"](this.date));
                            };

                            item.pickerCalendar.push({
                                itemId: item.id,
                                inputIndex: idx,
                                ax5uiInstance: new ax5.ui.calendar(calendarConfig)
                            });
                        });
                    },
                    'secure-num': function secureNum(queIdx) {
                        var item = this.queue[queIdx],
                            html = [];
                        for (var i = 0; i < item.inputLength; i++) {
                            html.push('<div ' + 'style="width:' + U.cssNumber(item.content.width) + ';float:left;" ' + 'class="ax-picker-content-box" ' + 'data-secure-num-target="' + i + '"></div>');
                            if (i < item.inputLength - 1) html.push('<div style="width:' + item.content.margin + 'px;float:left;height: 5px;"></div>');
                        }
                        html.push('<div style="clear:both;"></div>');
                        item.pickerContent.html(html.join(''));

                        // secure-num bind
                        item.pickerContent.find('[data-secure-num-target]').each(function () {
                            var idx = this.getAttribute("data-secure-num-target"),
                                po = [];

                            var numArray = function (a) {
                                var j, x, i;
                                for (i = a.length; i; i -= 1) {
                                    j = Math.floor(Math.random() * i);
                                    x = a[i - 1];
                                    a[i - 1] = a[j];
                                    a[j] = x;
                                }
                                return a;
                            }([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);

                            var specialArray = [{ label: "&#x02190", fn: "back" }, { label: "C", fn: "clear" }];

                            numArray.forEach(function (n) {
                                po.push('<div style="float:left;' + item.content.config.btnWrapStyle + '">');
                                po.push('<button class="btn btn-default btn-' + item.content.config.btnTheme + '" ' + 'style="' + item.content.config.btnStyle + '" data-secure-num-value="' + n + '">' + n + '</button>');
                                po.push('</div>');
                            });
                            specialArray.forEach(function (n) {
                                po.push('<div style="float:left;' + item.content.config.btnWrapStyle + '">');
                                po.push('<button class="btn btn-default btn-' + item.content.config.specialBtnTheme + '" ' + 'style="' + item.content.config.btnStyle + '" data-secure-num-value="' + n.fn + '">' + n.label + '</button>');
                                po.push('</div>');
                            });

                            po.push('<div style="clear:both;"></div>');

                            $(this).html(po.join('')).on("click", '[data-secure-num-value]', function () {
                                var act = this.getAttribute("data-secure-num-value");
                                var _input = item.$target.get(0).tagName.toUpperCase() == "INPUT" ? item.$target : jQuery(item.$target.find('input[type]').get(idx));
                                var val = _input.val();

                                if (act == "back") {
                                    _input.val(val.substring(0, val.length - 1));
                                } else if (act == "clear") {
                                    _input.val('');
                                } else {
                                    _input.val(val + act);
                                }

                                onStateChanged.call(this, item, {
                                    self: self,
                                    state: "changeValue",
                                    item: item,
                                    value: _input.val()
                                });
                            });
                        });
                    },
                    'keyboard': function keyboard(queIdx) {
                        var item = this.queue[queIdx];
                        var html = [];
                        for (var i = 0; i < item.inputLength; i++) {
                            html.push('<div ' + 'style="width:' + U.cssNumber(item.content.width) + ';float:left;" ' + 'class="ax-picker-content-box" ' + 'data-keyboard-target="' + i + '"></div>');
                            if (i < item.inputLength - 1) html.push('<div style="width:' + item.content.margin + 'px;float:left;height: 5px;"></div>');
                        }
                        html.push('<div style="clear:both;"></div>');
                        item.pickerContent.html(html.join(''));

                        var keyArray = [[{ value: "`", shiftValue: "~" }, { value: "1", shiftValue: "!" }, { value: "2", shiftValue: "@" }, { value: "3", shiftValue: "#" }, { value: "4", shiftValue: "$" }, { value: "5", shiftValue: "%" }, { value: "6", shiftValue: "^" }, { value: "7", shiftValue: "&" }, { value: "8", shiftValue: "*" }, { value: "9", shiftValue: "(" }, { value: "0", shiftValue: ")" }, { value: "-", shiftValue: "_" }, { value: "=", shiftValue: "+" }, { label: "&#x02190", fn: "back" }], [{ value: "q", shiftValue: "Q" }, { value: "w", shiftValue: "W" }, { value: "e", shiftValue: "E" }, { value: "r", shiftValue: "R" }, { value: "t", shiftValue: "T" }, { value: "y", shiftValue: "Y" }, { value: "u", shiftValue: "U" }, { value: "i", shiftValue: "I" }, { value: "o", shiftValue: "O" }, { value: "p", shiftValue: "P" }, { value: "[", shiftValue: "{" }, { value: "]", shiftValue: "}" }, { value: "\\", shiftValue: "|" }], [{ label: "Clear", fn: "clear" }, { value: "a", shiftValue: "A" }, { value: "s", shiftValue: "S" }, { value: "d", shiftValue: "D" }, { value: "f", shiftValue: "F" }, { value: "g", shiftValue: "G" }, { value: "h", shiftValue: "H" }, { value: "j", shiftValue: "J" }, { value: "k", shiftValue: "K" }, { value: "l", shiftValue: "L" }, { value: ";", shiftValue: ":" }, { value: "'", shiftValue: "\"" }], [{ label: "Shift", fn: "shift" }, { value: "z", shiftValue: "Z" }, { value: "x", shiftValue: "X" }, { value: "c", shiftValue: "C" }, { value: "v", shiftValue: "V" }, { value: "b", shiftValue: "B" }, { value: "n", shiftValue: "N" }, { value: "m", shiftValue: "M" }, { value: ",", shiftValue: "<" }, { value: ".", shiftValue: ">" }, { value: "/", shiftValue: "?" }, { label: "Close", fn: "close" }]];
                        var specialArray = [{ label: "&#x02190", fn: "back" }, { label: "C", fn: "clear" }];

                        var getKeyBoard = function getKeyBoard(isShiftKey) {
                            var po = [];
                            keyArray.forEach(function (row) {
                                po.push('<div style="display: table;margin:0 auto;">');
                                row.forEach(function (n) {

                                    var keyValue, keyLabel, btnWrapStyle, btnTheme, btnStyle;
                                    if (n.fn) {
                                        keyValue = n.fn;
                                        keyLabel = n.label;
                                        btnWrapStyle = item.content.config.specialBtnWrapStyle;
                                        btnTheme = item.content.config.specialBtnTheme;
                                        btnStyle = item.content.config.specialBtnStyle;
                                    } else {
                                        keyLabel = keyValue = isShiftKey ? n.shiftValue : n.value;
                                        btnWrapStyle = item.content.config.btnWrapStyle;
                                        btnTheme = item.content.config.btnTheme;
                                        btnStyle = item.content.config.btnStyle;
                                    }

                                    po.push('<div style="display: table-cell;' + btnWrapStyle + '">');
                                    po.push('<button class="btn btn-default btn-' + btnTheme + '" ' + 'style="' + btnStyle + '" data-keyboard-value="' + keyValue + '">' + keyLabel + '</button>');
                                    po.push('</div>');
                                });
                                po.push('</div>');
                            });
                            return po.join('');
                        };

                        // secure-num bind
                        item.pickerContent.find('[data-keyboard-target]').each(function () {
                            var idx = this.getAttribute("data-keyboard-target"),
                                $this = $(this),
                                isShiftKey = false,
                                toggleShift = function toggleShift() {
                                isShiftKey = !isShiftKey;
                                $this.html(getKeyBoard(isShiftKey));
                            };

                            $this.html(getKeyBoard(isShiftKey)).on("mousedown", '[data-keyboard-value]', function () {
                                var act = this.getAttribute("data-keyboard-value"),
                                    _input = item.$target.get(0).tagName.toUpperCase() == "INPUT" ? item.$target : jQuery(item.$target.find('input[type]').get(idx)),
                                    val = _input.val();

                                switch (act) {
                                    case "back":
                                        _input.val(val.substring(0, val.length - 1));
                                        break;
                                    case "clear":
                                        _input.val('');
                                        break;
                                    case "shift":
                                        toggleShift();
                                        return false;
                                        break;
                                    case "close":
                                        self.close();
                                        return false;
                                        break;
                                    default:
                                        _input.val(val + act);
                                }

                                onStateChanged.call(this, item, {
                                    self: self,
                                    state: "changeValue",
                                    item: item,
                                    value: _input.val()
                                });
                            });
                        });
                    },
                    'numpad': function numpad(queIdx) {
                        var item = this.queue[queIdx],
                            html = [];
                        for (var i = 0; i < item.inputLength; i++) {
                            html.push('<div ' + 'style="width:' + U.cssNumber(item.content.width) + ';float:left;" ' + 'class="ax-picker-content-box" ' + 'data-numpad-target="' + i + '"></div>');
                            if (i < item.inputLength - 1) html.push('<div style="width:' + item.content.margin + 'px;float:left;height: 5px;"></div>');
                        }
                        html.push('<div style="clear:both;"></div>');
                        item.pickerContent.html(html.join(''));

                        // secure-num bind
                        item.pickerContent.find('[data-numpad-target]').each(function () {
                            var idx = this.getAttribute("data-numpad-target"),
                                po = [],
                                keyArray = item.content.config.keyArray || [{ value: "7" }, { value: "8" }, { value: "9" }, { label: "BS", fn: "back" }, { value: "4" }, { value: "5" }, { value: "6" }, { label: "CLS", fn: "clear" }, { value: "1" }, { value: "2" }, { value: "3" }, { value: "" }, { value: "." }, { value: "0" }, { value: "" }, { label: "OK", fn: "enter" }];

                            keyArray.forEach(function (n) {
                                var keyValue = void 0,
                                    keyLabel = void 0,
                                    btnWrapStyle = void 0,
                                    btnTheme = void 0,
                                    btnStyle = void 0;

                                if (n.fn) {
                                    keyValue = n.fn;
                                    keyLabel = n.label;
                                    btnTheme = item.content.config.specialBtnTheme;
                                    btnWrapStyle = item.content.config.specialBtnWrapStyle;
                                    btnStyle = item.content.config.specialBtnStyle;
                                } else {
                                    keyLabel = keyValue = n.value;
                                    btnTheme = keyValue ? item.content.config.btnTheme : "";
                                    btnWrapStyle = item.content.config.btnWrapStyle;
                                    btnStyle = item.content.config.btnStyle;
                                }

                                po.push('<div style="float:left;' + btnWrapStyle + '">');
                                po.push('<button class="btn btn-default btn-' + btnTheme + '" ' + 'style="' + btnStyle + '" data-numpad-value="' + keyValue + '">' + (keyLabel || "&nbsp;") + '</button>');
                                po.push('</div>');
                            });

                            po.push('<div style="clear:both;"></div>');

                            $(this).html(po.join('')).on("mousedown", '[data-numpad-value]', function () {
                                var act = this.getAttribute("data-numpad-value"),
                                    _input = item.$target.get(0).tagName.toUpperCase() == "INPUT" ? item.$target : jQuery(item.$target.find('input[type]').get(idx)),
                                    val = _input.val(),
                                    state = "";

                                switch (act) {
                                    case "back":
                                        state = "changeValue";
                                        _input.val(val.substring(0, val.length - 1));
                                        break;
                                    case "clear":
                                        state = "changeValue";
                                        _input.val('');
                                        break;
                                    case "enter":
                                        self.close(item, "enter");
                                        return false;
                                        break;
                                    case "close":
                                        self.close();
                                        return false;
                                        break;
                                    default:
                                        state = "changeValue";
                                        _input.val(val + act);
                                }

                                onStateChanged.call(this, item, {
                                    self: self,
                                    state: state,
                                    item: item,
                                    value: _input.val()
                                });
                            });
                        });
                    },
                    'color': function color(queIdx) {
                        var item = this.queue[queIdx],
                            html = [],
                            paletteConfig = jQuery.extend({}, cfg.palette),
                            input = item.$target.get(0).tagName.toUpperCase() == "INPUT" ? item.$target : item.$target.find('input[type]');

                        for (var i = 0; i < item.inputLength; i++) {
                            html.push('<div ' + 'style="padding: 5px;width:' + U.cssNumber(item.content.width) + ';float:left;" ' + 'class="ax-picker-content-box" ' + 'data-palette-target="' + i + '" data-ax5palette="ax5picker-' + item.id + '"></div>');
                            if (i < item.inputLength - 1) html.push('<div style="width:' + item.content.margin + 'px;float:left;height: 5px;"></div>');
                        }
                        html.push('<div style="clear:both;"></div>');
                        item.pickerContent.html(html.join(''));

                        // calendar bind
                        item.pickerPalette = [];

                        item.pickerContent.find('[data-palette-target]').each(function () {
                            // calendarConfig extend ~
                            var idx = this.getAttribute("data-palette-target"),
                                dColor = input.get(idx).value;

                            paletteConfig.selectedColor = dColor;
                            paletteConfig = jQuery.extend(true, paletteConfig, item.content.config || {});
                            paletteConfig.target = this;
                            paletteConfig.onClick = function (color) {
                                self.setContentValue(item.id, idx, color);
                            };
                            paletteConfig.onUpdateColor = function (color) {
                                self.setContentValue(item.id, idx, color, { doNotClose: true });
                            };

                            item.pickerPalette.push({
                                itemId: item.id,
                                inputIndex: idx,
                                ax5uiInstance: new ax5.ui.palette(paletteConfig)
                            });
                        });
                    }
                };

                return function (boundID, tryCount) {
                    var queIdx = U.isNumber(boundID) ? boundID : getQueIdx.call(this, boundID),
                        item = this.queue[queIdx];

                    /**
                     다른 피커가 있는 경우와 다른 피커를 닫고 다시 오픈 명령이 내려진 경우에 대한 예외 처리 구문
                     */
                    if (this.openTimer) clearTimeout(this.openTimer);
                    if (this.activePicker) {
                        if (this.activePickerQueueIndex == queIdx) {
                            return this;
                        }

                        if (tryCount > 2) return this;
                        this.close();
                        this.openTimer = setTimeout(function () {
                            this.open(queIdx, (tryCount || 0) + 1);
                        }.bind(this), cfg.animateTime);
                        return this;
                    }

                    this.activePicker = jQuery(PICKER.tmpl.get.call(this, "pickerTmpl", item));
                    this.activePickerArrow = this.activePicker.find(".ax-picker-arrow");
                    this.activePickerQueueIndex = queIdx;
                    item.pickerContent = this.activePicker.find('[data-picker-els="content"]');

                    if (U.isFunction(item.content)) {
                        // 함수타입
                        item.pickerContent.html("Loading..");
                        pickerContent["@fn"].call(this, queIdx, function (html) {
                            item.pickerContent.html(html);
                        });
                    } else {
                        if (item.content.type in pickerContent) {
                            pickerContent[item.content.type].call(this, queIdx);
                        }
                    }

                    // bind event picker btns
                    this.activePicker.find("[data-picker-btn]").on(cfg.clickEventName, function (e) {
                        onBtnClick.call(this, e || window.event, queIdx);
                    }.bind(this));

                    alignPicker.call(this, "append");

                    jQuery(window).bind("resize.ax5picker", function () {
                        alignPicker.call(this);
                    }.bind(this));

                    // bind key event
                    jQuery(window).bind("keyup.ax5picker", function (e) {
                        e = e || window.event;
                        onBodyKeyup.call(this, e);
                        U.stopEvent(e);
                    }.bind(this));

                    jQuery(window).bind("click.ax5picker", function (e) {
                        e = e || window.event;
                        onBodyClick.call(this, e);
                        U.stopEvent(e);
                    }.bind(this));

                    onStateChanged.call(this, item, {
                        self: this,
                        state: "open",
                        item: item
                    });

                    return this;
                };
            }();

            /**
             * @method ax5picker.close
             * @returns {ax5picker} this
             */
            this.close = function (item, state) {
                if (this.closeTimer) clearTimeout(this.closeTimer);
                if (!this.activePicker) return this;

                item = this.queue[this.activePickerQueueIndex];

                this.activePicker.addClass("destroy");
                jQuery(window).unbind("resize.ax5picker");
                jQuery(window).unbind("click.ax5picker");
                jQuery(window).unbind("keyup.ax5picker");

                this.closeTimer = setTimeout(function () {
                    if (this.activePicker) this.activePicker.remove();
                    this.activePicker = null;
                    this.activePickerQueueIndex = -1;

                    onStateChanged.call(this, item, {
                        self: this,
                        state: state || "close"
                    });
                }.bind(this), cfg.animateTime);

                return this;
            };

            // 클래스 생성자
            this.main = function () {
                if (arguments && U.isObject(arguments[0])) {
                    this.setConfig(arguments[0]);
                }
            }.apply(this, arguments);
        };
    }());

    PICKER = ax5.ui.picker;
})();

// ax5.ui.picker.tmpl
(function () {

    var PICKER = ax5.ui.picker;
    var U = ax5.util;

    var pickerTmpl = function pickerTmpl() {
        return "\n<div class=\"ax5-ui-picker {{theme}}\" id=\"{{id}}\" data-picker-els=\"root\" {{#zIndex}}style=\"z-index:{{zIndex}};\"{{/zIndex}}>\n    {{#title}}\n        <div class=\"ax-picker-heading\">{{title}}</div>\n    {{/title}}\n    <div class=\"ax-picker-body\">\n        <div class=\"ax-picker-content\" data-picker-els=\"content\" style=\"width:{{contentWidth}}px;\"></div>\n        {{#btns}}\n            <div class=\"ax-picker-buttons\">\n            {{#btns}}\n                {{#@each}}\n                <button data-picker-btn=\"{{@key}}\" class=\"btn btn-default {{@value.theme}}\">{{@value.label}}</button>\n                {{/@each}}\n            {{/btns}}\n            </div>\n        {{/btns}}\n    </div>\n    <div class=\"ax-picker-arrow\"></div>\n</div>\n";
    };

    PICKER.tmpl = {
        "pickerTmpl": pickerTmpl,

        get: function get(tmplName, data, columnKeys) {
            return ax5.mustache.render(PICKER.tmpl[tmplName].call(this, columnKeys), data);
        }
    };
})();
/**
 * ax5.ui.picker_instance
 * @type {ax5picker}
 * @example
 * ```js
 * // picker 기본 속성을 변경해야 한다면
 * ax5.ui.picker_instance.setConfig({
 * });
 *
 * ```
 */
if (ax5 && ax5.ui && ax5.ui.picker) {
    ax5.ui.picker_instance = new ax5.ui.picker();

    jQuery.fn.ax5picker = function () {
        return function (config) {
            if (ax5.util.isString(arguments[0])) {
                var methodName = arguments[0];

                switch (methodName) {
                    case "open":
                        return ax5.ui.picker_instance.open(this);
                        break;
                    case "close":
                        return ax5.ui.picker_instance.close(this);
                        break;
                    case "setValue":
                        return ax5.ui.picker_instance.setContentValue(this, arguments[1], arguments[2]);
                        break;
                    default:
                        return this;
                }
            } else {
                if (typeof config == "undefined") config = {};
                jQuery.each(this, function () {
                    var defaultConfig = {
                        target: this
                    };
                    config = jQuery.extend(true, config, defaultConfig);
                    ax5.ui.picker_instance.bind(config);
                });
            }
            return this;
        };
    }();
}
"use strict";

// ax5.ui.formatter
(function () {
    var UI = ax5.ui;
    var U = ax5.util;
    var FORMATTER = void 0;

    UI.addClass({
        className: "formatter"
    }, function () {
        var TODAY = new Date();
        var setSelectionRange = function setSelectionRange(input, pos) {
            if (typeof pos == "undefined") {
                pos = input.value.length;
            }
            if (input.setSelectionRange) {
                input.focus();
                input.setSelectionRange(pos, pos);
            } else if (input.createTextRange) {
                var range = input.createTextRange();
                range.collapse(true);
                range.moveEnd('character', pos);
                range.moveStart('character', pos);
                range.select();
            } else if (input.selectionStart) {
                input.focus();
                input.selectionStart = pos;
                input.selectionEnd = pos;
            }
        };

        /**
         * @class ax5formatter
         * @classdesc
         * @author tom@axisj.com
         * @example
         * ```js
         * $('#idInputTime').attr('data-ax5formatter', 'time').ax5formatter();
         * $('#idInputMoney').attr('data-ax5formatter', 'money').ax5formatter();
         * $('#idInputPhone').attr('data-ax5formatter', 'phone').ax5formatter();
         * $('#idInputDate').attr('data-ax5formatter', 'date').ax5formatter();
         *
         * $('#ax5formatter-custom').ax5formatter({
         *     pattern: "custom",
         *     getEnterableKeyCodes: function(){
         *         return {
         *             '65':'a',
         *             '66':'b',
         *             '67':'c',
         *             '68':'d',
         *             '69':'e',
         *             '70':'f'
         *         };
         *     },
         *     getPatternValue: function(obj){
         *         return obj.value.replace(/./g, "*");
         *     }
         * });
         * ```
         */
        return function () {
            var self = this,
                cfg;

            this.instanceId = ax5.getGuid();
            this.config = {
                animateTime: 250
            };

            this.queue = [];
            this.openTimer = null;
            this.closeTimer = null;

            cfg = this.config;

            var formatterEvent = {
                'focus': function focus(opts, optIdx, e) {
                    if (!opts.$input.data("__originValue__")) opts.$input.data("__originValue__", opts.$input.val());
                },
                /* 키 다운 이벤트에서 입력할 수 없는 키 입력을 방어 */
                'keydown': function keydown(opts, optIdx, e) {
                    var isStop = false;
                    if (!opts.enterableKeyCodes) {} else if (e.which && opts.enterableKeyCodes[e.which]) {} else if (!e.metaKey && !e.ctrlKey && !e.shiftKey) {
                        //console.log(e.which, opts.enterableKeyCodes);
                        isStop = true;
                    }
                    if (isStop) ax5.util.stopEvent(e);
                },
                /* 키 업 이벤트에서 패턴을 적용 */
                'keyup': function keyup(opts, optIdx, e) {
                    var elem = opts.$input.get(0),
                        elemFocusPosition,
                        beforeValue,
                        newValue,
                        selection,
                        selectionLength;

                    if ('selectionStart' in elem) {
                        // Standard-compliant browsers
                        elemFocusPosition = elem.selectionStart;
                    } else if (document.selection) {
                        // IE
                        //elem.focus();
                        selection = document.selection.createRange();
                        selectionLength = document.selection.createRange().text.length;
                        selection.moveStart('character', -elem.value.length);
                        elemFocusPosition = selection.text.length - selectionLength;
                    }

                    beforeValue = elem.value;
                    if (opts.pattern in FORMATTER.formatter) {
                        newValue = FORMATTER.formatter[opts.pattern].getPatternValue.call(this, opts, optIdx, e, elem.value);
                    } else {
                        newValue = beforeValue;
                    }

                    if (newValue != beforeValue) {
                        opts.$input.val(newValue).trigger("change");
                        setSelectionRange(elem, elemFocusPosition + newValue.length - beforeValue.length);
                    }
                },
                'blur': function blur(opts, optIdx, e, _force) {
                    var elem = opts.$input.get(0),
                        beforeValue,
                        newValue;

                    opts.$input.removeData("__originValue__");

                    beforeValue = elem.value;
                    if (opts.pattern in FORMATTER.formatter) {
                        newValue = FORMATTER.formatter[opts.pattern].getPatternValue.call(this, opts, optIdx, e, elem.value, 'blur');
                    } else {
                        newValue = beforeValue;
                    }

                    if (_force) {
                        opts.$input.val(newValue);
                    } else {
                        if (newValue != beforeValue) {
                            opts.$input.val(newValue).trigger("change");
                        }
                    }
                }
            };

            var bindFormatterTarget = function bindFormatterTarget(opts, optIdx) {

                if (!opts.pattern) {
                    if (opts.$target.get(0).tagName == "INPUT") {
                        opts.pattern = opts.$target.attr('data-ax5formatter');
                    } else {
                        opts.pattern = opts.$target.find('input[type="text"]').attr('data-ax5formatter');
                    }
                    if (!opts.pattern) {
                        console.log(ax5.info.getError("ax5formatter", "501", "bind"));
                        console.log(opts.target);
                        return this;
                    }
                }

                var re = /[^\(^\))]+/gi,
                    matched = opts.pattern.match(re);

                opts.pattern = matched[0];
                opts.patternArgument = matched[1] || "";

                // 함수타입
                if (opts.pattern in FORMATTER.formatter) {
                    opts.enterableKeyCodes = FORMATTER.formatter[opts.pattern].getEnterableKeyCodes.call(this, opts, optIdx);
                }

                opts.$input.unbind('focus.ax5formatter').bind('focus.ax5formatter', formatterEvent.focus.bind(this, this.queue[optIdx], optIdx));

                opts.$input.unbind('keydown.ax5formatter').bind('keydown.ax5formatter', formatterEvent.keydown.bind(this, this.queue[optIdx], optIdx));

                opts.$input.unbind('keyup.ax5formatter').bind('keyup.ax5formatter', formatterEvent.keyup.bind(this, this.queue[optIdx], optIdx));

                opts.$input.unbind('blur.ax5formatter').bind('blur.ax5formatter', formatterEvent.blur.bind(this, this.queue[optIdx], optIdx));

                formatterEvent.blur.call(this, this.queue[optIdx], optIdx);

                return this;
            };

            var getQueIdx = function getQueIdx(boundID) {
                if (!U.isString(boundID)) {
                    boundID = jQuery(boundID).data("data-formatter");
                }
                /*
                 if (!U.isString(boundID)) {
                 console.log(ax5.info.getError("ax5formatter", "402", "getQueIdx"));
                 return;
                 }
                 */
                return U.search(this.queue, function () {
                    return this.id == boundID;
                });
            };

            /**
             * Preferences of formatter UI
             * @method ax5formatter.setConfig
             * @param {Object} config - 클래스 속성값
             * @returns {ax5.ui.formatter}
             * @example
             * ```
             * ```
             */
            this.init = function () {};

            this.bind = function (opts) {
                var formatterConfig = {},
                    optIdx = void 0;

                jQuery.extend(true, formatterConfig, cfg);
                if (opts) jQuery.extend(true, formatterConfig, opts);
                opts = formatterConfig;

                if (!opts.target) {
                    console.log(ax5.info.getError("ax5formatter", "401", "bind"));
                    return this;
                }
                opts.$target = jQuery(opts.target);

                if (opts.$target.get(0).tagName == "INPUT") {
                    opts.$input = opts.$target;
                } else {
                    opts.$input = opts.$target.find('input[type="text"]');
                    if (opts.$input.length > 1) {
                        opts.$input.each(function () {
                            opts.target = this;
                            self.bind(opts);
                        });
                        return this;
                    }
                }

                opts.$input = opts.$target.get(0).tagName == "INPUT" ? opts.$target : opts.$target.find('input[type="text"]');
                if (!opts.id) opts.id = opts.$input.data("ax5-formatter");

                if (!opts.id) {
                    opts.id = 'ax5-formatter-' + ax5.getGuid();
                    opts.$input.data("ax5-formatter", opts.id);
                }
                optIdx = U.search(this.queue, function () {
                    return this.id == opts.id;
                });

                if (optIdx === -1) {
                    this.queue.push(opts);
                    bindFormatterTarget.call(this, this.queue[this.queue.length - 1], this.queue.length - 1);
                } else {
                    this.queue[optIdx] = opts;
                    bindFormatterTarget.call(this, this.queue[optIdx], optIdx);
                }

                return this;
            };

            /**
             * formatter value 를 다시 적용합니다.
             * @method ax5formatter.formatting
             * @returns {ax5formatter}
             * @example
             * ```js
             * $('[data-ax5formatter="time"]').ax5formatter("formatting"); // 하나만
             * $('[data-ax5formatter]').ax5formatter("formatting"); // 모두
             * ```
             */
            this.formatting = function (boundID) {
                var queIdx = U.isNumber(boundID) ? boundID : getQueIdx.call(this, boundID);
                if (queIdx === -1) {
                    var i = this.queue.length;
                    while (i--) {
                        formatterEvent.blur.call(this, this.queue[i], i, null, true);
                    }
                } else {
                    formatterEvent.blur.call(this, this.queue[queIdx], queIdx, null, true);
                }
                return this;
            };

            this.unbind = function () {
                // 구현해야함.
            };

            // 클래스 생성자
            this.main = function () {
                if (arguments && U.isObject(arguments[0])) {
                    this.setConfig(arguments[0]);
                }
            }.apply(this, arguments);
        };
    }());

    FORMATTER = ax5.ui.formatter;
})();
// ax5.ui.formatter.formatter
(function () {

    var FORMATTER = ax5.ui.formatter;
    var U = ax5.util;
    var TODAY = new Date();

    var ctrlKeys = {
        "18": "KEY_ALT",
        "8": "KEY_BACKSPACE",
        "17": "KEY_CONTROL",
        "46": "KEY_DELETE",
        "40": "KEY_DOWN",
        "35": "KEY_END",
        "187": "KEY_EQUAL",
        "27": "KEY_ESC",
        "36": "KEY_HOME",
        "45": "KEY_INSERT",
        "37": "KEY_LEFT",
        "189": "KEY_MINUS",
        "34": "KEY_PAGEDOWN",
        "33": "KEY_PAGEUP",
        // "190": "KEY_PERIOD",
        "13": "KEY_RETURN",
        "39": "KEY_RIGHT",
        "16": "KEY_SHIFT",
        // "32": "KEY_SPACE",
        "9": "KEY_TAB",
        "38": "KEY_UP",
        "91": "KEY_WINDOW"
        //"107" : "NUMPAD_ADD",
        //"194" : "NUMPAD_COMMA",
        //"110" : "NUMPAD_DECIMAL",
        //"111" : "NUMPAD_DIVIDE",
        //"12" : "NUMPAD_EQUAL",
        //"106" : "NUMPAD_MULTIPLY",
        //"109" : "NUMPAD_SUBTRACT"
    };
    var numKeys = {
        '48': 1, '49': 1, '50': 1, '51': 1, '52': 1, '53': 1, '54': 1, '55': 1, '56': 1, '57': 1,
        '96': 1, '97': 1, '98': 1, '99': 1, '100': 1, '101': 1, '102': 1, '103': 1, '104': 1, '105': 1
    };
    var pattern_money = {
        getEnterableKeyCodes: function getEnterableKeyCodes(_opts) {
            var enterableKeyCodes = {
                '188': ','
            };
            if (_opts.patternArgument == "int") {
                // 소수점 입력 안됨
            } else {
                enterableKeyCodes['190'] = "."; // 소수점 입력 허용
            }
            return jQuery.extend(enterableKeyCodes, FORMATTER.formatter.ctrlKeys, FORMATTER.formatter.numKeys);
        },
        getPatternValue: function getPatternValue(_opts, optIdx, e, val, eType) {
            val = val.replace(/[^0-9^\.^\-]/g, "");
            var regExpPattern = new RegExp('([0-9])([0-9][0-9][0-9][,.])'),
                arrNumber = val.split('.'),
                returnValue;

            arrNumber[0] += '.';

            do {
                arrNumber[0] = arrNumber[0].replace(regExpPattern, '$1,$2');
            } while (regExpPattern.test(arrNumber[0]));

            if (arrNumber.length > 1) {
                if (U.isNumber(_opts.maxRound)) {
                    returnValue = arrNumber[0] + U.left(arrNumber[1], _opts.maxRound);
                } else {
                    returnValue = arrNumber.join('');
                }
            } else {
                returnValue = arrNumber[0].split('.')[0];
            }

            return returnValue;
        }
    };

    var pattern_number = {
        getEnterableKeyCodes: function getEnterableKeyCodes(_opts) {
            var enterableKeyCodes = {
                '190': '.',
                '110': '.'

            };
            return jQuery.extend(enterableKeyCodes, FORMATTER.formatter.ctrlKeys, FORMATTER.formatter.numKeys);
        },
        getPatternValue: function getPatternValue(_opts, optIdx, e, val, eType) {
            val = val.replace(/[^0-9^\.^\-]/g, "");
            var arrNumber = val.split('.'),
                returnValue;

            arrNumber[0] += ".";

            if (arrNumber.length > 1) {
                if (U.isNumber(_opts.maxRound)) {
                    returnValue = arrNumber[0] + U.left(arrNumber[1], _opts.maxRound);
                } else {
                    returnValue = arrNumber.join('');
                }
            } else {
                returnValue = arrNumber[0].split('.')[0];
            }

            return returnValue;
        }
    };

    var pattern_date = {
        getEnterableKeyCodes: function getEnterableKeyCodes(_opts) {
            var enterableKeyCodes = {
                '189': '-', '191': '/'
            };
            return jQuery.extend(enterableKeyCodes, FORMATTER.formatter.ctrlKeys, FORMATTER.formatter.numKeys);
        },
        getPatternValue: function getPatternValue(_opts, optIdx, e, val, eType) {
            val = val.replace(/\D/g, "");
            if (val == "") return val;
            var regExpPattern = /^([0-9]{4})\-?([0-9]{1,2})?\-?([0-9]{1,2})?.*$/;

            if (_opts.patternArgument == "time") {
                regExpPattern = /^([0-9]{4})\-?([0-9]{1,2})?\-?([0-9]{1,2})? ?([0-9]{1,2})?:?([0-9]{1,2})?:?([0-9]{1,2})?.*$/;
            } else if (_opts.patternArgument == "year") {
                regExpPattern = /^([0-9]{0,4})?.*$/;
            } else if (_opts.patternArgument == "month") {
                regExpPattern = /^([0-9]{4})\-?([0-9]{1,2})?.*$/;
            }

            var matchedPattern = val.match(regExpPattern),
                returnValue = "",
                inspectValue = function inspectValue(val, format, inspect, data) {
                var _val = {
                    'Y': function Y(v) {
                        if (typeof v == "undefined") v = TODAY.getFullYear();
                        if (v == '' || v == '0000') v = TODAY.getFullYear();
                        return v.length < 4 ? U.setDigit(v, 4) : v;
                    },
                    'M': function M(v) {
                        if (typeof v == "undefined") v = TODAY.getMonth() + 1;
                        return v > 12 ? 12 : v == 0 ? '01' : U.setDigit(v, 2);
                    },
                    'D': function D(v) {
                        if (typeof v == "undefined") v = TODAY.getDate() + 1;
                        var dLen = U.daysOfMonth(data[1], data[2] - 1);
                        return v > dLen ? dLen : v == 0 ? '01' : U.setDigit(v, 2);
                    },
                    'h': function h(v) {
                        if (!v) v = 0;
                        return v > 23 ? 23 : U.setDigit(v, 2);
                    },
                    'm': function m(v) {
                        if (!v) v = 0;
                        return v > 59 ? 59 : U.setDigit(v, 2);
                    },
                    's': function s(v) {
                        if (!v) v = 0;
                        return v > 59 ? 59 : U.setDigit(v, 2);
                    }
                };
                return inspect ? _val[format](val) : val;
            };

            returnValue = val.replace(regExpPattern, function (a, b) {
                var nval = [];

                if (_opts.patternArgument == "year") {
                    nval.push(inspectValue(arguments[1], "Y", eType));
                } else if (_opts.patternArgument == "month") {
                    nval.push(inspectValue(arguments[1], "Y", eType));
                    if (arguments[2] || eType) nval.push('-' + inspectValue(arguments[2], "M", eType));
                } else if (_opts.patternArgument == "time") {
                    nval.push(inspectValue(arguments[1], "Y", eType));
                    if (arguments[2] || eType) nval.push('-' + inspectValue(arguments[2], "M", eType));
                    if (arguments[3] || eType) nval.push('-' + inspectValue(arguments[3], "D", eType, arguments));
                    if (arguments[4] || eType) nval.push(' ' + inspectValue(arguments[4], "h", eType));
                    if (arguments[5] || eType) nval.push(':' + inspectValue(arguments[5], "m", eType));
                    if (arguments[6] || eType) nval.push(':' + inspectValue(arguments[6], "s", eType));
                } else {
                    nval.push(inspectValue(arguments[1], "Y", eType));
                    if (arguments[2] || eType) nval.push('-' + inspectValue(arguments[2], "M", eType));
                    if (arguments[3] || eType) nval.push('-' + inspectValue(arguments[3], "D", eType, arguments));
                }
                return nval.join('');
            });

            if (eType == 'blur' && !matchedPattern) {
                returnValue = function () {
                    var nval = [];

                    if (_opts.patternArgument == "year") {
                        nval.push(inspectValue(0, "Y", eType));
                    } else if (_opts.patternArgument == "month") {
                        nval.push(inspectValue(0, "Y", eType));
                        nval.push('-' + inspectValue(0, "M", eType));
                    } else if (_opts.patternArgument == "time") {
                        nval.push(inspectValue(0, "Y", eType));
                        nval.push('-' + inspectValue(0, "M", eType));
                        nval.push('-' + inspectValue(0, "D", eType, arguments));
                        nval.push(' ' + inspectValue(0, "h", eType));
                        nval.push(':' + inspectValue(0, "m", eType));
                        nval.push(':' + inspectValue(0, "s", eType));
                    } else {
                        nval.push(inspectValue(0, "Y", eType));
                        nval.push('-' + inspectValue(0, "M", eType));
                        nval.push('-' + inspectValue(0, "D", eType, arguments));
                    }
                    return nval.join('');
                }();
            } else if (!matchedPattern) returnValue = returnValue.length > 4 ? U.left(returnValue, 4) : returnValue;

            return returnValue;
        }
    };

    var pattern_time = {
        getEnterableKeyCodes: function getEnterableKeyCodes(_opts) {
            var enterableKeyCodes = {
                '186': ':'
            };
            return jQuery.extend(enterableKeyCodes, FORMATTER.formatter.ctrlKeys, FORMATTER.formatter.numKeys);
        },
        getPatternValue: function getPatternValue(_opts, optIdx, e, val, eType) {
            val = val.replace(/\D/g, "");
            var regExpPattern = /^([0-9]{1,2})?:?([0-9]{1,2})?:?([0-9]{1,2})?.*$/;

            var matchedPattern = val.match(regExpPattern),
                returnValue = val.replace(regExpPattern, function (a, b) {
                var nval = [arguments[1]];
                if (arguments[2]) nval.push(':' + arguments[2]);
                if (arguments[3]) nval.push(':' + arguments[3]);
                return nval.join('');
            });

            if (!matchedPattern) returnValue = returnValue.length > 2 ? U.left(returnValue, 2) : returnValue;

            return returnValue;
        }
    };

    var pattern_bizno = {
        getEnterableKeyCodes: function getEnterableKeyCodes(_opts) {
            var enterableKeyCodes = {
                '189': '-'
            };
            return jQuery.extend(enterableKeyCodes, FORMATTER.formatter.ctrlKeys, FORMATTER.formatter.numKeys);
        },
        getPatternValue: function getPatternValue(_opts, optIdx, e, val, eType) {
            val = val.replace(/\D/g, "");
            var regExpPattern = /^([0-9]{3})\-?([0-9]{1,2})?\-?([0-9]{1,5})?.*$/,
                returnValue = val.replace(regExpPattern, function (a, b) {
                var nval = [arguments[1]];
                if (arguments[2]) nval.push(arguments[2]);
                if (arguments[3]) nval.push(arguments[3]);
                return nval.join("-");
            });

            return returnValue;
        }
    };

    var pattern_phone = {
        getEnterableKeyCodes: function getEnterableKeyCodes(_opts) {
            var enterableKeyCodes = {
                '189': '-', '188': ','
            };
            return jQuery.extend(enterableKeyCodes, FORMATTER.formatter.ctrlKeys, FORMATTER.formatter.numKeys);
        },
        getPatternValue: function getPatternValue(_opts, optIdx, e, val, eType) {
            val = val.replace(/\D/g, "");
            var regExpPattern3 = /^([0-9]{3})\-?([0-9]{1,4})?\-?([0-9]{1,4})?\-?([0-9]{1,4})?\-?([0-9]{1,4})?/;
            if (val.substr(0, 2) == "02") {
                regExpPattern3 = /^([0-9]{2})\-?([0-9]{1,4})?\-?([0-9]{1,4})?\-?([0-9]{1,4})?\-?([0-9]{1,4})?/;
            }

            var returnValue = val.replace(regExpPattern3, function (a, b) {
                var nval = [arguments[1]];
                if (arguments[2]) nval.push(arguments[2]);
                if (arguments[3]) nval.push(arguments[3]);
                if (arguments[4]) nval.push(arguments[4]);
                if (arguments[5]) nval.push(arguments[5]);
                return nval.join("-");
            });
            return returnValue;
        }
    };

    var pattern_credit = {
        getEnterableKeyCodes: function getEnterableKeyCodes(_opts) {
            var enterableKeyCodes = {
                '189': '-'
            };
            return jQuery.extend(enterableKeyCodes, FORMATTER.formatter.ctrlKeys, FORMATTER.formatter.numKeys);
        },
        getPatternValue: function getPatternValue(_opts, optIdx, e, val, eType) {
            val = val.replace(/\D/g, "").substring(0, 16);

            var regExpPattern3 = /^([0-9]{4})\-?([0-9]{4})?\-?([0-9]{4})?\-?([0-9]{4})?/,
                returnValue = val.replace(regExpPattern3, function (a, b) {
                var nval = [arguments[1]];
                if (arguments[2]) nval.push(arguments[2]);
                if (arguments[3]) nval.push(arguments[3]);
                if (arguments[4]) nval.push(arguments[4]);
                return nval.join("-");
            });
            return returnValue;
        }
    };

    var pattern_custom = {
        getEnterableKeyCodes: function getEnterableKeyCodes(_opts) {
            if (_opts.getEnterableKeyCodes) {
                return _opts.getEnterableKeyCodes.call(_opts, { $input: _opts.$input });
            } else {
                return null;
            }
        },
        getPatternValue: function getPatternValue(_opts, optIdx, e, val, eType) {
            if (_opts.getPatternValue) {
                return _opts.getPatternValue.call(_opts, { event: e, $input: _opts.$input, value: val });
            }
        }
    };

    FORMATTER.formatter = {
        ctrlKeys: ctrlKeys,
        numKeys: numKeys,
        money: pattern_money,
        number: pattern_number,
        date: pattern_date,
        time: pattern_time,
        bizno: pattern_bizno,
        phone: pattern_phone,
        credit: pattern_credit,
        custom: pattern_custom
    };
})();

/*
 * Copyright (c) 2017. tom@axisj.com
 * - github.com/thomasjang
 * - www.axisj.com
 */

ax5.ui.formatter_instance = new ax5.ui.formatter();

jQuery.fn.ax5formatter = function () {
    return function (config) {
        if (ax5.util.isString(arguments[0])) {
            var methodName = arguments[0];

            switch (methodName) {
                case "formatting":
                    return ax5.ui.formatter_instance.formatting(this);
                    break;

                case "unbind":
                    return ax5.ui.formatter_instance.unbind(this);
                    break;

                default:
                    return this;
            }
        } else {
            if (typeof config == "undefined") config = {};
            jQuery.each(this, function () {
                var defaultConfig = {
                    target: this
                };
                config = jQuery.extend({}, config, defaultConfig);
                ax5.ui.formatter_instance.bind(config);
            });
        }
        return this;
    };
}();
"use strict";

// ax5.ui.menu
(function () {
    var UI = ax5.ui;
    var U = ax5.util;
    var MENU;

    UI.addClass({
        className: "menu"
    }, function () {
        /**
         * @class ax5.ui.menu
         * @classdesc
         * @author tom@axisj.com
         * @example
         * ```js
         * var menu = new ax5.ui.menu({
         *     theme: 'primary',
         *     iconWidth: 20,
         *     acceleratorWidth: 100,
         *     itemClickAndClose: false,
         *     icons: {
         *         'arrow': '<i class="fa fa-caret-right"></i>'
         *     },
         *     columnKeys: {
         *         label: 'name',
         *         items: 'chidren'
         *     },
         *     items: [
         *         {
         *             icon: '<i class="fa fa-archive"></i>',
         *             name: "Menu Parent 0",
         *             chidren: [
         *                 {
         *                     check: {
         *                         type: 'checkbox',
         *                         name: 'A',
         *                         value: '0',
         *                         checked: false
         *                     },
         *                     name: "Menu Z",
         *                     data: {},
         *                     role: "",
         *                     accelerator: "CmdOrCtrl+Z"
         *                 },
         *                 {
         *                     check: {
         *                         type: 'checkbox',
         *                         name: 'A',
         *                         value: '1',
         *                         checked: true
         *                     },
         *                     name: "Menu A",
         *                     data: {},
         *                     role: ""
         *                 }
         *             ],
         *             filterType: "A"
         *         },
         *         {
         *             divide: true,
         *             filterType: "A"
         *         },
         *         {
         *             icon: '<i class="fa fa-mixcloud"></i>',
         *             name: "Menu Parent 1",
         *             chidren: [
         *                 {
         *                     name: "Menu Z",
         *                     data: {},
         *                     role: "",
         *                     chidren: [
         *                         {
         *                             name: "Menu Z",
         *                             data: {},
         *                             role: ""
         *                         },
         *                         {
         *                             name: "Menu A",
         *                             data: {},
         *                             role: ""
         *                         }
         *                     ]
         *                 },
         *                 {
         *                     name: "Menu A",
         *                     data: {},
         *                     role: ""
         *                 }
         *             ],
         *             filterType: "A"
         *         },
         *         {
         *             check: {
         *                 type: 'radio',
         *                 name: 'radioName',
         *                 value: '1',
         *                 checked: false
         *             },
         *             icon: '<i class="fa fa-bluetooth"></i>',
         *             name: "Menu Parent 2"
         *         },
         *         {
         *             check: {
         *                 type: 'radio',
         *                 name: 'radioName',
         *                 value: '2',
         *                 checked: false
         *             },
         *             name: "Menu Parent 3"
         *         },
         *         {
         *             check: {
         *                 type: 'radio',
         *                 name: 'radioName',
         *                 value: '3',
         *                 checked: false
         *             },
         *             name: "Menu Parent 4"
         *         },
         *         {divide: true},
         *         {
         *             html: function () {
         *                 return '<div style="text-align: center;">' +
         *                     '<button class="btn btn-primary" data-menu-btn="OK">OK</button> ' +
         *                     '<button class="btn btn-danger" data-menu-btn="CANCEL">CANCEL</button>' +
         *                     '</div>';
         *             }
         *         }
         *     ]
         * });
         * ```
         */
        return function () {
            var self = this,
                cfg = void 0;

            this.instanceId = ax5.getGuid();
            this.config = {
                theme: "default",
                iconWidth: 22,
                acceleratorWidth: 100,
                menuBodyPadding: 5,
                //direction: "top", // top|bottom
                offset: { left: 0, top: 0 },
                position: "fixed",
                animateTime: 250,
                items: [],
                itemClickAndClose: true,
                columnKeys: {
                    label: 'label',
                    items: 'items'
                }
            };

            this.openTimer = null;
            this.closeTimer = null;
            this.queue = [];
            this.menuBar = {};
            this.state = undefined;

            cfg = this.config;

            var appEventAttach = function appEventAttach(active, opt) {
                if (active) {
                    jQuery(document.body).off("click.ax5menu-" + this.instanceId).on("click.ax5menu-" + this.instanceId, clickItem.bind(this, opt));

                    jQuery(window).off("keydown.ax5menu-" + this.instanceId).on("keydown.ax5menu-" + this.instanceId, function (e) {
                        if (e.which == ax5.info.eventKeys.ESC) {
                            self.close();
                        }
                    });

                    jQuery(window).on("resize.ax5menu-" + this.instanceId).on("resize.ax5menu-" + this.instanceId, function (e) {
                        self.close();
                    });
                } else {
                    jQuery(document.body).off("click.ax5menu-" + this.instanceId);
                    jQuery(window).off("keydown.ax5menu-" + this.instanceId);
                    jQuery(window).off("resize.ax5menu-" + this.instanceId);
                }
            };

            var onStateChanged = function onStateChanged(opts, that) {
                if (opts && opts.onStateChanged) {
                    opts.onStateChanged.call(that, that);
                } else if (this.onStateChanged) {
                    this.onStateChanged.call(that, that);
                }

                self.state = that.state;
                opts = null;
                that = null;
                return true;
            };

            var onLoad = function onLoad(that) {
                if (this.onLoad) {
                    this.onLoad.call(that, that);
                }

                that = null;
                return true;
            };

            var popup = function popup(opt, items, depth, path) {
                var data = opt,
                    activeMenu = void 0,
                    removed = void 0;

                data.theme = opt.theme || cfg.theme;
                data.cfg = {
                    icons: jQuery.extend({}, cfg.icons),
                    iconWidth: opt.iconWidth || cfg.iconWidth,
                    acceleratorWidth: opt.acceleratorWidth || cfg.acceleratorWidth
                };

                items.forEach(function (n) {
                    if (n.html || n.divide) {
                        n['@isMenu'] = false;
                        if (n.html) {
                            n['@html'] = n.html.call({
                                item: n,
                                config: cfg,
                                opt: opt
                            });
                        }
                    } else {
                        n['@isMenu'] = true;
                    }
                });

                data[cfg.columnKeys.items] = items;
                data['@depth'] = depth;
                data['@path'] = path || "root";
                data['@hasChild'] = function () {
                    return this[cfg.columnKeys.items] && this[cfg.columnKeys.items].length > 0;
                };
                activeMenu = jQuery(MENU.tmpl.get.call(this, "tmpl", data, cfg.columnKeys));
                jQuery(document.body).append(activeMenu);

                // remove queue

                removed = this.queue.splice(depth);
                removed.forEach(function (n) {
                    n.$target.remove();
                });

                this.queue.push({
                    '$target': activeMenu,
                    'data': jQuery.extend({}, data)
                });

                activeMenu.find('[data-menu-item-index]').bind("mouseover", function () {
                    var depth = this.getAttribute("data-menu-item-depth"),
                        index = this.getAttribute("data-menu-item-index"),
                        path = this.getAttribute("data-menu-item-path"),
                        $this = void 0,
                        offset = void 0,
                        scrollTop = void 0,
                        childOpt = void 0,
                        _items = void 0,
                        _activeMenu = void 0;

                    if (depth != null && typeof depth != "undefined") {
                        _items = self.queue[depth].data[cfg.columnKeys.items][index][cfg.columnKeys.items];
                        _activeMenu = self.queue[depth].$target;
                        _activeMenu.find('[data-menu-item-index]').removeClass("hover");
                        jQuery(this).addClass("hover");

                        if (_activeMenu.attr("data-selected-menu-item-index") != index) {
                            _activeMenu.attr("data-selected-menu-item-index", index);

                            if (_items && _items.length > 0) {

                                $this = jQuery(this);
                                offset = $this.offset();
                                scrollTop = cfg.position == "fixed" ? jQuery(document).scrollTop() : 0;
                                childOpt = {
                                    '@parent': {
                                        left: offset.left,
                                        top: offset.top,
                                        width: $this.outerWidth(),
                                        height: $this.outerHeight()
                                    },
                                    left: offset.left + $this.outerWidth() - cfg.menuBodyPadding,
                                    top: offset.top - cfg.menuBodyPadding - 1 - scrollTop
                                };

                                childOpt = jQuery.extend(true, opt, childOpt);
                                popup.call(self, childOpt, _items, Number(depth) + 1, path);
                            } else {
                                self.queue.splice(Number(depth) + 1).forEach(function (n) {
                                    n.$target.remove();
                                });
                            }
                        }
                    }

                    depth = null;
                    index = null;
                    path = null;
                    $this = null;
                    offset = null;
                    scrollTop = null;
                    childOpt = null;
                    _items = null;
                    _activeMenu = null;
                });

                // mouse out
                activeMenu.find('[data-menu-item-index]').bind("mouseout", function () {
                    var depth = this.getAttribute("data-menu-item-depth"),
                        index = this.getAttribute("data-menu-item-index"),
                        path = this.getAttribute("data-menu-item-path"),
                        _items = void 0;

                    if (path) {
                        _items = self.queue[depth].data[cfg.columnKeys.items][index][cfg.columnKeys.items];
                    }
                    if (_items && _items.length > 0) {} else {
                        jQuery(this).removeClass("hover");
                    }
                });

                // is Root
                if (depth == 0) {
                    if (data.direction) activeMenu.addClass("direction-" + data.direction);
                    onStateChanged.call(this, null, {
                        self: this,
                        items: items,
                        parent: function (path) {
                            if (!path) return false;
                            var item = null;
                            try {
                                item = Function("", "return this.config.items[" + path.substring(5).replace(/\./g, '].items[') + "];").call(self);
                            } catch (e) {}
                            return item;
                        }(data['@path']),
                        state: "popup"
                    });
                }

                align.call(this, activeMenu, data);
                onLoad.call(this, {
                    self: this,
                    items: items,
                    element: activeMenu.get(0)
                });

                data = null;
                activeMenu = null;
                removed = null;
                opt = null;
                items = null;
                depth = null;
                path = null;

                return this;
            };

            var clickItem = function clickItem(opt, e) {
                var target = void 0,
                    item = void 0;

                target = U.findParentNode(e.target, function (target) {
                    if (target.getAttribute("data-menu-item-index")) {
                        return true;
                    }
                });
                if (target) {
                    if (typeof opt === "undefined") opt = {};
                    item = function (path) {
                        if (!path) return false;
                        var item = void 0;

                        try {
                            item = Function("", "return this[" + path.substring(5).replace(/\./g, '].' + cfg.columnKeys.items + '[') + "];").call(opt.items || cfg.items);
                        } catch (e) {
                            console.log(ax5.info.getError("ax5menu", "501", "menuItemClick"));
                        }

                        try {
                            return item;
                        } finally {
                            item = null;
                        }
                    }(target.getAttribute("data-menu-item-path"));

                    if (!item) return this;

                    if (item.check) {
                        (function (items) {
                            var setValue = {
                                'checkbox': function checkbox(value) {
                                    this.checked = !value;
                                },
                                'radio': function radio(value) {
                                    var name = this.name;
                                    items.forEach(function (n) {
                                        if (n.check && n.check.type === 'radio' && n.check.name == name) {
                                            n.check.checked = false;
                                        }
                                    });
                                    this.checked = !value;
                                }
                            };
                            if (setValue[this.type]) setValue[this.type].call(this, this.checked);
                            setValue = null;
                        }).call(item.check, cfg.items);

                        if (!cfg.itemClickAndClose) {
                            self.queue.forEach(function (n) {
                                n.$target.find('[data-menu-item-index]').each(function () {
                                    var item = n.data[cfg.columnKeys.items][this.getAttribute("data-menu-item-index")];
                                    if (item.check) {
                                        jQuery(this).find(".item-checkbox-wrap").attr("data-item-checked", item.check.checked);
                                    }
                                });
                            });
                        }
                    }

                    if (self.onClick) {
                        if (self.onClick.call(item, item, opt.param)) {
                            self.close();
                        }
                    }
                    if ((!item[cfg.columnKeys.items] || item[cfg.columnKeys.items].length == 0) && cfg.itemClickAndClose) self.close();
                } else {
                    self.close();
                }

                target = null;
                item = null;
                return this;
            };

            var align = function align(activeMenu, data) {
                var $window = jQuery(window),
                    $document = jQuery(document),
                    wh = cfg.position == "fixed" ? $window.height() : $document.height(),
                    ww = $window.width(),
                    h = activeMenu.outerHeight(),
                    w = activeMenu.outerWidth(),
                    l = data.left,
                    t = data.top,
                    position = cfg.position || "fixed";

                if (l + w > ww) {
                    if (data['@parent']) {
                        l = data['@parent'].left - w + cfg.menuBodyPadding;
                    } else {
                        l = ww - w;
                    }
                }

                if (t + h > wh) {
                    t = wh - h;
                }

                activeMenu.css({ left: l, top: t, position: position });

                activeMenu = null;
                data = null;
                $window = null;
                $document = null;
                wh = null;
                ww = null;
                h = null;
                w = null;
                l = null;
                t = null;
                position = null;
                return this;
            };

            /// private end

            this.init = function () {
                /**
                 * config에 선언된 이벤트 함수들을 this로 이동시켜 주어 나중에 인스턴스.on... 으로 처리 가능 하도록 변경
                 */
                this.onStateChanged = cfg.onStateChanged;
                this.onClick = cfg.onClick;
                this.onLoad = cfg.onLoad;

                onStateChanged.call(this, null, {
                    self: this,
                    state: "init"
                });
            };

            /**
             * @method ax5.ui.menu.popup
             * @param {Event|Object} e - Event or Object
             * @param {Object} [opt]
             * @param {String} [opt.theme]
             * @param {Function} [opt.filter]
             * @returns {ax5.ui.menu} this
             */
            this.popup = function () {

                var getOption = {
                    'event': function event(e, opt) {
                        //var xOffset = Math.max(document.documentElement.scrollLeft, document.body.scrollLeft);
                        //var yOffset = Math.max(document.documentElement.scrollTop, document.body.scrollTop);
                        //console.log(e.pageY);

                        e = {
                            left: e.clientX,
                            top: cfg.position == "fixed" ? e.clientY : e.pageY,
                            width: cfg.width,
                            theme: cfg.theme
                        };

                        e.left -= 5;
                        e.top -= 5;

                        if (cfg.offset) {
                            if (cfg.offset.left) e.left += cfg.offset.left;
                            if (cfg.offset.top) e.top += cfg.offset.top;
                        }
                        opt = jQuery.extend(true, e, opt);

                        try {
                            return opt;
                        } finally {
                            e = null;
                            //opt = null;
                        }
                    },
                    'object': function object(e, opt) {
                        e = {
                            left: e.left,
                            top: e.top,
                            width: e.width || cfg.width,
                            theme: e.theme || cfg.theme
                        };

                        if (cfg.offset) {
                            if (cfg.offset.left) e.left += cfg.offset.left;
                            if (cfg.offset.top) e.top += cfg.offset.top;
                        }

                        opt = jQuery.extend(true, e, opt);

                        try {
                            return opt;
                        } finally {
                            e = null;
                            //opt = null;
                        }
                    }
                },
                    updateTheme = function updateTheme(theme) {
                    if (theme) cfg.theme = theme;
                };

                return function (e, opt) {

                    if (!e) return this;
                    opt = getOption[typeof e.clientX == "undefined" ? "object" : "event"].call(this, e, opt);
                    updateTheme(opt.theme);

                    var items = [].concat(cfg.items),
                        _filteringItem = void 0;
                    opt.items = items;

                    if (opt.filter) {
                        _filteringItem = function filteringItem(_items) {
                            var arr = [];
                            _items.forEach(function (n) {
                                if (n.items && n.items.length > 0) {
                                    n.items = _filteringItem(n.items);
                                }
                                if (opt.filter.call(n)) {
                                    arr.push(n);
                                }
                            });
                            return arr;
                        };
                        opt.items = items = _filteringItem(items);
                    }

                    if (items.length) {
                        appEventAttach.call(this, false);
                        popup.call(this, opt, items, 0); // 0 is seq of queue

                        if (this.popupEventAttachTimer) clearTimeout(this.popupEventAttachTimer);
                        this.popupEventAttachTimer = setTimeout(function () {
                            appEventAttach.call(this, true, opt); // 이벤트 연결
                        }.bind(this), 500);
                    }

                    e = null;
                    return this;
                };
            }();

            /**
             * @method ax5.ui.menu.attach
             * @param {Element|jQueryObject} el
             * @returns {ax5.ui.menu} this
             */
            this.attach = function () {

                var getOption = {
                    'object': function object(e, opt) {
                        e = {
                            left: e.left,
                            top: e.top,
                            width: e.width || cfg.width,
                            theme: e.theme || cfg.theme,
                            direction: e.direction || cfg.direction
                        };
                        opt = jQuery.extend(true, opt, e);

                        try {
                            return opt;
                        } finally {
                            e = null;
                            opt = null;
                        }
                    }
                };

                var popUpChildMenu = function popUpChildMenu(target, opt, eType) {
                    var $target = jQuery(target),
                        offset = $target.offset(),
                        height = $target.outerHeight(),
                        index = Number(target.getAttribute("data-menu-item-index")),
                        scrollTop = cfg.position == "fixed" ? jQuery(document).scrollTop() : 0;

                    if (cfg.items && cfg.items[index][cfg.columnKeys.items] && cfg.items[index][cfg.columnKeys.items].length) {

                        if (self.menuBar.openedIndex == index) {
                            if (eType == "click") self.close();
                            return false;
                        }

                        self.menuBar.target.find('[data-menu-item-index]').removeClass("hover");
                        self.menuBar.opened = true;
                        self.menuBar.openedIndex = index;

                        $target.attr("data-menu-item-opened", "true");
                        $target.addClass("hover");

                        if (cfg.offset) {
                            if (cfg.offset.left) offset.left += cfg.offset.left;
                            if (cfg.offset.top) offset.top += cfg.offset.top;
                        }

                        opt = getOption["object"].call(this, { left: offset.left, top: offset.top + height - scrollTop }, opt);

                        popup.call(self, opt, cfg.items[index][cfg.columnKeys.items], 0, 'root.' + target.getAttribute("data-menu-item-index")); // 0 is seq of queue
                        appEventAttach.call(self, true, {}); // 이벤트 연결
                    }

                    target = null;
                    opt = null;
                    $target = null;
                    offset = null;
                    height = null;
                    index = null;
                    scrollTop = null;
                };
                var clickParentMenu = function clickParentMenu(target, opt, eType) {
                    var $target = jQuery(target),
                        offset = $target.offset(),
                        height = $target.outerHeight(),
                        index = Number(target.getAttribute("data-menu-item-index")),
                        scrollTop = cfg.position == "fixed" ? jQuery(document).scrollTop() : 0;
                    if (cfg.items && (!cfg.items[index][cfg.columnKeys.items] || cfg.items[index][cfg.columnKeys.items].length == 0)) {
                        if (self.onClick) {
                            self.onClick.call(cfg.items[index], cfg.items[index]);
                        }
                    }
                };

                return function (el, opt) {
                    var data = {},
                        items = cfg.items,
                        activeMenu;

                    if (typeof opt === "undefined") opt = {};

                    data.theme = opt.theme || cfg.theme;
                    data.cfg = {
                        icons: jQuery.extend({}, cfg.icons),
                        iconWidth: opt.iconWidth || cfg.iconWidth,
                        acceleratorWidth: opt.acceleratorWidth || cfg.acceleratorWidth
                    };

                    items.forEach(function (n) {
                        if (n.html || n.divide) {
                            n['@isMenu'] = false;
                            if (n.html) {
                                n['@html'] = n.html.call({
                                    item: n,
                                    config: cfg,
                                    opt: opt
                                });
                            }
                        } else {
                            n['@isMenu'] = true;
                        }
                    });

                    data[cfg.columnKeys.items] = items;

                    activeMenu = jQuery(MENU.tmpl.get.call(this, "tmplMenubar", data, cfg.columnKeys));
                    self.menuBar = {
                        target: jQuery(el),
                        opened: false
                    };
                    self.menuBar.target.html(activeMenu);

                    // click, mouseover
                    self.menuBar.target.bind("click", function (e) {
                        if (!e) return this;
                        var target = U.findParentNode(e.target, function (target) {
                            if (target.getAttribute("data-menu-item-index")) {
                                return true;
                            }
                        });
                        if (target) {
                            clickParentMenu(target, opt, "click");
                            popUpChildMenu(target, opt, "click");
                        }

                        target = null;
                    });
                    self.menuBar.target.bind("mouseover", function (e) {
                        if (!self.menuBar.opened) return false;
                        var target = U.findParentNode(e.target, function (target) {
                            if (target.getAttribute("data-menu-item-index")) {
                                return true;
                            }
                        });
                        if (target) popUpChildMenu(target, opt, "mouseover");

                        target = null;
                    });

                    el = null;
                    opt = null;
                    data = null;
                    items = null;
                    activeMenu = null;

                    return this;
                };
            }();

            /**
             * @method ax5.ui.menu.close
             * @returns {ax5.ui.menu} this
             */
            this.close = function () {

                if (self.menuBar && self.menuBar.target) {
                    self.menuBar.target.find('[data-menu-item-index]').removeClass("hover");
                    self.menuBar.opened = false;
                    self.menuBar.openedIndex = null;
                }

                appEventAttach.call(this, false); // 이벤트 제거

                this.queue.forEach(function (n) {
                    n.$target.remove();
                });
                this.queue = [];

                onStateChanged.call(this, null, {
                    self: this,
                    state: "close"
                });

                return this;
            };

            /**
             * @method ax5.ui.menu.getCheckValue
             * @returns {Object} statusCheckItem
             */
            this.getCheckValue = function () {
                var checkItems = {},
                    _collectItem = function collectItem(items) {
                    var i = items.length;
                    while (i--) {
                        if (items[i].check && items[i].check.checked) {
                            if (!checkItems[items[i].check.name]) checkItems[items[i].check.name] = items[i].check.value;else {
                                if (U.isString(checkItems[items[i].check.name])) checkItems[items[i].check.name] = [checkItems[items[i].check.name]];
                                checkItems[items[i].check.name].push(items[i].check.value);
                            }
                        }
                        if (items[i].items && items[i].items.length > 0) _collectItem(items[i].items);
                    }
                };

                _collectItem(cfg.items);

                try {
                    return checkItems;
                } finally {
                    checkItems = null;
                    _collectItem = null;
                }
            };

            // 클래스 생성자
            this.main = function () {

                UI.menu_instance = UI.menu_instance || [];
                UI.menu_instance.push(this);

                if (arguments && U.isObject(arguments[0])) {
                    this.setConfig(arguments[0]);
                }
            }.apply(this, arguments);
        };
    }());

    MENU = ax5.ui.menu;
})();

// todo : menu 드랍다운 아이콘 설정 기능 추가
// ax5.ui.menu.tmpl
(function () {
    var MENU = ax5.ui.menu;

    var tmpl = function tmpl(columnKeys) {
        return "\n        <div class=\"ax5-ui-menu {{theme}}\" {{#width}}style=\"width:{{width}}px;\"{{/width}}>\n            <div class=\"ax-menu-body\">\n                {{#" + columnKeys.items + "}}\n                    {{^@isMenu}}\n                        {{#divide}}\n                        <div class=\"ax-menu-item-divide\" data-menu-item-index=\"{{@i}}\"></div>\n                        {{/divide}}\n                        {{#html}}\n                        <div class=\"ax-menu-item-html\" data-menu-item-index=\"{{@i}}\">{{{@html}}}</div>\n                        {{/html}}\n                    {{/@isMenu}}\n                    {{#@isMenu}}\n                    <div class=\"ax-menu-item\" data-menu-item-depth=\"{{@depth}}\" data-menu-item-index=\"{{@i}}\" data-menu-item-path=\"{{@path}}.{{@i}}\">\n                        <span class=\"ax-menu-item-cell ax-menu-item-checkbox\">\n                            {{#check}}\n                            <span class=\"item-checkbox-wrap useCheckBox\" {{#checked}}data-item-checked=\"true\"{{/checked}}></span>\n                            {{/check}}\n                            {{^check}}\n                            <span class=\"item-checkbox-wrap\"></span>\n                            {{/check}}\n                        </span>\n                        {{#icon}}\n                        <span class=\"ax-menu-item-cell ax-menu-item-icon\" style=\"width:{{cfg.iconWidth}}px;\">{{{.}}}</span>\n                        {{/icon}}\n                        <span class=\"ax-menu-item-cell ax-menu-item-label\">{{{" + columnKeys.label + "}}}</span>\n                        {{#accelerator}}\n                        <span class=\"ax-menu-item-cell ax-menu-item-accelerator\" style=\"width:{{cfg.acceleratorWidth}}px;\"><span class=\"item-wrap\">{{.}}</span></span>\n                        {{/accelerator}}\n                        {{#@hasChild}}\n                        <span class=\"ax-menu-item-cell ax-menu-item-handle\">{{{cfg.icons.arrow}}}</span>\n                        {{/@hasChild}}\n                    </div>\n                    {{/@isMenu}}\n\n                {{/" + columnKeys.items + "}}\n            </div>\n            <div class=\"ax-menu-arrow\"></div>\n        </div>\n        ";
    };
    var tmplMenubar = function tmplMenubar(columnKeys) {
        return "\n        <div class=\"ax5-ui-menubar {{theme}}\">\n            <div class=\"ax-menu-body\">\n                {{#" + columnKeys.items + "}}\n                    {{^@isMenu}}\n                        {{#divide}}\n                        <div class=\"ax-menu-item-divide\" data-menu-item-index=\"{{@i}}\"></div>\n                        {{/divide}}\n                        {{#html}}\n                        <div class=\"ax-menu-item-html\" data-menu-item-index=\"{{@i}}\">{{{@html}}}</div>\n                        {{/html}}\n                    {{/@isMenu}}\n                    {{#@isMenu}}\n                    <div class=\"ax-menu-item\" data-menu-item-index=\"{{@i}}\">\n                        {{#icon}}\n                        <span class=\"ax-menu-item-cell ax-menu-item-icon\" style=\"width:{{cfg.iconWidth}}px;\">{{{.}}}</span>\n                        {{/icon}}\n                        <span class=\"ax-menu-item-cell ax-menu-item-label\">{{{" + columnKeys.label + "}}}</span>\n                    </div>\n                    {{/@isMenu}}\n                {{/" + columnKeys.items + "}}\n            </div>\n        </div>\n        ";
    };

    MENU.tmpl = {
        "tmpl": tmpl,
        "tmplMenubar": tmplMenubar,

        get: function get(tmplName, data, columnKeys) {
            return ax5.mustache.render(MENU.tmpl[tmplName].call(this, columnKeys), data);
        }
    };
})();
'use strict';

// ax5.ui.select
(function () {

    var UI = ax5.ui,
        U = ax5.util,
        SELECT = void 0;

    UI.addClass({
        className: "select"
    }, function () {
        /**
         * @class ax5select
         * @classdesc
         * @author tom@axisj.com
         */
        var ax5select = function ax5select() {
            var self = this,
                cfg = void 0;

            this.instanceId = ax5.getGuid();
            this.config = {
                theme: 'default',
                animateTime: 100,
                lang: {
                    noSelected: '',
                    noOptions: 'no options',
                    loading: 'now loading..',
                    multipleLabel: '"{{label}}"외 {{length}}건'
                },
                columnKeys: {
                    optionValue: 'value',
                    optionText: 'text',
                    optionSelected: 'selected'
                }
            };
            this.queue = [];
            this.activeSelectOptionGroup = null;
            this.activeSelectQueueIndex = -1;
            this.openTimer = null;
            this.closeTimer = null;
            this.waitOptionsCallback = null;
            this.keyUpTimer = null;
            this.xvar = {};

            cfg = this.config;

            var $window = jQuery(window),
                ctrlKeys = {
                "18": "KEY_ALT",
                "8": "KEY_BACKSPACE",
                "17": "KEY_CONTROL",
                "46": "KEY_DELETE",
                "40": "KEY_DOWN",
                "35": "KEY_END",
                "187": "KEY_EQUAL",
                "27": "KEY_ESC",
                "36": "KEY_HOME",
                "45": "KEY_INSERT",
                "37": "KEY_LEFT",
                "189": "KEY_MINUS",
                "34": "KEY_PAGEDOWN",
                "33": "KEY_PAGEUP",
                // "190": "KEY_PERIOD",
                "13": "KEY_RETURN",
                "39": "KEY_RIGHT",
                "16": "KEY_SHIFT",
                // "32": "KEY_SPACE",
                "9": "KEY_TAB",
                "38": "KEY_UP",
                "91": "KEY_WINDOW"
                //"107" : "NUMPAD_ADD",
                //"194" : "NUMPAD_COMMA",
                //"110" : "NUMPAD_DECIMAL",
                //"111" : "NUMPAD_DIVIDE",
                //"12" : "NUMPAD_EQUAL",
                //"106" : "NUMPAD_MULTIPLY",
                //"109" : "NUMPAD_SUBTRACT"
            },
                onStateChanged = function onStateChanged(item, that) {
                if (item && item.onStateChanged) {
                    item.onStateChanged.call(that, that);
                } else if (this.onStateChanged) {
                    this.onStateChanged.call(that, that);
                }

                if (that.state == "changeValue") {
                    if (item && item.onChange) {
                        item.onChange.call(that, that);
                    } else if (this.onChange) {
                        this.onChange.call(that, that);
                    }
                }

                item = null;
                that = null;
                return true;
            },
                alignSelectDisplay = function alignSelectDisplay() {
                var i = this.queue.length,
                    w = void 0;
                while (i--) {
                    if (this.queue[i].$display) {
                        w = Math.max(this.queue[i].$select.outerWidth(), U.number(this.queue[i].minWidth));
                        this.queue[i].$display.css({
                            "min-width": w
                        });
                        if (this.queue[i].reset) {
                            this.queue[i].$display.find(".addon-icon-reset").css({
                                "line-height": this.queue[i].$display.height() + "px"
                            });
                        }
                    }
                }

                i = null;
                w = null;
                return this;
            },
                alignSelectOptionGroup = function alignSelectOptionGroup(append) {
                if (!this.activeSelectOptionGroup) return this;

                var item = this.queue[this.activeSelectQueueIndex],
                    pos = {},
                    positionMargin = 0,
                    dim = {},
                    pickerDim = {},
                    pickerDirection = void 0;

                if (append) jQuery(document.body).append(this.activeSelectOptionGroup);

                pos = item.$target.offset();
                dim = {
                    width: item.$target.outerWidth(),
                    height: item.$target.outerHeight()
                };
                pickerDim = {
                    winWidth: Math.max($window.width(), jQuery(document.body).width()),
                    winHeight: Math.max($window.height(), jQuery(document.body).height()),
                    width: this.activeSelectOptionGroup.outerWidth(),
                    height: this.activeSelectOptionGroup.outerHeight()
                };

                // picker css(width, left, top) & direction 결정
                if (!item.direction || item.direction === "" || item.direction === "auto") {
                    // set direction
                    pickerDirection = "top";

                    if (pos.top - pickerDim.height - positionMargin < 0) {
                        pickerDirection = "top";
                    } else if (pos.top + dim.height + pickerDim.height + positionMargin > pickerDim.winHeight) {
                        pickerDirection = "bottom";
                    }
                } else {
                    pickerDirection = item.direction;
                }
                // todo : 표현할 공간이 없다면..
                if (append) {
                    this.activeSelectOptionGroup.addClass("direction-" + pickerDirection);
                }
                this.activeSelectOptionGroup.css(function () {
                    if (pickerDirection == "top") {
                        if (pos.top + dim.height + pickerDim.height + positionMargin > pickerDim.winHeight) {

                            var newTop = pos.top + dim.height / 2 - pickerDim.height / 2;
                            if (newTop + pickerDim.height + positionMargin > pickerDim.winHeight) {
                                newTop = 0;
                            }
                            if (newTop < 0) {
                                newTop = 0;
                            }

                            return {
                                left: pos.left,
                                top: newTop,
                                width: dim.width
                            };
                        }
                        return {
                            left: pos.left,
                            top: pos.top + dim.height + 1,
                            width: dim.width
                        };
                    } else if (pickerDirection == "bottom") {
                        return {
                            left: pos.left,
                            top: pos.top - pickerDim.height - 1,
                            width: dim.width
                        };
                    }
                }.call(this));
            },
                onBodyClick = function onBodyClick(e, target) {
                if (!this.activeSelectOptionGroup) return this;

                var item = this.queue[this.activeSelectQueueIndex],
                    clickEl = "display";

                target = U.findParentNode(e.target, function (target) {
                    if (target.getAttribute("data-option-value") || target.getAttribute("data-option-value") == "") {
                        clickEl = "optionItem";
                        return true;
                    } else if (item.$target.get(0) == target) {
                        clickEl = "display";
                        return true;
                    }
                });

                if (!target) {
                    this.close();
                    return this;
                } else if (clickEl === "optionItem") {
                    this.val(item.id, {
                        index: {
                            gindex: target.getAttribute("data-option-group-index"),
                            index: target.getAttribute("data-option-index")
                        }
                    }, undefined, "internal");
                    item.$select.trigger("change");
                    item.$display.focus();
                    if (!item.multiple) this.close();
                } else {
                    //open and display click
                    //console.log(this.instanceId);
                }

                return this;
            },
                onBodyKeyup = function onBodyKeyup(e) {
                if (e.keyCode == ax5.info.eventKeys.ESC) {
                    this.close();
                } else if (e.which == ax5.info.eventKeys.RETURN) {
                    if (this.queue[this.activeSelectQueueIndex].optionFocusIndex > -1) {
                        // 아이템에 포커스가 활성화 된 후, 마우스 이벤트 이면 무시
                        var $option = this.activeSelectOptionGroup.find('[data-option-focus-index="' + this.queue[this.activeSelectQueueIndex].optionFocusIndex + '"]');
                        this.val(this.queue[this.activeSelectQueueIndex].id, {
                            index: {
                                gindex: $option.attr("data-option-group-index"),
                                index: $option.attr("data-option-index")
                            }
                        }, undefined, "internal");
                        this.queue[this.activeSelectQueueIndex].$select.trigger("change");
                        if (!this.queue[this.activeSelectQueueIndex].multiple) this.close();
                    } else {
                        this.close();
                    }
                }
            },
                getLabel = function getLabel(queIdx) {
                var item = this.queue[queIdx],
                    labels = [];

                if (U.isArray(item.selected) && item.selected.length > 0) {
                    item.selected.forEach(function (n) {
                        if (n.selected) labels.push(n[item.columnKeys.optionText]);
                    });
                } else {
                    if (!item.multiple && item.options && item.options[0]) {
                        if (item.options[0].optgroup) {
                            labels[0] = item.options[0].options[0][item.columnKeys.optionText];
                        } else {
                            labels[0] = item.options[0][item.columnKeys.optionText];
                        }
                    } else {
                        labels[0] = item.lang.noSelected;
                    }
                }

                return function () {
                    if (item.multiple && labels.length > 1) {
                        var data = {
                            label: labels[0],
                            length: labels.length - 1
                        };
                        return ax5.mustache.render(item.lang.multipleLabel, data);
                    } else {
                        return labels[0];
                    }
                }();
            },
                syncLabel = function syncLabel(queIdx) {
                this.queue[queIdx].$displayLabel.html(getLabel.call(this, queIdx));
            },
                focusWord = function focusWord(queIdx, searchWord) {
                var options = [],
                    i = -1,
                    l = this.queue[queIdx].indexedOptions.length - 1,
                    n = void 0;
                if (searchWord) {
                    while (l - i++) {
                        n = this.queue[queIdx].indexedOptions[i];
                        if (('' + n.value).toLowerCase() == searchWord.toLowerCase()) {
                            options = [{ '@findex': n['@findex'], optionsSort: 0 }];
                            break;
                        } else {
                            var sort = ('' + n.value).toLowerCase().search(searchWord.toLowerCase());
                            if (sort > -1) {
                                options.push({ '@findex': n['@findex'], optionsSort: sort });
                                if (options.length > 2) break;
                            }
                            sort = null;
                        }
                    }
                    options.sort(function (a, b) {
                        return a.optionsSort - b.optionsSort;
                    });
                }
                if (options && options.length > 0) {
                    focusMove.call(this, queIdx, undefined, options[0]['@findex']);
                }

                try {
                    return options;
                } finally {
                    options = null;
                    i = null;
                    l = null;
                    n = null;
                }
            },
                focusMove = function focusMove(queIdx, direction, findex) {
                var _focusIndex = void 0,
                    _prevFocusIndex = void 0,
                    focusOptionEl = void 0,
                    optionGroupScrollContainer = void 0;

                if (this.activeSelectOptionGroup && this.queue[queIdx].options && this.queue[queIdx].options.length > 0) {

                    if (typeof findex !== "undefined") {
                        _focusIndex = findex;
                    } else {
                        _prevFocusIndex = this.queue[queIdx].optionFocusIndex == -1 ? this.queue[queIdx].optionSelectedIndex || -1 : this.queue[queIdx].optionFocusIndex;
                        if (_prevFocusIndex == -1) {
                            _focusIndex = direction > 0 ? 0 : this.queue[queIdx].optionItemLength - 1;
                        } else {
                            _focusIndex = _prevFocusIndex + direction;
                            if (_focusIndex < 0) _focusIndex = 0;else if (_focusIndex > this.queue[queIdx].optionItemLength - 1) _focusIndex = this.queue[queIdx].optionItemLength - 1;
                        }
                    }

                    this.queue[queIdx].optionFocusIndex = _focusIndex;

                    this.activeSelectOptionGroup.find('[data-option-focus-index]').removeClass("hover");

                    focusOptionEl = this.activeSelectOptionGroup.find('[data-option-focus-index="' + _focusIndex + '"]').addClass("hover");

                    optionGroupScrollContainer = this.activeSelectOptionGroup.find('[data-els="content"]');

                    var focusOptionElHeight = focusOptionEl.outerHeight(),
                        optionGroupScrollContainerHeight = optionGroupScrollContainer.innerHeight(),
                        optionGroupScrollContainerScrollTop = optionGroupScrollContainer.scrollTop(),
                        focusOptionElTop = focusOptionEl.position().top + optionGroupScrollContainer.scrollTop();

                    if (optionGroupScrollContainerHeight + optionGroupScrollContainerScrollTop < focusOptionElTop + focusOptionElHeight) {
                        optionGroupScrollContainer.scrollTop(focusOptionElTop + focusOptionElHeight - optionGroupScrollContainerHeight);
                    } else if (optionGroupScrollContainerScrollTop > focusOptionElTop) {
                        optionGroupScrollContainer.scrollTop(focusOptionElTop);
                    }
                    // optionGroup scroll check
                }
            },
                bindSelectTarget = function () {
                var focusWordCall = U.debounce(function (searchWord, queIdx) {
                    focusWord.call(self, queIdx, searchWord);
                    self.queue[queIdx].$displayInput.val('');
                }, 300);

                var selectEvent = {
                    'click': function click(queIdx, e) {
                        var target = U.findParentNode(e.target, function (target) {
                            if (target.getAttribute("data-selected-clear")) {
                                //clickEl = "clear";
                                return true;
                            }
                        });

                        if (target) {
                            this.val(queIdx, { clear: true });
                        } else {
                            if (self.activeSelectQueueIndex == queIdx) {
                                if (this.queue[queIdx].optionFocusIndex == -1) {
                                    // 아이템에 포커스가 활성화 된 후, 마우스 이벤트 이면 무시
                                    self.close();
                                }
                            } else {
                                self.open(queIdx);
                                U.stopEvent(e);
                            }
                        }
                    },
                    'keyUp': function keyUp(queIdx, e) {
                        if (e.which == ax5.info.eventKeys.SPACE) {
                            selectEvent.click.call(this, queIdx, e);
                        } else if (!ctrlKeys[e.which]) {
                            // 사용자 입력이 뜸해지면 찾고 검색 값 제거...
                            focusWordCall(this.queue[queIdx].$displayInput.val(), queIdx);
                        }
                    },
                    'keyDown': function keyDown(queIdx, e) {
                        if (e.which == ax5.info.eventKeys.DOWN) {
                            focusMove.call(this, queIdx, 1);
                            U.stopEvent(e);
                        } else if (e.which == ax5.info.eventKeys.UP) {
                            focusMove.call(this, queIdx, -1);
                            U.stopEvent(e);
                        }
                    },
                    'blur': function blur(queIdx, e) {},
                    'selectChange': function selectChange(queIdx, e) {
                        this.val(queIdx, this.queue[queIdx].$select.val(), true);
                    }
                };
                return function (queIdx) {
                    var item = this.queue[queIdx],
                        data = {};

                    // find selected
                    item.selected = [];
                    if (!item.options) item.options = [];
                    item.options.forEach(function (n) {
                        if (n[cfg.columnKeys.optionSelected]) item.selected.push(jQuery.extend({}, n));
                    });

                    if (!item.$display) {
                        /// 템플릿에 전달할 오브젝트 선언
                        data.instanceId = this.instanceId;
                        data.id = item.id;
                        data.name = item.name;
                        data.theme = item.theme;
                        data.tabIndex = item.tabIndex;
                        data.multiple = item.multiple;
                        data.reset = item.reset;

                        data.label = getLabel.call(this, queIdx);
                        data.formSize = function () {
                            return item.size ? "input-" + item.size : "";
                        }();

                        item.$display = SELECT.tmpl.get.call(this, "tmpl", data);
                        item.$displayLabel = item.$display.find('[data-ax5select-display="label"]');

                        if (item.$target.find("select").get(0)) {
                            item.$select = item.$target.find("select");
                            // select 속성만 변경
                            item.$select.attr("tabindex", "-1").attr("class", "form-control " + data.formSize);
                            if (data.name) {
                                item.$select.attr("name", "name");
                            }
                            if (data.multiple) {
                                item.$select.attr("multiple", "multiple");
                            }
                        } else {
                            item.$select = SELECT.tmpl.get.call(this, "selectTmpl", data);
                            item.$target.append(item.$select);
                            // select append
                        }

                        item.$target.append(item.$display);
                        item.$displayInput = item.$display.find('[data-ax5select-display="input"]'); // 사용자 입력값을 받기위한 숨음 입력필드
                        item.options = syncSelectOptions.call(this, queIdx, item.options);

                        alignSelectDisplay.call(this);

                        item.$displayInput.unbind("blur.ax5select").bind("blur.ax5select", selectEvent.blur.bind(this, queIdx)).unbind('keyup.ax5select').bind('keyup.ax5select', selectEvent.keyUp.bind(this, queIdx)).unbind("keydown.ax5select").bind("keydown.ax5select", selectEvent.keyDown.bind(this, queIdx));
                    } else {
                        item.$displayLabel.html(getLabel.call(this, queIdx));
                        item.options = syncSelectOptions.call(this, queIdx, item.options);

                        alignSelectDisplay.call(this);
                    }

                    item.$display.unbind('click.ax5select').bind('click.ax5select', selectEvent.click.bind(this, queIdx)).unbind('keyup.ax5select').bind('keyup.ax5select', selectEvent.keyUp.bind(this, queIdx));

                    // select 태그에 대한 change 이벤트 감시
                    item.$select.unbind('change.ax5select').bind('change.ax5select', selectEvent.selectChange.bind(this, queIdx));

                    data = null;
                    item = null;
                    queIdx = null;
                    return this;
                };
            }(),
                syncSelectOptions = function () {
                var setSelected = function setSelected(queIdx, O) {
                    if (!O) {
                        this.queue[queIdx].selected = [];
                    } else {
                        if (this.queue[queIdx].multiple) this.queue[queIdx].selected.push(jQuery.extend({}, O));else this.queue[queIdx].selected[0] = jQuery.extend({}, O);
                    }
                };

                return function (queIdx, options) {
                    var item = this.queue[queIdx],
                        po = void 0,
                        elementOptions = void 0,
                        newOptions = void 0,
                        focusIndex = 0;

                    setSelected.call(this, queIdx, false); // item.selected 초기화

                    if (options) {
                        item.options = options;
                        item.indexedOptions = [];

                        // select options 태그 생성
                        po = [];
                        item.options.forEach(function (O, OIndex) {
                            if (O.optgroup) {

                                O['@gindex'] = OIndex;
                                O.options.forEach(function (OO, OOIndex) {
                                    OO['@index'] = OOIndex;
                                    OO['@findex'] = focusIndex;
                                    po.push('<option value="' + OO[item.columnKeys.optionValue] + '" ' + (OO[item.columnKeys.optionSelected] ? ' selected="selected"' : '') + '>' + OO[item.columnKeys.optionText] + '</option>');
                                    if (OO[item.columnKeys.optionSelected]) {
                                        setSelected.call(self, queIdx, OO);
                                    }

                                    item.indexedOptions.push({
                                        '@findex': focusIndex, value: OO[item.columnKeys.optionValue], text: OO[item.columnKeys.optionText]
                                    });
                                    focusIndex++;
                                });
                            } else {
                                O['@index'] = OIndex;
                                O['@findex'] = focusIndex;
                                po.push('<option value="' + O[item.columnKeys.optionValue] + '" ' + (O[item.columnKeys.optionSelected] ? ' selected="selected"' : '') + '>' + O[item.columnKeys.optionText] + '</option>');
                                if (O[item.columnKeys.optionSelected]) {
                                    setSelected.call(self, queIdx, O);
                                }

                                item.indexedOptions.push({
                                    '@findex': focusIndex, value: O[item.columnKeys.optionValue], text: O[item.columnKeys.optionText]
                                });
                                focusIndex++;
                            }
                        });
                        item.optionItemLength = focusIndex;
                        item.$select.html(po.join(''));
                    } else {
                        /// select > options 태그로 스크립트 options를 만들어주는 역할
                        elementOptions = U.toArray(item.$select.get(0).options);
                        // select option 스크립트 생성
                        newOptions = [];
                        elementOptions.forEach(function (O, OIndex) {
                            var option = {};
                            //if (O.value != "") {
                            option[item.columnKeys.optionValue] = O.value;
                            option[item.columnKeys.optionText] = O.text;
                            option[item.columnKeys.optionSelected] = O.selected;
                            option['@index'] = OIndex;
                            option['@findex'] = OIndex;
                            if (O.selected) setSelected.call(self, queIdx, option);
                            newOptions.push(option);
                            //}
                            option = null;
                        });
                        item.options = newOptions;
                        item.indexedOptions = newOptions;
                    }

                    if (!item.multiple && item.selected.length == 0 && item.options && item.options[0]) {
                        if (item.options[0].optgroup) {
                            item.options[0].options[0][item.columnKeys.optionSelected] = true;
                            item.selected.push(jQuery.extend({}, item.options[0].options[0]));
                        } else {
                            item.options[0][item.columnKeys.optionSelected] = true;
                            item.selected.push(jQuery.extend({}, item.options[0]));
                        }
                    }

                    po = null;
                    elementOptions = null;
                    newOptions = null;
                    return item.options;
                };
            }(),
                getQueIdx = function getQueIdx(boundID) {
                if (!U.isString(boundID)) {
                    boundID = jQuery(boundID).data("data-ax5select-id");
                }
                if (!U.isString(boundID)) {
                    console.log(ax5.info.getError("ax5select", "402", "getQueIdx"));
                    return;
                }
                return U.search(this.queue, function () {
                    return this.id == boundID;
                });
            };
            /// private end

            /**
             * Preferences of select UI
             * @method ax5select.setConfig
             * @param {Object} config - 클래스 속성값
             * @returns {ax5select}
             * @example
             * ```js
             * var options = [];
             * for (var i = 0; i < 20; i++) {
            *     options.push({value: i, text: "optionText" + i});
            * }
              * var mySelect = new ax5.ui.select({
            *     theme: "danger"
            * });
              * mySelect.bind({
            *     theme: "primary",
            *     target: $('[data-ax5select="select1"]'),
            *     options: options,
            *     onChange: function () {
            *         console.log(this);
            *     },
            *     onClose: function () {
            *         console.log(this);
            *     },
            *     onStateChanged: function () {
            *         console.log(this);
            *     }
            * });
             * ```
             */
            this.init = function () {
                this.onStateChanged = cfg.onStateChanged;
                this.onChange = cfg.onChange;

                jQuery(window).bind("resize.ax5select-display-" + this.instanceId, function () {
                    alignSelectDisplay.call(this);
                }.bind(this));
            };

            /**
             * bind select
             * @method ax5select.bind
             * @param {Object} item
             * @param {String} [item.id]
             * @param {String} [item.theme]
             * @param {Boolean} [item.multiple]
             * @param {Element} item.target
             * @param {Object[]} item.options
             * @returns {ax5select}
             * @example
             * ```js
             * var mySelect = new ax5.ui.select();
             * mySelect.bind({
             *  columnKeys: {
             *      optionValue: "value",
             *      optionText: "text"
             *  },
             *  target: $('[data-ax5select="select1"]'),
             *  options: [
             *      {value: "", text: ""}
             *  ],
             *  onChange: function(){
             *
             *  },
             *  onClose: function(){
             *
             *  },
             *  onStateChanged: function(){
             *
             *  }
             * });
             * ```
             */
            this.bind = function (item) {
                var selectConfig = {},
                    queIdx = void 0;

                item = jQuery.extend(true, selectConfig, cfg, item);

                if (!item.target) {
                    console.log(ax5.info.getError("ax5select", "401", "bind"));
                    return this;
                }

                item.$target = jQuery(item.target);

                if (!item.id) item.id = item.$target.data("data-ax5select-id");
                if (!item.id) {
                    item.id = 'ax5select-' + ax5.getGuid();
                    item.$target.data("data-ax5select-id", item.id);
                }
                item.name = item.$target.attr("data-ax5select");

                if (item.options) {
                    item.options = JSON.parse(JSON.stringify(item.options));
                }

                // target attribute data
                (function (data) {
                    if (U.isObject(data) && !data.error) {
                        item = jQuery.extend(true, item, data);
                    }
                })(U.parseJson(item.$target.attr("data-ax5select-config"), true));

                queIdx = U.search(this.queue, function () {
                    return this.id == item.id;
                });

                if (queIdx === -1) {
                    this.queue.push(item);
                    bindSelectTarget.call(this, this.queue.length - 1);
                } else {
                    this.queue[queIdx].selected = [];
                    this.queue[queIdx].options = item.options;
                    this.queue[queIdx] = jQuery.extend(true, {}, this.queue[queIdx], item);
                    bindSelectTarget.call(this, queIdx);
                }

                selectConfig = null;
                queIdx = null;
                return this;
            };

            /**
             * open the optionBox of select
             * @method ax5select.open
             * @param {(String|Number|Element)} boundID
             * @param {Number} [tryCount]
             * @returns {ax5select}
             */
            this.open = function () {

                var onExpand = function onExpand(item) {
                    item.onExpand.call({
                        self: this,
                        item: item
                    }, function (O) {
                        if (this.waitOptionsCallback) {
                            var data = {};
                            var item = this.queue[this.activeSelectQueueIndex];

                            /// 현재 selected 검증후 처리
                            (function (item, O) {
                                var optionsMap = {};
                                O.options.forEach(function (_O, _OIndex) {
                                    _O["@index"] = _OIndex;
                                    optionsMap[_O[item.columnKeys.optionValue]] = _O;
                                });
                                if (U.isArray(item.selected)) {
                                    item.selected.forEach(function (_O) {
                                        if (optionsMap[_O[item.columnKeys.optionValue]]) {
                                            O.options[optionsMap[_O[item.columnKeys.optionValue]]["@index"]][item.columnKeys.optionSelected] = true;
                                        }
                                    });
                                }
                            })(item, O);

                            item.$displayLabel.html(getLabel.call(this, this.activeSelectQueueIndex));
                            item.options = syncSelectOptions.call(this, this.activeSelectQueueIndex, O.options);

                            alignSelectDisplay.call(this);

                            /// 템플릿에 전달할 오브젝트 선언
                            data.id = item.id;
                            data.theme = item.theme;
                            data.size = "ax5select-option-group-" + item.size;
                            data.multiple = item.multiple;
                            data.lang = item.lang;
                            data.options = item.options;
                            this.activeSelectOptionGroup.find('[data-els="content"]').html(SELECT.tmpl.get.call(this, "optionsTmpl", data, item.columnKeys));
                        }
                    }.bind(this));
                };

                return function (boundID, tryCount) {
                    this.waitOptionsCallback = null;

                    /**
                     * open select from the outside
                     */
                    var queIdx = U.isNumber(boundID) ? boundID : getQueIdx.call(this, boundID),
                        item = this.queue[queIdx],
                        data = {},
                        focusTop = void 0,
                        selectedOptionEl = void 0;

                    if (item.$display.attr("disabled")) return this;

                    if (this.openTimer) clearTimeout(this.openTimer);
                    if (this.activeSelectOptionGroup) {
                        if (this.activeSelectQueueIndex == queIdx) {
                            return this;
                        }

                        if (tryCount > 2) return this;
                        this.close();
                        this.openTimer = setTimeout(function () {
                            this.open(queIdx, (tryCount || 0) + 1);
                        }.bind(this), cfg.animateTime);

                        return this;
                    }

                    item.optionFocusIndex = -1; // optionGroup이 열리면 포커스 인덱스 초기화 -1로
                    if (item.selected && item.selected.length > 0) {
                        item.optionSelectedIndex = item.selected[0]["@findex"];
                    }

                    /// 템플릿에 전달할 오브젝트 선언
                    data.id = item.id;
                    data.theme = item.theme;
                    data.size = "ax5select-option-group-" + item.size;
                    data.multiple = item.multiple;

                    data.lang = item.lang;
                    item.$display.attr("data-select-option-group-opened", "true");
                    //console.log(data.lang);

                    if (item.onExpand) {
                        // onExpand 인 경우 UI 대기모드 추가
                        data.waitOptions = true;
                    }

                    data.options = item.options;
                    this.activeSelectOptionGroup = SELECT.tmpl.get.call(this, "optionGroupTmpl", data);
                    this.activeSelectOptionGroup.find('[data-els="content"]').html(SELECT.tmpl.get.call(this, "optionsTmpl", data, item.columnKeys));
                    this.activeSelectQueueIndex = queIdx;

                    alignSelectOptionGroup.call(this, "append"); // alignSelectOptionGroup 에서 body append
                    jQuery(window).bind("resize.ax5select-" + this.instanceId, function () {
                        alignSelectOptionGroup.call(this);
                    }.bind(this));

                    if (item.selected && item.selected.length > 0) {
                        selectedOptionEl = this.activeSelectOptionGroup.find('[data-option-index="' + item.selected[0]["@index"] + '"]');

                        if (selectedOptionEl.get(0)) {
                            focusTop = selectedOptionEl.position().top - this.activeSelectOptionGroup.height() / 3;
                            this.activeSelectOptionGroup.find('[data-els="content"]').stop().animate({ scrollTop: focusTop }, item.animateTime, 'swing', function () {});
                        }
                    }

                    /// 사용자 입력으로 옵션을 검색하기 위한 시나리오
                    // 옵션그룹이 활성화 되면 사용자 입력을 받기위한 input 값 초기화 및 포커스 다른 select가 닫히면서 display focus 이벤트와 충돌하는 문제가 있으므로
                    // 1밀리세컨 지연후 포커스 처리. input에 포커스가 되므로 input value로 options를 검색 할 수 있게 됩니다.
                    item.$displayInput.val('');

                    setTimeout(function () {
                        item.$displayInput.trigger("focus");

                        jQuery(window).bind("keyup.ax5select-" + this.instanceId, function (e) {
                            e = e || window.event;
                            onBodyKeyup.call(this, e);
                            U.stopEvent(e);
                        }.bind(this));

                        jQuery(window).bind("click.ax5select-" + this.instanceId, function (e) {
                            e = e || window.event;
                            onBodyClick.call(this, e);
                            U.stopEvent(e);
                        }.bind(this));
                    }.bind(this), 300);

                    onStateChanged.call(this, item, {
                        self: this,
                        state: "open",
                        item: item
                    });

                    // waitOption timer
                    if (item.onExpand) {
                        this.waitOptionsCallback = true;
                        onExpand.call(this, item);
                    }

                    data = null;
                    focusTop = null;
                    selectedOptionEl = null;
                    return this;
                };
            }();

            /**
             * @method ax5select.update
             * @param {(Object|String)} item
             * @returns {ax5select}
             */
            this.update = function (_item) {
                this.bind(_item);
                return this;
            };

            /**
             * @method ax5select.setOptions
             * @param boundID
             * @param options
             * @returns {ax5select}
             */
            this.setOptions = function (boundID, options) {
                var queIdx = getQueIdx.call(this, boundID);
                this.queue[queIdx].selected = [];
                this.queue[queIdx].options = options;
                bindSelectTarget.call(this, queIdx);
                return this;
            };

            /**
             * @method ax5select.val
             * @param {(String|Number|Element)} boundID
             * @param {(String|Object|Array)} [value]
             * @param {Boolean} [selected]
             * @returns {ax5select}
             */
            this.val = function () {

                // todo : val 함수 리팩토링 필요
                var getSelected = function getSelected(_item, o, selected) {
                    if (typeof selected === "undefined") {
                        return _item.multiple ? !o : true;
                    } else {
                        return selected;
                    }
                },
                    clearSelected = function clearSelected(queIdx) {
                    this.queue[queIdx].options.forEach(function (n) {
                        if (n.optgroup) {
                            n.options.forEach(function (nn) {
                                nn.selected = false;
                            });
                        } else {
                            n.selected = false;
                        }
                    });
                },
                    processor = {
                    'index': function index(queIdx, value, selected) {
                        // 클래스 내부에서 호출된 형태, 그런 이유로 옵션그룹에 대한 상태를 변경 하고 있다.
                        var item = this.queue[queIdx];

                        /*
                         if (U.isArray(value.index)) {
                         value.index.forEach(function (n) {
                         item.options[n][item.columnKeys.optionSelected] = getSelected(item, item.options[n][item.columnKeys.optionSelected], selected);
                         self.activeSelectOptionGroup
                         .find('[data-option-index="' + n + '"]')
                         .attr("data-option-selected", item.options[n][item.columnKeys.optionSelected].toString());
                         });
                         }
                         else {
                         }
                         */
                        if (U.isString(value.index.gindex)) {
                            item.options[value.index.gindex].options[value.index.index][item.columnKeys.optionSelected] = getSelected(item, item.options[value.index.gindex].options[value.index.index][item.columnKeys.optionSelected], selected);
                            self.activeSelectOptionGroup.find('[data-option-group-index="' + value.index.gindex + '"][data-option-index="' + value.index.index + '"]').attr("data-option-selected", item.options[value.index.gindex].options[value.index.index][item.columnKeys.optionSelected].toString());
                        } else {
                            item.options[value.index.index][item.columnKeys.optionSelected] = getSelected(item, item.options[value.index.index][item.columnKeys.optionSelected], selected);
                            self.activeSelectOptionGroup.find('[data-option-index="' + value.index.index + '"]').attr("data-option-selected", item.options[value.index.index][item.columnKeys.optionSelected].toString());
                        }

                        syncSelectOptions.call(this, queIdx, item.options);
                        syncLabel.call(this, queIdx);
                        alignSelectOptionGroup.call(this);
                    },
                    'arr': function arr(queIdx, values, selected) {
                        values.forEach(function (value) {
                            if (U.isString(value) || U.isNumber(value)) {
                                processor.value.call(self, queIdx, value, selected);
                            } else {
                                for (var key in processor) {
                                    if (value[key]) {
                                        processor[key].call(self, queIdx, value, selected);
                                        break;
                                    }
                                }
                            }
                        });
                    },
                    'value': function value(queIdx, _value, selected) {
                        var item = this.queue[queIdx],
                            optionIndex = U.search(item.options, function () {
                            return this[item.columnKeys.optionValue] == _value;
                        });
                        if (optionIndex > -1) {
                            item.options[optionIndex][item.columnKeys.optionSelected] = getSelected(item, item.options[optionIndex][item.columnKeys.optionSelected], selected);
                        } else {
                            console.log(ax5.info.getError("ax5select", "501", "val"));
                            return;
                        }

                        syncSelectOptions.call(this, queIdx, item.options);
                        syncLabel.call(this, queIdx);
                    },
                    'text': function text(queIdx, value, selected) {
                        var item = this.queue[queIdx],
                            optionIndex = U.search(item.options, function () {
                            return this[item.columnKeys.optionText] == value;
                        });
                        if (optionIndex > -1) {
                            item.options[optionIndex][item.columnKeys.optionSelected] = getSelected(item, item.options[optionIndex][item.columnKeys.optionSelected], selected);
                        } else {
                            console.log(ax5.info.getError("ax5select", "501", "val"));
                            return;
                        }

                        syncSelectOptions.call(this, queIdx, item.options);
                        syncLabel.call(this, queIdx);
                    },
                    'clear': function clear(queIdx) {
                        clearSelected.call(this, queIdx);
                        syncSelectOptions.call(this, queIdx, this.queue[queIdx].options);
                        syncLabel.call(this, queIdx);

                        if (this.activeSelectOptionGroup) {
                            this.activeSelectOptionGroup.find('[data-option-index]').attr("data-option-selected", "false");
                        }
                    }
                };

                return function (boundID, value, selected, internal) {
                    var queIdx = U.isNumber(boundID) ? boundID : getQueIdx.call(this, boundID);
                    if (!this.queue[queIdx]) {
                        return this;
                    }
                    if (typeof value !== "undefined" && !this.queue[queIdx].multiple) {
                        clearSelected.call(this, queIdx);
                    }

                    if (typeof value == "undefined") {
                        return this.queue[queIdx].selected;
                    } else if (U.isArray(value)) {
                        processor.arr.call(this, queIdx, value, selected);
                    } else if (U.isString(value) || U.isNumber(value)) {
                        processor.value.call(this, queIdx, value, selected);
                    } else {
                        if (value === null) {
                            processor.clear.call(this, queIdx);
                        } else {
                            for (var key in processor) {
                                if (value[key]) {
                                    processor[key].call(this, queIdx, value, selected);
                                    break;
                                }
                            }
                        }
                    }

                    if (typeof value !== "undefined") {
                        onStateChanged.call(this, this.queue[queIdx], {
                            self: this,
                            item: this.queue[queIdx],
                            state: internal ? "changeValue" : "setValue",
                            value: this.queue[queIdx].selected,
                            internal: internal
                        });
                    }

                    boundID = null;
                    return this;
                };
            }();

            /**
             * @method ax5select.close
             * @returns {ax5select}
             */
            this.close = function (item) {
                if (this.closeTimer) clearTimeout(this.closeTimer);
                if (!this.activeSelectOptionGroup) return this;

                item = this.queue[this.activeSelectQueueIndex];
                item.optionFocusIndex = -1;

                item.$displayInput.val('').trigger("blur");
                item.$display.removeAttr("data-select-option-group-opened").trigger("focus");

                this.activeSelectOptionGroup.addClass("destroy");

                jQuery(window).unbind("resize.ax5select-" + this.instanceId).unbind("click.ax5select-" + this.instanceId).unbind("keyup.ax5select-" + this.instanceId);

                this.closeTimer = setTimeout(function () {
                    if (this.activeSelectOptionGroup) this.activeSelectOptionGroup.remove();
                    this.activeSelectOptionGroup = null;
                    this.activeSelectQueueIndex = -1;

                    var that = {
                        self: this,
                        item: item,
                        value: item.selected,
                        state: "close"
                    };

                    onStateChanged.call(this, item, that);

                    // waitOption timer
                    if (item.onClose) {
                        item.onClose.call(that);
                    }
                }.bind(this), cfg.animateTime);
                this.waitOptionsCallback = null;
                return this;
            };

            this.enable = function (boundID) {
                var queIdx = getQueIdx.call(this, boundID);
                this.queue[queIdx].$display.removeAttr("disabled");
                this.queue[queIdx].$select.removeAttr("disabled");

                onStateChanged.call(this, this.queue[queIdx], {
                    self: this,
                    state: "enable"
                });

                return this;
            };

            this.disable = function (boundID) {
                var queIdx = getQueIdx.call(this, boundID);
                this.queue[queIdx].$display.attr("disabled", "disabled");
                this.queue[queIdx].$select.attr("disabled", "disabled");

                onStateChanged.call(this, this.queue[queIdx], {
                    self: this,
                    state: "disable"
                });

                return this;
            };

            // 클래스 생성자
            this.main = function () {
                if (arguments && U.isObject(arguments[0])) {
                    this.setConfig(arguments[0]);
                } else {
                    this.init();
                }
            }.apply(this, arguments);
        };
        return ax5select;
    }());
    SELECT = ax5.ui.select;
})();

ax5.ui.select_instance = new ax5.ui.select();
jQuery.fn.ax5select = function () {
    return function (config) {
        if (ax5.util.isString(arguments[0])) {
            var methodName = arguments[0];

            switch (methodName) {
                case "open":
                    return ax5.ui.select_instance.open(this);
                    break;
                case "close":
                    return ax5.ui.select_instance.close(this);
                    break;
                case "setValue":
                    return ax5.ui.select_instance.val(this, arguments[1], arguments[2]);
                    break;
                case "getValue":
                    return ax5.ui.select_instance.val(this);
                    break;
                case "enable":
                    return ax5.ui.select_instance.enable(this);
                    break;
                case "disable":
                    return ax5.ui.select_instance.disable(this);
                    break;
                case "setOptions":
                    return ax5.ui.select_instance.setOptions(this, arguments[1]);
                    break;
                default:
                    return this;
            }
        } else {
            if (typeof config == "undefined") config = {};
            jQuery.each(this, function () {
                var defaultConfig = {
                    target: this
                };
                config = jQuery.extend({}, config, defaultConfig);
                ax5.ui.select_instance.bind(config);
            });
        }
        return this;
    };
}();

// muliple 속성이 없는 select의 기본 선택 해제 방법.. 결정 필요..
// onExpand 가 있으면..?
// ax5.ui.select.tmpl
(function () {

    var SELECT = ax5.ui.select;

    var optionGroupTmpl = function optionGroupTmpl(columnKeys) {
        return '\n<div class="ax5select-option-group {{theme}} {{size}}" data-ax5select-option-group="{{id}}">\n    <div class="ax-select-body">\n        <div class="ax-select-option-group-content" data-els="content"></div>\n    </div>\n    <div class="ax-select-arrow"></div> \n</div>\n';
    };
    var tmpl = function tmpl(columnKeys) {
        return '\n<a {{^tabIndex}}href="#ax5select-{{id}}" {{/tabIndex}}{{#tabIndex}}tabindex="{{tabIndex}}" {{/tabIndex}}class="form-control {{formSize}} ax5select-display {{theme}}" \ndata-ax5select-display="{{id}}" data-ax5select-instance="{{instanceId}}">\n    <div class="ax5select-display-table" data-els="display-table">\n        <div data-ax5select-display="label">{{label}}</div>\n        <div data-ax5select-display="addon"> \n            {{#multiple}}{{#reset}}\n            <span class="addon-icon-reset" data-selected-clear="true">{{{.}}}</span>\n            {{/reset}}{{/multiple}}\n            {{#icons}}\n            <span class="addon-icon-closed">{{clesed}}</span>\n            <span class="addon-icon-opened">{{opened}}</span>\n            {{/icons}}\n            {{^icons}}\n            <span class="addon-icon-closed"><span class="addon-icon-arrow"></span></span>\n            <span class="addon-icon-opened"><span class="addon-icon-arrow"></span></span>\n            {{/icons}}\n        </div>\n    </div>\n    <input type="text" tabindex="-1" data-ax5select-display="input" \n    style="position:absolute;z-index:0;left:0px;top:0px;font-size:1px;opacity: 0;width:1px;border: 0px none;color : transparent;text-indent: -9999em;" />\n</a>\n';
    };
    var selectTmpl = function selectTmpl(columnKeys) {
        return '\n<select tabindex="-1" class="form-control {{formSize}}" name="{{name}}" {{#multiple}}multiple="multiple"{{/multiple}}></select>\n';
    };
    var optionsTmpl = function optionsTmpl(columnKeys) {
        return '\n{{#waitOptions}}\n    <div class="ax-select-option-item">\n            <div class="ax-select-option-item-holder">\n                <span class="ax-select-option-item-cell ax-select-option-item-label">\n                    {{{lang.loading}}}\n                </span>\n            </div>\n        </div>\n{{/waitOptions}}\n{{^waitOptions}}\n    {{#options}}\n        {{#optgroup}}\n            <div class="ax-select-option-group">\n                <div class="ax-select-option-item-holder">\n                    <span class="ax-select-option-group-label">\n                        {{{.}}}\n                    </span>\n                </div>\n                {{#options}}\n                <div class="ax-select-option-item" data-option-focus-index="{{@findex}}" data-option-group-index="{{@gindex}}" data-option-index="{{@index}}" \n                data-option-value="{{' + columnKeys.optionValue + '}}" \n                {{#' + columnKeys.optionSelected + '}}data-option-selected="true"{{/' + columnKeys.optionSelected + '}}>\n                    <div class="ax-select-option-item-holder">\n                        {{#multiple}}\n                        <span class="ax-select-option-item-cell ax-select-option-item-checkbox">\n                            <span class="item-checkbox-wrap useCheckBox" data-option-checkbox-index="{{@i}}"></span>\n                        </span>\n                        {{/multiple}}\n                        <span class="ax-select-option-item-cell ax-select-option-item-label">{{{' + columnKeys.optionText + '}}}</span>\n                    </div>\n                </div>\n                {{/options}}\n            </div>                            \n        {{/optgroup}}\n        {{^optgroup}}\n        <div class="ax-select-option-item" data-option-focus-index="{{@findex}}" data-option-index="{{@index}}" data-option-value="{{' + columnKeys.optionValue + '}}" {{#' + columnKeys.optionSelected + '}}data-option-selected="true"{{/' + columnKeys.optionSelected + '}}>\n            <div class="ax-select-option-item-holder">\n                {{#multiple}}\n                <span class="ax-select-option-item-cell ax-select-option-item-checkbox">\n                    <span class="item-checkbox-wrap useCheckBox" data-option-checkbox-index="{{@i}}"></span>\n                </span>\n                {{/multiple}}\n                <span class="ax-select-option-item-cell ax-select-option-item-label">{{{' + columnKeys.optionText + '}}}</span>\n            </div>\n        </div>\n        {{/optgroup}}\n    {{/options}}\n    {{^options}}\n        <div class="ax-select-option-item">\n            <div class="ax-select-option-item-holder">\n                <span class="ax-select-option-item-cell ax-select-option-item-label">\n                    {{{lang.noOptions}}}\n                </span>\n            </div>\n        </div>\n    {{/options}}\n{{/waitOptions}}\n';
    };

    SELECT.tmpl = {
        "optionGroupTmpl": optionGroupTmpl,
        "tmpl": tmpl,
        "selectTmpl": selectTmpl,
        "optionsTmpl": optionsTmpl,

        get: function get(tmplName, data, columnKeys) {
            return jQuery(ax5.mustache.render(SELECT.tmpl[tmplName].call(this, columnKeys), data));
        }
    };
})();
"use strict";

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };

/*
 * Copyright (c) 2016. tom@axisj.com
 * - github.com/thomasjang
 * - www.axisj.com
 */

// ax5.ui.grid
(function () {

    var UI = ax5.ui;
    var U = ax5.util;
    var GRID = void 0;

    UI.addClass({
        className: "grid"
    }, function () {
        /**
         * @class ax5grid
         * @classdesc
         * @author tom@axisj.com
         * @example
         * ```
         * var myGrid = new ax5.ui.grid();
         * ```
         */
        return function () {
            var self = this,
                cfg = void 0,
                ctrlKeys = {
                "33": "KEY_PAGEUP",
                "34": "KEY_PAGEDOWN",
                "35": "KEY_END",
                "36": "KEY_HOME",
                "37": "KEY_LEFT",
                "38": "KEY_UP",
                "39": "KEY_RIGHT",
                "40": "KEY_DOWN"
            };

            this.instanceId = ax5.getGuid();
            this.config = {
                theme: 'default',
                animateTime: 250,
                debounceTime: 250,
                appendDebouncer: null,
                appendDebounceTimes: 0,
                appendProgressIcon: '...',
                appendProgress: false,

                // 틀고정 속성
                frozenColumnIndex: 0,
                frozenRowIndex: 0,
                showLineNumber: false,
                showRowSelector: false,
                multipleSelect: true,
                virtualScrollY: true,
                virtualScrollX: true,
                headerSelect: true,

                // 스크롤될 때 body 페인팅 딜레이를 주어 성능이 좋은 않은 브라우저에서 반응을 빠르게 할 때 사용하는 옵션들
                virtualScrollYCountMargin: 0,
                virtualScrollAccelerated: true,
                virtualScrollAcceleratedDelayTime: 10,

                height: 0,
                columnMinWidth: 100,
                lineNumberColumnWidth: 30,
                rowSelectorColumnWidth: 26,
                sortable: undefined,
                remoteSort: false,

                header: {
                    display: true,
                    align: false,
                    columnHeight: 26,
                    columnPadding: 3,
                    columnBorderWidth: 1,
                    selector: true
                },
                body: {
                    align: false,
                    columnHeight: 26,
                    columnPadding: 3,
                    columnBorderWidth: 1,
                    grouping: false,
                    mergeCells: false
                },
                rightSum: false,
                footSum: false,
                page: {
                    height: 25,
                    display: true,
                    statusDisplay: true,
                    navigationItemCount: 5
                },
                scroller: {
                    size: 15,
                    barMinSize: 15,
                    trackPadding: 4
                },
                columnKeys: {
                    selected: '__selected__',
                    modified: '__modified__',
                    deleted: '__deleted__',
                    disableSelection: '__disable_selection__'
                },
                tree: {
                    use: false,
                    hashDigit: 8,
                    indentWidth: 10,
                    arrowWidth: 15,
                    iconWidth: 18,
                    icons: {
                        openedArrow: '▾',
                        collapsedArrow: '▸',
                        groupIcon: '⊚',
                        collapsedGroupIcon: '⊚',
                        itemIcon: '⊙'
                    },
                    columnKeys: {
                        parentKey: "pid",
                        selfKey: "id",
                        collapse: "collapse",
                        hidden: "hidden",
                        parentHash: "__hp__",
                        selfHash: "__hs__",
                        children: "__children__",
                        depth: "__depth__"
                    }
                }
            };
            this.xvar = {
                bodyTrHeight: 0, // 한줄의 높이
                scrollContentWidth: 0, // 스크롤 될 내용물의 너비 (스크롤 될 내용물 : panel['body-scroll'] 안에 컬럼이 있는)
                scrollContentHeight: 0, // 스크롤 된 내용물의 높이
                scrollTimer: null
            };

            // 그리드 데이터셋
            this.columns = []; // config.columns에서 복제된 오브젝트
            this.colGroup = []; // columns를 table태그로 출력하기 좋게 변환한 오브젝트
            this.footSumColumns = [];
            this.bodyGrouping = {};

            this.list = []; // 그리드의 데이터
            this.proxyList = null; // 그리드 데이터의 대리자
            this.page = null; // 그리드의 페이지 정보
            this.selectedDataIndexs = [];
            this.deletedList = [];
            this.sortInfo = {}; // 그리드의 헤더 정렬 정보
            this.focusedColumn = {}; // 그리드 바디의 포커스된 셀 정보
            this.selectedColumn = {}; // 그리드 바디의 선택된 셀 정보
            this.isInlineEditing = false;
            this.inlineEditing = {};
            this.listIndexMap = {}; // tree데이터 사용시 데이터 인덱싱 맵
            this.contextMenu = null; // contentMenu 의 인스턴스

            // header
            this.headerTable = {};
            this.leftHeaderData = {};
            this.headerData = {};
            this.rightHeaderData = {};

            // body
            this.bodyRowTable = {};
            this.leftBodyRowData = {};
            this.bodyRowData = {};
            this.rightBodyRowData = {};
            this.bodyRowMap = {};

            this.bodyGroupingTable = {};
            this.leftBodyGroupingData = {};
            this.bodyGroupingData = {};
            this.rightBodyGroupingData = {};
            this.bodyGroupingMap = {};

            // footSum
            this.footSumTable = {}; // footSum의 출력레이아웃
            this.leftFootSumData = {}; // frozenColumnIndex 를 기준으로 나누어진 출력 레이아웃 왼쪽
            this.footSumData = {}; // frozenColumnIndex 를 기준으로 나누어진 출력 레이아웃 오른쪽
            this.needToPaintSum = true; // 데이터 셋이 변경되어 summary 변경 필요여부

            cfg = this.config;

            var initGrid = function initGrid() {
                // 그리드 템플릿에 전달하고자 하는 데이터를 정리합시다.

                var data = {
                    instanceId: this.id
                };

                this.$target.html(GRID.tmpl.get("main", data));

                // 그리드 패널 프레임의 각 엘리먼트를 캐쉬합시다.
                this.$ = {
                    "container": {
                        "hidden": this.$target.find('[data-ax5grid-container="hidden"]'),
                        "root": this.$target.find('[data-ax5grid-container="root"]'),
                        "header": this.$target.find('[data-ax5grid-container="header"]'),
                        "body": this.$target.find('[data-ax5grid-container="body"]'),
                        "page": this.$target.find('[data-ax5grid-container="page"]'),
                        "scroller": this.$target.find('[data-ax5grid-container="scroller"]')
                    },
                    "panel": {
                        "aside-header": this.$target.find('[data-ax5grid-panel="aside-header"]'),
                        "left-header": this.$target.find('[data-ax5grid-panel="left-header"]'),
                        "header": this.$target.find('[data-ax5grid-panel="header"]'),
                        "header-scroll": this.$target.find('[data-ax5grid-panel-scroll="header"]'),
                        "right-header": this.$target.find('[data-ax5grid-panel="right-header"]'),
                        "top-aside-body": this.$target.find('[data-ax5grid-panel="top-aside-body"]'),
                        "top-left-body": this.$target.find('[data-ax5grid-panel="top-left-body"]'),
                        "top-body": this.$target.find('[data-ax5grid-panel="top-body"]'),
                        "top-body-scroll": this.$target.find('[data-ax5grid-panel-scroll="top-body"]'),
                        "top-right-body": this.$target.find('[data-ax5grid-panel="top-right-body"]'),
                        "aside-body": this.$target.find('[data-ax5grid-panel="aside-body"]'),
                        "aside-body-scroll": this.$target.find('[data-ax5grid-panel-scroll="aside-body"]'),
                        "left-body": this.$target.find('[data-ax5grid-panel="left-body"]'),
                        "left-body-scroll": this.$target.find('[data-ax5grid-panel-scroll="left-body"]'),
                        "body": this.$target.find('[data-ax5grid-panel="body"]'),
                        "body-scroll": this.$target.find('[data-ax5grid-panel-scroll="body"]'),
                        "right-body": this.$target.find('[data-ax5grid-panel="right-body"]'),
                        "right-body-scroll": this.$target.find('[data-ax5grid-panel-scroll="right-body"]'),
                        "bottom-aside-body": this.$target.find('[data-ax5grid-panel="bottom-aside-body"]'),
                        "bottom-left-body": this.$target.find('[data-ax5grid-panel="bottom-left-body"]'),
                        "bottom-body": this.$target.find('[data-ax5grid-panel="bottom-body"]'),
                        "bottom-body-scroll": this.$target.find('[data-ax5grid-panel-scroll="bottom-body"]'),
                        "bottom-right-body": this.$target.find('[data-ax5grid-panel="bottom-right-body"]')
                    },
                    "livePanelKeys": [], // 현재 사용중인 패널들 (grid-body repaint에서 수집하여 처리)
                    "scroller": {
                        "vertical": this.$target.find('[data-ax5grid-scroller="vertical"]'),
                        "vertical-bar": this.$target.find('[data-ax5grid-scroller="vertical-bar"]'),
                        "horizontal": this.$target.find('[data-ax5grid-scroller="horizontal"]'),
                        "horizontal-bar": this.$target.find('[data-ax5grid-scroller="horizontal-bar"]'),
                        "corner": this.$target.find('[data-ax5grid-scroller="corner"]')
                    },
                    "page": {
                        "navigation": this.$target.find('[data-ax5grid-page="navigation"]'),
                        "status": this.$target.find('[data-ax5grid-page="status"]')
                    },
                    "form": {
                        "clipboard": this.$target.find('[data-ax5grid-form="clipboard"]')
                    },
                    "resizer": {
                        "vertical": this.$target.find('[data-ax5grid-resizer="vertical"]'),
                        "horizontal": this.$target.find('[data-ax5grid-resizer="horizontal"]')
                    }
                };

                this.$["container"]["root"].css({ height: this.config.height || this.config._height });

                return this;
            };
            var initColumns = function initColumns(_columns) {
                if (!U.isArray(_columns)) _columns = [];
                this.columns = U.deepCopy(_columns);

                this.headerTable = GRID.util.makeHeaderTable.call(this, this.columns);
                this.xvar.frozenColumnIndex = cfg.frozenColumnIndex || 0;

                this.bodyRowTable = GRID.util.makeBodyRowTable.call(this, this.columns);
                this.bodyRowMap = GRID.util.makeBodyRowMap.call(this, this.bodyRowTable);
                // 바디에 표현될 한줄의 높이를 계산합니다.
                this.xvar.bodyTrHeight = this.bodyRowTable.rows.length * this.config.body.columnHeight;

                var colGroupMap = {};
                for (var r = 0, rl = this.headerTable.rows.length; r < rl; r++) {
                    var row = this.headerTable.rows[r];
                    for (var c = 0, cl = row.cols.length; c < cl; c++) {
                        colGroupMap[row.cols[c].colIndex] = jQuery.extend({}, row.cols[c]);
                    }
                }

                this.colGroup = [];
                for (var k in colGroupMap) {
                    this.colGroup.push(colGroupMap[k]);
                }

                return this;
            };
            var onResetColumns = function onResetColumns() {
                initColumns.call(this, this.config.columns);
                resetColGroupWidth.call(this);
                if (this.config.footSum) {
                    initFootSum.call(this, this.config.footSum);
                    this.needToPaintSum = true;
                }
                if (this.config.body.grouping) initBodyGroup.call(this, this.config.body.grouping);
                alignGrid.call(this, true);
                GRID.header.repaint.call(this, true);
                GRID.body.repaint.call(this, true);
                GRID.scroller.resize.call(this);
            };
            var resetColGroupWidth = function resetColGroupWidth() {
                /// !! 그리드 target의 크기가 변경되면 이 함수를 호출하려 this.colGroup의 _width 값을 재 계산 하여야 함. [tom]
                var CT_WIDTH = this.$["container"]["root"].width() - function () {
                    var width = 0;
                    if (cfg.showLineNumber) width += cfg.lineNumberColumnWidth;
                    if (cfg.showRowSelector) width += cfg.rowSelectorColumnWidth;
                    width += cfg.scroller.size;
                    return width;
                }(),
                    totalWidth = 0,
                    computedWidth = void 0,
                    autoWidthColgroupIndexs = [],
                    colGroup = this.colGroup,
                    i = void 0,
                    l = void 0;

                for (i = 0, l = colGroup.length; i < l; i++) {
                    if (U.isNumber(colGroup[i].width)) {
                        totalWidth += colGroup[i]._width = colGroup[i].width;
                    } else if (colGroup[i].width === "*") {
                        autoWidthColgroupIndexs.push(i);
                    } else if (U.right(colGroup[i].width, 1) === "%") {
                        totalWidth += colGroup[i]._width = CT_WIDTH * U.left(colGroup[i].width, "%") / 100;
                    }
                }
                if (autoWidthColgroupIndexs.length > 0) {
                    computedWidth = (CT_WIDTH - totalWidth) / autoWidthColgroupIndexs.length;
                    for (i = 0, l = autoWidthColgroupIndexs.length; i < l; i++) {
                        colGroup[autoWidthColgroupIndexs[i]]._width = computedWidth;
                    }
                }
            };
            var initFootSum = function initFootSum(_footSum) {
                if (U.isArray(_footSum)) {
                    this.footSumTable = GRID.util.makeFootSumTable.call(this, this.footSumColumns = _footSum);
                } else {
                    this.footSumColumns = [];
                    this.footSumTable = {};
                }
            };
            var initBodyGroup = function initBodyGroup(_grouping) {
                var grouping = jQuery.extend({}, _grouping);
                if ("by" in grouping && "columns" in grouping) {
                    this.bodyGrouping = {
                        by: grouping.by,
                        columns: grouping.columns
                    };
                    this.bodyGroupingTable = GRID.util.makeBodyGroupingTable.call(this, this.bodyGrouping.columns);
                    this.sortInfo = function () {
                        var sortInfo = {};
                        for (var k = 0, kl = this.bodyGrouping.by.length; k < kl; k++) {
                            sortInfo[this.bodyGrouping.by[k]] = {
                                orderBy: "asc",
                                seq: k,
                                fixed: true
                            };
                            for (var c = 0, cl = this.colGroup.length; c < cl; c++) {
                                if (this.colGroup[c].key === this.bodyGrouping.by[k]) {
                                    this.colGroup[c].sort = "asc";
                                    this.colGroup[c].sortFixed = true;
                                }
                            }
                        }
                        return sortInfo;
                    }.call(this);
                } else {
                    cfg.body.grouping = false;
                }
            };
            var alignGrid = function alignGrid(_isFirst) {
                var list = this.proxyList ? this.proxyList : this.list;
                // 대상이 크기가 컬럼의 최소 크기 보다 작업 금지
                if (Math.min(this.$target.innerWidth(), this.$target.innerHeight()) < 5) {
                    return false;
                }

                if (!this.config.height) {
                    this.$["container"]["root"].css({ height: this.config._height = this.$target.height() });
                }

                var CT_WIDTH = this.$["container"]["root"].width(),
                    CT_HEIGHT = this.$["container"]["root"].height(),
                    CT_INNER_WIDTH = CT_WIDTH,
                    CT_INNER_HEIGHT = CT_HEIGHT,
                    asidePanelWidth = cfg.asidePanelWidth = function () {
                    var width = 0;
                    if (cfg.showLineNumber) width += cfg.lineNumberColumnWidth;
                    if (cfg.showRowSelector) width += cfg.rowSelectorColumnWidth;
                    return width;
                }(),
                    frozenPanelWidth = cfg.frozenPanelWidth = function (colGroup, endIndex) {
                    var width = 0;
                    for (var i = 0, l = endIndex; i < l; i++) {
                        width += colGroup[i]._width;
                    }
                    return width;
                }(this.colGroup, cfg.frozenColumnIndex),
                    verticalScrollerWidth = void 0,
                    horizontalScrollerHeight = void 0,
                    bodyHeight = void 0;

                // todo : 우측 함계컬럼 너비 계산
                var rightPanelWidth = 0,
                    frozenRowHeight = function (bodyTrHeight) {
                    return cfg.frozenRowIndex * bodyTrHeight;
                }(this.xvar.bodyTrHeight),
                    footSumHeight = function (bodyTrHeight) {
                    return this.footSumColumns.length * bodyTrHeight;
                }.call(this, this.xvar.bodyTrHeight),
                    headerHeight = cfg.header.display ? this.headerTable.rows.length * cfg.header.columnHeight : 0,
                    pageHeight = cfg.page.display ? cfg.page.height : 0;

                (function () {
                    verticalScrollerWidth = CT_HEIGHT - headerHeight - pageHeight - footSumHeight < list.length * this.xvar.bodyTrHeight ? this.config.scroller.size : 0;
                    // 남은 너비가 colGroup의 너비보다 넓을때. 수평 스크롤 활성화.
                    horizontalScrollerHeight = function () {
                        var totalColGroupWidth = 0;
                        // aside 빼고 너비
                        // 수직 스크롤이 있으면 또 빼고 비교
                        var bodyWidth = CT_WIDTH - asidePanelWidth - verticalScrollerWidth;
                        for (var i = 0, l = this.colGroup.length; i < l; i++) {
                            totalColGroupWidth += this.colGroup[i]._width;
                        }
                        return totalColGroupWidth > bodyWidth ? this.config.scroller.size : 0;
                    }.call(this);

                    if (horizontalScrollerHeight > 0) {
                        verticalScrollerWidth = CT_HEIGHT - headerHeight - pageHeight - footSumHeight - horizontalScrollerHeight < list.length * this.xvar.bodyTrHeight ? this.config.scroller.size : 0;
                    }
                }).call(this);

                // 수평 너비 결정
                CT_INNER_WIDTH = CT_WIDTH - verticalScrollerWidth;
                // 수직 스크롤러의 높이 결정.
                CT_INNER_HEIGHT = CT_HEIGHT - pageHeight - horizontalScrollerHeight;

                bodyHeight = CT_INNER_HEIGHT - headerHeight;

                var panelDisplayProcess = function panelDisplayProcess(panel, vPosition, hPosition, containerType) {
                    var css = {},
                        isHide = false;

                    switch (hPosition) {
                        case "aside":
                            if (asidePanelWidth === 0) {
                                isHide = true;
                            } else {
                                css["left"] = 0;
                                css["width"] = asidePanelWidth;
                            }
                            break;
                        case "left":
                            if (cfg.frozenColumnIndex === 0) {
                                isHide = true;
                            } else {
                                css["left"] = asidePanelWidth;
                                css["width"] = frozenPanelWidth;
                            }
                            break;
                        case "right":
                            if (!cfg.rightSum) {
                                isHide = true;
                            } else {}
                            break;
                        default:
                            if (containerType !== "page") {
                                if (cfg.frozenColumnIndex === 0) {
                                    css["left"] = asidePanelWidth;
                                } else {
                                    css["left"] = frozenPanelWidth + asidePanelWidth;
                                }
                                css["width"] = CT_INNER_WIDTH - asidePanelWidth - frozenPanelWidth - rightPanelWidth;
                            }
                            break;
                    }

                    if (isHide) {
                        panel.hide();
                        // 프로세스 중지
                        return this;
                    }

                    if (containerType === "body") {
                        switch (vPosition) {
                            case "top":
                                if (cfg.frozenRowIndex == 0) {
                                    isHide = true;
                                } else {
                                    css["top"] = 0;
                                    css["height"] = frozenRowHeight;
                                }
                                break;
                            case "bottom":
                                if (!cfg.footSum) {
                                    isHide = true;
                                } else {
                                    css["top"] = bodyHeight - footSumHeight;
                                    css["height"] = footSumHeight; // footSum height
                                }
                                break;
                            default:
                                css["top"] = frozenRowHeight;
                                css["height"] = bodyHeight - frozenRowHeight - footSumHeight;

                                break;
                        }
                    } else if (containerType === "header") {
                        css["height"] = headerHeight;
                    } else if (containerType === "page") {
                        if (pageHeight == 0) {
                            isHide = true;
                        } else {
                            css["height"] = pageHeight;
                        }
                    }

                    if (isHide) {
                        panel.hide();
                        // 프로세스 중지
                        return this;
                    }

                    panel.show().css(css);
                    return this;
                };
                var scrollerDisplayProcess = function scrollerDisplayProcess(panel, scrollerWidth, scrollerHeight, containerType) {
                    var css = {},
                        isHide = false;

                    switch (containerType) {
                        case "vertical":
                            if (scrollerWidth > 0) {
                                css["width"] = scrollerWidth;
                                css["height"] = CT_INNER_HEIGHT;
                                css["bottom"] = scrollerHeight + pageHeight;
                            } else {
                                isHide = true;
                            }
                            break;
                        case "horizontal":
                            if (scrollerHeight > 0) {
                                css["width"] = CT_INNER_WIDTH;
                                css["height"] = scrollerHeight;
                                css["right"] = scrollerWidth;
                                css["bottom"] = pageHeight;
                            } else {
                                isHide = true;
                            }
                            break;
                        case "corner":
                            if (scrollerWidth > 0 && scrollerHeight > 0) {
                                css["width"] = scrollerWidth;
                                css["height"] = scrollerHeight;
                                css["bottom"] = pageHeight;
                            } else {
                                isHide = true;
                            }
                            break;
                    }

                    if (isHide) {
                        panel.hide();
                        // 프로세스 중지
                        return this;
                    }

                    panel.show().css(css);
                };

                this.$["container"]["header"].css({ height: headerHeight });
                this.$["container"]["body"].css({ height: bodyHeight });

                // 각 패널들의 크기 표시여부를 결정합니다
                panelDisplayProcess.call(this, this.$["panel"]["aside-header"], "", "aside", "header");
                panelDisplayProcess.call(this, this.$["panel"]["left-header"], "", "left", "header");
                panelDisplayProcess.call(this, this.$["panel"]["header"], "", "", "header");
                panelDisplayProcess.call(this, this.$["panel"]["right-header"], "", "right", "header");

                panelDisplayProcess.call(this, this.$["panel"]["top-aside-body"], "top", "aside", "body");
                panelDisplayProcess.call(this, this.$["panel"]["top-left-body"], "top", "left", "body");
                panelDisplayProcess.call(this, this.$["panel"]["top-body"], "top", "", "body");
                panelDisplayProcess.call(this, this.$["panel"]["top-right-body"], "top", "right", "body");

                panelDisplayProcess.call(this, this.$["panel"]["aside-body"], "", "aside", "body");
                panelDisplayProcess.call(this, this.$["panel"]["left-body"], "", "left", "body");
                panelDisplayProcess.call(this, this.$["panel"]["body"], "", "", "body");
                panelDisplayProcess.call(this, this.$["panel"]["right-body"], "", "right", "body");

                panelDisplayProcess.call(this, this.$["panel"]["bottom-aside-body"], "bottom", "aside", "body");
                panelDisplayProcess.call(this, this.$["panel"]["bottom-left-body"], "bottom", "left", "body");
                panelDisplayProcess.call(this, this.$["panel"]["bottom-body"], "bottom", "", "body");
                panelDisplayProcess.call(this, this.$["panel"]["bottom-right-body"], "bottom", "right", "body");

                scrollerDisplayProcess.call(this, this.$["scroller"]["vertical"], verticalScrollerWidth, horizontalScrollerHeight, "vertical");
                scrollerDisplayProcess.call(this, this.$["scroller"]["horizontal"], verticalScrollerWidth, horizontalScrollerHeight, "horizontal");
                scrollerDisplayProcess.call(this, this.$["scroller"]["corner"], verticalScrollerWidth, horizontalScrollerHeight, "corner");

                panelDisplayProcess.call(this, this.$["container"]["page"], "", "", "page");

                // 각 패널의 사이즈 결정
                /// 다른 패널의 사이즈 정보가 필요한 경우 여기서 정의해주고 사용함.
                this.xvar.bodyHeight = this.$.panel["body"].height();
                this.xvar.bodyWidth = this.$.panel["body"].width();
                // scrollContentWidth 는 grid-header repaint에서 결정합니다. 까먹지 맙시다. > this.xvar.scrollContentWidth

                return true;
            };
            var sortColumns = function sortColumns(_sortInfo) {
                GRID.header.repaint.call(this);

                if (U.isFunction(this.config.remoteSort)) {
                    var that = { sortInfo: [] };
                    for (var k in _sortInfo) {
                        that.sortInfo.push({
                            key: k,
                            orderBy: _sortInfo[k].orderBy,
                            seq: _sortInfo[k].seq
                        });
                    }
                    that.sortInfo.sort(function (a, b) {
                        return a.seq > b.seq;
                    });
                    this.config.remoteSort.call(that, that);
                } else {
                    if (this.config.body.grouping) {
                        this.list = GRID.data.initData.call(this, GRID.data.sort.call(this, _sortInfo, GRID.data.clearGroupingData.call(this, this.list)));
                    } else {
                        this.list = GRID.data.sort.call(this, _sortInfo, GRID.data.clearGroupingData.call(this, this.list), { resetLineNumber: true });
                    }
                    GRID.body.repaint.call(this, true);
                    GRID.scroller.resize.call(this);
                }
            };
            /// private end

            /**
            /**
             * Preferences of grid UI
             * @method ax5grid.setConfig
             * @param {Object} _config - 클래스 속성값
             * @param {Element} _config.target
             * @param {Number} [_config.frozenColumnIndex=0]
             * @param {Number} [_config.frozenRowIndex=0]
             * @param {Boolean} [_config.showLineNumber=false]
             * @param {Boolean} [_config.showRowSelector=false]
             * @param {Boolean} [_config.multipleSelect=true]
             * @param {Number} [_config.columnMinWidth=100]
             * @param {Number} [_config.lineNumberColumnWidth=30]
             * @param {Number} [_config.rowSelectorColumnWidth=25]
             * @param {Boolean} [_config.sortable=false]
             * @param {Boolean} [_config.multiSort=false]
             * @param {Function} [_config.remoteSort=false]
             * @param {Boolean} [_config.virtualScrollY=true] - 세로축 가상스크롤 처리여부
             * @param {Boolean} [_config.virtualScrollX=true] - 가로축 가상스크롤 처리여부
             * @param {Object} [_config.header]
             * @param {Object} [_config.header.selector=true] - 헤더 checkbox 표시여부
             * @param {String} [_config.header.align]
             * @param {Number} [_config.header.columnHeight=25]
             * @param {Number} [_config.header.columnPadding=3]
             * @param {Number} [_config.header.columnBorderWidth=1]
             * @param {Object} [_config.body]
             * @param {Function} [_config.body.onClick]
             * @param {Function} [_config.body.onDBLClick]
             * @param {Function} [_config.body.onDataChanged]
             * @param {String|Array} [_config.body.mergeCells=false] -
             * @param {String} [_config.body.align]
             * @param {Number} [_config.body.columnHeight=25]
             * @param {Number} [_config.body.columnPadding=3]
             * @param {Number} [_config.body.columnBorderWidth=1]
             * @param {Object} [_config.body.grouping]
             * @param {Array} [_config.body.grouping.by] - list grouping keys
             * @param {Array} [_config.body.grouping.columns] - list grouping columns
             * @param {(String|Function)} [_config.body.trStyleClass]
             *
             * @param {Object} [_config.page]
             * @param {Number} [_config.page.height=25]
             * @param {Boolean} [_config.page.display=true] - grid page display
             * @param {Boolean} [_config.page.statusDisplay=true] - grid status display
             * @param {Number} [_config.page.navigationItemCount=5]
             * @param {Object} [_config.scroller]
             * @param {Number} [_config.scroller.size=15]
             * @param {Number} [_config.scroller.barMinSize=15]
             * @param {Number} [_config.scroller.trackPadding=4]
             * @param {Object} [_config.columnKeys]
             * @param {String} [_config.columnKeys.selected="_SELECTED"]
             * @param {Object[]} _config.columns
             * @param {String} _config.columns[].key
             * @param {String} _config.columns[].label
             * @param {Number} _config.columns[].width
             * @param {(String|Function)} _config.columns[].styleClass
             * @param {(String|Function)} _config.columns[].headerStyleClass
             * @param {Boolean} _config.columns[].enableFilter
             * @param {Boolean} _config.columns[].sortable
             * @param {String} _config.columns[].align
             * @param {(String|Function)} _config.columns[].formatter
             * @param {Object} _config.columns[].editor
             * @param {String} _config.columns[].editor.type - text,number,money,date
             * @param {Object} _config.columns[].editor.config
             * @param {Array} _config.columns[].editor.updateWith
             * @param {Function} _config.columns[].editor.disabled - disable editor
             * @param {Boolean} [_config.columns[].multiLine=false]
             * @param {Object} [_config.tree]
             * @param {Boolean} [_config.tree.use=false] - Whether tree-type data is used
             * @param {Number} [_config.tree.hashDigit=8]
             * @param {Number} [_config.tree.indentWidth=10]
             * @param {Number} [_config.tree.arrowWidth=15]
             * @param {Number} [_config.tree.iconWidth=18]
             * @param {Object} [_config.tree.icons]
             * @param {String} [_config.tree.icons.openedArrow='▾']
             * @param {String} [_config.tree.icons.collapsedArrow='▸']
             * @param {String} [_config.tree.icons.groupIcon='⊚']
             * @param {String} [_config.tree.icons.collapsedGroupIcon='⊚']
             * @param {String} [_config.tree.icons.itemIcon='⊙']
             * @param {Object} [_config.tree.columnKeys]
             * @param {String} [_config.tree.columnKeys.parentKey="pid"]
             * @param {String} [_config.tree.columnKeys.selfKey="id"]
             * @param {String} [_config.tree.columnKeys.collapse="collapse"]
             * @param {String} [_config.tree.columnKeys.hidden="hidden"]
             * @param {String} [_config.tree.columnKeys.parentHash="__hp__"]
             * @param {String} [_config.tree.columnKeys.selfHash="__hs__"]
             * @param {String} [_config.tree.columnKeys.children="__children__"]
             * @param {String} [_config.tree.columnKeys.depth="__depth__"]
             * @returns {ax5grid}
             * @example
             * ```js
             * var firstGrid = new ax5.ui.grid();
             *
             * ax5.ui.grid.formatter["myType"] = function () {
             *     return "myType" + (this.value || "");
             * };
             * ax5.ui.grid.formatter["capital"] = function(){
             *     return (''+this.value).toUpperCase();
             * };
             *
             * ax5.ui.grid.collector["myType"] = function () {
             *     return "myType" + (this.value || "");
             * };
             *
             * var sampleData = [
             *     {a: "A", b: "A01", price: 1000, amount: 12, cost: 12000, saleDt: "2016-08-29", customer: "장기영", saleType: "A"},
             *     {companyJson: {"대표자명":"abcd"}, a: "A", b: "B01", price: 1100, amount: 11, cost: 12100, saleDt: "2016-08-28", customer: "장서우", saleType: "B"},
             *     {companyJson: {"대표자명":"abcd"}, a: "A", b: "C01", price: 1200, amount: 10, cost: 12000, saleDt: "2016-08-27", customer: "이영희", saleType: "A"},
             *     {companyJson: {"대표자명":"위세라"}, a: "A", b: "A01", price: 1300, amount: 8, cost: 10400, saleDt: "2016-08-25", customer: "황인서", saleType: "C"},
             *     {companyJson: {"대표자명":"abcd"}, a: "A", b: "B01", price: 1400, amount: 5, cost: 7000, saleDt: "2016-08-29", customer: "황세진", saleType: "D"},
             *     {companyJson: {"대표자명":"abcd"}, a: "A", b: "A01", price: 1500, amount: 2, cost: 3000, saleDt: "2016-08-26", customer: "이서연", saleType: "A"}
             * ];
             *
             * var gridView = {
             *     initView: function () {
             *         firstGrid.setConfig({
             *             target: $('[data-ax5grid="first-grid"]'),
             *             columns: [
             *                 {
             *                     key: "companyJson['대표자명']",
             *                     label: "필드A",
             *                     width: 80,
             *                     styleClass: function () {
             *                         return "ABC";
             *                     },
             *                     enableFilter: true,
             *                     align: "center",
             *                     editor: {type:"text"}
             *                 },
             *                 {key: "b", label: "필드B", align: "center"},
             *                 {
             *                     key: undefined, label: "필드C", columns: [
             *                         {key: "price", label: "단가", formatter: "money", align: "right"},
             *                         {key: "amount", label: "수량", formatter: "money", align: "right"},
             *                         {key: "cost", label: "금액", align: "right", formatter: "money"}
             *                     ]
             *                 },
             *                 {key: "saleDt", label: "판매일자", align: "center"},
             *                 {key: "customer", label: "고객명"},
             *                 {key: "saleType", label: "판매타입"}
             *             ]
             *         });
             *         return this;
             *     },
             *     setData: function (_pageNo) {
             *         firstGrid.setData(sampleData);
             *         return this;
             *     }
             * };
             *
             * // onClick, onDBLClick, onDataChanged
             * firstGrid.setConfig({
             *      target: $('[data-ax5grid="first-grid"]'),
             *      columns: [...],
             *      body: {
             *          onClick: function(){
             *              console.log(this);
             *          },
             *          onDBLClick: function(){
             *              console.log(this);
             *              // If the column does not have an editor attribute, an event is raised.
             *          },
             *          onDataChanged: function(){
             *              console.log(this);
             *              // If change Data
             *          }
             *      }
             * });
             * ```
             */
            this.init = function (_config) {
                cfg = jQuery.extend(true, {}, cfg, _config);
                if (!cfg.target) {
                    console.log(ax5.info.getError("ax5grid", "401", "init"));
                    return this;
                }

                // 그리드의 이벤트 정의 구간
                this.onStateChanged = cfg.onStateChanged;
                this.onClick = cfg.onClick;
                //this.onDblClick = cfg.onDblClick;
                this.onLoad = cfg.onLoad;
                this.onDataChanged = cfg.body.onDataChanged;
                // todo event에 대한 추가 정의 필요

                this.$target = jQuery(cfg.target);

                // target attribute data
                (function (data) {
                    if (U.isObject(data) && !data.error) {
                        cfg = jQuery.extend(true, cfg, data);
                    }
                }).call(this, U.parseJson(this.$target.attr("data-ax5grid-config"), true));

                var grid = this.config = cfg;

                if (!this.config.height) {
                    this.config._height = this.$target.height();
                }

                if (!this.id) this.id = this.$target.data("data-ax5grid-id");
                if (!this.id) {
                    //this.id = 'ax5grid-' + ax5.getGuid();
                    this.id = 'ax5grid-' + this.instanceId;
                    this.$target.data("data-ax5grid-id", grid.id);
                }

                GRID.data.init.call(this);

                if (this.config.tree.use) {
                    // 트리라면
                    this.sortInfo = {};
                    this.sortInfo[this.config.tree.columnKeys.selfHash] = { orderBy: "asc", seq: 0, fixed: true };
                }

                ///========
                // 그리드를 그리기 위한 가장 기초적인 작업 뼈대와 틀을 준비합니다. 이 메소드는 초기화 시 한번만 호출 되게 됩니다.
                initGrid.call(this);

                // columns데이터를 분석하여 미리 처리해야하는 데이터를 정리합니다.
                initColumns.call(this, grid.columns);
                resetColGroupWidth.call(this);

                // footSum 데이터를 분석하여 미리 처리해야 하는 데이터를 정리
                if (grid.footSum) initFootSum.call(this, grid.footSum);

                // bodyGrouping 데이터를 분석하여 미리 처리해야 하는 데이터를 정리
                if (grid.body.grouping) initBodyGroup.call(this, grid.body.grouping);

                // 그리드의 각 요소의 크기를 맞춤니다.
                alignGrid.call(this, true);

                // columns의 데이터로 header데이터를 만들고
                GRID.header.init.call(this);
                // header를 출력합니다.
                GRID.header.repaint.call(this);

                // columns의 데이터로 body데이터를 만들고
                GRID.body.init.call(this);
                // body를 출력합니다.
                GRID.body.repaint.call(this);

                // scroller
                GRID.scroller.init.call(this);
                GRID.scroller.resize.call(this);

                jQuery(window).bind("resize.ax5grid-" + this.id, function () {
                    alignGrid.call(self);
                    GRID.scroller.resize.call(self);
                    GRID.body.repaint.call(self); // window resize시 repaint 함수 호출
                });

                jQuery(document.body).on("click.ax5grid-" + this.id, function (e) {
                    var isPickerClick = false,
                        target = U.findParentNode(e.target, function (_target) {
                        if (isPickerClick = _target.getAttribute("data-ax5grid-inline-edit-picker")) {
                            return true;
                        }
                        return _target.getAttribute("data-ax5grid-container") === "root";
                    });

                    if (target && target.getAttribute("data-ax5grid-instance") === this.id) {
                        self.focused = true;
                    } else {
                        self.focused = false;
                        GRID.body.blur.call(this);
                    }
                }.bind(this));

                jQuery(window).on("keydown.ax5grid-" + this.instanceId, function (e) {
                    if (self.focused) {
                        if (self.isInlineEditing) {

                            if (e.which == ax5.info.eventKeys.ESC) {
                                self.keyDown("ESC", e.originalEvent);
                            } else if (e.which == ax5.info.eventKeys.RETURN) {
                                self.keyDown("RETURN", e.originalEvent);
                            } else if (e.which == ax5.info.eventKeys.TAB) {
                                self.keyDown("TAB", e.originalEvent);
                                U.stopEvent(e);
                            } else if (e.which == ax5.info.eventKeys.UP) {
                                self.keyDown("RETURN", { shiftKey: true });
                            } else if (e.which == ax5.info.eventKeys.DOWN) {
                                self.keyDown("RETURN", {});
                            }
                        } else {

                            if (e.metaKey || e.ctrlKey) {
                                if (e.which == 67) {
                                    // c
                                    self.copySelect();
                                }
                            } else {
                                if (ctrlKeys[e.which]) {
                                    self.keyDown(ctrlKeys[e.which], e.originalEvent); // 키다운 이벤트 호출
                                    U.stopEvent(e);
                                } else if (e.which == ax5.info.eventKeys.ESC) {
                                    if (self.focused) {
                                        GRID.body.blur.call(self);
                                    }
                                } else if (e.which == ax5.info.eventKeys.RETURN || e.which == ax5.info.eventKeys.SPACE) {
                                    self.keyDown("RETURN", e.originalEvent);
                                } else if (e.which == ax5.info.eventKeys.TAB) {
                                    //self.keyDown("RETURN", e.originalEvent);
                                    U.stopEvent(e);
                                } else if (Object.keys(self.focusedColumn).length) {
                                    /*
                                    self.keyDown("INLINE_EDIT", e.originalEvent);
                                    */
                                }
                            }
                        }
                    }
                });

                jQuery(window).on("keyup.ax5grid-" + this.instanceId, function (e) {
                    if (self.focused) {
                        if (self.isInlineEditing) {} else {
                            if (e.metaKey || e.ctrlKey) {} else {
                                if (ctrlKeys[e.which]) {} else if (e.which == ax5.info.eventKeys.ESC) {} else if (e.which == ax5.info.eventKeys.RETURN || e.which == ax5.info.eventKeys.SPACE) {} else if (e.which == ax5.info.eventKeys.TAB) {} else if (Object.keys(self.focusedColumn).length) {
                                    self.keyDown("INLINE_EDIT", e.originalEvent);
                                }
                            }
                        }
                    }
                });

                // 그리드 레이아웃이 모든 준비를 마친시점에 onLoad존재 여부를 확인하고 호출하여 줍니다.
                setTimeout(function () {
                    if (this.onLoad) {
                        this.onLoad.call({
                            self: this
                        });
                    }
                }.bind(this));
                return this;
            };

            /**
             * align grid size
             * @method ax5grid.align
             * @returns {ax5grid}
             * @example
             * ```js
             * ax5Grid.repaint();
             * ```
             */
            this.align = function () {
                if (alignGrid.call(this)) {
                    GRID.body.repaint.call(this);
                    GRID.scroller.resize.call(this);
                }
                return this;
            };

            /**
             * repaint grid
             * @method ax5grid.repaint
             * @return {ax5grid}
             * @example
             * ```js
             * ax5Grid.repaint();
             * ```
             */
            this.repaint = function () {
                GRID.header.repaint.call(this);
                GRID.body.repaint.call(this, true); // 강제로 다시 그리기
                GRID.scroller.resize.call(this);
                return this;
            };

            /**
             * @method ax5grid.keyDown
             * @param {String} _keyName
             * @param {Event|Object} _data
             * @return {ax5grid}
             */
            this.keyDown = function () {
                var processor = {
                    "KEY_UP": function KEY_UP() {
                        GRID.body.moveFocus.call(this, "UP");
                    },
                    "KEY_DOWN": function KEY_DOWN() {
                        GRID.body.moveFocus.call(this, "DOWN");
                    },
                    "KEY_LEFT": function KEY_LEFT() {
                        GRID.body.moveFocus.call(this, "LEFT");
                    },
                    "KEY_RIGHT": function KEY_RIGHT() {
                        GRID.body.moveFocus.call(this, "RIGHT");
                    },
                    "KEY_HOME": function KEY_HOME() {
                        GRID.body.moveFocus.call(this, "HOME");
                    },
                    "KEY_END": function KEY_END() {
                        GRID.body.moveFocus.call(this, "END");
                    },
                    "INLINE_EDIT": function INLINE_EDIT(_e) {
                        GRID.body.inlineEdit.active.call(this, this.focusedColumn, _e);
                        if (!/[0-9a-zA-Z]/.test(_e.key)) {
                            U.stopEvent(_e);
                        }
                    },
                    "ESC": function ESC(_e) {
                        GRID.body.inlineEdit.keydown.call(this, "ESC");
                    },
                    "RETURN": function RETURN(_e) {
                        var activeEditLength = 0;
                        for (var columnKey in this.inlineEditing) {
                            activeEditLength++;

                            if (!GRID.body.inlineEdit.keydown.call(this, "RETURN", columnKey)) {
                                return false;
                                U.stopEvent(_e);
                            }
                            // next focus
                            if (activeEditLength == 1) {
                                if (GRID.body.moveFocus.call(this, _e.shiftKey ? "UP" : "DOWN")) {
                                    GRID.body.inlineEdit.keydown.call(this, "RETURN");
                                }
                            }
                        }
                        if (activeEditLength == 0) {
                            GRID.body.inlineEdit.keydown.call(this, "RETURN");
                            U.stopEvent(_e);
                        } else {}
                    },
                    "TAB": function TAB(_e) {

                        var activeEditLength = 0;
                        for (var columnKey in this.inlineEditing) {
                            activeEditLength++;

                            GRID.body.inlineEdit.keydown.call(this, "RETURN", columnKey, { moveFocus: true });
                            // next focus
                            if (activeEditLength == 1) {
                                if (GRID.body.moveFocus.call(this, _e.shiftKey ? "LEFT" : "RIGHT")) {
                                    GRID.body.inlineEdit.keydown.call(this, "RETURN", undefined, { moveFocus: true });
                                }
                            }
                        }
                    }
                };
                return function (_act, _data) {
                    if (_act in processor) processor[_act].call(this, _data);
                    return this;
                };
            }();

            /**
             * @method ax5grid.copySelect
             * @returns {Boolean} copysuccess
             */
            this.copySelect = function () {
                var copysuccess = void 0,
                    $clipBoard = this.$["form"]["clipboard"],
                    copyTextArray = [],
                    copyText = "",
                    _rowIndex = void 0,
                    _colIndex = void 0,
                    _dindex = void 0,
                    _di = 0;

                for (var c in this.selectedColumn) {
                    var _column = this.selectedColumn[c];

                    if (_column) {
                        if (typeof _dindex === "undefined") {
                            _dindex = _column.dindex;
                            _rowIndex = _column.rowIndex;
                            _colIndex = _column.rowIndex;
                        }

                        if (_dindex != _column.dindex || _rowIndex != _column.rowIndex) {
                            _di++;
                        }

                        if (!copyTextArray[_di]) {
                            copyTextArray[_di] = [];
                        }
                        var originalColumn = this.bodyRowMap[_column.rowIndex + "_" + _column.colIndex];
                        if (originalColumn) {
                            if (this.list[_column.dindex].__isGrouping) {
                                copyTextArray[_di].push(this.list[_column.dindex][_column.colIndex]);
                            } else {
                                copyTextArray[_di].push(this.list[_column.dindex][originalColumn.key]);
                            }
                        } else {
                            copyTextArray[_di].push("");
                        }

                        _dindex = _column.dindex;
                        _rowIndex = _column.rowIndex;
                    }
                }

                copyTextArray.forEach(function (r) {
                    copyText += r.join('\t') + "\n";
                });

                $clipBoard.get(0).innerText = copyText;
                $clipBoard.select();

                try {
                    copysuccess = document.execCommand("copy");
                } catch (e) {
                    copysuccess = false;
                }
                return copysuccess;
            };

            /**
             * @method ax5grid.setData
             * @param {Array} _data
             * @returns {ax5grid}
             * @example
             * ```js
             * ax5Grid.setData({
             *  list: [],
             *  page: {
             *      currentPage: 0,
             *      pageSize: 50,
             *      totalElements: 500,
             *      totalPages: 100
             *  }
             * });
             *
             * // onlyList
             * ax5Grid.setData([]);
             * ```
             */
            this.setData = function (_data) {
                var isFirstPaint = typeof this.xvar.paintStartRowIndex === "undefined";

                GRID.data.set.call(this, _data);
                GRID.body.repaint.call(this);
                if (!isFirstPaint) GRID.body.scrollTo.call(this, { top: 0 });

                // 가로/세로 스크롤바 show/hide 처리
                alignGrid.call(this);
                // 가로세로 스크롤바의 크기 재 계산.
                GRID.scroller.resize.call(this);
                GRID.page.navigationUpdate.call(this);

                isFirstPaint = null;
                return this;
            };

            /**
             * @method ax5grid.getList
             * @param {String} _type - selected|modified|deleted
             * @returns {Array}
             * @example
             * ```js
             * ax5Grid.getList();
             * ax5Grid.getList("selected");
             * ax5Grid.getList("modified");
             * ax5Grid.getList("deleted");
             * ```
             */
            this.getList = function (_type) {
                return GRID.data.getList.call(this, _type);
            };

            /**
             * @method ax5grid.setHeight
             * @param {Number} _height
             * @returns {ax5grid}
             * @example
             * ```js
             * ax5Grid.setHeight(height);
             * ```
             */
            this.setHeight = function (_height) {
                //console.log(this.$target);
                if (_height == "100%") {
                    _height = this.$target.offsetParent().innerHeight();
                }
                this.$target.css({ height: _height });
                this.$["container"]["root"].css({ height: _height });
                alignGrid.call(this);
                GRID.body.repaint.call(this, "reset");
                GRID.scroller.resize.call(this);
                return this;
            };

            /**
             * @method ax5grid.addRow
             * @param {Object} _row
             * @param {Number|String} [_dindex=last]
             * @param {Object} [_options] - options of addRow
             * @param {Boolean} [_options.sort] - sortData
             * @param {Number|String} [_options.focus] - HOME|END|[dindex]
             * @returns {ax5grid}
             * @example
             * ```js
             * ax5Grid.addRow($.extend({}, {...}), "first");
             * ax5Grid.addRow($.extend({}, {...}), "last", {focus: "END"});
             * ax5Grid.addRow($.extend({}, {...}), "last", {focus: "HOME"});
             * ax5Grid.addRow($.extend({}, {...}), "last", {focus: 10});
             * ```
             */
            this.addRow = function (_row, _dindex, _options) {
                var _this = this;

                GRID.data.add.call(this, _row, _dindex, _options);
                alignGrid.call(this);
                GRID.body.repaint.call(this, "reset");

                if (_options && _options.focus) {
                    //GRID.body.moveFocus.call(this, (this.config.body.grouping) ? "START" : "END");
                    setTimeout(function () {
                        GRID.body.moveFocus.call(_this, _options.focus);
                    }, 1);
                } else {
                    GRID.scroller.resize.call(this);
                }

                return this;
            };

            /**
             * @method ax5grid.appendToList
             * @param _list
             * @returns {ax5grid}
             * @example
             * ```js
             * ax5Grid.appendToList([{},{},{}]);
             * ax5Grid.appendToList([{},{},{}]);
             * ```
             */
            this.appendToList = function (_list) {
                GRID.data.append.call(this, _list, function () {
                    alignGrid.call(this);
                    GRID.body.repaint.call(this);
                    GRID.scroller.resize.call(this);
                }.bind(this));
                return this;
            };

            /**
             * @method ax5grid.removeRow
             * @param {Number|String} [_dindex=last]
             * @returns {ax5grid}
             * @example
             * ```js
             * ax5Grid.removeRow();
             * ax5Grid.removeRow("first");
             * ax5Grid.removeRow("last");
             * ax5Grid.removeRow(1);
             * ax5Grid.removeRow("selected");
             * ```
             */
            this.removeRow = function (_dindex) {
                GRID.data.remove.call(this, _dindex);
                alignGrid.call(this);
                GRID.body.repaint.call(this, "reset");
                GRID.body.moveFocus.call(this, this.config.body.grouping ? "START" : "END");
                GRID.scroller.resize.call(this);
                return this;
            };

            /**
             * @method ax5grid.updateRow
             * @param {Object} _row
             * @param {Number} _dindex
             * @returns {ax5grid}
             * @example
             * ```js
             * firstGrid.updateRow({price: 100, amount: 100, cost: 10000}, 1);
             * ```
             */
            this.updateRow = function (_row, _dindex) {
                GRID.data.update.call(this, _row, _dindex);
                // todo : mergeCells 옵션에 따라 예외처리

                GRID.body.repaintRow.call(this, _dindex);
                return this;
            };

            /**
             * @method ax5grid.updateChildRows
             * @param {Number} _dindex
             * @param {Object} _updateData
             * @param {Object} [_options]
             * @param {Function} [_options.filter]
             * @returns {ax5grid}
             * @example
             * ```js
             * onDataChanged: function () {
             *      this.self.updateChildRows(this.dindex, {isChecked: this.item.isChecked});
             * }
             *
             * onDataChanged: function () {
             *      this.self.updateChildRows(this.dindex, {isChecked: this.item.isChecked}, {filter: function(){
             *          return this.item.type == "A";
             *      });
             * }
             * ```
             */
            this.updateChildRows = function (_dindex, _updateData, _options) {
                GRID.data.updateChild.call(this, _dindex, _updateData, _options);
                this.xvar.paintStartRowIndex = undefined;
                this.xvar.paintStartColumnIndex = undefined;
                GRID.body.repaint.call(this);
                return this;
            };

            /**
             * @method ax5grid.deleteRow
             * @param {Number|String} _dindex
             * @returns {ax5grid}
             * @example
             * ```js
             * ax5Grid.deleteRow("first");
             * ax5Grid.deleteRow("last");
             * ax5Grid.deleteRow(1);
             * ax5Grid.deleteRow("selected");
             * ```
             */
            this.deleteRow = function (_dindex) {
                GRID.data.deleteRow.call(this, _dindex);
                alignGrid.call(this);
                GRID.body.repaint.call(this, "reset");
                // 삭제시엔 포커스 ?
                // GRID.body.moveFocus.call(this, (this.config.body.grouping) ? "START" : "END");
                GRID.scroller.resize.call(this);
                return this;
            };

            /**
             * @method ax5grid.setValue
             * @param _dindex
             * @param _key
             * @param _value
             * @returns {ax5grid}
             * @example
             * ```js
             * ax5Grid.setValue(0, "price", 100);
             * ```
             */
            this.setValue = function (_dindex, _key, _value) {
                // getPanelname;
                // let doindex = (typeof _doindex === "undefined") ? _dindex : _doindex;
                // setValue를 doindex로 처리하는 상황이 아직 발생전으므로 선언만 하고 넘어감
                var doindex = void 0;

                if (GRID.data.setValue.call(this, _dindex, doindex, _key, _value)) {
                    var repaintCell = function repaintCell(_panelName, _rows, __dindex, __doindex, __key, __value) {
                        for (var r = 0, rl = _rows.length; r < rl; r++) {
                            for (var c = 0, cl = _rows[r].cols.length; c < cl; c++) {
                                if (_rows[r].cols[c].key == __key) {
                                    if (this.xvar.frozenRowIndex > __dindex) {
                                        GRID.body.repaintCell.call(this, "top-" + _panelName, __dindex, __doindex, r, c, __value);
                                    } else {
                                        GRID.body.repaintCell.call(this, _panelName + "-scroll", __dindex, __doindex, r, c, __value);
                                    }
                                }
                            }
                        }
                    };

                    repaintCell.call(this, "left-body", this.leftBodyRowData.rows, _dindex, doindex, _key, _value);
                    repaintCell.call(this, "body", this.bodyRowData.rows, _dindex, doindex, _key, _value);
                }

                return this;
            };

            /**
             * @method ax5grid.addColumn
             * @param {Object} _column
             * @param {Number|String} [_cindex=last]
             * @returns {ax5grid}
             */
            this.addColumn = function () {
                var processor = {
                    "first": function first(_column) {
                        this.config.columns = [].concat(_column).concat(this.config.columns);
                    },
                    "last": function last(_column) {
                        this.config.columns = this.config.columns.concat([].concat(_column));
                    }
                };

                return function (_column, _cindex) {
                    if (typeof _column === "undefined") throw '_column must not be null';
                    if (typeof _cindex === "undefined") _cindex = "last";
                    if (_cindex in processor) {
                        processor[_cindex].call(this, _column);
                    } else {
                        if (!U.isNumber(_cindex)) {
                            throw 'invalid argument _cindex';
                        }
                        if (U.isArray(_column)) {
                            for (var _i = 0, _l = _column.length; _i < _l; _i++) {
                                this.config.columns.splice(_cindex + _i, 0, _column[_i]);
                            }
                        } else {
                            this.config.columns.splice(_cindex, 0, _column);
                        }
                    }
                    onResetColumns.call(this); // 컬럼이 변경되었을 때.
                    return this;
                };
            }();

            /**
             * @method ax5grid.removeColumn
             * @param {Number|String} [_cindex=last]
             * @returns {ax5grid}
             */
            this.removeColumn = function () {
                var processor = {
                    "first": function first(_cindex) {
                        this.config.columns.splice(_cindex, 1);
                    },
                    "last": function last() {
                        this.config.columns.splice(this.config.columns.length - 1, 1);
                    }
                };
                return function (_cindex) {
                    if (typeof _cindex === "undefined") _cindex = "last";
                    if (_cindex in processor) {
                        processor[_cindex].call(this, _cindex);
                    } else {
                        if (!U.isNumber(_cindex)) {
                            throw 'invalid argument _cindex';
                        }
                        //
                        this.config.columns.splice(_cindex, 1);
                    }
                    onResetColumns.call(this); // 컬럼이 변경되었을 때.
                    return this;
                };
            }();

            /**
             * @method ax5grid.updateColumn
             * @param {Object} _column
             * @param {Number} _cindex
             * @returns {ax5grid}
             */
            this.updateColumn = function (_column, _cindex) {
                if (!U.isNumber(_cindex)) {
                    throw 'invalid argument _cindex';
                }
                //
                this.config.columns.splice(_cindex, 1, _column);
                onResetColumns.call(this); // 컬럼이 변경되었을 때.
                return this;
            };

            /**
             * @method ax5grid.setColumnWidth
             * @param {Number} _width
             * @param {Number} _cindex
             * @returns {ax5grid}
             */
            this.setColumnWidth = function (_width, _cindex) {
                this.colGroup[this.xvar.columnResizerIndex]._width = _width;
                this.needToPaintSum = true;

                // 컬럼너비 변경사항 적용.
                GRID.header.repaint.call(this);
                GRID.body.repaint.call(this, true);
                GRID.scroller.resize.call(this);

                alignGrid.call(this);
                return this;
            };

            /**
             * @method ax5grid.getColumnSortInfo
             * @returns {Object} sortInfo
             */
            this.getColumnSortInfo = function () {
                var that = { sortInfo: [] };
                for (var k in this.sortInfo) {
                    that.sortInfo.push({
                        key: k,
                        orderBy: this.sortInfo[k].orderBy,
                        seq: this.sortInfo[k].seq
                    });
                }
                that.sortInfo.sort(function (a, b) {
                    return a.seq > b.seq;
                });
                return that.sortInfo;
            };

            /**
             * @method ax5grid.setColumnSort
             * @param {Object} _sortInfo
             * @param {Object} _sortInfo.key
             * @param {Number} _sortInfo.key.seq - seq of sortOrder
             * @param {String} _sortInfo.key.orderBy - "desc"|"asc"
             * @returns {ax5grid}
             * @example
             * ```js
             * ax5grid.setColumnSort({a:{seq:0, orderBy:"desc"}, b:{seq:1, orderBy:"asc"}});
             * ```
             */
            this.setColumnSort = function (_sortInfo) {
                if (typeof _sortInfo !== "undefined") {
                    this.sortInfo = _sortInfo;
                    GRID.header.applySortStatus.call(this, _sortInfo);
                }

                sortColumns.call(this, _sortInfo || this.sortInfo);
                return this;
            };

            /**
             * @method ax5grid.select
             * @param {Number|Object} _selectObject
             * @param {Number} _selectObject.index - index of row
             * @param {Number} _selectObject.rowIndex - rowIndex of columns
             * @param {Number} _selectObject.conIndex - colIndex of columns
             * @param {Object} _options
             * @param {Boolean} _options.selectedClear
             * @param {Boolean} _options.selected
             * @returns {ax5grid}
             * @example
             * ```js
             * firstGrid.select(0);
             * firstGrid.select(0, {selected: true});
             * firstGrid.select(0, {selected: false});
             * firstGrid.select(0, {selectedClear: true});
             * ```
             */
            this.select = function (_selectObject, _options) {
                if (U.isNumber(_selectObject)) {
                    var _dindex2 = _selectObject;

                    if (!this.config.multipleSelect) {
                        this.clearSelect();
                    } else {
                        if (_options && _options.selectedClear) {
                            this.clearSelect();
                        }
                    }

                    GRID.data.select.call(this, _dindex2, undefined, _options && _options.selected);
                    GRID.body.updateRowState.call(this, ["selected"], _dindex2, undefined);
                }
                return this;
            };

            /**
             * @method firstGrid.clickBody
             * @param {Number} _dindex
             * @returns {ax5grid}
             */
            this.clickBody = function (_dindex) {
                GRID.body.click.call(this, _dindex);
                return this;
            };

            /**
             * @method firstGrid.DBLClickBody
             * @param {Number} _dindex
             * @returns {ax5grid}
             */
            this.DBLClickBody = function (_dindex) {
                GRID.body.dblClick.call(this, _dindex);
                return this;
            };

            /**
             * @method ax5grid.clearSelect
             * @returns {ax5grid}
             * @example
             * ```js
             * firstGrid.clearSelect();
             * ```
             */
            this.clearSelect = function () {
                GRID.body.updateRowState.call(this, ["selectedClear"]);
                GRID.data.clearSelect.call(this);
                return this;
            };

            /**
             * @method ax5grid.selectAll
             * @param {Object} _options
             * @param {Boolean} _options.selected
             * @param {Function} _options.filter
             * @returns {ax5grid}
             * @example
             * ```js
             * firstGrid.selectAll();
             * firstGrid.selectAll({selected: true});
             * firstGrid.selectAll({selected: false});
             * firstGrid.selectAll({filter: function(){
             *      return this["b"] == "A01";
             * });
             * firstGrid.selectAll({selected: true, filter: function(){
             *      return this["b"] == "A01";
             * });
             * ```
             */
            this.selectAll = function (_options) {
                GRID.data.selectAll.call(this, _options && _options.selected, _options);
                GRID.body.updateRowStateAll.call(this, ["selected"]);
                return this;
            };

            /**
             * @method ax5grid.exportExcel
             * @param {String} _fileName
             * @returns {ax5grid|String}
             * @example
             * ```js
             * firstGrid.exportExcel("grid-to-excel.xls");
             * console.log(firstGrid.exportExcel());
             * ```
             */
            this.exportExcel = function (_fileName) {
                var table = [];
                table.push('<table border="1">');
                table.push(GRID.header.getExcelString.call(this));
                table.push(GRID.body.getExcelString.call(this));
                table.push('</table>');

                if (typeof _fileName === "undefined") {
                    return table.join('');
                } else {
                    GRID.excel.export.call(this, [table.join('')], _fileName);
                }

                return this;
            };

            /**
             * @method ax5grid.focus
             * @param {String|Number} _pos - UP, DOWN, LEFT, RIGHT, HOME, END
             * @returns {ax5grid}
             * @example
             * ```js
             * firstGrid.focus("UP");
             * firstGrid.focus("DOWN");
             * firstGrid.focus("HOME");
             * firstGrid.focus("END");
             * ```
             */
            this.focus = function (_pos) {

                if (GRID.body.moveFocus.call(this, _pos)) {
                    var focusedColumn = void 0;
                    for (var c in this.focusedColumn) {
                        focusedColumn = jQuery.extend({}, this.focusedColumn[c], true);
                        break;
                    }
                    if (focusedColumn) {
                        this.select(focusedColumn.dindex, { selectedClear: true });
                    }
                } else {
                    if (typeof this.selectedDataIndexs[0] === "undefined") {
                        this.select(0);
                    } else {
                        var selectedIndex = this.selectedDataIndexs[0];

                        var processor = {
                            "UP": function UP() {
                                if (selectedIndex > 0) {
                                    this.select(selectedIndex - 1, { selectedClear: true });
                                    GRID.body.moveFocus.call(this, selectedIndex - 1);
                                }
                            },
                            "DOWN": function DOWN() {
                                if (selectedIndex < this.list.length - 1) {
                                    this.select(selectedIndex + 1, { selectedClear: true });
                                    GRID.body.moveFocus.call(this, selectedIndex + 1);
                                }
                            },
                            "HOME": function HOME() {
                                this.select(0, { selectedClear: true });
                                GRID.body.moveFocus.call(this, 0);
                            },
                            "END": function END() {
                                this.select(this.list.length - 1, { selectedClear: true });
                                GRID.body.moveFocus.call(this, this.list.length - 1);
                            }
                        };

                        if (_pos in processor) {
                            processor[_pos].call(this);
                        }
                    }
                }
                return this;
            };

            /**
             * @method ax5grid.destroy
             * @returns {null}
             */
            this.destroy = function () {
                var instanceId = this.instanceId;
                this.$target.empty();
                this.list = [];
                UI.grid_instance = ax5.util.filter(UI.grid_instance, function () {
                    return this.instanceId != instanceId;
                });
                return null;
            };

            // 클래스 생성자
            this.main = function () {
                UI.grid_instance = UI.grid_instance || [];
                UI.grid_instance.push(this);

                if (arguments && U.isObject(arguments[0])) {
                    this.setConfig(arguments[0]);
                }
            }.apply(this, arguments);
        };
    }());

    GRID = ax5.ui.grid;
})();

// todo : filter
// todo : column reorder
// todo : editor 필수값 속성 지정
// ax5.ui.grid.body
(function () {

    var GRID = ax5.ui.grid;

    var U = ax5.util;

    var columnSelect = {
        focusClear: function focusClear() {
            var self = this,
                _column = void 0;
            for (var c in self.focusedColumn) {
                _column = self.focusedColumn[c];
                if (_column) {
                    self.$.panel[_column.panelName].find('[data-ax5grid-tr-data-index="' + _column.dindex + '"]').find('[data-ax5grid-column-rowindex="' + _column.rowIndex + '"][data-ax5grid-column-colindex="' + _column.colIndex + '"]').removeAttr('data-ax5grid-column-focused');
                }
            }
            self.focusedColumn = {};
        },
        clear: function clear() {
            var self = this,
                _column = void 0;
            for (var c in self.selectedColumn) {
                _column = self.selectedColumn[c];
                if (_column) {
                    self.$.panel[_column.panelName].find('[data-ax5grid-tr-data-index="' + _column.dindex + '"]').find('[data-ax5grid-column-rowindex="' + _column.rowIndex + '"][data-ax5grid-column-colindex="' + _column.colIndex + '"]').removeAttr('data-ax5grid-column-selected');
                }
            }
            self.selectedColumn = {};
        },
        init: function init(column) {
            var self = this;
            if (this.isInlineEditing) {
                for (var editKey in this.inlineEditing) {
                    if (editKey == column.dindex + "_" + column.colIndex + "_" + column.rowIndex) {
                        return this;
                    }
                }
            }

            // focus
            columnSelect.focusClear.call(self);
            self.focusedColumn[column.dindex + "_" + column.colIndex + "_" + column.rowIndex] = {
                panelName: column.panelName,
                dindex: column.dindex,
                doindex: column.doindex,
                rowIndex: column.rowIndex,
                colIndex: column.colIndex,
                colspan: column.colspan
            };

            // select
            columnSelect.clear.call(self);
            self.xvar.selectedRange = {
                start: [column.dindex, column.rowIndex, column.colIndex, column.colspan - 1],
                end: null
            };
            self.selectedColumn[column.dindex + "_" + column.colIndex + "_" + column.rowIndex] = function (data) {
                if (data) {
                    return false;
                } else {
                    return {
                        panelName: column.panelName,
                        dindex: column.dindex,
                        doindex: column.doindex,
                        rowIndex: column.rowIndex,
                        colIndex: column.colIndex,
                        colspan: column.colspan
                    };
                }
            }(self.selectedColumn[column.dindex + "_" + column.colIndex + "_" + column.rowIndex]);

            this.$.panel[column.panelName].find('[data-ax5grid-tr-data-index="' + column.dindex + '"]').find('[data-ax5grid-column-rowindex="' + column.rowIndex + '"][data-ax5grid-column-colindex="' + column.colIndex + '"]').attr('data-ax5grid-column-focused', "true").attr('data-ax5grid-column-selected', "true");

            if (this.isInlineEditing) {
                GRID.body.inlineEdit.deActive.call(this, "RETURN");
            }
        },
        update: function update(column) {
            var self = this;
            var dindex = void 0,
                doindex = void 0,
                colIndex = void 0,
                rowIndex = void 0,
                trl = void 0;

            self.xvar.selectedRange["end"] = [column.dindex, column.rowIndex, column.colIndex, column.colspan - 1];
            columnSelect.clear.call(self);

            var range = {
                r: {
                    s: Math.min(self.xvar.selectedRange["start"][0], self.xvar.selectedRange["end"][0]),
                    e: Math.max(self.xvar.selectedRange["start"][0], self.xvar.selectedRange["end"][0])
                },
                c: {
                    s: Math.min(self.xvar.selectedRange["start"][2], self.xvar.selectedRange["end"][2]),
                    e: Math.max(self.xvar.selectedRange["start"][2] + self.xvar.selectedRange["start"][3], self.xvar.selectedRange["end"][2] + self.xvar.selectedRange["end"][3])
                }
            };

            dindex = range.r.s;
            for (; dindex <= range.r.e; dindex++) {

                trl = this.bodyRowTable.rows.length;
                rowIndex = 0;
                for (; rowIndex < trl; rowIndex++) {
                    colIndex = range.c.s;
                    for (; colIndex <= range.c.e; colIndex++) {
                        var _panels = [],
                            panelName = "";

                        if (self.xvar.frozenRowIndex > dindex) _panels.push("top");
                        if (self.xvar.frozenColumnIndex > colIndex) _panels.push("left");
                        _panels.push("body");
                        if (_panels[0] !== "top") _panels.push("scroll");
                        panelName = _panels.join("-");

                        self.selectedColumn[dindex + "_" + colIndex + "_" + rowIndex] = {
                            panelName: panelName,
                            dindex: dindex,
                            rowIndex: rowIndex,
                            colIndex: colIndex,
                            colspan: column.colspan
                        };

                        _panels = null;
                        panelName = null;
                    }
                }
            }
            dindex = null;
            doindex = null;
            colIndex = null;
            rowIndex = null;

            for (var c in self.selectedColumn) {
                var _column = self.selectedColumn[c];
                if (_column) {
                    self.$.panel[_column.panelName].find('[data-ax5grid-tr-data-index="' + _column.dindex + '"]').find('[data-ax5grid-column-rowindex="' + _column.rowIndex + '"][data-ax5grid-column-colindex="' + _column.colIndex + '"]').attr('data-ax5grid-column-selected', 'true');
                }
            }
        }
    };

    var columnSelector = {
        "on": function on(cell) {
            var self = this;

            if (this.inlineEditing[cell.dindex + "_" + cell.colIndex + "_" + cell.rowIndex]) {
                return;
            }

            columnSelect.init.call(self, cell);

            this.$["container"]["body"].on("mousemove.ax5grid-" + this.instanceId, '[data-ax5grid-column-attr="default"]', function (e) {
                if (this.getAttribute("data-ax5grid-column-rowIndex")) {
                    columnSelect.update.call(self, {
                        panelName: this.getAttribute("data-ax5grid-panel-name"),
                        dindex: Number(this.getAttribute("data-ax5grid-data-index")),
                        doindex: Number(this.getAttribute("data-ax5grid-data-o-index")),
                        rowIndex: Number(this.getAttribute("data-ax5grid-column-rowIndex")),
                        colIndex: Number(this.getAttribute("data-ax5grid-column-colIndex")),
                        colspan: Number(this.getAttribute("colspan"))
                    });
                    U.stopEvent(e);
                }
            }).on("mouseup.ax5grid-" + this.instanceId, function () {
                columnSelector.off.call(self);
            }).on("mouseleave.ax5grid-" + this.instanceId, function () {
                columnSelector.off.call(self);
            });

            jQuery(document.body).attr('unselectable', 'on').css('user-select', 'none').on('selectstart', false);
        },
        "off": function off() {

            this.$["container"]["body"].off("mousemove.ax5grid-" + this.instanceId).off("mouseup.ax5grid-" + this.instanceId).off("mouseleave.ax5grid-" + this.instanceId);

            jQuery(document.body).removeAttr('unselectable').css('user-select', 'auto').off('selectstart');
        }
    };

    var updateRowState = function updateRowState(_states, _dindex, _doindex, _data) {
        var self = this,
            cfg = this.config,
            processor = {
            "selected": function selected(_dindex, _doindex) {
                if (this.list[_doindex]) {
                    var i = this.$.livePanelKeys.length;
                    while (i--) {
                        this.$.panel[this.$.livePanelKeys[i]].find('[data-ax5grid-tr-data-index="' + _dindex + '"]').attr("data-ax5grid-selected", this.list[_doindex][cfg.columnKeys.selected]);
                    }
                }
            },
            "selectedClear": function selectedClear() {
                var di = this.list.length;
                var pi = void 0;

                if (!this.proxyList) {
                    while (di--) {
                        if (this.list[di][cfg.columnKeys.selected]) {
                            pi = this.$.livePanelKeys.length;
                            while (pi--) {
                                this.$.panel[this.$.livePanelKeys[pi]].find('[data-ax5grid-tr-data-index="' + di + '"]').attr("data-ax5grid-selected", false);
                            }
                        }
                        this.list[di][cfg.columnKeys.selected] = false;
                    }
                } else {
                    while (di--) {
                        this.list[di][cfg.columnKeys.selected] = false;
                    }
                    di = this.proxyList.length;
                    while (di--) {
                        if (this.list[doi][cfg.columnKeys.selected]) {
                            pi = this.$.livePanelKeys.length;
                            while (pi--) {
                                this.$.panel[this.$.livePanelKeys[pi]].find('[data-ax5grid-tr-data-index="' + di + '"]').attr("data-ax5grid-selected", false);
                            }
                        }

                        this.proxyList[di][cfg.columnKeys.selected] = false;
                        var doi = this.proxyList[di].__original_index__;
                    }
                }
            },
            "cellChecked": function cellChecked(_dindex, _doindex, _data) {
                var key = _data.key,
                    rowIndex = _data.rowIndex,
                    colIndex = _data.colIndex;

                var panelName = function () {
                    var _panels = [];
                    if (this.xvar.frozenRowIndex > _dindex) _panels.push("top");
                    if (this.xvar.frozenColumnIndex > colIndex) _panels.push("left");
                    _panels.push("body");
                    if (_panels[0] !== "top") _panels.push("scroll");
                    return _panels.join("-");
                }.call(this);

                this.$.panel[panelName].find('[data-ax5grid-tr-data-index="' + _dindex + '"]').find('[data-ax5grid-column-rowIndex="' + rowIndex + '"][data-ax5grid-column-colIndex="' + colIndex + '"]').find('[data-ax5grid-editor="checkbox"]').attr("data-ax5grid-checked", '' + _data.checked);
            }
        };

        if (typeof _doindex === "undefined") _doindex = _dindex;

        _states.forEach(function (_state) {
            if (!processor[_state]) throw 'invaild state name';
            processor[_state].call(self, _dindex, _doindex, _data);
        });
    };

    var updateRowStateAll = function updateRowStateAll(_states, _data) {
        var self = this,
            cfg = this.config,
            processor = {
            "selected": function selected(_dindex) {
                GRID.body.repaint.call(this, true);
            }
        };

        _states.forEach(function (_state) {
            if (!processor[_state]) throw 'invaild state name';
            processor[_state].call(self, _data);
        });
    };

    var init = function init() {
        var self = this;

        this.$["container"]["body"].on("click", '[data-ax5grid-column-attr]', function (e) {
            var panelName = void 0,
                attr = void 0,
                row = void 0,
                col = void 0,
                dindex = void 0,
                doindex = void 0,
                rowIndex = void 0,
                colIndex = void 0,
                disableSelection = void 0,
                targetClick = {
                "default": function _default(_column) {
                    var column = self.bodyRowMap[_column.rowIndex + "_" + _column.colIndex],
                        that = {
                        self: self,
                        page: self.page,
                        list: self.list,
                        item: self.list[_column.doindex],
                        dindex: _column.dindex,
                        doindex: _column.doindex,
                        rowIndex: _column.rowIndex,
                        colIndex: _column.colIndex,
                        column: column,
                        value: self.list[_column.dindex][column.key]
                    };

                    if (column.editor && column.editor.type == "checkbox") {
                        // todo : GRID.inlineEditor에서 처리 할수 있도록 구문 변경 필요.
                        var value = GRID.data.getValue.call(self, _column.dindex, _column.doindex, column.key),
                            checked = void 0,
                            newValue = void 0;

                        if (column.editor.config && column.editor.config.trueValue) {
                            if (checked = !(value == column.editor.config.trueValue)) {
                                newValue = column.editor.config.trueValue;
                            } else {
                                newValue = column.editor.config.falseValue;
                            }
                        } else {
                            newValue = checked = value == false || value == "false" || value < "1" ? "true" : "false";
                        }

                        GRID.data.setValue.call(self, _column.dindex, _column.doindex, column.key, newValue);

                        updateRowState.call(self, ["cellChecked"], _column.dindex, _column.doindex, {
                            key: column.key, rowIndex: _column.rowIndex, colIndex: _column.colIndex,
                            editorConfig: column.editor.config, checked: checked
                        });
                    } else {
                        if (self.config.body.onClick) {
                            self.config.body.onClick.call(that);
                        }
                    }
                },
                "rowSelector": function rowSelector(_column) {
                    var item = self.list[_column.doindex];
                    if (item[self.config.columnKeys.disableSelection]) {
                        return false;
                    }

                    if (!self.config.multipleSelect && self.selectedDataIndexs[0] !== _column.doindex) {
                        updateRowState.call(self, ["selectedClear"]);
                        GRID.data.clearSelect.call(self);
                    }

                    GRID.data.select.call(self, _column.dindex, _column.doindex, undefined, {
                        internalCall: true
                    });
                    updateRowState.call(self, ["selected"], _column.dindex, _column.doindex);
                },
                "lineNumber": function lineNumber(_column) {},
                "tree-control": function treeControl(_column, _el) {
                    //console.log(_column);
                    toggleCollapse.call(self, _column.dindex, _column.doindex);
                }
            };

            panelName = this.getAttribute("data-ax5grid-panel-name");
            attr = this.getAttribute("data-ax5grid-column-attr");
            row = Number(this.getAttribute("data-ax5grid-column-row"));
            col = Number(this.getAttribute("data-ax5grid-column-col"));
            rowIndex = Number(this.getAttribute("data-ax5grid-column-rowIndex"));
            colIndex = Number(this.getAttribute("data-ax5grid-column-colIndex"));
            dindex = Number(this.getAttribute("data-ax5grid-data-index"));
            doindex = Number(this.getAttribute("data-ax5grid-data-o-index"));

            if (attr in targetClick) {
                targetClick[attr]({
                    panelName: panelName,
                    attr: attr,
                    row: row,
                    col: col,
                    dindex: dindex,
                    doindex: doindex,
                    rowIndex: rowIndex,
                    colIndex: colIndex
                }, this);
            }
        });
        this.$["container"]["body"].on("dblclick", '[data-ax5grid-column-attr]', function (e) {
            var panelName = void 0,
                attr = void 0,
                row = void 0,
                col = void 0,
                dindex = void 0,
                doindex = void 0,
                rowIndex = void 0,
                colIndex = void 0,
                targetDBLClick = {
                "default": function _default(_column) {
                    if (self.isInlineEditing) {
                        for (var columnKey in self.inlineEditing) {
                            if (columnKey == _column.dindex + "_" + _column.colIndex + "_" + _column.rowIndex) {
                                return this;
                            }
                        }
                    }

                    var column = self.bodyRowMap[_column.rowIndex + "_" + _column.colIndex],
                        value = "";
                    if (column) {
                        if (!self.list[dindex].__isGrouping) {
                            value = GRID.data.getValue.call(self, dindex, doindex, column.key);
                        }
                    }

                    var editor = self.colGroup[_column.colIndex].editor;
                    if (U.isObject(editor)) {
                        GRID.body.inlineEdit.active.call(self, self.focusedColumn, e, value);
                    } else {
                        // 더블클릭 실행
                        if (self.config.body.onDBLClick) {
                            var that = {
                                self: self,
                                page: self.page,
                                list: self.list,
                                item: self.list[_column.dindex],
                                dindex: _column.dindex,
                                doindex: _column.doindex,
                                rowIndex: _column.rowIndex,
                                colIndex: _column.colIndex,
                                column: column,
                                value: self.list[_column.dindex][column.key]
                            };
                            self.config.body.onDBLClick.call(that);
                        }
                    }
                },
                "rowSelector": function rowSelector(_column) {},
                "lineNumber": function lineNumber(_column) {}
            };

            panelName = this.getAttribute("data-ax5grid-panel-name");
            attr = this.getAttribute("data-ax5grid-column-attr");
            row = Number(this.getAttribute("data-ax5grid-column-row"));
            col = Number(this.getAttribute("data-ax5grid-column-col"));
            rowIndex = Number(this.getAttribute("data-ax5grid-column-rowIndex"));
            colIndex = Number(this.getAttribute("data-ax5grid-column-colIndex"));
            dindex = Number(this.getAttribute("data-ax5grid-data-index"));
            doindex = Number(this.getAttribute("data-ax5grid-data-o-index"));

            if (attr in targetDBLClick) {
                targetDBLClick[attr]({
                    panelName: panelName,
                    attr: attr,
                    row: row,
                    col: col,
                    dindex: dindex,
                    doindex: doindex,
                    rowIndex: rowIndex,
                    colIndex: colIndex
                });
            }
        });

        if (this.config.contextMenu) {
            this.$["container"]["body"].on("contextmenu", function (e) {
                var target = void 0,
                    dindex = void 0,
                    doindex = void 0,
                    rowIndex = void 0,
                    colIndex = void 0,
                    item = void 0,
                    column = void 0,
                    param = {};

                target = U.findParentNode(e.target, function (t) {
                    if (t.getAttribute("data-ax5grid-column-attr")) {
                        return true;
                    }
                });

                if (target) {
                    // item 찾기
                    rowIndex = Number(target.getAttribute("data-ax5grid-column-rowIndex"));
                    colIndex = Number(target.getAttribute("data-ax5grid-column-colIndex"));
                    dindex = Number(target.getAttribute("data-ax5grid-data-index"));
                    doindex = Number(target.getAttribute("data-ax5grid-data-o-index"));
                    column = self.bodyRowMap[rowIndex + "_" + colIndex];
                    item = self.list[dindex];
                }

                if (!self.contextMenu) {
                    self.contextMenu = new ax5.ui.menu();
                }

                self.contextMenu.setConfig(self.config.contextMenu);

                param = {
                    element: target,
                    dindex: dindex,
                    doindex: doindex,
                    rowIndex: rowIndex,
                    colIndex: colIndex,
                    item: item,
                    column: column,
                    gridSelf: self
                };

                self.contextMenu.popup(e, {
                    filter: function filter() {
                        return self.config.contextMenu.popupFilter.call(this, this, param);
                    },
                    param: param
                });

                U.stopEvent(e.originalEvent);
                target = null;
                dindex = null;
                doindex = null;
                rowIndex = null;
                colIndex = null;
                item = null;
                column = null;
                param = null;
            });
        }

        this.$["container"]["body"].on("mousedown", '[data-ax5grid-column-attr="default"]', function (e) {
            if (self.xvar.touchmoved) return false;
            if (this.getAttribute("data-ax5grid-column-rowIndex")) {
                columnSelector.on.call(self, {
                    panelName: this.getAttribute("data-ax5grid-panel-name"),
                    dindex: Number(this.getAttribute("data-ax5grid-data-index")),
                    doindex: Number(this.getAttribute("data-ax5grid-data-o-index")),
                    rowIndex: Number(this.getAttribute("data-ax5grid-column-rowIndex")),
                    colIndex: Number(this.getAttribute("data-ax5grid-column-colIndex")),
                    colspan: Number(this.getAttribute("colspan"))
                });
            }
        }).on("dragstart", function (e) {
            U.stopEvent(e);
            return false;
        });

        resetFrozenColumn.call(this);

        // 그리드 바디에 출력할 여유 카운트
        this.xvar.paintRowCountMargin = this.config.virtualScrollYCountMargin;
        this.xvar.paintRowCountTopMargin = this.config.virtualScrollYCountMargin - Math.floor(this.config.virtualScrollYCountMargin / 2);

        if (this.config.virtualScrollAccelerated) {
            this.__throttledScroll = U.throttle(function (css, opts) {
                if (this.config.virtualScrollY && !opts.noRepaint && "top" in css) {
                    repaint.call(this);
                } else if (this.config.virtualScrollX && !opts.noRepaint && "left" in css) {
                    repaint.call(this);
                }
                if (opts.callback) {
                    opts.callback();
                }
            }, this.config.virtualScrollAcceleratedDelayTime);
        } else {
            this.__throttledScroll = false;
        }
    };

    var resetFrozenColumn = function resetFrozenColumn() {
        var cfg = this.config,
            dividedBodyRowObj = GRID.util.divideTableByFrozenColumnIndex(this.bodyRowTable, this.xvar.frozenColumnIndex);

        this.asideBodyRowData = function (dataTable) {
            var data = { rows: [] };
            for (var i = 0, l = dataTable.rows.length; i < l; i++) {
                data.rows[i] = { cols: [] };
                if (i === 0) {
                    var col = {
                        label: "",
                        colspan: 1,
                        rowspan: dataTable.rows.length,
                        colIndex: null
                    },
                        _col = {};

                    if (cfg.showLineNumber) {
                        _col = jQuery.extend({}, col, {
                            width: cfg.lineNumberColumnWidth,
                            _width: cfg.lineNumberColumnWidth,
                            columnAttr: "lineNumber",
                            label: "&nbsp;", key: "__d-index__"
                        });
                        data.rows[i].cols.push(_col);
                    }
                    if (cfg.showRowSelector) {
                        _col = jQuery.extend({}, col, {
                            width: cfg.rowSelectorColumnWidth,
                            _width: cfg.rowSelectorColumnWidth,
                            columnAttr: "rowSelector",
                            label: "", key: "__d-checkbox__"
                        });
                        data.rows[i].cols.push(_col);
                    }
                }
            }

            return data;
        }.call(this, this.bodyRowTable);

        //console.log(dividedBodyRowObj);

        this.leftBodyRowData = dividedBodyRowObj.leftData;
        this.bodyRowData = dividedBodyRowObj.rightData;

        if (cfg.body.grouping) {
            var dividedBodyGroupingObj = GRID.util.divideTableByFrozenColumnIndex(this.bodyGroupingTable, this.xvar.frozenColumnIndex);
            this.asideBodyGroupingData = function (dataTable) {
                var data = { rows: [] };
                for (var i = 0, l = dataTable.rows.length; i < l; i++) {
                    data.rows[i] = { cols: [] };
                    if (i === 0) {
                        var col = {
                            label: "",
                            colspan: 1,
                            rowspan: dataTable.rows.length,
                            colIndex: null
                        },
                            _col = {};

                        if (cfg.showLineNumber) {
                            _col = jQuery.extend({}, col, {
                                width: cfg.lineNumberColumnWidth,
                                _width: cfg.lineNumberColumnWidth,
                                columnAttr: "lineNumber",
                                label: "&nbsp;", key: "__d-index__"
                            });
                            data.rows[i].cols.push(_col);
                        }
                        if (cfg.showRowSelector) {
                            _col = jQuery.extend({}, col, {
                                width: cfg.rowSelectorColumnWidth,
                                _width: cfg.rowSelectorColumnWidth,
                                columnAttr: "rowSelector",
                                label: "", key: "__d-checkbox__"
                            });
                            data.rows[i].cols.push(_col);
                        }
                    }
                }

                return data;
            }.call(this, this.bodyGroupingTable);
            this.leftBodyGroupingData = dividedBodyGroupingObj.leftData;
            this.bodyGroupingData = dividedBodyGroupingObj.rightData;
            this.bodyGroupingMap = GRID.util.makeBodyRowMap.call(this, this.bodyGroupingTable);
        }

        this.leftFootSumData = {};
        this.footSumData = {};
        if (this.config.footSum) {
            var dividedFootSumObj = GRID.util.divideTableByFrozenColumnIndex(this.footSumTable, this.xvar.frozenColumnIndex);
            this.leftFootSumData = dividedFootSumObj.leftData;
            this.footSumData = dividedFootSumObj.rightData;
        }
    };

    var getFieldValue = function getFieldValue(_list, _item, _index, _col, _value, _returnPlainText) {

        var _key = _col.key,
            tagsToReplace = {
            '<': '&lt;',
            '>': '&gt;'
        };

        if (_key === "__d-index__") {
            return typeof _item["__index"] !== "undefined" ? _item["__index"] + 1 : "";
        } else if (_key === "__d-checkbox__") {
            return "<div class=\"checkBox\" style=\"max-height: " + (_col.width - 10) + "px;min-height: " + (_col.width - 10) + "px;\"></div>";
        } else {
            if (_col.editor && function (_editor) {
                if (_editor.type in GRID.inlineEditor) {
                    return GRID.inlineEditor[_editor.type].editMode == "inline";
                }
                return false;
            }(_col.editor)) {
                // editor가 inline타입이라면

                _value = _value || GRID.data.getValue.call(this, _index, _item.__origin_index__, _key);

                if (U.isFunction(_col.editor.disabled)) {
                    if (_col.editor.disabled.call({
                        list: _list,
                        dindex: _index,
                        item: _list[_index],
                        key: _key,
                        value: _value
                    })) {
                        return _value;
                    }
                }

                // print editor
                return _returnPlainText ? _value : GRID.inlineEditor[_col.editor.type].getHtml(this, _col.editor, _value);
            }

            var valueProcessor = {
                "formatter": function formatter() {
                    var that = {
                        key: _key,
                        value: _value || GRID.data.getValue.call(this, _index, _item.__origin_index__, _key),
                        dindex: _index,
                        item: _item,
                        list: _list
                    };
                    if (U.isFunction(_col.formatter)) {
                        return _col.formatter.call(that);
                    } else {
                        return GRID.formatter[_col.formatter].call(that);
                    }
                },
                "default": function _default() {
                    var returnValue = "";

                    if (typeof _value !== "undefined") {
                        returnValue = _value;
                    } else {
                        if (/[\.\[\]]/.test(_key)) {
                            _value = GRID.data.getValue.call(this, _index, _item.__origin_index__, _key);
                        } else {
                            _value = _item[_key];
                        }

                        if (_value !== null && typeof _value !== "undefined") returnValue = _value;
                    }

                    // 키값이 Boolean일때 오류 발생하여 수정.
                    return typeof returnValue !== "string" ? returnValue : returnValue.replace(/[<>]/g, function (tag) {
                        return tagsToReplace[tag] || tag;
                    });
                },
                "treeControl": function treeControl(__value) {
                    var cfg = this.config,
                        keys = this.config.tree.columnKeys,
                        indentNodeHtml = '';

                    if (_item[keys.children].length) {
                        indentNodeHtml += '<a ' + 'data-ax5grid-data-index="' + _index + '" ' + 'data-ax5grid-column-attr="tree-control" ' + 'data-ax5grid-tnode-arrow="" ' + 'style="width: ' + cfg.tree.arrowWidth + 'px;padding-left:' + _item[keys.depth] * cfg.tree.indentWidth + 'px;"' + '>';
                        indentNodeHtml += _item[keys.collapse] ? cfg.tree.icons.collapsedArrow : cfg.tree.icons.openedArrow;
                        indentNodeHtml += '</a>';
                    } else {
                        indentNodeHtml += '<span ' + 'data-ax5grid-tnode-arrow="" ' + 'style="width: ' + cfg.tree.arrowWidth + 'px;padding-left:' + _item[keys.depth] * cfg.tree.indentWidth + 'px;"' + '>&nbsp;</span>';
                    }

                    indentNodeHtml += '<span ' + 'data-ax5grid-tnode-item="' + (_item[keys.children].length ? 'group' : 'item') + '" ' + 'style="width: ' + cfg.tree.iconWidth + 'px;"' + '>';
                    indentNodeHtml += _item[keys.children].length ? _item[keys.collapse] ? cfg.tree.icons.collapsedGroupIcon : cfg.tree.icons.groupIcon : cfg.tree.icons.itemIcon;
                    indentNodeHtml += '</span>';

                    return indentNodeHtml + __value;
                }
            };

            var returnValue = _col.formatter ? valueProcessor.formatter.call(this) : valueProcessor.default.call(this);
            if (this.config.tree.use && _col.treeControl) {
                returnValue = valueProcessor.treeControl.call(this, returnValue);
            }

            return returnValue;
        }
    };

    var getGroupingValue = function getGroupingValue(_item, _index, _col) {
        var value = void 0,
            that = void 0,
            _key = _col.key,
            _label = _col.label;

        if (typeof _key === "undefined") {
            that = {
                key: _key,
                list: _item.__groupingList,
                groupBy: _item.__groupingBy
            };
            if (U.isFunction(_label)) {
                value = _label.call(that);
            } else {
                value = _label;
            }
            _item[_col.colIndex] = value;
            return value;
        } else if (_key === "__d-index__") {
            return '';
        } else if (_key === "__d-checkbox__") {
            return '';
        } else {
            if (_col.collector) {
                that = {
                    key: _key,
                    list: _item.__groupingList
                };
                if (U.isFunction(_col.collector)) {
                    value = _col.collector.call(that);
                } else {
                    value = GRID.collector[_col.collector].call(that);
                }
                _item[_col.colIndex] = value;

                if (_col.formatter) {
                    that.value = value;
                    if (U.isFunction(_col.formatter)) {
                        return _col.formatter.call(that);
                    } else {
                        return GRID.formatter[_col.formatter].call(that);
                    }
                } else {
                    return value;
                }
            } else {
                return "&nbsp;";
            }
        }
    };

    var getSumFieldValue = function getSumFieldValue(_list, _col) {
        var _key = _col.key,
            _label = _col.label;
        //, _collector, _formatter
        if (typeof _key === "undefined") {
            return _label;
        } else if (_key === "__d-index__" || _key === "__d-checkbox__") {
            return '&nbsp;';
        } else {
            if (_col.collector) {
                var that = {
                    key: _key,
                    list: _list
                },
                    value = void 0;

                if (U.isFunction(_col.collector)) {
                    value = _col.collector.call(that);
                } else {
                    value = GRID.collector[_col.collector].call(that);
                }

                if (_col.formatter) {
                    that.value = value;
                    if (U.isFunction(_col.formatter)) {
                        return _col.formatter.call(that);
                    } else {
                        return GRID.formatter[_col.formatter].call(that);
                    }
                } else {
                    return value;
                }
            } else {
                return "&nbsp;";
            }
        }
    };

    var repaint = function repaint(_reset) {
        // debugger;
        var cfg = this.config,
            list = this.proxyList ? this.proxyList : this.list;

        /// repaint reset 타입이면 고정컬럼을 재조정
        if (_reset) {
            resetFrozenColumn.call(this);
            // 틀고정 이 변경되면 출력 시작 인덱스 값을 초기화
            this.xvar.paintStartRowIndex = undefined;
            this.xvar.paintStartColumnIndex = undefined;
        }

        /// 출력시작 인덱스
        var paintStartRowIndex = void 0,
            virtualPaintStartRowIndex = void 0;

        if (this.config.virtualScrollY) {
            virtualPaintStartRowIndex = paintStartRowIndex = Math.floor(-this.$.panel["body-scroll"].position().top / this.xvar.bodyTrHeight) + this.xvar.frozenRowIndex;
            if (this.xvar.paintRowCountTopMargin < paintStartRowIndex) {
                paintStartRowIndex -= this.xvar.paintRowCountTopMargin;
            }
        } else {
            paintStartRowIndex = this.xvar.frozenRowIndex;
        }

        if (isNaN(paintStartRowIndex)) return this;

        var paintStartColumnIndex = 0,
            paintEndColumnIndex = 0,
            nopaintLeftColumnsWidth = null,
            nopaintRightColumnsWidth = null;
        var bodyScrollLeft = -this.$.panel["body-scroll"].position().left;

        if (this.config.virtualScrollX) {
            // 페인트 시작컬럼위치와 종료컬럼위치 구하기
            for (var ci = this.xvar.frozenColumnIndex; ci < this.colGroup.length; ci++) {
                // bodyScrollLeft
                this.colGroup[ci]._sx = ci == this.xvar.frozenColumnIndex ? 0 : this.colGroup[ci - 1]._ex;
                this.colGroup[ci]._ex = this.colGroup[ci]._sx + this.colGroup[ci]._width;

                if (this.colGroup[ci]._sx <= bodyScrollLeft && this.colGroup[ci]._ex >= bodyScrollLeft) {
                    paintStartColumnIndex = ci;
                }
                if (this.colGroup[ci]._sx <= bodyScrollLeft + this.xvar.bodyWidth && this.colGroup[ci]._ex >= bodyScrollLeft + this.xvar.bodyWidth) {
                    paintEndColumnIndex = ci;

                    if (nopaintLeftColumnsWidth === null) nopaintLeftColumnsWidth = this.colGroup[paintStartColumnIndex]._sx;
                    if (nopaintRightColumnsWidth === null) nopaintRightColumnsWidth = this.xvar.scrollContentWidth - this.colGroup[ci]._ex;
                }
            }

            if (nopaintLeftColumnsWidth === null) nopaintLeftColumnsWidth = 0;
            if (nopaintRightColumnsWidth === null) nopaintRightColumnsWidth = 0;
            this.$.panel["top-body-scroll"].css({ "padding-left": nopaintLeftColumnsWidth, "padding-right": nopaintRightColumnsWidth });
            this.$.panel["body-scroll"].css({ "padding-left": nopaintLeftColumnsWidth, "padding-right": nopaintRightColumnsWidth });
            this.$.panel["bottom-body-scroll"].css({ "padding-left": nopaintLeftColumnsWidth, "padding-right": nopaintRightColumnsWidth });
        }

        var isFirstPaint = typeof this.xvar.paintStartRowIndex === "undefined",
            headerColGroup = this.headerColGroup,
            asideBodyRowData = this.asideBodyRowData,
            leftBodyRowData = this.leftBodyRowData,
            bodyRowData = this.bodyRowData,
            leftFootSumData = this.leftFootSumData,
            footSumData = this.footSumData,
            asideBodyGroupingData = this.asideBodyGroupingData,
            leftBodyGroupingData = this.leftBodyGroupingData,
            bodyGroupingData = this.bodyGroupingData,
            bodyAlign = cfg.body.align,
            paintRowCount = void 0,
            virtualPaintRowCount = void 0;

        if (!this.config.virtualScrollY) {
            virtualPaintRowCount = paintRowCount = list.length;
        } else {
            virtualPaintRowCount = Math.ceil(this.xvar.bodyHeight / this.xvar.bodyTrHeight);
            paintRowCount = virtualPaintRowCount + (this.xvar.paintRowCountMargin || 1);
        }

        // 여유범위 안에 있으면 페인팅 안할수 있게 paintStartRowIndex 변경하지 않음.
        if (this.xvar.paintRowCountTopMargin < paintStartRowIndex && Math.abs(this.xvar.paintStartRowIndex - paintStartRowIndex) <= this.xvar.paintRowCountTopMargin) {
            paintStartRowIndex = this.xvar.paintStartRowIndex;
        }

        if (this.xvar.dataRowCount === list.length && this.xvar.paintStartRowIndex === paintStartRowIndex && this.xvar.paintRowCount === paintRowCount && this.xvar.paintStartColumnIndex === paintStartColumnIndex && this.xvar.paintEndColumnIndex === paintEndColumnIndex) return this; // 스크롤 포지션 변경 여부에 따라 프로세스 진행여부 결정

        // bodyRowData 수정 : 페인트 컬럼 포지션이 달라지므로
        if (nopaintLeftColumnsWidth || nopaintRightColumnsWidth) {
            headerColGroup = [].concat(headerColGroup).splice(paintStartColumnIndex - this.xvar.frozenColumnIndex, paintEndColumnIndex - paintStartColumnIndex + 1 + this.xvar.frozenColumnIndex);
            bodyRowData = GRID.util.getTableByStartEndColumnIndex(bodyRowData, paintStartColumnIndex, paintEndColumnIndex);

            if (cfg.body.grouping) {
                bodyGroupingData = GRID.util.getTableByStartEndColumnIndex(bodyGroupingData, paintStartColumnIndex, paintEndColumnIndex);
            }
            if (cfg.footSum) {
                footSumData = GRID.util.getTableByStartEndColumnIndex(footSumData, paintStartColumnIndex, paintEndColumnIndex);
            }
            if (this.xvar.paintStartColumnIndex !== paintStartColumnIndex || this.xvar.paintEndColumnIndex !== paintEndColumnIndex) {
                this.needToPaintSum = true;
            }
        }

        /// 스크롤 컨텐츠의 높이 : 그리드 스크롤의 실제 크기와는 관계 없이 데이터 갯수에 따라 스크롤 컨텐츠 높이값 구해서 저장해두기.
        // todo scrollContentHeight
        this.xvar.scrollContentHeight = this.xvar.bodyTrHeight * (list.length - this.xvar.frozenRowIndex);
        if (this.xvar.scrollContentHeight < 0) this.xvar.scrollContentHeight = 0;

        /// 사용된 패널들의 키 모음
        this.$.livePanelKeys = [];

        // 그리드 바디 영역 페인트 함수
        /**
         * @param _elTargetKey
         * @param _colGroup
         * @param _bodyRow
         * @param _groupRow
         * @param _list
         * @param [_scrollConfig]
         * @returns {boolean}
         */
        var repaintBody = function repaintBody(_elTargetKey, _colGroup, _bodyRow, _groupRow, _list, _scrollConfig) {
            var _elTarget = this.$.panel[_elTargetKey];

            if (!isFirstPaint && !_scrollConfig) {
                this.$.livePanelKeys.push(_elTargetKey); // 사용중인 패널키를 모아둠. (뷰의 상태 변경시 사용하려고)
                return false;
            }

            var SS = [],
                cgi = void 0,
                cgl = void 0,
                di = void 0,
                dl = void 0,
                tri = void 0,
                trl = void 0,
                ci = void 0,
                cl = void 0,
                col = void 0,
                cellHeight = void 0,
                colAlign = void 0,
                isScrolled = function () {
                // 스크롤값이 변경되거나 처음 호출되었습니까?
                if (typeof _scrollConfig === "undefined" || typeof _scrollConfig['paintStartRowIndex'] === "undefined") {
                    _scrollConfig = {
                        paintStartRowIndex: 0,
                        paintRowCount: _list.length
                    };
                    return false;
                } else {
                    return true;
                }
            }(),
                stripeString = '#fff 0px, #fff ' + (cfg.body.columnHeight - cfg.body.columnBorderWidth) + 'px, #eee ' + (cfg.body.columnHeight - cfg.body.columnBorderWidth) + 'px, #eee ' + cfg.body.columnHeight + 'px';

            if (isScrolled) {
                SS.push('<div style="background:repeating-linear-gradient(to top, ' + stripeString + ');' + 'font-size:0;' + 'line-height:0;height: ' + (_scrollConfig.paintStartRowIndex - this.xvar.frozenRowIndex) * _scrollConfig.bodyTrHeight + 'px;"></div>');
            }

            SS.push('<table border="0" cellpadding="0" cellspacing="0">');
            SS.push('<colgroup>');
            for (cgi = 0, cgl = _colGroup.length; cgi < cgl; cgi++) {
                SS.push('<col style="width:' + _colGroup[cgi]._width + 'px;"  />');
            }
            SS.push('<col  />');
            SS.push('</colgroup>');

            di = _scrollConfig.paintStartRowIndex;

            for (dl = function () {
                var len = void 0;
                len = _list.length;
                if (_scrollConfig.paintRowCount + _scrollConfig.paintStartRowIndex < len) {
                    len = _scrollConfig.paintRowCount + _scrollConfig.paintStartRowIndex;
                }
                return len;
            }(); di < dl; di++) {

                if (_list[di]) {
                    var isGroupingRow = false,
                        rowTable = void 0,
                        odi = typeof _list[di].__origin_index__ !== "undefined" ? _list[di].__origin_index__ : di;
                    if (_groupRow && "__isGrouping" in _list[di]) {
                        rowTable = _groupRow;
                        isGroupingRow = true;
                    } else {
                        rowTable = _bodyRow;
                    }

                    for (tri = 0, trl = rowTable.rows.length; tri < trl; tri++) {

                        SS.push('<tr class="tr-' + di % 4 + '', cfg.body.trStyleClass ? U.isFunction(cfg.body.trStyleClass) ? ' ' + cfg.body.trStyleClass.call({
                            item: _list[di],
                            index: di
                        }, _list[di], di) : ' ' + cfg.body.trStyleClass : '', '"', isGroupingRow ? ' data-ax5grid-grouping-tr="true"' : '', ' data-ax5grid-tr-data-index="' + di + '"', ' data-ax5grid-tr-data-o-index="' + odi + '"', ' data-ax5grid-selected="' + (_list[di][cfg.columnKeys.selected] || "false") + '"', ' data-ax5grid-disable-selection="' + (_list[di][cfg.columnKeys.disableSelection] || "false") + '"', '>');

                        for (ci = 0, cl = rowTable.rows[tri].cols.length; ci < cl; ci++) {
                            col = rowTable.rows[tri].cols[ci];
                            cellHeight = cfg.body.columnHeight * col.rowspan - cfg.body.columnBorderWidth;
                            colAlign = col.align || bodyAlign;

                            SS.push('<td ', 'data-ax5grid-panel-name="' + _elTargetKey + '" ', 'data-ax5grid-data-index="' + di + '" ', 'data-ax5grid-data-o-index="' + odi + '" ', 'data-ax5grid-column-row="' + tri + '" ', 'data-ax5grid-column-col="' + ci + '" ', 'data-ax5grid-column-rowIndex="' + col.rowIndex + '" ', 'data-ax5grid-column-colIndex="' + col.colIndex + '" ', 'data-ax5grid-column-attr="' + (col.columnAttr || "default") + '" ', function (_focusedColumn, _selectedColumn) {
                                var attrs = "";
                                if (_focusedColumn) {
                                    attrs += 'data-ax5grid-column-focused="true" ';
                                }
                                if (_selectedColumn) {
                                    attrs += 'data-ax5grid-column-selected="true" ';
                                }
                                return attrs;
                            }(this.focusedColumn[di + "_" + col.colIndex + "_" + col.rowIndex], this.selectedColumn[di + "_" + col.colIndex + "_" + col.rowIndex]), 'colspan="' + col.colspan + '" ', 'rowspan="' + col.rowspan + '" ', 'class="' + function (_col) {
                                var tdCSS_class = "";
                                if (_col.styleClass) {
                                    if (U.isFunction(_col.styleClass)) {
                                        tdCSS_class += _col.styleClass.call({
                                            column: _col,
                                            key: _col.key,
                                            item: _list[di],
                                            index: di
                                        }) + " ";
                                    } else {
                                        tdCSS_class += _col.styleClass + " ";
                                    }
                                }
                                if (cfg.body.columnBorderWidth) tdCSS_class += "hasBorder ";
                                if (ci == cl - 1) tdCSS_class += "isLastColumn ";
                                return tdCSS_class;
                            }.call(this, col) + '" ', 'style="height: ' + cellHeight + 'px;min-height: 1px;">');

                            SS.push(function (_cellHeight) {
                                var lineHeight = cfg.body.columnHeight - cfg.body.columnPadding * 2 - cfg.body.columnBorderWidth;
                                if (!col.multiLine) {
                                    _cellHeight = cfg.body.columnHeight - cfg.body.columnBorderWidth;
                                }

                                return '<span data-ax5grid-cellHolder="' + (col.multiLine ? 'multiLine' : '') + '" ' + (colAlign ? 'data-ax5grid-text-align="' + colAlign + '"' : '') + '" style="height:' + _cellHeight + 'px;' + (col.multiLine ? '' : 'line-height: ' + lineHeight + 'px;') + '">';
                            }(cellHeight), isGroupingRow ? getGroupingValue.call(this, _list[di], di, col) : getFieldValue.call(this, _list, _list[di], di, col), '</span>');

                            SS.push('</td>');
                        }
                        SS.push('<td ', 'data-ax5grid-column-row="null" ', 'data-ax5grid-column-col="null" ', 'data-ax5grid-data-index="' + di + '" ', 'data-ax5grid-data-o-index="' + odi + '" ', 'data-ax5grid-column-attr="' + "default" + '" ', 'style="height: ' + cfg.body.columnHeight + 'px;min-height: 1px;" ', '></td>');
                        SS.push('</tr>');
                    }
                }
            }
            SS.push('</table>');

            if (isScrolled && _list.length) {
                SS.push('<div style="background:repeating-linear-gradient(to bottom, ' + stripeString + ');' + 'font-size:0;' + 'line-height:0;height: ' + (_list.length - di) * _scrollConfig.bodyTrHeight + 'px;"></div>');
            }

            _elTarget.empty();
            SS = SS.join('');

            _elTarget.get(0).innerHTML = SS;

            this.$.livePanelKeys.push(_elTargetKey); // 사용중인 패널키를 모아둠. (뷰의 상태 변경시 사용하려고)
            return true;
        };

        /**
         * @param _elTargetKey
         * @param _colGroup
         * @param _bodyRow
         * @param _list
         * @param [_scrollConfig]
         * @returns {boolean}
         */
        var repaintSum = function repaintSum(_elTargetKey, _colGroup, _bodyRow, _list, _scrollConfig) {
            var _elTarget = this.$.panel[_elTargetKey];

            if (!isFirstPaint && !_scrollConfig) {
                this.$.livePanelKeys.push(_elTargetKey); // 사용중인 패널키를 모아둠. (뷰의 상태 변경시 사용하려고)
                return false;
            }

            var SS = [],
                cgi = void 0,
                cgl = void 0,
                tri = void 0,
                trl = void 0,
                ci = void 0,
                cl = void 0,
                col = void 0,
                cellHeight = void 0,
                colAlign = void 0;

            SS.push('<table border="0" cellpadding="0" cellspacing="0">');
            SS.push('<colgroup>');
            for (cgi = 0, cgl = _colGroup.length; cgi < cgl; cgi++) {
                SS.push('<col style="width:' + _colGroup[cgi]._width + 'px;"  />');
            }
            SS.push('<col  />');
            SS.push('</colgroup>');

            for (tri = 0, trl = _bodyRow.rows.length; tri < trl; tri++) {
                SS.push('<tr class="tr-sum">');
                for (ci = 0, cl = _bodyRow.rows[tri].cols.length; ci < cl; ci++) {
                    col = _bodyRow.rows[tri].cols[ci];
                    cellHeight = cfg.body.columnHeight * col.rowspan - cfg.body.columnBorderWidth;
                    colAlign = col.align || bodyAlign;

                    SS.push('<td ', 'data-ax5grid-panel-name="' + _elTargetKey + '" ', 'data-ax5grid-column-row="' + tri + '" ', 'data-ax5grid-column-col="' + ci + '" ', 'data-ax5grid-column-rowIndex="' + tri + '" ', 'data-ax5grid-column-colIndex="' + col.colIndex + '" ', 'data-ax5grid-column-attr="' + (col.columnAttr || "sum") + '" ', function (_focusedColumn, _selectedColumn) {
                        var attrs = "";
                        if (_focusedColumn) {
                            attrs += 'data-ax5grid-column-focused="true" ';
                        }
                        if (_selectedColumn) {
                            attrs += 'data-ax5grid-column-selected="true" ';
                        }
                        return attrs;
                    }(this.focusedColumn["sum_" + col.colIndex + "_" + tri], this.selectedColumn["sum_" + col.colIndex + "_" + tri]), 'colspan="' + col.colspan + '" ', 'rowspan="' + col.rowspan + '" ', 'class="' + function (_col) {
                        var tdCSS_class = "";
                        if (_col.styleClass) {
                            if (U.isFunction(_col.styleClass)) {
                                tdCSS_class += _col.styleClass.call({
                                    column: _col,
                                    key: _col.key,
                                    isFootSum: true
                                }) + " ";
                            } else {
                                tdCSS_class += _col.styleClass + " ";
                            }
                        }
                        if (cfg.body.columnBorderWidth) tdCSS_class += "hasBorder ";
                        if (ci == cl - 1) tdCSS_class += "isLastColumn ";
                        return tdCSS_class;
                    }.call(this, col) + '" ', 'style="height: ' + cellHeight + 'px;min-height: 1px;">');

                    SS.push(function (_cellHeight) {
                        var lineHeight = cfg.body.columnHeight - cfg.body.columnPadding * 2 - cfg.body.columnBorderWidth;
                        if (!col.multiLine) {
                            _cellHeight = cfg.body.columnHeight - cfg.body.columnBorderWidth;
                        }

                        return '<span data-ax5grid-cellHolder="' + (col.multiLine ? 'multiLine' : '') + '" ' + (colAlign ? 'data-ax5grid-text-align="' + colAlign + '"' : '') + '" style="height:' + _cellHeight + 'px;' + (col.multiLine ? '' : 'line-height: ' + lineHeight + 'px;') + '">';
                    }(cellHeight), getSumFieldValue.call(this, _list, col), '</span>');

                    SS.push('</td>');
                }
                SS.push('<td ', 'data-ax5grid-column-row="null" ', 'data-ax5grid-column-col="null" ', 'data-ax5grid-column-attr="' + "sum" + '" ', 'style="height: ' + cfg.body.columnHeight + 'px;min-height: 1px;" ', '></td>');
                SS.push('</tr>');
            }

            SS.push('</table>');

            _elTarget.empty();
            SS = SS.join('');

            _elTarget.get(0).innerHTML = SS;

            this.$.livePanelKeys.push(_elTargetKey); // 사용중인 패널키를 모아둠. (뷰의 상태 변경시 사용하려고)
            return true;
        };

        /**
         * @param _elTargetKey
         * @param _colGroup
         * @param _bodyRow
         * @param _list
         * @param [_scrollConfig]
         * @returns {boolean}
         */
        var mergeCellsBody = function mergeCellsBody(_elTargetKey, _colGroup, _bodyRow, _list, _scrollConfig) {
            var tblRowMaps = [];
            var _elTarget = this.$.panel[_elTargetKey];
            var token = {},
                hasMergeTd = void 0;

            // 테이블의 td들을 수잡하여 저장해두고 스크립트로 반복하여 정리.
            var tableTrs = _elTarget.find("tr");
            for (var ri = 0, rl = tableTrs.length; ri < rl; ri++) {
                var tableTrTds = void 0,
                    trMaps = void 0;
                tableTrTds = tableTrs[ri].childNodes;
                trMaps = [];

                for (var _ci = 0, cl = tableTrTds.length; _ci < cl; _ci++) {
                    var tdObj = {
                        "$": jQuery(tableTrTds[_ci])
                    };

                    if (tdObj["$"].attr("data-ax5grid-column-col") != "null") {
                        tdObj.dindex = tdObj["$"].attr("data-ax5grid-data-index");
                        tdObj.tri = tdObj["$"].attr("data-ax5grid-column-row");
                        tdObj.ci = tdObj["$"].attr("data-ax5grid-column-col");
                        tdObj.rowIndex = tdObj["$"].attr("data-ax5grid-column-rowIndex");
                        tdObj.colIndex = tdObj["$"].attr("data-ax5grid-column-colIndex");
                        tdObj.rowspan = tdObj["$"].attr("rowspan");
                        tdObj.text = tdObj["$"].text();
                        trMaps.push(tdObj);
                    }

                    tdObj = null;
                }
                tblRowMaps.push(trMaps);
            }

            // 두줄이상 일 때 의미가 있으니.
            if (tblRowMaps.length > 1) {
                hasMergeTd = false;

                var _loop = function _loop(_ri, _rl) {
                    var prevTokenColIndexs = [];

                    var _loop2 = function _loop2(_ci3, _cl2) {
                        // 적용 하려는 컬럼에 editor 속성이 없다면 머지 대상입니다.

                        if (!_colGroup[_ci3].editor && function () {
                            if (U.isArray(cfg.body.mergeCells)) {
                                return ax5.util.search(cfg.body.mergeCells, _colGroup[_ci3].key) > -1;
                            } else {
                                return true;
                            }
                        }()) {

                            // 앞줄과 값이 같다면.
                            if (token[_ci3] && function () {
                                if (prevTokenColIndexs.length > 0) {
                                    var hasFalse = true;
                                    prevTokenColIndexs.forEach(function (ti) {
                                        if (tblRowMaps[_ri - 1][ti].text != tblRowMaps[_ri][ti].text) {
                                            hasFalse = false;
                                        }
                                    });
                                    return hasFalse;
                                } else {
                                    return true;
                                }
                            }() && token[_ci3].text == tblRowMaps[_ri][_ci3].text) {
                                tblRowMaps[_ri][_ci3].rowspan = 0;
                                tblRowMaps[token[_ci3].ri][_ci3].rowspan++;
                                hasMergeTd = true;
                            } else {
                                token[_ci3] = {
                                    ri: _ri,
                                    ci: _ci3,
                                    text: tblRowMaps[_ri][_ci3].text
                                };
                            }

                            prevTokenColIndexs.push(_ci3);
                        }
                    };

                    for (var _ci3 = 0, _cl2 = tblRowMaps[_ri].length; _ci3 < _cl2; _ci3++) {
                        _loop2(_ci3, _cl2);
                    }
                };

                for (var _ri = 0, _rl = tblRowMaps.length; _ri < _rl; _ri++) {
                    _loop(_ri, _rl);
                }

                // rowspan을 다 구했으면 적용합니다.
                if (hasMergeTd) {
                    for (var _ri2 = 0, _rl2 = tblRowMaps.length; _ri2 < _rl2; _ri2++) {
                        for (var _ci2 = 0, _cl = tblRowMaps[_ri2].length; _ci2 < _cl; _ci2++) {
                            if (tblRowMaps[_ri2][_ci2].rowspan == 0) {
                                tblRowMaps[_ri2][_ci2]["$"].remove();
                            } else if (tblRowMaps[_ri2][_ci2].rowspan > 1) {
                                tblRowMaps[_ri2][_ci2]["$"].attr("rowspan", tblRowMaps[_ri2][_ci2].rowspan).addClass("merged");
                            }
                        }
                    }
                }
            }
        };

        var scrollConfig = {
            paintStartRowIndex: paintStartRowIndex,
            paintRowCount: paintRowCount,
            paintStartColumnIndex: paintStartColumnIndex,
            paintEndColumnIndex: paintEndColumnIndex,
            nopaintLeftColumnsWidth: nopaintLeftColumnsWidth,
            nopaintRightColumnsWidth: nopaintRightColumnsWidth,
            bodyTrHeight: this.xvar.bodyTrHeight,
            virtualScrollX: this.config.virtualScrollX,
            virtualScrollY: this.config.virtualScrollY
        };
        var frozenScrollConfig = jQuery.extend({}, scrollConfig, {
            paintStartRowIndex: 0,
            paintRowCount: this.xvar.frozenRowIndex
        });

        // aside
        if (cfg.asidePanelWidth > 0) {
            if (this.xvar.frozenRowIndex > 0) {
                // 상단 행고정
                repaintBody.call(this, "top-aside-body", this.asideColGroup, asideBodyRowData, asideBodyGroupingData, list.slice(0, this.xvar.frozenRowIndex), frozenScrollConfig);
            }

            repaintBody.call(this, "aside-body-scroll", this.asideColGroup, asideBodyRowData, asideBodyGroupingData, list, scrollConfig);

            if (cfg.footSum) {
                // 바닥 요약 (footSum에 대한 aside 사용안함)
                repaintSum.call(this, "bottom-aside-body", this.asideColGroup, asideBodyRowData, null, this.list);
            }
        }

        // left
        if (this.xvar.frozenColumnIndex > 0) {
            if (this.xvar.frozenRowIndex > 0) {
                // 상단 행고정
                repaintBody.call(this, "top-left-body", this.leftHeaderColGroup, leftBodyRowData, leftBodyGroupingData, list.slice(0, this.xvar.frozenRowIndex), frozenScrollConfig);
            }

            repaintBody.call(this, "left-body-scroll", this.leftHeaderColGroup, leftBodyRowData, leftBodyGroupingData, list, scrollConfig);

            if (cfg.footSum && this.needToPaintSum) {
                // 바닥 요약
                repaintSum.call(this, "bottom-left-body", this.leftHeaderColGroup, leftFootSumData, this.list);
            }
        }

        // body
        if (this.xvar.frozenRowIndex > 0) {
            // 상단 행고정
            repaintBody.call(this, "top-body-scroll", headerColGroup, bodyRowData, bodyGroupingData, list.slice(0, this.xvar.frozenRowIndex), frozenScrollConfig);
        }
        repaintBody.call(this, "body-scroll", headerColGroup, bodyRowData, bodyGroupingData, list, scrollConfig);

        // 바닥 요약
        if (cfg.footSum && this.needToPaintSum) {
            repaintSum.call(this, "bottom-body-scroll", headerColGroup, footSumData, this.list, scrollConfig);
        }
        // right
        if (cfg.rightSum) {}
        // todo : right 표현 정리


        /// mergeCells
        if (cfg.body.mergeCells && list.length) {
            // left
            if (this.xvar.frozenColumnIndex > 0) {
                if (this.xvar.frozenRowIndex > 0) {
                    // 상단 행고정
                    // console.log(this.leftHeaderColGroup, leftBodyRowData);
                    mergeCellsBody.call(this, "top-left-body", this.leftHeaderColGroup, leftBodyRowData, list.slice(0, this.xvar.frozenRowIndex));
                }
                mergeCellsBody.call(this, "left-body-scroll", this.leftHeaderColGroup, leftBodyRowData, list, scrollConfig);
            }

            // body
            if (this.xvar.frozenRowIndex > 0) {
                // 상단 행고정
                mergeCellsBody.call(this, "top-body-scroll", this.headerColGroup, bodyRowData, list.slice(0, this.xvar.frozenRowIndex));
            }
            mergeCellsBody.call(this, "body-scroll", this.headerColGroup, bodyRowData, list, scrollConfig);
        }

        this.xvar.virtualPaintStartRowIndex = virtualPaintStartRowIndex;
        this.xvar.paintStartRowIndex = paintStartRowIndex;
        this.xvar.paintRowCount = paintRowCount;
        this.xvar.virtualPaintRowCount = virtualPaintRowCount;
        this.xvar.paintStartColumnIndex = paintStartColumnIndex;
        this.xvar.paintEndColumnIndex = paintEndColumnIndex;
        this.xvar.nopaintLeftColumnsWidth = nopaintLeftColumnsWidth;
        this.xvar.nopaintRightColumnsWidth = nopaintRightColumnsWidth;
        this.xvar.dataRowCount = list.length;
        this.needToPaintSum = false;

        GRID.page.statusUpdate.call(this);
    };

    var repaintCell = function repaintCell(_panelName, _dindex, _doindex, _rowIndex, _colIndex, _newValue) {
        var self = this,
            cfg = this.config,
            list = this.list;

        var updateCell = this.$["panel"][_panelName].find('[data-ax5grid-tr-data-index="' + _dindex + '"]').find('[data-ax5grid-column-rowindex="' + _rowIndex + '"][data-ax5grid-column-colindex="' + _colIndex + '"]').find('[data-ax5grid-cellholder]'),
            colGroup = this.colGroup,
            col = colGroup[_colIndex];

        updateCell.html(getFieldValue.call(this, list, list[_dindex], _dindex, col));

        if (col.editor && col.editor.updateWith) {
            col.editor.updateWith.forEach(function (updateColumnKey) {
                colGroup.forEach(function (col) {
                    if (col.key == updateColumnKey) {
                        var rowIndex = col.rowIndex,
                            colIndex = col.colIndex,
                            panelName = GRID.util.findPanelByColumnIndex.call(self, _dindex, colIndex, rowIndex).panelName,
                            updateWithCell = self.$["panel"][panelName].find('[data-ax5grid-tr-data-index="' + _dindex + '"]').find('[data-ax5grid-column-rowindex="' + rowIndex + '"][data-ax5grid-column-colindex="' + colIndex + '"]').find('[data-ax5grid-cellholder]');

                        updateWithCell.html(getFieldValue.call(self, list, list[_dindex], _dindex, col));
                    }
                });
            });
        }

        /// ~~~~~~

        var paintStartRowIndex = Math.floor(Math.abs(this.$.panel["body-scroll"].position().top) / this.xvar.bodyTrHeight) + this.xvar.frozenRowIndex,
            headerColGroup = this.headerColGroup,
            leftFootSumData = this.leftFootSumData,
            footSumData = this.footSumData,
            leftBodyGroupingData = this.leftBodyGroupingData,
            bodyGroupingData = this.bodyGroupingData,
            bodyAlign = cfg.body.align,
            paintRowCount = Math.ceil(this.$.panel["body"].height() / this.xvar.bodyTrHeight) + 1,
            scrollConfig = {
            paintStartRowIndex: paintStartRowIndex,
            paintRowCount: paintRowCount,
            bodyTrHeight: this.xvar.bodyTrHeight
        };

        if (this.xvar.nopaintLeftColumnsWidth || this.xvar.nopaintRightColumnsWidth) {
            headerColGroup = [].concat(headerColGroup).splice(this.xvar.paintStartColumnIndex, this.xvar.paintEndColumnIndex - this.xvar.paintStartColumnIndex + 1);
            if (cfg.body.grouping) {
                bodyGroupingData = GRID.util.getTableByStartEndColumnIndex(bodyGroupingData, this.xvar.paintStartColumnIndex, this.xvar.paintEndColumnIndex);
            }
            if (cfg.footSum) {
                footSumData = GRID.util.getTableByStartEndColumnIndex(footSumData, this.xvar.paintStartColumnIndex, this.xvar.paintEndColumnIndex);
            }
        }

        var repaintSum = function repaintSum(_elTargetKey, _colGroup, _bodyRow, _list, _scrollConfig) {
            var _elTarget = this.$.panel[_elTargetKey],
                SS = [],
                cgi = void 0,
                cgl = void 0,
                tri = void 0,
                trl = void 0,
                ci = void 0,
                cl = void 0,
                col = void 0,
                cellHeight = void 0,
                colAlign = void 0;

            SS.push('<table border="0" cellpadding="0" cellspacing="0">');
            SS.push('<colgroup>');
            for (cgi = 0, cgl = _colGroup.length; cgi < cgl; cgi++) {
                SS.push('<col style="width:' + _colGroup[cgi]._width + 'px;"  />');
            }
            SS.push('<col  />');
            SS.push('</colgroup>');

            for (tri = 0, trl = _bodyRow.rows.length; tri < trl; tri++) {
                SS.push('<tr class="tr-sum">');
                for (ci = 0, cl = _bodyRow.rows[tri].cols.length; ci < cl; ci++) {
                    col = _bodyRow.rows[tri].cols[ci];
                    cellHeight = cfg.body.columnHeight * col.rowspan - cfg.body.columnBorderWidth;
                    colAlign = col.align || bodyAlign;

                    SS.push('<td ', 'data-ax5grid-panel-name="' + _elTargetKey + '" ', 'data-ax5grid-column-row="' + tri + '" ', 'data-ax5grid-column-col="' + ci + '" ', 'data-ax5grid-column-rowIndex="' + tri + '" ', 'data-ax5grid-column-colIndex="' + col.colIndex + '" ', 'data-ax5grid-column-attr="' + (col.columnAttr || "sum") + '" ', function (_focusedColumn, _selectedColumn) {
                        var attrs = "";
                        if (_focusedColumn) {
                            attrs += 'data-ax5grid-column-focused="true" ';
                        }
                        if (_selectedColumn) {
                            attrs += 'data-ax5grid-column-selected="true" ';
                        }
                        return attrs;
                    }(this.focusedColumn["sum_" + col.colIndex + "_" + tri], this.selectedColumn["sum_" + col.colIndex + "_" + tri]), 'colspan="' + col.colspan + '" ', 'rowspan="' + col.rowspan + '" ', 'class="' + function (_col) {
                        var tdCSS_class = "";
                        if (_col.styleClass) {
                            if (U.isFunction(_col.styleClass)) {
                                tdCSS_class += _col.styleClass.call({
                                    column: _col,
                                    key: _col.key,
                                    isFootSum: true
                                }) + " ";
                            } else {
                                tdCSS_class += _col.styleClass + " ";
                            }
                        }
                        if (cfg.body.columnBorderWidth) tdCSS_class += "hasBorder ";
                        if (ci == cl - 1) tdCSS_class += "isLastColumn ";
                        return tdCSS_class;
                    }.call(this, col) + '" ', 'style="height: ' + cellHeight + 'px;min-height: 1px;">');

                    SS.push(function (_cellHeight) {
                        var lineHeight = cfg.body.columnHeight - cfg.body.columnPadding * 2 - cfg.body.columnBorderWidth;
                        if (!col.multiLine) {
                            _cellHeight = cfg.body.columnHeight - cfg.body.columnBorderWidth;
                        }

                        return '<span data-ax5grid-cellHolder="' + (col.multiLine ? 'multiLine' : '') + '" ' + (colAlign ? 'data-ax5grid-text-align="' + colAlign + '"' : '') + '" style="height:' + _cellHeight + 'px;' + (col.multiLine ? '' : 'line-height: ' + lineHeight + 'px;') + '">';
                    }(cellHeight), getSumFieldValue.call(this, _list, col), '</span>');

                    SS.push('</td>');
                }
                SS.push('<td ', 'data-ax5grid-column-row="null" ', 'data-ax5grid-column-col="null" ', 'data-ax5grid-column-attr="' + "sum" + '" ', 'style="height: ' + cfg.body.columnHeight + 'px;min-height: 1px;" ', '></td>');
                SS.push('</tr>');
            }

            SS.push('</table>');

            _elTarget.empty().get(0).innerHTML = SS.join('');
            return true;
        };
        var replaceGroupTr = function replaceGroupTr(_elTargetKey, _colGroup, _groupRow, _list, _scrollConfig) {
            var _elTarget = this.$.panel[_elTargetKey],
                SS = [],
                di = void 0,
                dl = void 0,
                tri = void 0,
                trl = void 0,
                ci = void 0,
                cl = void 0,
                col = void 0,
                cellHeight = void 0,
                colAlign = void 0;

            for (di = _scrollConfig.paintStartRowIndex, dl = function () {
                var len = void 0;
                len = _list.length;
                if (_scrollConfig.paintRowCount + _scrollConfig.paintStartRowIndex < len) {
                    len = _scrollConfig.paintRowCount + _scrollConfig.paintStartRowIndex;
                }
                return len;
            }(); di < dl; di++) {
                if (_list[di] && _groupRow && "__isGrouping" in _list[di]) {
                    var rowTable = _groupRow;
                    SS = [];
                    for (tri = 0, trl = rowTable.rows.length; tri < trl; tri++) {
                        for (ci = 0, cl = rowTable.rows[tri].cols.length; ci < cl; ci++) {
                            col = rowTable.rows[tri].cols[ci];
                            cellHeight = cfg.body.columnHeight * col.rowspan - cfg.body.columnBorderWidth;
                            colAlign = col.align || bodyAlign;

                            SS.push('<td ', 'data-ax5grid-panel-name="' + _elTargetKey + '" ', 'data-ax5grid-data-index="' + di + '" ', 'data-ax5grid-column-row="' + tri + '" ', 'data-ax5grid-column-col="' + ci + '" ', 'data-ax5grid-column-rowIndex="' + col.rowIndex + '" ', 'data-ax5grid-column-colIndex="' + col.colIndex + '" ', 'data-ax5grid-column-attr="' + (col.columnAttr || "default") + '" ', function (_focusedColumn, _selectedColumn) {
                                var attrs = "";
                                if (_focusedColumn) {
                                    attrs += 'data-ax5grid-column-focused="true" ';
                                }
                                if (_selectedColumn) {
                                    attrs += 'data-ax5grid-column-selected="true" ';
                                }
                                return attrs;
                            }(this.focusedColumn[di + "_" + col.colIndex + "_" + col.rowIndex], this.selectedColumn[di + "_" + col.colIndex + "_" + col.rowIndex]), 'colspan="' + col.colspan + '" ', 'rowspan="' + col.rowspan + '" ', 'class="' + function (_col) {
                                var tdCSS_class = "";
                                if (_col.styleClass) {
                                    if (U.isFunction(_col.styleClass)) {
                                        tdCSS_class += _col.styleClass.call({
                                            column: _col,
                                            key: _col.key,
                                            item: _list[di],
                                            index: di
                                        }) + " ";
                                    } else {
                                        tdCSS_class += _col.styleClass + " ";
                                    }
                                }
                                if (cfg.body.columnBorderWidth) tdCSS_class += "hasBorder ";
                                if (ci == cl - 1) tdCSS_class += "isLastColumn ";
                                return tdCSS_class;
                            }.call(this, col) + '" ', 'style="height: ' + cellHeight + 'px;min-height: 1px;">');

                            SS.push(function (_cellHeight) {
                                var lineHeight = cfg.body.columnHeight - cfg.body.columnPadding * 2 - cfg.body.columnBorderWidth;
                                if (!col.multiLine) {
                                    _cellHeight = cfg.body.columnHeight - cfg.body.columnBorderWidth;
                                }

                                return '<span data-ax5grid-cellHolder="' + (col.multiLine ? 'multiLine' : '') + '" ' + (colAlign ? 'data-ax5grid-text-align="' + colAlign + '"' : '') + '" style="height:' + _cellHeight + 'px;' + (col.multiLine ? '' : 'line-height: ' + lineHeight + 'px;') + '">';
                            }(cellHeight), getGroupingValue.call(this, _list[di], di, col), '</span>');

                            SS.push('</td>');
                        }
                        SS.push('<td ', 'data-ax5grid-column-row="null" ', 'data-ax5grid-column-col="null" ', 'data-ax5grid-data-index="' + di + '" ', 'data-ax5grid-column-attr="' + "default" + '" ', 'style="height: ' + cfg.body.columnHeight + 'px;min-height: 1px;" ', '></td>');
                    }
                    _elTarget.find('tr[data-ax5grid-tr-data-index="' + di + '"]').empty().get(0).innerHTML = SS.join('');
                }
            }
        };

        // body.grouping tr 다시 그리기..
        if (cfg.body.grouping) {
            // left
            if (this.xvar.frozenColumnIndex > 0) {
                if (this.xvar.frozenRowIndex > 0) {
                    // 상단 행고정
                    replaceGroupTr.call(this, "top-left-body", headerColGroup, leftBodyGroupingData, list.slice(0, this.xvar.frozenRowIndex), {
                        paintStartRowIndex: 0,
                        paintRowCount: this.xvar.frozenRowIndex,
                        bodyTrHeight: this.xvar.bodyTrHeight
                    });
                }
                replaceGroupTr.call(this, "left-body-scroll", headerColGroup, leftBodyGroupingData, list, scrollConfig);
            }

            // body
            if (this.xvar.frozenRowIndex > 0) {
                // 상단 행고정
                replaceGroupTr.call(this, "top-body-scroll", headerColGroup, bodyGroupingData, list.slice(0, this.xvar.frozenRowIndex), {
                    paintStartRowIndex: 0,
                    paintRowCount: this.xvar.frozenRowIndex,
                    bodyTrHeight: this.xvar.bodyTrHeight
                });
            }

            replaceGroupTr.call(this, "body-scroll", headerColGroup, bodyGroupingData, list, scrollConfig);
        }

        if (this.xvar.frozenColumnIndex > 0) {
            if (cfg.footSum && this.needToPaintSum) {
                // 바닥 요약
                repaintSum.call(this, "bottom-left-body", headerColGroup, leftFootSumData, list);
            }
        }

        if (cfg.footSum && this.needToPaintSum) {
            // 바닥 요약
            repaintSum.call(this, "bottom-body-scroll", headerColGroup, footSumData, list, scrollConfig);
        }
    };

    var repaintRow = function repaintRow(_dindex) {
        var self = this,
            cfg = this.config,
            list = this.list;
        /// ~~~~~~

        var paintStartRowIndex = Math.floor(Math.abs(this.$.panel["body-scroll"].position().top) / this.xvar.bodyTrHeight) + this.xvar.frozenRowIndex,
            asideBodyRowData = this.asideBodyRowData,
            leftBodyRowData = this.leftBodyRowData,
            bodyRowData = this.bodyRowData,
            leftFootSumData = this.leftFootSumData,
            footSumData = this.footSumData,
            asideBodyGroupingData = this.asideBodyGroupingData,
            leftBodyGroupingData = this.leftBodyGroupingData,
            bodyGroupingData = this.bodyGroupingData,
            bodyAlign = cfg.body.align,
            paintRowCount = Math.ceil(this.$.panel["body"].height() / this.xvar.bodyTrHeight) + 1,
            scrollConfig = {
            paintStartRowIndex: paintStartRowIndex,
            paintRowCount: paintRowCount,
            bodyTrHeight: this.xvar.bodyTrHeight
        };

        var repaintSum = function repaintSum(_elTargetKey, _colGroup, _bodyRow, _list) {
            var _elTarget = this.$.panel[_elTargetKey],
                SS = [],
                cgi = void 0,
                cgl = void 0,
                tri = void 0,
                trl = void 0,
                ci = void 0,
                cl = void 0,
                col = void 0,
                cellHeight = void 0,
                colAlign = void 0;

            SS.push('<table border="0" cellpadding="0" cellspacing="0">');
            SS.push('<colgroup>');
            for (cgi = 0, cgl = _colGroup.length; cgi < cgl; cgi++) {
                SS.push('<col style="width:' + _colGroup[cgi]._width + 'px;"  />');
            }
            SS.push('<col  />');
            SS.push('</colgroup>');

            for (tri = 0, trl = _bodyRow.rows.length; tri < trl; tri++) {
                SS.push('<tr class="tr-sum">');
                for (ci = 0, cl = _bodyRow.rows[tri].cols.length; ci < cl; ci++) {
                    col = _bodyRow.rows[tri].cols[ci];
                    cellHeight = cfg.body.columnHeight * col.rowspan - cfg.body.columnBorderWidth;
                    colAlign = col.align || bodyAlign;

                    SS.push('<td ', 'data-ax5grid-panel-name="' + _elTargetKey + '" ', 'data-ax5grid-column-row="' + tri + '" ', 'data-ax5grid-column-col="' + ci + '" ', 'data-ax5grid-column-rowIndex="' + tri + '" ', 'data-ax5grid-column-colIndex="' + col.colIndex + '" ', 'data-ax5grid-column-attr="' + (col.columnAttr || "sum") + '" ', function (_focusedColumn, _selectedColumn) {
                        var attrs = "";
                        if (_focusedColumn) {
                            attrs += 'data-ax5grid-column-focused="true" ';
                        }
                        if (_selectedColumn) {
                            attrs += 'data-ax5grid-column-selected="true" ';
                        }
                        return attrs;
                    }(this.focusedColumn["sum_" + col.colIndex + "_" + tri], this.selectedColumn["sum_" + col.colIndex + "_" + tri]), 'colspan="' + col.colspan + '" ', 'rowspan="' + col.rowspan + '" ', 'class="' + function (_col) {
                        var tdCSS_class = "";
                        if (_col.styleClass) {
                            if (U.isFunction(_col.styleClass)) {
                                tdCSS_class += _col.styleClass.call({
                                    column: _col,
                                    key: _col.key,
                                    isFootSum: true
                                }) + " ";
                            } else {
                                tdCSS_class += _col.styleClass + " ";
                            }
                        }
                        if (cfg.body.columnBorderWidth) tdCSS_class += "hasBorder ";
                        if (ci == cl - 1) tdCSS_class += "isLastColumn ";
                        return tdCSS_class;
                    }.call(this, col) + '" ', 'style="height: ' + cellHeight + 'px;min-height: 1px;">');

                    SS.push(function (_cellHeight) {
                        var lineHeight = cfg.body.columnHeight - cfg.body.columnPadding * 2 - cfg.body.columnBorderWidth;
                        if (!col.multiLine) {
                            _cellHeight = cfg.body.columnHeight - cfg.body.columnBorderWidth;
                        }

                        return '<span data-ax5grid-cellHolder="' + (col.multiLine ? 'multiLine' : '') + '" ' + (colAlign ? 'data-ax5grid-text-align="' + colAlign + '"' : '') + '" style="height:' + _cellHeight + 'px;line-height: ' + lineHeight + 'px;">';
                    }(cellHeight), getSumFieldValue.call(this, _list, col), '</span>');

                    SS.push('</td>');
                }
                SS.push('<td ', 'data-ax5grid-column-row="null" ', 'data-ax5grid-column-col="null" ', 'data-ax5grid-column-attr="' + "sum" + '" ', 'style="height: ' + cfg.body.columnHeight + 'px;min-height: 1px;" ', '></td>');
                SS.push('</tr>');
            }

            SS.push('</table>');

            _elTarget.empty().get(0).innerHTML = SS.join('');
            return true;
        };
        var replaceGroupTr = function replaceGroupTr(_elTargetKey, _colGroup, _groupRow, _list, _scrollConfig) {
            var _elTarget = this.$.panel[_elTargetKey],
                SS = [],
                di = void 0,
                dl = void 0,
                tri = void 0,
                trl = void 0,
                ci = void 0,
                cl = void 0,
                col = void 0,
                cellHeight = void 0,
                colAlign = void 0;

            if (typeof _scrollConfig === "undefined" || typeof _scrollConfig['paintStartRowIndex'] === "undefined") {
                _scrollConfig = {
                    paintStartRowIndex: 0,
                    paintRowCount: _list.length
                };
            }

            for (di = _scrollConfig.paintStartRowIndex, dl = function () {
                var len = void 0;
                len = _list.length;
                if (_scrollConfig.paintRowCount + _scrollConfig.paintStartRowIndex < len) {
                    len = _scrollConfig.paintRowCount + _scrollConfig.paintStartRowIndex;
                }
                return len;
            }(); di < dl; di++) {
                if (_list[di] && _groupRow && "__isGrouping" in _list[di]) {
                    var rowTable = _groupRow;
                    SS = [];
                    for (tri = 0, trl = rowTable.rows.length; tri < trl; tri++) {
                        for (ci = 0, cl = rowTable.rows[tri].cols.length; ci < cl; ci++) {
                            col = rowTable.rows[tri].cols[ci];
                            cellHeight = cfg.body.columnHeight * col.rowspan - cfg.body.columnBorderWidth;
                            colAlign = col.align || bodyAlign;

                            SS.push('<td ', 'data-ax5grid-panel-name="' + _elTargetKey + '" ', 'data-ax5grid-data-index="' + di + '" ', 'data-ax5grid-column-row="' + tri + '" ', 'data-ax5grid-column-col="' + ci + '" ', 'data-ax5grid-column-rowIndex="' + col.rowIndex + '" ', 'data-ax5grid-column-colIndex="' + col.colIndex + '" ', 'data-ax5grid-column-attr="' + (col.columnAttr || "default") + '" ', function (_focusedColumn, _selectedColumn) {
                                var attrs = "";
                                if (_focusedColumn) {
                                    attrs += 'data-ax5grid-column-focused="true" ';
                                }
                                if (_selectedColumn) {
                                    attrs += 'data-ax5grid-column-selected="true" ';
                                }
                                return attrs;
                            }(this.focusedColumn[di + "_" + col.colIndex + "_" + col.rowIndex], this.selectedColumn[di + "_" + col.colIndex + "_" + col.rowIndex]), 'colspan="' + col.colspan + '" ', 'rowspan="' + col.rowspan + '" ', 'class="' + function (_col) {
                                var tdCSS_class = "";
                                if (_col.styleClass) {
                                    if (U.isFunction(_col.styleClass)) {
                                        tdCSS_class += _col.styleClass.call({
                                            column: _col,
                                            key: _col.key,
                                            item: _list[di],
                                            index: di
                                        }) + " ";
                                    } else {
                                        tdCSS_class += _col.styleClass + " ";
                                    }
                                }
                                if (cfg.body.columnBorderWidth) tdCSS_class += "hasBorder ";
                                if (ci == cl - 1) tdCSS_class += "isLastColumn ";
                                return tdCSS_class;
                            }.call(this, col) + '" ', 'style="height: ' + cellHeight + 'px;min-height: 1px;">');

                            SS.push(function (_cellHeight) {
                                var lineHeight = cfg.body.columnHeight - cfg.body.columnPadding * 2 - cfg.body.columnBorderWidth;
                                if (!col.multiLine) {
                                    _cellHeight = cfg.body.columnHeight - cfg.body.columnBorderWidth;
                                }

                                return '<span data-ax5grid-cellHolder="' + (col.multiLine ? 'multiLine' : '') + '" ' + (colAlign ? 'data-ax5grid-text-align="' + colAlign + '"' : '') + '" style="height:' + _cellHeight + 'px;line-height: ' + lineHeight + 'px;">';
                            }(cellHeight), getGroupingValue.call(this, _list[di], di, col), '</span>');

                            SS.push('</td>');
                        }
                        SS.push('<td ', 'data-ax5grid-column-row="null" ', 'data-ax5grid-column-col="null" ', 'data-ax5grid-data-index="' + di + '" ', 'data-ax5grid-column-attr="' + "default" + '" ', 'style="height: ' + cfg.body.columnHeight + 'px;min-height: 1px;" ', '></td>');
                    }
                    _elTarget.find('tr[data-ax5grid-tr-data-index="' + di + '"]').empty().get(0).innerHTML = SS.join('');
                }
            }
        };
        var replaceTr = function replaceTr(_elTargetKey, _colGroup, _bodyRow, _list, di) {
            var _elTarget = this.$.panel[_elTargetKey],
                SS = [],
                tri = void 0,
                trl = void 0,
                ci = void 0,
                cl = void 0,
                col = void 0,
                cellHeight = void 0,
                colAlign = void 0,
                rowTable = _bodyRow,
                odi = typeof _list[di].__origin_index__ !== "undefined" ? _list[di].__origin_index__ : di;

            var tr_element = _elTarget.find('tr[data-ax5grid-tr-data-index="' + di + '"]').empty().get(0);
            if (tr_element) {
                for (tri = 0, trl = rowTable.rows.length; tri < trl; tri++) {
                    for (ci = 0, cl = rowTable.rows[tri].cols.length; ci < cl; ci++) {
                        col = rowTable.rows[tri].cols[ci];
                        cellHeight = cfg.body.columnHeight * col.rowspan - cfg.body.columnBorderWidth;
                        colAlign = col.align || bodyAlign;

                        SS.push('<td ', 'data-ax5grid-panel-name="' + _elTargetKey + '" ', 'data-ax5grid-data-index="' + di + '" ', 'data-ax5grid-data-o-index="' + odi + '" ', 'data-ax5grid-column-row="' + tri + '" ', 'data-ax5grid-column-col="' + ci + '" ', 'data-ax5grid-column-rowIndex="' + col.rowIndex + '" ', 'data-ax5grid-column-colIndex="' + col.colIndex + '" ', 'data-ax5grid-column-attr="' + (col.columnAttr || "default") + '" ', function (_focusedColumn, _selectedColumn) {
                            var attrs = "";
                            if (_focusedColumn) {
                                attrs += 'data-ax5grid-column-focused="true" ';
                            }
                            if (_selectedColumn) {
                                attrs += 'data-ax5grid-column-selected="true" ';
                            }
                            return attrs;
                        }(this.focusedColumn[di + "_" + col.colIndex + "_" + col.rowIndex], this.selectedColumn[di + "_" + col.colIndex + "_" + col.rowIndex]), 'colspan="' + col.colspan + '" ', 'rowspan="' + col.rowspan + '" ', 'class="' + function (_col) {
                            var tdCSS_class = "";
                            if (_col.styleClass) {
                                if (U.isFunction(_col.styleClass)) {
                                    tdCSS_class += _col.styleClass.call({
                                        column: _col,
                                        key: _col.key,
                                        item: _list[di],
                                        index: di
                                    }) + " ";
                                } else {
                                    tdCSS_class += _col.styleClass + " ";
                                }
                            }
                            if (cfg.body.columnBorderWidth) tdCSS_class += "hasBorder ";
                            if (ci == cl - 1) tdCSS_class += "isLastColumn ";
                            return tdCSS_class;
                        }.call(this, col) + '" ', 'style="height: ' + cellHeight + 'px;min-height: 1px;">');

                        SS.push(function (_cellHeight) {
                            var lineHeight = cfg.body.columnHeight - cfg.body.columnPadding * 2 - cfg.body.columnBorderWidth;
                            if (!col.multiLine) {
                                _cellHeight = cfg.body.columnHeight - cfg.body.columnBorderWidth;
                            }

                            return '<span data-ax5grid-cellHolder="' + (col.multiLine ? 'multiLine' : '') + '" ' + (colAlign ? 'data-ax5grid-text-align="' + colAlign + '"' : '') + '" style="height:' + _cellHeight + 'px;' + (col.multiLine ? '' : 'line-height: ' + lineHeight + 'px;') + '">';
                        }(cellHeight), getFieldValue.call(this, _list, _list[di], di, col), '</span>');
                        SS.push('</td>');
                    }
                    SS.push('<td ', 'data-ax5grid-column-row="null" ', 'data-ax5grid-column-col="null" ', 'data-ax5grid-data-index="' + di + '" ', 'data-ax5grid-column-attr="' + "default" + '" ', 'style="height: ' + cfg.body.columnHeight + 'px;min-height: 1px;" ', '></td>');
                }
                tr_element.innerHTML = SS.join('');
            }
        };

        // left
        if (this.xvar.frozenColumnIndex > 0) {
            if (this.xvar.frozenRowIndex > _dindex) {
                // 상단 행고정
                replaceTr.call(this, "top-left-body", this.leftHeaderColGroup, leftBodyRowData, list.slice(0, this.xvar.frozenRowIndex), _dindex);
            } else {
                replaceTr.call(this, "left-body-scroll", this.leftHeaderColGroup, leftBodyRowData, list, _dindex);
            }
        }

        // body
        if (this.xvar.frozenRowIndex > _dindex) {
            // 상단 행고정
            replaceTr.call(this, "top-body-scroll", this.headerColGroup, bodyRowData, list.slice(0, this.xvar.frozenRowIndex), _dindex);
        } else {
            replaceTr.call(this, "body-scroll", this.headerColGroup, bodyRowData, list, _dindex);
        }

        // body.grouping tr 다시 그리기..
        if (cfg.body.grouping) {
            // left
            if (this.xvar.frozenColumnIndex > 0) {
                if (this.xvar.frozenRowIndex > _dindex) {
                    // 상단 행고정
                    replaceGroupTr.call(this, "top-left-body", this.leftHeaderColGroup, leftBodyGroupingData, list.slice(0, this.xvar.frozenRowIndex));
                } else {
                    replaceGroupTr.call(this, "left-body-scroll", this.leftHeaderColGroup, leftBodyGroupingData, list, scrollConfig);
                }
            }

            // body
            if (this.xvar.frozenRowIndex > _dindex) {
                // 상단 행고정
                replaceGroupTr.call(this, "top-body-scroll", this.headerColGroup, bodyGroupingData, list.slice(0, this.xvar.frozenRowIndex));
            } else {
                replaceGroupTr.call(this, "body-scroll", this.headerColGroup, bodyGroupingData, list, scrollConfig);
            }
        }

        if (this.xvar.frozenColumnIndex > 0) {
            if (cfg.footSum && this.needToPaintSum) {
                // 바닥 요약
                repaintSum.call(this, "bottom-left-body", this.leftHeaderColGroup, leftFootSumData, list);
            }
        }

        if (cfg.footSum && this.needToPaintSum) {
            // 바닥 요약
            repaintSum.call(this, "bottom-body-scroll", this.headerColGroup, footSumData, list, scrollConfig);
        }
    };

    var scrollTo = function scrollTo(css, opts) {
        if (typeof opts === "undefined") opts = { timeoutUnUse: false };
        if (this.isInlineEditing) {
            for (var key in this.inlineEditing) {
                //if(this.inlineEditing[key].editor.type === "select") {}
                // 인라인 에디팅 인데 스크롤 이벤트가 발생하면 디액티브 처리
                GRID.body.inlineEdit.deActive.call(this, "ESC", key);
            }
        }

        if (this.config.asidePanelWidth > 0 && "top" in css) {
            this.$.panel["aside-body-scroll"].css({ top: css.top });
        }
        if (this.xvar.frozenColumnIndex > 0 && "top" in css) {
            this.$.panel["left-body-scroll"].css({ top: css.top });
        }
        if (this.xvar.frozenRowIndex > 0 && "left" in css) {
            this.$.panel["top-body-scroll"].css({ left: css.left });
        }

        this.$.panel["body-scroll"].css(css);

        if (this.config.footSum && "left" in css) {
            this.$.panel["bottom-body-scroll"].css({ left: css.left });
        }

        // 바디 리페인팅 this.__throttledScroll 은 body init 에서 초기화
        if (this.__throttledScroll) {
            this.__throttledScroll(css, opts);
        } else {
            if (this.config.virtualScrollY && !opts.noRepaint && "top" in css) {
                repaint.call(this);
            } else if (this.config.virtualScrollX && !opts.noRepaint && "left" in css) {
                repaint.call(this);
            }
            if (opts.callback) {
                opts.callback();
            }
        }
    };

    var blur = function blur() {
        columnSelect.focusClear.call(this);
        columnSelect.clear.call(this);
        if (this.isInlineEditing) {
            inlineEdit.deActive.call(this);
        }
    };

    var moveFocus = function moveFocus(_position) {
        var focus = {
            "UD": function UD(_dy) {
                var moveResult = true,
                    focusedColumn = void 0,
                    originalColumn = void 0,
                    while_i = void 0,
                    nPanelInfo = void 0;

                for (var c in this.focusedColumn) {
                    focusedColumn = jQuery.extend({}, this.focusedColumn[c], true);
                    break;
                }

                if (!focusedColumn) return false;

                originalColumn = this.bodyRowMap[focusedColumn.rowIndex + "_" + focusedColumn.colIndex];
                columnSelect.focusClear.call(this);
                columnSelect.clear.call(this);

                if (_dy > 0) {
                    // 아래로
                    if (focusedColumn.rowIndex + (originalColumn.rowspan - 1) + _dy > this.bodyRowTable.rows.length - 1) {
                        focusedColumn.dindex = focusedColumn.dindex + _dy;
                        focusedColumn.doindex = focusedColumn.doindex + _dy;
                        focusedColumn.rowIndex = 0;
                        if (focusedColumn.dindex > this.list.length - 1) {
                            focusedColumn.dindex = focusedColumn.doindex = this.list.length - 1;
                            moveResult = false;
                        }
                    } else {
                        focusedColumn.rowIndex = focusedColumn.rowIndex + _dy;
                    }
                } else {
                    // 위로
                    if (focusedColumn.rowIndex + _dy < 0) {
                        focusedColumn.dindex = focusedColumn.dindex + _dy;
                        focusedColumn.doindex = focusedColumn.doindex + _dy;
                        focusedColumn.rowIndex = this.bodyRowTable.rows.length - 1;
                        if (focusedColumn.dindex < 0) {
                            focusedColumn.dindex = focusedColumn.doindex = 0;
                            moveResult = false;
                        }
                    } else {
                        focusedColumn.rowIndex = focusedColumn.rowIndex + _dy;
                    }
                }

                while_i = 0;
                while (typeof this.bodyRowMap[focusedColumn.rowIndex + "_" + focusedColumn.colIndex] === "undefined") {
                    if (focusedColumn.rowIndex == 0 || while_i % 2 == (_dy > 0 ? 0 : 1)) {
                        focusedColumn.colIndex--;
                    } else {
                        focusedColumn.rowIndex--;
                    }

                    if (focusedColumn.rowIndex <= 0 && focusedColumn.colIndex <= 0) {
                        // find fail
                        moveResult = false;
                        break;
                    }
                    while_i++;
                }

                nPanelInfo = GRID.util.findPanelByColumnIndex.call(this, focusedColumn.dindex, focusedColumn.colIndex);

                // if mergeCells
                if (this.config.body.mergeCells && this.list.length) {
                    while (!this.$.panel[nPanelInfo.panelName].find('[data-ax5grid-tr-data-index="' + focusedColumn.dindex + '"]').find('[data-ax5grid-column-rowindex="' + focusedColumn.rowIndex + '"][data-ax5grid-column-colindex="' + focusedColumn.colIndex + '"]').get(0)) {

                        if (_dy > 0) {
                            focusedColumn.dindex++;
                        } else {
                            focusedColumn.dindex--;
                        }

                        if (focusedColumn.dindex < 0 || focusedColumn.dindex > this.list.length - 1) {
                            break;
                        }
                    }
                    nPanelInfo = GRID.util.findPanelByColumnIndex.call(this, focusedColumn.dindex, focusedColumn.colIndex);
                }

                focusedColumn.panelName = nPanelInfo.panelName;

                // 포커스 컬럼의 위치에 따라 스크롤 처리.ㅊㅇ

                if (focusedColumn.dindex + 1 > this.xvar.frozenRowIndex) {
                    if (focusedColumn.dindex <= this.xvar.virtualPaintStartRowIndex) {
                        var newTop = (focusedColumn.dindex - this.xvar.frozenRowIndex - 1) * this.xvar.bodyTrHeight;
                        if (newTop < 0) newTop = 0;
                        scrollTo.call(this, { top: -newTop, timeoutUnUse: false });
                        GRID.scroller.resize.call(this);
                    } else if (focusedColumn.dindex + 1 > this.xvar.virtualPaintStartRowIndex + (this.xvar.virtualPaintRowCount - 2)) {
                        scrollTo.call(this, { top: (this.xvar.virtualPaintRowCount - 2 - focusedColumn.dindex) * this.xvar.bodyTrHeight, timeoutUnUse: false });
                        GRID.scroller.resize.call(this);
                    }
                }

                this.focusedColumn[focusedColumn.dindex + "_" + focusedColumn.colIndex + "_" + focusedColumn.rowIndex] = focusedColumn;
                this.$.panel[focusedColumn.panelName].find('[data-ax5grid-tr-data-index="' + focusedColumn.dindex + '"]').find('[data-ax5grid-column-rowindex="' + focusedColumn.rowIndex + '"][data-ax5grid-column-colindex="' + focusedColumn.colIndex + '"]').attr('data-ax5grid-column-focused', "true");

                return moveResult;
            },
            "LR": function LR(_dx) {
                var moveResult = true,
                    focusedColumn = void 0,
                    originalColumn = void 0,
                    while_i = 0,
                    isScrollPanel = false,
                    containerPanelName = "",
                    nPanelInfo = void 0;

                for (var c in this.focusedColumn) {
                    focusedColumn = jQuery.extend({}, this.focusedColumn[c], true);
                    break;
                }
                if (!focusedColumn) return false;

                originalColumn = this.bodyRowMap[focusedColumn.rowIndex + "_" + focusedColumn.colIndex];

                columnSelect.focusClear.call(this);
                columnSelect.clear.call(this);

                if (_dx < 0) {
                    focusedColumn.colIndex = focusedColumn.colIndex + _dx;
                    if (focusedColumn.colIndex < 0) {
                        focusedColumn.colIndex = 0;
                        moveResult = false;
                    }
                } else {
                    focusedColumn.colIndex = focusedColumn.colIndex + _dx;
                    if (focusedColumn.colIndex > this.colGroup.length - 1) {
                        focusedColumn.colIndex = this.colGroup.length - 1;
                        moveResult = false;
                    }
                }

                if (typeof this.bodyRowMap[focusedColumn.rowIndex + "_" + focusedColumn.colIndex] === "undefined") {
                    focusedColumn.rowIndex = 0;
                }

                if (this.list[focusedColumn.dindex] && this.list[focusedColumn.dindex].__isGrouping) {
                    if (_dx < 0) {
                        while (typeof this.bodyGroupingMap[focusedColumn.rowIndex + "_" + focusedColumn.colIndex] === "undefined") {
                            focusedColumn.colIndex--;
                            if (focusedColumn.colIndex <= 0) {
                                // find fail
                                moveResult = false;
                                break;
                            }
                        }
                    } else {
                        while (typeof this.bodyGroupingMap[focusedColumn.rowIndex + "_" + focusedColumn.colIndex] === "undefined") {
                            focusedColumn.colIndex++;
                            if (focusedColumn.colIndex >= this.colGroup.length) {
                                // find fail
                                moveResult = false;
                                break;
                            }
                        }
                    }
                } else {
                    if (_dx < 0) {
                        while (typeof this.bodyRowMap[focusedColumn.rowIndex + "_" + focusedColumn.colIndex] === "undefined") {
                            focusedColumn.colIndex--;
                            if (focusedColumn.colIndex <= 0) {
                                // find fail
                                moveResult = false;
                                break;
                            }
                        }
                    } else {
                        while (typeof this.bodyRowMap[focusedColumn.rowIndex + "_" + focusedColumn.colIndex] === "undefined") {
                            focusedColumn.colIndex++;
                            if (focusedColumn.colIndex >= this.colGroup.length) {
                                // find fail
                                moveResult = false;
                                break;
                            }
                        }
                    }
                }

                nPanelInfo = GRID.util.findPanelByColumnIndex.call(this, focusedColumn.dindex, focusedColumn.colIndex);

                // if mergeCells
                if (this.config.body.mergeCells && this.list.length && focusedColumn.dindex > 1) {
                    while (!this.$.panel[nPanelInfo.panelName].find('[data-ax5grid-tr-data-index="' + focusedColumn.dindex + '"]').find('[data-ax5grid-column-rowindex="' + focusedColumn.rowIndex + '"][data-ax5grid-column-colindex="' + focusedColumn.colIndex + '"]').get(0)) {

                        focusedColumn.dindex--;

                        if (focusedColumn.dindex < 0 || focusedColumn.dindex > this.list.length - 1) {
                            break;
                        }
                    }
                    nPanelInfo = GRID.util.findPanelByColumnIndex.call(this, focusedColumn.dindex, focusedColumn.colIndex);
                }

                focusedColumn.panelName = nPanelInfo.panelName;

                // 포커스 컬럼의 위치에 따라 스크롤 처리
                var isScrollTo = function () {
                    if (!this.config.virtualScrollX) return false;
                    var scrollLeft = 0;
                    if (focusedColumn.colIndex + 1 > this.xvar.frozenColumnIndex) {
                        if (focusedColumn.colIndex <= this.xvar.paintStartColumnIndex && this.colGroup[focusedColumn.colIndex]) {
                            scrollLeft = -this.colGroup[Number(focusedColumn.colIndex)]._sx;
                            scrollTo.call(this, { left: scrollLeft });
                            GRID.header.scrollTo.call(this, { left: scrollLeft });
                            GRID.scroller.resize.call(this);
                            return true;
                        } else if (focusedColumn.colIndex >= this.xvar.paintEndColumnIndex && this.colGroup[Number(focusedColumn.colIndex)]) {
                            if (this.colGroup[Number(focusedColumn.colIndex)]._ex > this.xvar.bodyWidth) {
                                scrollLeft = this.colGroup[Number(focusedColumn.colIndex)]._ex - this.xvar.bodyWidth;
                                scrollTo.call(this, { left: -scrollLeft });
                                GRID.header.scrollTo.call(this, { left: -scrollLeft });
                                GRID.scroller.resize.call(this);
                            }
                            return true;
                        }
                    }
                    scrollLeft = null;
                    return false;
                }.call(this);

                containerPanelName = nPanelInfo.containerPanelName;
                isScrollPanel = nPanelInfo.isScrollPanel;

                this.focusedColumn[focusedColumn.dindex + "_" + focusedColumn.colIndex + "_" + focusedColumn.rowIndex] = focusedColumn;

                var $column = this.$.panel[focusedColumn.panelName].find('[data-ax5grid-tr-data-index="' + focusedColumn.dindex + '"]').find('[data-ax5grid-column-rowindex="' + focusedColumn.rowIndex + '"][data-ax5grid-column-colindex="' + focusedColumn.colIndex + '"]').attr('data-ax5grid-column-focused', "true");

                if (!isScrollTo && $column && isScrollPanel) {
                    // 스크롤 패널 이라면~
                    // todo : 컬럼이동할 때에도 scrollTo 체크
                    var newLeft = function () {
                        if ($column.position().left + $column.outerWidth() > Math.abs(this.$.panel[focusedColumn.panelName].position().left) + this.$.panel[containerPanelName].width()) {
                            return $column.position().left + $column.outerWidth() - this.$.panel[containerPanelName].width();
                        } else if (Math.abs(this.$.panel[focusedColumn.panelName].position().left) > $column.position().left) {
                            return $column.position().left;
                        } else {
                            return;
                        }
                    }.call(this);

                    if (typeof newLeft !== "undefined") {
                        GRID.header.scrollTo.call(this, { left: -newLeft });
                        scrollTo.call(this, { left: -newLeft });
                        GRID.scroller.resize.call(this);
                    }
                }

                return moveResult;
            },
            "INDEX": function INDEX(_dindex) {
                var moveResult = true,
                    focusedColumn = void 0,
                    originalColumn = void 0,
                    while_i = void 0;

                for (var c in this.focusedColumn) {
                    focusedColumn = jQuery.extend({}, this.focusedColumn[c], true);
                    break;
                }
                if (!focusedColumn) {
                    focusedColumn = {
                        rowIndex: 0,
                        colIndex: 0
                    };
                }
                originalColumn = this.bodyRowMap[focusedColumn.rowIndex + "_" + focusedColumn.colIndex];

                columnSelect.focusClear.call(this);
                columnSelect.clear.call(this);

                if (_dindex == "end") {
                    _dindex = this.list.length - 1;
                }

                focusedColumn.dindex = _dindex;
                focusedColumn.rowIndex = 0;

                while_i = 0;
                while (typeof this.bodyRowMap[focusedColumn.rowIndex + "_" + focusedColumn.colIndex] === "undefined") {
                    if (focusedColumn.rowIndex == 0 || while_i % 2 == (_dy > 0 ? 0 : 1)) {
                        focusedColumn.colIndex--;
                    } else {
                        focusedColumn.rowIndex--;
                    }

                    if (focusedColumn.rowIndex <= 0 && focusedColumn.colIndex <= 0) {
                        // find fail
                        break;
                    }
                    while_i++;
                }

                var nPanelInfo = GRID.util.findPanelByColumnIndex.call(this, focusedColumn.dindex, focusedColumn.colIndex);
                focusedColumn.panelName = nPanelInfo.panelName;

                // 포커스 컬럼의 위치에 따라 스크롤 처리.
                {
                    if (focusedColumn.dindex + 1 > this.xvar.frozenRowIndex) {
                        var virtualPaintStartRowIndex = this.xvar.virtualPaintStartRowIndex || 0;

                        if (focusedColumn.dindex < virtualPaintStartRowIndex) {
                            scrollTo.call(this, { top: -(focusedColumn.dindex - this.xvar.frozenRowIndex) * this.xvar.bodyTrHeight });
                            GRID.scroller.resize.call(this);
                        } else if (focusedColumn.dindex + 1 > virtualPaintStartRowIndex + (this.xvar.virtualPaintRowCount - 2)) {
                            //debugger;
                            //console.log((focusedColumn.dindex - this.xvar.frozenRowIndex - this.xvar.virtualPaintRowCount + 3));
                            var scrollTopValue = !this.config.virtualScrollY ? focusedColumn.dindex - this.xvar.frozenRowIndex : focusedColumn.dindex - this.xvar.frozenRowIndex - this.xvar.virtualPaintRowCount + 3;
                            scrollTo.call(this, { top: -scrollTopValue * this.xvar.bodyTrHeight });
                            GRID.scroller.resize.call(this);
                        }
                    }
                }

                this.focusedColumn[focusedColumn.dindex + "_" + focusedColumn.colIndex + "_" + focusedColumn.rowIndex] = focusedColumn;
                this.$.panel[focusedColumn.panelName].find('[data-ax5grid-tr-data-index="' + focusedColumn.dindex + '"]').find('[data-ax5grid-column-rowindex="' + focusedColumn.rowIndex + '"][data-ax5grid-column-colindex="' + focusedColumn.colIndex + '"]').attr('data-ax5grid-column-focused', "true");

                return moveResult;
            }
        };

        var processor = {
            "UP": function UP() {
                return focus["UD"].call(this, -1);
            },
            "DOWN": function DOWN() {
                return focus["UD"].call(this, 1);
            },
            "LEFT": function LEFT() {
                return focus["LR"].call(this, -1);
            },
            "RIGHT": function RIGHT() {
                return focus["LR"].call(this, 1);
            },
            "HOME": function HOME() {
                return focus["INDEX"].call(this, 0);
            },
            "END": function END() {
                return focus["INDEX"].call(this, "end");
            },
            "position": function position(_position) {
                return focus["INDEX"].call(this, _position);
            }
        };

        if (_position in processor) {
            return processor[_position].call(this);
        } else {
            return processor["position"].call(this, _position);
        }
    };

    var inlineEdit = {
        active: function active(_focusedColumn, _e, _initValue) {
            var self = this,
                dindex = void 0,
                doindex = void 0,
                colIndex = void 0,
                rowIndex = void 0,
                panelName = void 0,
                colspan = void 0,
                col = void 0,
                editor = void 0;

            for (var key in _focusedColumn) {
                panelName = _focusedColumn[key].panelName;
                dindex = _focusedColumn[key].dindex;
                doindex = _focusedColumn[key].doindex;
                colIndex = _focusedColumn[key].colIndex;
                rowIndex = _focusedColumn[key].rowIndex;
                colspan = _focusedColumn[key].colspan;

                // 인라인 에디팅을 멈춰야 하는 경우 조건
                col = this.colGroup[colIndex];
                if (!(editor = col.editor)) return this;

                // editor disabled 체크
                if (U.isFunction(editor.disabled)) {
                    if (editor.disabled.call({
                        list: this.list,
                        dindex: dindex,
                        item: this.list[dindex],
                        key: col.key,
                        value: _initValue
                    })) {
                        return this;
                    }
                }

                // 조건에 맞지 않는 에디팅 타입이면 반응 없음.
                if (!function (_editor, _type) {
                    if (_editor.type in GRID.inlineEditor) {
                        return GRID.inlineEditor[_editor.type].editMode == "popup";
                    }
                }(editor)) {
                    // 체크 박스 타입이면 값 변경 시도
                    if (editor.type == "checkbox") {
                        var checked = void 0,
                            newValue = void 0;
                        if (editor.config && editor.config.trueValue) {
                            if (checked = !(_initValue == editor.config.trueValue)) {
                                newValue = editor.config.trueValue;
                            } else {
                                newValue = editor.config.falseValue;
                            }
                        } else {
                            newValue = checked = _initValue == false || _initValue == "false" || _initValue < "1" ? "true" : "false";
                        }

                        GRID.data.setValue.call(self, dindex, doindex, col.key, newValue);
                        updateRowState.call(self, ["cellChecked"], dindex, doindex, {
                            key: col.key, rowIndex: rowIndex, colIndex: colIndex,
                            editorConfig: col.editor.config, checked: checked
                        });
                    }
                    return this;
                }

                if (this.list[dindex].__isGrouping) {
                    return false;
                }
                if (key in this.inlineEditing) {
                    return false;
                }

                this.inlineEditing[key] = {
                    editor: editor,
                    panelName: panelName,
                    columnKey: key,
                    column: _focusedColumn[key],
                    useReturnToSave: GRID.inlineEditor[editor.type].useReturnToSave
                };
                this.isInlineEditing = true;
            }
            if (this.isInlineEditing) {

                var originalValue = GRID.data.getValue.call(self, dindex, doindex, col.key),
                    initValue = function (__value, __editor) {
                    if (U.isNothing(__value)) {
                        __value = U.isNothing(originalValue) ? "" : originalValue;
                    }

                    if (__editor.type == "money") {
                        return U.number(__value, { "money": true });
                    } else {
                        return __value;
                    }
                }.call(this, _initValue, editor);

                this.inlineEditing[key].$inlineEditorCell = this.$["panel"][panelName].find('[data-ax5grid-tr-data-index="' + dindex + '"]').find('[data-ax5grid-column-rowindex="' + rowIndex + '"][data-ax5grid-column-colindex="' + colIndex + '"]').find('[data-ax5grid-cellholder]');

                this.inlineEditing[key].$inlineEditor = GRID.inlineEditor[editor.type].init(this, key, editor, this.inlineEditing[key].$inlineEditorCell, initValue);

                return true;
            }
        },
        deActive: function deActive(_msg, _key, _value) {
            // console.log(this.inlineEditing.column.dindex, this.inlineEditing.$inlineEditor.val());
            if (!this.inlineEditing[_key]) return this;

            var panelName = this.inlineEditing[_key].panelName,
                dindex = this.inlineEditing[_key].column.dindex,
                doindex = this.inlineEditing[_key].column.doindex,
                rowIndex = this.inlineEditing[_key].column.rowIndex,
                colIndex = this.inlineEditing[_key].column.colIndex,
                column = this.bodyRowMap[this.inlineEditing[_key].column.rowIndex + "_" + this.inlineEditing[_key].column.colIndex],
                editorValue = function ($inlineEditor) {
                if (typeof _value === "undefined") {
                    if ($inlineEditor.get(0).tagName == "SELECT" || $inlineEditor.get(0).tagName == "INPUT" || $inlineEditor.get(0).tagName == "TEXTAREA") {
                        return $inlineEditor.val();
                    } else {
                        _msg = "CANCEL";
                        return false;
                    }
                } else {
                    return _value;
                }
            }(this.inlineEditing[_key].$inlineEditor),
                newValue = function (__value, __editor) {
                if (__editor.type == "money") {
                    return U.number(__value);
                } else {
                    return __value;
                }
            }.call(this, editorValue, column.editor);

            var action = {
                "CANCEL": function CANCEL(_dindex, _column, _newValue) {
                    action["__clear"].call(this);
                },
                "RETURN": function RETURN(_dindex, _doindex, _column, _newValue) {
                    if (GRID.data.setValue.call(this, _dindex, _doindex, _column.key, _newValue)) {
                        action["__clear"].call(this);
                        GRID.body.repaintCell.call(this, panelName, _dindex, _doindex, rowIndex, colIndex, _newValue);
                    } else {
                        action["__clear"].call(this);
                    }
                },
                "__clear": function __clear() {
                    this.isInlineEditing = false;

                    if (this.inlineEditing[_key] && this.inlineEditing[_key].$inlineEditor) {
                        var bindedAx5ui = this.inlineEditing[_key].$inlineEditor.data("binded-ax5ui");
                        if (bindedAx5ui == "ax5picker") {
                            this.inlineEditing[_key].$inlineEditor.ax5picker("close");
                        } else if (bindedAx5ui == "ax5select") {
                            this.inlineEditing[_key].$inlineEditor.ax5select("close");
                        }
                        this.inlineEditing[_key].$inlineEditor.remove();
                        this.inlineEditing[_key].$inlineEditor = null;
                        this.inlineEditing[_key].$inlineEditorCell = null;
                    }

                    this.inlineEditing[_key] = undefined;
                    delete this.inlineEditing[_key]; // delete 지원안하는 브라우저 테스트..
                }
            };

            if (_msg in action) {
                action[_msg || "RETURN"].call(this, dindex, doindex, column, newValue);
            } else {
                action["__clear"].call(this);
            }
        },
        keydown: function keydown(key, columnKey, _options) {
            var processor = {
                "ESC": function ESC() {
                    for (var columnKey in this.inlineEditing) {
                        inlineEdit.deActive.call(this, "CANCEL", columnKey);
                    }
                },
                "RETURN": function RETURN() {
                    if (this.isInlineEditing) {
                        if (this.inlineEditing[columnKey] && this.inlineEditing[columnKey].useReturnToSave) {
                            // todo : 네이밍 검증 할 필요있음.
                            inlineEdit.deActive.call(this, "RETURN", columnKey);
                        } else {
                            return false;
                        }
                    } else {

                        for (var k in this.focusedColumn) {
                            var _column = this.focusedColumn[k],
                                column = this.bodyRowMap[_column.rowIndex + "_" + _column.colIndex],
                                _dindex3 = _column.dindex,
                                doindex = _column.doindex,
                                value = "",
                                col = this.colGroup[_column.colIndex];

                            if (column) {
                                if (!this.list[_dindex3].__isGrouping) {
                                    value = GRID.data.getValue.call(this, _dindex3, doindex, column.key);
                                }
                            }

                            if (col.editor && GRID.inlineEditor[col.editor.type].editMode === "inline") {
                                if (_options && _options.moveFocus) {} else {
                                    if (column.editor && column.editor.type == "checkbox") {
                                        value = GRID.data.getValue.call(this, _dindex3, doindex, column.key);

                                        var checked = void 0,
                                            newValue = void 0;
                                        if (column.editor.config && column.editor.config.trueValue) {
                                            // console.log(value, column.editor.config.trueValue);

                                            if (value != column.editor.config.trueValue) {
                                                newValue = column.editor.config.trueValue;
                                                checked = true;
                                            } else {
                                                newValue = column.editor.config.falseValue;
                                                checked = false;
                                            }
                                        } else {
                                            newValue = checked = value == false || value == "false" || value < "1" ? "true" : "false";
                                        }

                                        GRID.data.setValue.call(this, _dindex3, doindex, column.key, newValue);
                                        updateRowState.call(this, ["cellChecked"], _dindex3, doindex, {
                                            key: column.key, rowIndex: _column.rowIndex, colIndex: _column.colIndex,
                                            editorConfig: column.editor.config, checked: checked
                                        });
                                    }
                                }
                            } else {
                                GRID.body.inlineEdit.active.call(this, this.focusedColumn, null, value);
                            }
                        }
                    }
                    return true;
                }
            };

            if (key in processor) {
                processor[key].call(this, key, columnKey, _options);
            }
        }
    };

    var getExcelString = function getExcelString() {
        var cfg = this.config,
            list = this.list,
            bodyRowData = this.bodyRowTable,
            footSumData = this.footSumTable,
            bodyGroupingData = this.bodyGroupingTable;

        // body-scroll 의 포지션에 의존적이므로..
        var getBody = function getBody(_colGroup, _bodyRow, _groupRow, _list) {
            var SS = [],
                di = void 0,
                dl = void 0,
                tri = void 0,
                trl = void 0,
                ci = void 0,
                cl = void 0,
                col = void 0,
                val = void 0;

            //SS.push('<table border="1">');
            for (di = 0, dl = _list.length; di < dl; di++) {
                var isGroupingRow = false,
                    rowTable = void 0;

                if (_groupRow && "__isGrouping" in _list[di]) {
                    rowTable = _groupRow;
                    isGroupingRow = true;
                } else {
                    rowTable = _bodyRow;
                }

                for (tri = 0, trl = rowTable.rows.length; tri < trl; tri++) {
                    SS.push('\n<tr>');
                    for (ci = 0, cl = rowTable.rows[tri].cols.length; ci < cl; ci++) {
                        col = rowTable.rows[tri].cols[ci];
                        SS.push('<td ', 'colspan="' + col.colspan + '" ', 'rowspan="' + col.rowspan + '" ', '>', (isGroupingRow ? getGroupingValue.call(this, _list[di], di, col) : getFieldValue.call(this, _list, _list[di], di, col, val, "text")) || '&nbsp;', '</td>');
                    }
                    SS.push('\n</tr>');
                }
            }
            //SS.push('</table>');
            return SS.join('');
        };
        var getSum = function getSum(_colGroup, _bodyRow, _list) {
            var SS = [],
                tri = void 0,
                trl = void 0,
                ci = void 0,
                cl = void 0,
                col = void 0;

            //SS.push('<table border="1">');
            for (tri = 0, trl = _bodyRow.rows.length; tri < trl; tri++) {
                SS.push('\n<tr>');
                for (ci = 0, cl = _bodyRow.rows[tri].cols.length; ci < cl; ci++) {
                    col = _bodyRow.rows[tri].cols[ci];
                    SS.push('<td ', 'colspan="' + col.colspan + '" ', 'rowspan="' + col.rowspan + '" ', '>', getSumFieldValue.call(this, _list, col), '</td>');
                }
                SS.push('\n</tr>');
            }
            //SS.push('</table>');

            return SS.join('');
        };

        var po = [];
        po.push(getBody.call(this, this.headerColGroup, bodyRowData, bodyGroupingData, list));
        if (cfg.footSum) {
            // 바닥 요약
            po.push(getSum.call(this, this.headerColGroup, footSumData, list));
        }

        // right
        if (cfg.rightSum) {
            // todo : right 표현 정리
        }

        return po.join('');
    };

    var toggleCollapse = function toggleCollapse(_dindex, _doindex, _collapse) {
        if (GRID.data.toggleCollapse.call(this, _dindex, _doindex, _collapse)) {
            this.proxyList = GRID.data.getProxyList.call(this, this.list);
            this.align();
        }
    };

    // todo : tree에서 dindex만으로 구현 했을 때 오류 발생 해결. (_doindex 이용)
    var click = function click(_dindex, _doindex) {
        var that = {
            self: this,
            page: this.page,
            list: this.list,
            item: this.list[_dindex],
            dindex: _dindex
        };

        moveFocus.call(this, _dindex);
        if (this.config.body.onClick) {
            this.config.body.onClick.call(that);
        }

        that = null;
        // console.log(this.$["panel"]["body-scroll"].find('[data-ax5grid-tr-data-index="' + _dindex + '"]>td:first-child'));
    };

    var dblClick = function dblClick(_dindex, _doindex) {
        var that = {
            self: this,
            page: this.page,
            list: this.list,
            item: this.list[_dindex],
            dindex: _dindex
        };

        moveFocus.call(this, _dindex);

        if (this.config.body.onDBLClick) {
            this.config.body.onDBLClick.call(that);
        }

        that = null;
    };

    GRID.body = {
        init: init,
        repaint: repaint,
        repaintCell: repaintCell,
        repaintRow: repaintRow,
        updateRowState: updateRowState,
        updateRowStateAll: updateRowStateAll,
        scrollTo: scrollTo,
        blur: blur,
        moveFocus: moveFocus,
        inlineEdit: inlineEdit,
        getExcelString: getExcelString,
        toggleCollapse: toggleCollapse,
        click: click,
        dblClick: dblClick
    };
})();
// ax5.ui.grid.collector
(function () {

    var GRID = ax5.ui.grid;

    var U = ax5.util;

    var sum = function sum() {
        var value = 0,
            i = this.list.length;
        while (i--) {
            if (!("__groupingList" in this.list[i])) {
                value += U.number(this.list[i][this.key]);
            }
        }
        return value;
    };

    var avg = function avg() {
        var value = 0,
            i = this.list.length,
            listLength = 0;
        while (i--) {
            if (!("__groupingList" in this.list[i])) {
                value += U.number(this.list[i][this.key]);
                listLength++;
            }
        }
        return U.number(value / (listLength || 1), { "round": 2 });
    };

    GRID.collector = {
        sum: sum,
        avg: avg
    };
})();
// ax5.ui.grid.layout
(function () {

    var GRID = ax5.ui.grid,
        U = ax5.util;

    var init = function init() {};

    var clearGroupingData = function clearGroupingData(_list) {
        var i = 0,
            l = _list.length,
            returnList = [];
        for (; i < l; i++) {
            if (_list[i] && !_list[i]["__isGrouping"]) {
                if (_list[i][this.config.columnKeys.selected]) {
                    this.selectedDataIndexs.push(i);
                }
                returnList.push(jQuery.extend({}, _list[i]));
            }
        }
        return returnList;
    };

    var initData = function initData(_list) {
        this.selectedDataIndexs = [];
        // this.deletedList = [];
        // todo : deletedList 초기화 시점이 언제로 하는게 좋은가. set 메소드에서 초기화 하는 것으로 수정

        var i = 0,
            l = _list.length,
            returnList = [],
            appendIndex = 0,
            dataRealRowCount = 0,
            lineNumber = 0;

        if (this.config.body.grouping) {

            var groupingKeys = U.map(this.bodyGrouping.by, function () {
                return {
                    key: this,
                    compareString: "",
                    grouping: false,
                    list: []
                };
            });

            var gi = 0,
                gl = groupingKeys.length,
                compareString = void 0,
                appendRow = [],
                ari = void 0;
            for (; i < l + 1; i++) {
                gi = 0;

                if (_list[i] && _list[i][this.config.columnKeys.deleted]) {
                    this.deletedList.push(_list[i]);
                } else {
                    compareString = ""; // 그룹핑 구문검사용
                    appendRow = []; // 현재줄 앞에 추가해줘야 하는 줄

                    // 그룹핑 구문검사
                    for (; gi < gl; gi++) {
                        if (_list[i]) {
                            compareString += "$|$" + _list[i][groupingKeys[gi].key];
                        }

                        if (appendIndex > 0 && compareString != groupingKeys[gi].compareString) {
                            var appendRowItem = { keys: [], labels: [], list: groupingKeys[gi].list };
                            for (var ki = 0; ki < gi + 1; ki++) {
                                appendRowItem.keys.push(groupingKeys[ki].key);
                                appendRowItem.labels.push(_list[i - 1][groupingKeys[ki].key]);
                            }
                            appendRow.push(appendRowItem);
                            groupingKeys[gi].list = [];
                        }

                        groupingKeys[gi].list.push(_list[i]);
                        groupingKeys[gi].compareString = compareString;
                    }

                    // 새로 추가해야할 그룹핑 row
                    ari = appendRow.length;
                    while (ari--) {
                        returnList.push({ __isGrouping: true, __groupingList: appendRow[ari].list, __groupingBy: { keys: appendRow[ari].keys, labels: appendRow[ari].labels } });
                    }
                    //~ 그룹핑 구문 검사 완료

                    if (_list[i]) {
                        if (_list[i][this.config.columnKeys.selected]) {
                            this.selectedDataIndexs.push(i);
                        }
                        // 그룹핑이 적용된 경우 오리지널 인덱스 의미 없음 : 정렬보다 그룹핑이 더 중요하므로.
                        _list[i]["__original_index"] = _list[i]["__index"] = lineNumber;
                        returnList.push(_list[i]);

                        dataRealRowCount++;
                        appendIndex++;
                        lineNumber++;
                    }
                }
            }
        } else {
            for (; i < l; i++) {
                if (_list[i]) {
                    if (_list[i][this.config.columnKeys.deleted]) {
                        this.deletedList.push(_list[i]);
                    } else {

                        if (_list[i][this.config.columnKeys.selected]) {
                            this.selectedDataIndexs.push(i);
                        }

                        // __original_index 인덱스 키가 없다면 추가.
                        if (typeof _list[i]["__original_index"] === "undefined") {
                            _list[i]["__original_index"] = lineNumber;
                        }
                        _list[i]["__index"] = lineNumber;
                        dataRealRowCount++;
                        lineNumber++;
                        returnList.push(_list[i]);
                    }
                }
            }
        }

        // 원본 데이터의 갯수
        // grouping은 제외하고 수집됨.
        this.xvar.dataRealRowCount = dataRealRowCount;
        return returnList;
    };

    var arrangeData4tree = function arrangeData4tree(_list) {
        this.selectedDataIndexs = [];
        this.deletedList = [];
        var i = 0,
            seq = 0,
            appendIndex = 0,
            dataRealRowCount = 0,
            lineNumber = 0;

        var li = _list.length;
        var keys = this.config.tree.columnKeys;
        var hashDigit = this.config.tree.hashDigit;
        var listIndexMap = {};

        while (li--) {
            delete _list[li][keys.parentHash];
            delete _list[li][keys.selfHash];
            //delete _list[li][keys.childrenLength];
        }

        /// 루트 아이템 수집
        i = 0;
        seq = 0;
        li = _list.length;
        for (; i < li; i++) {
            if (_list[i]) {
                listIndexMap[_list[i][keys.selfKey]] = i; // 인덱싱

                if (U.isNothing(_list[i][keys.parentKey]) || _list[i][keys.parentKey] === "top") {
                    // 최상위 아이템인 경우
                    _list[i][keys.parentKey] = "top";
                    _list[i][keys.children] = [];
                    _list[i][keys.parentHash] = U.setDigit("0", hashDigit);
                    _list[i][keys.selfHash] = U.setDigit("0", hashDigit) + "." + U.setDigit(seq, hashDigit);
                    _list[i][keys.depth] = 0;
                    _list[i][keys.hidden] = false;

                    seq++;
                }
            }
        }

        /// 자식 아이템 수집
        i = 0;
        lineNumber = 0;
        for (; i < li; i++) {
            var _parent = void 0,
                _parentHash = void 0;
            if (_list[i] && _list[i][keys.parentKey] !== "top" && typeof _list[i][keys.parentHash] === "undefined") {

                if (_parent = _list[listIndexMap[_list[i][keys.parentKey]]]) {
                    _parentHash = _parent[keys.selfHash];
                    _list[i][keys.children] = [];
                    _list[i][keys.parentHash] = _parentHash;
                    _list[i][keys.selfHash] = _parentHash + "." + U.setDigit(_parent[keys.children].length, hashDigit);
                    _list[i][keys.depth] = _parent[keys.depth] + 1;
                    if (_parent[keys.collapse] || _parent[keys.hidden]) _list[i][keys.hidden] = true;
                    _parent[keys.children].push(_list[i][keys.selfKey]);
                } else {
                    _list[i][keys.parentKey] = "top";
                    _list[i][keys.children] = [];
                    _list[i][keys.parentHash] = U.setDigit("0", hashDigit);
                    _list[i][keys.selfHash] = U.setDigit("0", hashDigit) + "." + U.setDigit(seq, hashDigit);
                    _list[i][keys.hidden] = false;

                    seq++;
                }
            }

            if (_list[i]) {
                if (_list[i][this.config.columnKeys.deleted]) {
                    this.deletedList.push(_list[i]);
                    _list[i][keys.hidden] = true;
                } else if (_list[i][this.config.columnKeys.selected]) {
                    this.selectedDataIndexs.push(i);
                }

                _list[i]["__index"] = lineNumber;
                dataRealRowCount++;
                lineNumber++;
            }
        }

        this.listIndexMap = listIndexMap;
        this.xvar.dataRealRowCount = dataRealRowCount;

        return _list;
    };

    var getProxyList = function getProxyList(_list) {
        var i = 0,
            l = _list.length,
            returnList = [];
        for (; i < l; i++) {

            if (_list[i] && !_list[i][this.config.tree.columnKeys.hidden]) {
                _list[i].__origin_index__ = i;
                returnList.push(_list[i]);
            }
        }
        return returnList;
    };

    var set = function set(data) {

        var list = void 0;
        if (U.isArray(data)) {
            this.page = null;
            list = data;
        } else if ("page" in data) {
            this.page = jQuery.extend({}, data.page);
            list = data.list;
        }

        // console.log(this.list.length);

        if (this.config.tree.use) {
            this.list = arrangeData4tree.call(this, list);
            this.proxyList = getProxyList.call(this, sort.call(this, this.sortInfo, this.list));
        } else {
            this.proxyList = null;
            this.list = initData.call(this, !this.config.remoteSort && Object.keys(this.sortInfo).length ? sort.call(this, this.sortInfo, list) : list);
        }
        this.selectedDataIndexs = [];
        this.deletedList = [];

        this.needToPaintSum = true;
        this.xvar.frozenRowIndex = this.config.frozenRowIndex > this.list.length ? this.list.length : this.config.frozenRowIndex;
        this.xvar.paintStartRowIndex = undefined; // 스크롤 포지션 저장변수 초기화
        this.xvar.virtualPaintStartRowIndex = undefined; // 스크롤 포지션 저장변수 초기화
        GRID.page.navigationUpdate.call(this);

        if (this.config.body.grouping) {}
        return this;
    };

    var get = function get(_type) {
        return {
            list: this.list,
            page: this.page
        };
    };

    var getList = function getList(_type) {
        var returnList = [];
        //let list = (this.proxyList) ? this.proxyList : this.list;
        var list = this.list;
        var i = 0,
            l = list.length;
        switch (_type) {
            case "modified":
                for (; i < l; i++) {
                    if (list[i] && !list[i]["__isGrouping"] && list[i][this.config.columnKeys.modified]) {
                        returnList.push(jQuery.extend({}, list[i]));
                    }
                }
                break;
            case "selected":
                for (; i < l; i++) {
                    if (list[i] && !list[i]["__isGrouping"] && list[i][this.config.columnKeys.selected]) {
                        returnList.push(jQuery.extend({}, list[i]));
                    }
                }
                break;
            case "deleted":
                //_list = GRID.data.clearGroupingData(this.list);
                returnList = [].concat(this.deletedList);
                break;
            default:
                returnList = GRID.data.clearGroupingData.call(this, list);
        }
        return returnList;
    };

    var add = function add(_row, _dindex, _options) {
        var list = this.config.body.grouping ? clearGroupingData.call(this, this.list) : this.list;
        var processor = {
            "first": function first() {
                list = [].concat(_row).concat(list);
            },
            "last": function last() {
                list = list.concat([].concat(_row));
            }
        };

        if (this.config.tree.use) {
            var _list2 = this.list.concat([].concat(_row));

            this.list = arrangeData4tree.call(this, _list2);
            this.proxyList = getProxyList.call(this, sort.call(this, this.sortInfo, this.list));
        } else {
            if (typeof _dindex === "undefined") _dindex = "last";
            if (_dindex in processor) {
                _row[this.config.columnKeys.modified] = true;
                processor[_dindex].call(this, _row);
            } else {
                if (!U.isNumber(_dindex)) {
                    throw 'invalid argument _dindex';
                }
                if (U.isArray(_row)) {
                    for (var _i = 0, _l = _row.length; _i < _l; _i++) {
                        list.splice(_dindex + _i, 0, _row[_i]);
                    }
                } else {
                    list.splice(_dindex, 0, _row);
                }
            }

            if (this.config.body.grouping) {
                list = initData.call(this, sort.call(this, this.sortInfo, list));
            } else if (_options && _options.sort && Object.keys(this.sortInfo).length) {
                list = initData.call(this, sort.call(this, this.sortInfo, list));
            } else {
                list = initData.call(this, list);
            }

            this.list = list;
        }

        this.needToPaintSum = true;
        this.xvar.frozenRowIndex = this.config.frozenRowIndex > this.list.length ? this.list.length : this.config.frozenRowIndex;
        this.xvar.paintStartRowIndex = undefined; // 스크롤 포지션 저장변수 초기화
        this.xvar.virtualPaintStartRowIndex = undefined; // 스크롤 포지션 저장변수 초기화
        GRID.page.navigationUpdate.call(this);
        return this;
    };

    /**
     * list에서 완전 제거 하는 경우 사용.
     * ax5grid.data.remove
     */
    var remove = function remove(_dindex) {
        var list = this.config.body.grouping ? clearGroupingData.call(this, this.list) : this.list;
        var processor = {
            "first": function first() {
                if (this.config.tree.use) {
                    processor.tree.call(this, 0);
                } else {
                    list.splice(0, 1);
                }
            },
            "last": function last() {
                if (this.config.tree.use) {
                    processor.tree.call(this, list.length - 1);
                } else {
                    list.splice(list.length - 1, 1);
                }
            },
            "index": function index(_dindex) {
                if (this.config.tree.use) {
                    processor.tree.call(this, _dindex);
                } else {
                    list.splice(_dindex, 1);
                }
            },
            "selected": function selected() {
                if (this.config.tree.use) {
                    processor.tree.call(this, "selected");
                } else {
                    var __list = [],
                        i = void 0,
                        l = void 0;

                    for (i = 0, l = list.length; i < l; i++) {
                        if (!list[i][this.config.columnKeys.selected]) {
                            __list.push(list[i]);
                        }
                    }
                    list = __list;
                    __list = null;
                    i = null;
                }
            },
            "tree": function tree(_dindex) {
                var treeKeys = this.config.tree.columnKeys,
                    selfHash = list[_dindex][this.config.tree.columnKeys.selfHash];
                list = U.filter(list, function () {
                    return this[treeKeys.selfHash].substr(0, selfHash.length) != selfHash;
                });
                treeKeys = null;
                selfHash = null;
            }
        };

        if (typeof _dindex === "undefined") _dindex = "last";
        if (_dindex in processor) {
            processor[_dindex].call(this, _dindex);
        } else {
            if (!U.isNumber(_dindex)) {
                throw 'invalid argument _dindex';
            }
            processor["index"].call(this, _dindex);
        }

        if (this.config.tree.use) {
            this.list = arrangeData4tree.call(this, list);
            this.proxyList = getProxyList.call(this, sort.call(this, this.sortInfo, this.list));
        } else {
            if (this.config.body.grouping) {
                list = initData.call(this, sort.call(this, this.sortInfo, list));
            } else if (Object.keys(this.sortInfo).length) {
                list = initData.call(this, sort.call(this, this.sortInfo, list));
            } else {
                list = initData.call(this, list);
            }
            this.list = list;
        }

        this.needToPaintSum = true;
        this.xvar.frozenRowIndex = this.config.frozenRowIndex > this.list.length ? this.list.length : this.config.frozenRowIndex;
        this.xvar.paintStartRowIndex = undefined; // 스크롤 포지션 저장변수 초기화
        this.xvar.virtualPaintStartRowIndex = undefined; // 스크롤 포지션 저장변수 초기화
        GRID.page.navigationUpdate.call(this);
        return this;
    };

    /**
     * list에서 deleted 처리 repaint
     * ax5grid.data.deleteRow
     */
    var deleteRow = function deleteRow(_dindex) {
        var list = this.config.body.grouping ? clearGroupingData.call(this, this.list) : this.list;
        var processor = {
            "first": function first() {
                if (this.config.tree.use) {
                    processor.tree.call(this, 0);
                } else {
                    list[0][this.config.columnKeys.deleted] = true;
                }
            },
            "last": function last() {
                if (this.config.tree.use) {
                    processor.tree.call(this, list.length - 1);
                } else {
                    list[list.length - 1][this.config.columnKeys.deleted] = true;
                }
            },
            "selected": function selected() {
                if (this.config.tree.use) {
                    processor.tree.call(this, "selected");
                } else {
                    var i = list.length;
                    while (i--) {
                        if (list[i][this.config.columnKeys.selected]) {
                            list[i][this.config.columnKeys.deleted] = true;
                        }
                    }
                    i = null;
                }
            },
            "tree": function tree(_dindex) {
                var keys = this.config.columnKeys,
                    treeKeys = this.config.tree.columnKeys;

                if (_dindex === "selected") {

                    var i = list.length;
                    while (i--) {
                        if (list[i][this.config.columnKeys.selected]) {
                            list[i][this.config.columnKeys.deleted] = true;

                            var selfHash = list[i][treeKeys.selfHash];
                            var ii = list.length;

                            while (ii--) {
                                if (list[ii][treeKeys.selfHash].substr(0, selfHash.length) === selfHash) {
                                    list[ii][keys.deleted] = true;
                                }
                            }

                            selfHash = null;
                            ii = null;
                        }
                    }
                    i = null;
                } else {
                    var _selfHash = list[_dindex][treeKeys.selfHash];
                    var _i2 = list.length;
                    while (_i2--) {
                        if (list[_i2][treeKeys.selfHash].substr(0, _selfHash.length) !== _selfHash) {
                            list[_i2][keys.deleted] = true;
                        }
                    }
                    _selfHash = null;
                    _i2 = null;
                }

                keys = null;
                treeKeys = null;
            }
        };

        if (typeof _dindex === "undefined") _dindex = "last";

        if (_dindex in processor) {
            processor[_dindex].call(this, _dindex);
        } else {
            if (!U.isNumber(_dindex)) {
                throw 'invalid argument _dindex';
            }
            list[_dindex][this.config.columnKeys.deleted] = true;
        }

        if (this.config.tree.use) {
            this.list = arrangeData4tree.call(this, list);
            this.proxyList = getProxyList.call(this, sort.call(this, this.sortInfo, this.list));
        } else {
            if (this.config.body.grouping) {
                list = initData.call(this, sort.call(this, this.sortInfo, list));
            } else if (Object.keys(this.sortInfo).length) {
                list = initData.call(this, sort.call(this, this.sortInfo, list));
            } else {
                list = initData.call(this, list);
            }

            this.list = list;
        }

        this.needToPaintSum = true;
        this.xvar.frozenRowIndex = this.config.frozenRowIndex > this.list.length ? this.list.length : this.config.frozenRowIndex;
        this.xvar.paintStartRowIndex = undefined; // 스크롤 포지션 저장변수 초기화
        this.xvar.virtualPaintStartRowIndex = undefined; // 스크롤 포지션 저장변수 초기화
        GRID.page.navigationUpdate.call(this);
        return this;
    };

    var update = function update(_row, _dindex) {
        if (!U.isNumber(_dindex)) {
            throw 'invalid argument _dindex';
        }
        //
        this.needToPaintSum = true;
        this.list.splice(_dindex, 1, _row);

        if (this.config.body.grouping) {
            this.list = initData.call(this, clearGroupingData.call(this, this.list));
        }
    };

    var updateChild = function updateChild(_dindex, _updateData, _options) {
        var keys = this.config.tree.columnKeys,
            selfHash = void 0,
            originIndex = void 0;

        if (typeof _dindex === "undefined") return false;
        originIndex = this.proxyList[_dindex].__origin_index__;

        if (this.list[originIndex][keys.children]) {
            this.proxyList = []; // 리셋 프록시

            if (_options && _options.filter) {
                if (_options.filter.call({ item: this.list[originIndex], dindex: originIndex }, this.list[originIndex])) {
                    for (var _k in _updateData) {
                        this.list[originIndex][_k] = _updateData[_k];
                    }
                }
            } else {
                for (var _k2 in _updateData) {
                    this.list[originIndex][_k2] = _updateData[_k2];
                }
            }

            selfHash = this.list[originIndex][keys.selfHash];

            var i = 0,
                l = this.list.length;
            for (; i < l; i++) {
                if (this.list[i]) {
                    if (this.list[i][keys.parentHash].substr(0, selfHash.length) === selfHash) {

                        if (_options && _options.filter) {
                            if (_options.filter.call({ item: this.list[i], dindex: i }, this.list[i])) {
                                for (var _k3 in _updateData) {
                                    this.list[i][_k3] = _updateData[_k3];
                                }
                            }
                        } else {
                            for (var _k4 in _updateData) {
                                this.list[i][_k4] = _updateData[_k4];
                            }
                        }
                    }

                    if (!this.list[i][keys.hidden]) {
                        this.proxyList.push(this.list[i]);
                    }
                }
            }

            return true;
        } else {
            return false;
        }
    };

    var setValue = function setValue(_dindex, _doindex, _key, _value) {
        var originalValue = getValue.call(this, _dindex, _doindex, _key);
        var list = this.list;
        var listIndex = typeof _doindex === "undefined" ? _dindex : _doindex;
        this.needToPaintSum = true;

        if (originalValue !== _value) {
            if (/[\.\[\]]/.test(_key)) {
                try {
                    list[listIndex][this.config.columnKeys.modified] = true;
                    Function("val", "this" + GRID.util.getRealPathForDataItem(_key) + " = val;").call(list[listIndex], _value);
                } catch (e) {}
            } else {
                list[listIndex][this.config.columnKeys.modified] = true;
                list[listIndex][_key] = _value;
            }

            if (this.onDataChanged) {
                this.onDataChanged.call({
                    self: this,
                    list: this.list,
                    dindex: _dindex,
                    doindex: _doindex,
                    item: this.list[_dindex],
                    key: _key,
                    value: _value
                });
            }
        }

        return true;
    };

    var getValue = function getValue(_dindex, _doindex, _key, _value) {
        var list = this.list;
        var listIndex = typeof _doindex === "undefined" ? _dindex : _doindex;

        if (/[\.\[\]]/.test(_key)) {
            try {
                _value = Function("", "return this" + GRID.util.getRealPathForDataItem(_key) + ";").call(list[listIndex]);
            } catch (e) {}
        } else {
            _value = list[listIndex][_key];
        }
        return _value;
    };

    var clearSelect = function clearSelect() {
        this.selectedDataIndexs = [];
    };

    var select = function select(_dindex, _doindex, _selected, _options) {
        var cfg = this.config;

        if (typeof _doindex === "undefined") _doindex = _dindex;

        if (!this.list[_doindex]) return false;
        if (this.list[_doindex].__isGrouping) return false;
        if (this.list[_doindex][cfg.columnKeys.disableSelection]) return false;

        if (typeof _selected === "undefined") {
            if (this.list[_doindex][cfg.columnKeys.selected] = !this.list[_doindex][cfg.columnKeys.selected]) {
                this.selectedDataIndexs.push(_doindex);
            } else {
                this.selectedDataIndexs.splice(U.search(this.selectedDataIndexs, function () {
                    return this == _doindex;
                }), 1);
            }
        } else {
            if (this.list[_doindex][cfg.columnKeys.selected] = _selected) {
                this.selectedDataIndexs.push(_doindex);
            } else {
                this.selectedDataIndexs.splice(U.search(this.selectedDataIndexs, function () {
                    return this == _doindex;
                }), 1);
            }
        }

        if (this.onDataChanged && _options && _options.internalCall) {
            this.onDataChanged.call({
                self: this,
                list: this.list,
                dindex: _dindex,
                doindex: _doindex,
                item: this.list[_doindex],
                key: cfg.columnKeys.selected,
                value: this.list[_doindex][cfg.columnKeys.selected]
            });
        }

        return this.list[_doindex][cfg.columnKeys.selected];
    };

    var selectAll = function selectAll(_selected, _options) {
        var cfg = this.config,
            dindex = this.list.length;

        this.selectedDataIndexs = [];

        if (typeof _selected === "undefined") {
            while (dindex--) {
                if (this.list[dindex].__isGrouping) continue;
                if (_options && _options.filter) {
                    if (_options.filter.call(this.list[dindex]) !== true) {
                        continue;
                    }
                }
                if (this.list[dindex][cfg.columnKeys.disableSelection]) continue;

                if (this.list[dindex][cfg.columnKeys.selected] = !this.list[dindex][cfg.columnKeys.selected]) {
                    this.selectedDataIndexs.push(dindex);
                }
            }
        } else {
            while (dindex--) {
                if (this.list[dindex].__isGrouping) continue;
                if (_options && _options.filter) {
                    if (_options.filter.call(this.list[dindex]) !== true) {
                        continue;
                    }
                }
                if (this.list[dindex][cfg.columnKeys.disableSelection]) continue;

                if (this.list[dindex][cfg.columnKeys.selected] = _selected) {
                    this.selectedDataIndexs.push(dindex);
                }
            }
        }

        if (this.onDataChanged && _options && _options.internalCall) {
            this.onDataChanged.call({
                self: this,
                list: this.list
            });
        }

        return this.list;
    };

    var sort = function sort(_sortInfo, _list, _options) {
        var self = this,
            list = _list || this.list,
            sortInfoArray = [],
            lineNumber = 0;
        var getKeyValue = function getKeyValue(_item, _key, _value) {
            if (/[\.\[\]]/.test(_key)) {
                try {
                    _value = Function("", "return this" + GRID.util.getRealPathForDataItem(_key) + ";").call(_item);
                } catch (e) {}
            } else {
                _value = _item[_key];
            }
            return _value;
        };

        for (var k in _sortInfo) {
            sortInfoArray[_sortInfo[k].seq] = { key: k, order: _sortInfo[k].orderBy };
        }
        sortInfoArray = U.filter(sortInfoArray, function () {
            return typeof this !== "undefined";
        });

        // 정렬조건이 없으면 original_index값을 이용하여 정렬처리
        if (_options && _options.resetLineNumber && sortInfoArray.length === 0) {
            sortInfoArray[0] = { key: '__original_index', order: "asc" };
        }

        var i = 0,
            l = sortInfoArray.length,
            _a_val = void 0,
            _b_val = void 0;

        list.sort(function (_a, _b) {
            for (i = 0; i < l; i++) {
                _a_val = getKeyValue(_a, sortInfoArray[i].key);
                _b_val = getKeyValue(_b, sortInfoArray[i].key);

                if ((typeof _a_val === "undefined" ? "undefined" : _typeof(_a_val)) !== (typeof _b_val === "undefined" ? "undefined" : _typeof(_b_val))) {
                    _a_val = '' + _a_val;
                    _b_val = '' + _b_val;
                }
                if (_a_val < _b_val) {
                    return sortInfoArray[i].order === "asc" ? -1 : 1;
                } else if (_a_val > _b_val) {
                    return sortInfoArray[i].order === "asc" ? 1 : -1;
                }
            }
        });

        if (_options && _options.resetLineNumber) {
            i = 0, l = list.length, lineNumber = 0;
            for (; i < l; i++) {
                if (_list[i] && !_list[i]["__isGrouping"]) {
                    _list[i]["__index"] = lineNumber++;
                }
            }
        }

        if (_list) {
            return list;
        } else {
            this.xvar.frozenRowIndex = this.config.frozenRowIndex > this.list.length ? this.list.length : this.config.frozenRowIndex;
            this.xvar.paintStartRowIndex = undefined; // 스크롤 포지션 저장변수 초기화
            this.xvar.virtualPaintStartRowIndex = undefined; // 스크롤 포지션 저장변수 초기화
            GRID.page.navigationUpdate.call(this);
            return this;
        }
    };

    var append = function append(_list, _callback) {
        var self = this;

        if (this.config.tree.use) {
            var list = this.list.concat([].concat(_list));

            this.list = arrangeData4tree.call(this, list);
            this.proxyList = getProxyList.call(this, sort.call(this, this.sortInfo, this.list));
            list = null;
        } else {
            this.list = this.list.concat([].concat(_list));
        }

        this.appendProgress = true;
        GRID.page.statusUpdate.call(this);

        if (this.appendDebouncer) {
            if (self.appendDebounceTimes < this.config.debounceTime / 10) {
                clearTimeout(this.appendDebouncer);
                self.appendDebounceTimes++;
            } else {
                self.appendDebounceTimes = 0;
                appendIdle.call(self);
                _callback();
                return false;
            }
        }

        this.appendDebouncer = setTimeout(function () {
            self.appendDebounceTimes = 0;
            appendIdle.call(self);
            _callback();
        }, this.config.debounceTime);

        // todo : append bounce animation
    };

    var appendIdle = function appendIdle() {
        this.appendProgress = false;
        if (this.config.body.grouping) {
            this.list = initData.call(this, sort.call(this, this.sortInfo, this.list));
        } else {
            this.list = initData.call(this, this.list);
        }

        this.needToPaintSum = true;
        this.xvar.frozenRowIndex = this.config.frozenRowIndex > this.list.length ? this.list.length : this.config.frozenRowIndex;
        this.xvar.paintStartRowIndex = undefined; // 스크롤 포지션 저장변수 초기화
        this.xvar.virtualPaintStartRowIndex = undefined; // 스크롤 포지션 저장변수 초기화
        GRID.page.navigationUpdate.call(this);
    };

    var toggleCollapse = function toggleCollapse(_dindex, _doindx, _collapse) {
        var keys = this.config.tree.columnKeys,
            selfHash = void 0,
            originIndex = void 0;

        if (typeof _dindex === "undefined") return false;
        originIndex = this.proxyList[_dindex].__origin_index__;

        if (this.list[originIndex][keys.children]) {
            this.proxyList = []; // 리셋 프록시
            if (typeof _collapse == "undefined") {
                _collapse = !(this.list[originIndex][keys.collapse] || false);
            }

            this.list[originIndex][keys.collapse] = _collapse;
            selfHash = this.list[originIndex][keys.selfHash];

            var i = this.list.length;
            while (i--) {
                if (this.list[i]) {
                    // console.log(this.list[i][keys.parentHash].substr(0, selfHash.length), selfHash);
                    if (this.list[i][keys.parentHash].substr(0, selfHash.length) === selfHash) {
                        this.list[i][keys.hidden] = _collapse;
                    }

                    if (!this.list[i][keys.hidden]) {
                        this.proxyList.push(this.list[i]);
                    }
                }
            }

            return true;
        } else {
            return false;
        }
    };

    GRID.data = {
        init: init,
        set: set,
        get: get,
        getList: getList,
        getProxyList: getProxyList,
        setValue: setValue,
        getValue: getValue,
        clearSelect: clearSelect,
        select: select,
        selectAll: selectAll,
        add: add,
        remove: remove,
        deleteRow: deleteRow,
        update: update,
        updateChild: updateChild,
        sort: sort,
        initData: initData,
        clearGroupingData: clearGroupingData,
        append: append,
        toggleCollapse: toggleCollapse
    };
})();
/*
 * Copyright (c) 2016. tom@axisj.com
 * - github.com/thomasjang
 * - www.axisj.com
 */

// ax5.ui.grid.excel
(function () {

    var GRID = ax5.ui.grid;

    var U = ax5.util;

    var base64 = function base64(s) {
        return window.btoa(unescape(encodeURIComponent(s)));
    };

    var uri = "data:application/vnd.ms-excel;base64,";

    var getExcelTmpl = function getExcelTmpl() {
        return "\uFEFF\n{{#tables}}{{{body}}}{{/tables}}\n";
    };

    var tableToExcel = function tableToExcel(table, fileName) {
        var link = void 0,
            a = void 0,
            output = void 0,
            tables = [].concat(table);

        output = ax5.mustache.render(getExcelTmpl(), {
            worksheet: function () {
                var arr = [];
                tables.forEach(function (t, ti) {
                    arr.push({ name: "Sheet" + (ti + 1) });
                });
                return arr;
            }(),
            tables: function () {
                var arr = [];
                tables.forEach(function (t, ti) {
                    arr.push({ body: t });
                });
                return arr;
            }()
        });

        var isChrome = navigator.userAgent.indexOf("Chrome") > -1,
            isSafari = !isChrome && navigator.userAgent.indexOf("Safari") > -1,
            isIE = /*@cc_on!@*/false || !!document.documentMode; // this works with IE10 and IE11 both :)

        var blob1 = void 0,
            blankWindow = void 0,
            $iframe = void 0,
            iframe = void 0,
            anchor = void 0;

        if (navigator.msSaveOrOpenBlob) {
            blob1 = new Blob([output], { type: "text/html" });
            window.navigator.msSaveOrOpenBlob(blob1, fileName);
        } else if (isSafari) {
            // 사파리는 지원이 안되므로 그냥 테이블을 클립보드에 복사처리
            //tables
            blankWindow = window.open('about:blank', this.id + '-excel-export', 'width=600,height=400');
            blankWindow.document.write(output);
            blankWindow = null;
        } else {
            if (isIE && typeof Blob === "undefined") {
                //otherwise use the iframe and save
                //requires a blank iframe on page called txtArea1
                $iframe = jQuery('<iframe id="' + this.id + '-excel-export" style="display:none"></iframe>');
                jQuery(document.body).append($iframe);

                iframe = window[this.id + '-excel-export'];
                iframe.document.open("text/html", "replace");
                iframe.document.write(output);
                iframe.document.close();
                iframe.focus();
                iframe.document.execCommand("SaveAs", true, fileName);
                $iframe.remove();
            } else {
                // Attempt to use an alternative method
                anchor = document.body.appendChild(document.createElement("a"));

                // If the [download] attribute is supported, try to use it
                if ("download" in anchor) {
                    anchor.download = fileName;
                    anchor.href = URL.createObjectURL(new Blob([output], { type: 'text/csv' }));
                    anchor.click();
                    document.body.removeChild(anchor);
                }
            }
        }

        return true;
    };

    GRID.excel = {
        export: tableToExcel
    };
})();
// ax5.ui.grid.formatter
(function () {

    var GRID = ax5.ui.grid;

    var U = ax5.util;

    var money = function money() {
        if (typeof this.value !== "undefined") {
            var val = ('' + this.value).replace(/[^0-9^\.^\-]/g, ""),
                regExpPattern = new RegExp('([0-9])([0-9][0-9][0-9][,.])'),
                arrNumber = val.split('.'),
                returnValue = void 0;

            arrNumber[0] += '.';

            do {
                arrNumber[0] = arrNumber[0].replace(regExpPattern, '$1,$2');
            } while (regExpPattern.test(arrNumber[0]));

            return arrNumber.length > 1 ? arrNumber[0] + U.left(arrNumber[1], 2) : arrNumber[0].split('.')[0];
        } else {
            return "";
        }
    };

    GRID.formatter = {
        money: money
    };
})();
// ax5.ui.grid.header
(function () {

    var GRID = ax5.ui.grid;

    var U = ax5.util;

    var columnResizerEvent = {
        "on": function on(_columnResizer, _colIndex) {
            var self = this;
            var $columnResizer = $(_columnResizer);
            var columnResizerPositionLeft = $columnResizer.offset().left;
            var gridTargetOffsetLeft = self.$["container"]["root"].offset().left;
            self.xvar.columnResizerIndex = _colIndex;
            var resizeRange = {
                min: -self.colGroup[_colIndex]._width + 2,
                max: self.$["container"]["root"].width() - self.colGroup[_colIndex]._width
            };

            jQuery(document.body).bind(GRID.util.ENM["mousemove"] + ".ax5grid-" + this.instanceId, function (e) {
                var mouseObj = GRID.util.getMousePosition(e);
                self.xvar.__da = mouseObj.clientX - self.xvar.mousePosition.clientX;

                if (resizeRange.min > self.xvar.__da) {
                    self.xvar.__da = resizeRange.min;
                } else if (resizeRange.max < self.xvar.__da) {
                    self.xvar.__da = resizeRange.max;
                }

                if (!self.xvar.columnResizerLived) {
                    self.$["resizer"]["horizontal"].addClass("live");
                }
                self.xvar.columnResizerLived = true;
                self.$["resizer"]["horizontal"].css({
                    left: columnResizerPositionLeft + self.xvar.__da - gridTargetOffsetLeft
                });
            }).bind(GRID.util.ENM["mouseup"] + ".ax5grid-" + this.instanceId, function (e) {
                columnResizerEvent.off.call(self);
                U.stopEvent(e);
            }).bind("mouseleave.ax5grid-" + this.instanceId, function (e) {
                columnResizerEvent.off.call(self);
                U.stopEvent(e);
            });

            jQuery(document.body).attr('unselectable', 'on').css('user-select', 'none').on('selectstart', false);
        },
        "off": function off() {
            this.$["resizer"]["horizontal"].removeClass("live");
            this.xvar.columnResizerLived = false;

            if (typeof this.xvar.__da === "undefined") {} else {
                this.setColumnWidth(this.colGroup[this.xvar.columnResizerIndex]._width + this.xvar.__da, this.xvar.columnResizerIndex);
            }

            jQuery(document.body).unbind(GRID.util.ENM["mousemove"] + ".ax5grid-" + this.instanceId).unbind(GRID.util.ENM["mouseup"] + ".ax5grid-" + this.instanceId).unbind("mouseleave.ax5grid-" + this.instanceId);

            jQuery(document.body).removeAttr('unselectable').css('user-select', 'auto').off('selectstart');
        }
    };

    var init = function init() {
        // 헤더 초기화
        var self = this;

        this.$["container"]["header"].on("click", '[data-ax5grid-column-attr]', function (e) {
            var key = this.getAttribute("data-ax5grid-column-key"),
                colIndex = this.getAttribute("data-ax5grid-column-colindex"),

            //rowIndex = this.getAttribute("data-ax5grid-column-rowindex"),
            col = self.colGroup[colIndex];

            if (key === "__checkbox_header__" && self.config.header.selector) {
                var selected = this.getAttribute("data-ax5grid-selected");
                selected = U.isNothing(selected) ? true : selected !== "true";

                $(this).attr("data-ax5grid-selected", selected);
                self.selectAll({ selected: selected });

                selected = null;
            } else {
                if (key && col && col.sortable !== false && !col.sortFixed) {
                    if (col.sortable === true || self.config.sortable === true) {
                        toggleSort.call(self, col.key);
                    }
                }
            }

            GRID.body.blur.call(self);

            key = null;
            colIndex = null;
            col = null;
        });
        this.$["container"]["header"].on("mousedown", '[data-ax5grid-column-resizer]', function (e) {
            var colIndex = this.getAttribute("data-ax5grid-column-resizer");

            self.xvar.mousePosition = GRID.util.getMousePosition(e);
            columnResizerEvent.on.call(self, this, Number(colIndex));
            U.stopEvent(e);

            colIndex = null;
        }).on("dragstart", function (e) {
            U.stopEvent(e);
            return false;
        });

        resetFrozenColumn.call(this);
    };

    var resetFrozenColumn = function resetFrozenColumn() {
        var cfg = this.config,
            dividedHeaderObj = GRID.util.divideTableByFrozenColumnIndex(this.headerTable, this.xvar.frozenColumnIndex);

        this.asideHeaderData = function (dataTable) {
            var colGroup = [];
            var data = { rows: [] };
            for (var i = 0, l = dataTable.rows.length; i < l; i++) {
                data.rows[i] = { cols: [] };
                if (i === 0) {
                    var col = {
                        label: "",
                        colspan: 1,
                        rowspan: dataTable.rows.length,
                        colIndex: null
                    },
                        _col = {};

                    if (cfg.showLineNumber) {
                        _col = jQuery.extend({}, col, {
                            width: cfg.lineNumberColumnWidth,
                            _width: cfg.lineNumberColumnWidth,
                            columnAttr: "lineNumber",
                            key: "__index_header__", label: "&nbsp;"
                        });
                        colGroup.push(_col);
                        data.rows[i].cols.push(_col);
                    }
                    if (cfg.showRowSelector) {
                        _col = jQuery.extend({}, col, {
                            width: cfg.rowSelectorColumnWidth,
                            _width: cfg.rowSelectorColumnWidth,
                            columnAttr: "rowSelector",
                            key: "__checkbox_header__", label: ""
                        });
                        colGroup.push(_col);
                        data.rows[i].cols.push(_col);
                    }

                    col = null;
                }
            }

            this.asideColGroup = colGroup;
            return data;
        }.call(this, this.headerTable);

        this.leftHeaderData = dividedHeaderObj.leftData;
        this.headerData = dividedHeaderObj.rightData;
    };

    var getFieldValue = function getFieldValue(_col) {
        return _col.key === "__checkbox_header__" ? this.config.header.selector ? "<div class=\"checkBox\" style=\"max-height: " + (_col.width - 10) + "px;min-height: " + (_col.width - 10) + "px;\"></div>" : "&nbsp;" : _col.label || "&nbsp;";
    };

    var repaint = function repaint(_reset) {
        var cfg = this.config,
            colGroup = this.colGroup;

        if (_reset) {
            resetFrozenColumn.call(this);
            this.xvar.paintStartRowIndex = undefined;
            this.xvar.virtualPaintStartRowIndex = undefined; // 스크롤 포지션 저장변수 초기화
        }
        var asideHeaderData = this.asideHeaderData,
            leftHeaderData = this.leftHeaderData,
            headerData = this.headerData,
            headerAlign = cfg.header.align;

        // this.asideColGroup : asideHeaderData에서 처리 함.
        this.leftHeaderColGroup = colGroup.slice(0, this.config.frozenColumnIndex);
        this.headerColGroup = colGroup.slice(this.config.frozenColumnIndex);

        var repaintHeader = function repaintHeader(_elTarget, _colGroup, _bodyRow) {
            var tableWidth = 0,
                SS = [];
            SS.push('<table border="0" cellpadding="0" cellspacing="0">');
            SS.push('<colgroup>');
            for (var cgi = 0, cgl = _colGroup.length; cgi < cgl; cgi++) {
                SS.push('<col style="width:' + _colGroup[cgi]._width + 'px;"  />');
                tableWidth += _colGroup[cgi]._width;
            }
            SS.push('<col  />');
            SS.push('</colgroup>');

            for (var tri = 0, trl = _bodyRow.rows.length; tri < trl; tri++) {
                var trCSS_class = "";
                SS.push('<tr class="' + trCSS_class + '">');
                for (var ci = 0, cl = _bodyRow.rows[tri].cols.length; ci < cl; ci++) {
                    var col = _bodyRow.rows[tri].cols[ci];
                    var cellHeight = cfg.header.columnHeight * col.rowspan - cfg.header.columnBorderWidth;
                    var colAlign = headerAlign || col.align;
                    SS.push('<td ', 'data-ax5grid-column-attr="' + (col.columnAttr || "default") + '" ', 'data-ax5grid-column-row="' + tri + '" ', 'data-ax5grid-column-col="' + ci + '" ', function () {
                        return typeof col.key !== "undefined" ? 'data-ax5grid-column-key="' + col.key + '" ' : '';
                    }(), 'data-ax5grid-column-colindex="' + col.colIndex + '" ', 'data-ax5grid-column-rowindex="' + col.rowIndex + '" ', 'colspan="' + col.colspan + '" ', 'rowspan="' + col.rowspan + '" ', 'class="' + function (_col) {
                        var tdCSS_class = "";
                        if (_col.headerStyleClass) {
                            if (U.isFunction(_col.headerStyleClass)) {
                                tdCSS_class += _col.headerStyleClass.call({
                                    column: _col,
                                    key: _col.key
                                }) + " ";
                            } else {
                                tdCSS_class += _col.headerStyleClass + " ";
                            }
                        }
                        if (cfg.header.columnBorderWidth) tdCSS_class += "hasBorder ";
                        if (ci == cl - 1) tdCSS_class += "isLastColumn ";
                        return tdCSS_class;
                    }.call(this, col) + '" ', 'style="height: ' + cellHeight + 'px;min-height: 1px;">');

                    SS.push(function () {
                        var lineHeight = cfg.header.columnHeight - cfg.header.columnPadding * 2 - cfg.header.columnBorderWidth;
                        return '<span data-ax5grid-cellHolder="" ' + (colAlign ? 'data-ax5grid-text-align="' + colAlign + '"' : '') + ' style="height: ' + (cfg.header.columnHeight - cfg.header.columnBorderWidth) + 'px;line-height: ' + lineHeight + 'px;">';
                    }(), function () {
                        var _SS = "";

                        if (!U.isNothing(col.key) && !U.isNothing(col.colIndex) && (cfg.sortable === true || col.sortable === true) && col.sortable !== false) {
                            _SS += '<span data-ax5grid-column-sort="' + col.colIndex + '" data-ax5grid-column-sort-order="' + (colGroup[col.colIndex].sort || "") + '" />';
                        }
                        return _SS;
                    }(), getFieldValue.call(this, col), '</span>');

                    if (!U.isNothing(col.colIndex)) {
                        if (cfg.enableFilter) {
                            SS.push('<span data-ax5grid-column-filter="' + col.colIndex + '" data-ax5grid-column-filter-value=""  />');
                        }
                    }

                    SS.push('</td>');
                }
                SS.push('<td ', 'data-ax5grid-column-row="null" ', 'data-ax5grid-column-col="null" ', 'style="height: ' + cfg.header.columnHeight + 'px;min-height: 1px;" ', '></td>');
                SS.push('</tr>');
            }
            SS.push('</table>');
            _elTarget.html(SS.join(''));

            /// append column-resizer
            (function () {
                var resizerHeight = cfg.header.columnHeight * _bodyRow.rows.length - cfg.header.columnBorderWidth,
                    resizerLeft = 0,
                    AS = [];

                for (var cgi = 0, cgl = _colGroup.length; cgi < cgl; cgi++) {
                    var col = _colGroup[cgi];
                    if (!U.isNothing(col.colIndex)) {
                        //_colGroup[cgi]._width
                        resizerLeft += col._width;
                        AS.push('<div data-ax5grid-column-resizer="' + col.colIndex + '" style="height:' + resizerHeight + 'px;left: ' + (resizerLeft - 4) + 'px;"  />');
                    }
                }
                _elTarget.append(AS);
            }).call(this);

            return tableWidth;
        };

        if (cfg.asidePanelWidth > 0) {
            repaintHeader.call(this, this.$.panel["aside-header"], this.asideColGroup, asideHeaderData);
        }
        if (cfg.frozenColumnIndex > 0) {
            repaintHeader.call(this, this.$.panel["left-header"], this.leftHeaderColGroup, leftHeaderData);
        }

        this.xvar.scrollContentWidth = repaintHeader.call(this, this.$.panel["header-scroll"], this.headerColGroup, headerData);

        if (cfg.rightSum) {}
    };

    var scrollTo = function scrollTo(css) {
        this.$.panel["header-scroll"].css(css);
        return this;
    };

    var toggleSort = function toggleSort(_key) {
        var sortOrder = "",
            sortInfo = {},
            seq = 0;

        for (var k in this.sortInfo) {
            if (this.sortInfo[k].fixed) {
                sortInfo[k] = this.sortInfo[k];
                seq++;
            }
        }

        for (var i = 0, l = this.colGroup.length; i < l; i++) {
            if (this.colGroup[i].key == _key) {
                if (sortOrder == "") {
                    if (typeof this.colGroup[i].sort === "undefined") {
                        sortOrder = "desc";
                    } else if (this.colGroup[i].sort === "desc") {
                        sortOrder = "asc";
                    } else {
                        sortOrder = undefined;
                    }
                }
                this.colGroup[i].sort = sortOrder;
            } else if (!this.config.multiSort) {
                this.colGroup[i].sort = undefined;
            }

            if (typeof this.colGroup[i].sort !== "undefined") {
                if (!sortInfo[this.colGroup[i].key]) {
                    sortInfo[this.colGroup[i].key] = {
                        seq: seq++,
                        orderBy: this.colGroup[i].sort
                    };
                }
            }
        }

        this.setColumnSort(sortInfo);
        return this;
    };

    var applySortStatus = function applySortStatus(_sortInfo) {
        for (var i = 0, l = this.colGroup.length; i < l; i++) {
            for (var _key in _sortInfo) {
                if (this.colGroup[i].key == _key) {
                    this.colGroup[i].sort = _sortInfo[_key].orderBy;
                }
            }
        }
        return this;
    };

    var select = function select(_options) {
        GRID.data.select.call(this, dindex, _options && _options.selected);
        GRID.body.updateRowState.call(this, ["selected"], dindex);
    };

    var getExcelString = function getExcelString() {
        var cfg = this.config,
            colGroup = this.colGroup,
            headerData = this.headerTable,
            getHeader = function getHeader(_colGroup, _bodyRow) {
            var SS = [];
            //SS.push('<table border="1">');
            for (var tri = 0, trl = _bodyRow.rows.length; tri < trl; tri++) {
                SS.push('<tr>');
                for (var ci = 0, cl = _bodyRow.rows[tri].cols.length; ci < cl; ci++) {
                    var col = _bodyRow.rows[tri].cols[ci];
                    SS.push('<td ', 'colspan="' + col.colspan + '" ', 'rowspan="' + col.rowspan + '" ', '>', getFieldValue.call(this, col), '</td>');
                }
                SS.push('</tr>');
            }
            //SS.push('</table>');

            return SS.join('');
        };

        return getHeader.call(this, colGroup, headerData);
    };

    GRID.header = {
        init: init,
        repaint: repaint,
        scrollTo: scrollTo,
        toggleSort: toggleSort,
        applySortStatus: applySortStatus,
        getExcelString: getExcelString
    };
})();
// ax5.ui.grid.inlineEditor
(function () {

    var GRID = ax5.ui.grid;

    var edit_text = {
        useReturnToSave: true,
        editMode: "popup",
        getHtml: function getHtml(_root, _columnKey, _editor, _value) {
            if (typeof _editor.attributes !== "undefined") {
                var attributesText = "";
                for (var k in _editor.attributes) {
                    attributesText += " " + k + "='" + _editor.attributes[k] + "'";
                }
            }
            return "<input type=\"text\" data-ax5grid-editor=\"text\" value=\"" + _value + "\" " + attributesText + ">";
        },
        init: function init(_root, _columnKey, _editor, _$parent, _value) {
            var $el;
            _$parent.append($el = jQuery(this.getHtml(_root, _columnKey, _editor, _value)));
            this.bindUI(_root, _columnKey, $el, _editor, _$parent, _value);
            $el.on("blur", function () {
                GRID.body.inlineEdit.deActive.call(_root, "RETURN", _columnKey);
            });
            return $el;
        },
        bindUI: function bindUI(_root, _columnKey, _$el, _editor, _$parent, _value) {
            _$el.focus().select();
        }
    };

    var edit_money = {
        useReturnToSave: true,
        editMode: "popup",
        getHtml: function getHtml(_root, _columnKey, _editor, _value) {
            var attributesText = "";
            if (typeof _editor.attributes !== "undefined") {
                for (var k in _editor.attributes) {
                    attributesText += " " + k + "='" + _editor.attributes[k] + "'";
                }
            }
            return '<input type="text" data-ax5grid-editor="money" value="' + _value + '" ' + attributesText + '" />';
        },
        init: function init(_root, _columnKey, _editor, _$parent, _value) {
            var $el = void 0;
            _$parent.append($el = jQuery(this.getHtml(_root, _columnKey, _editor, _value)));
            this.bindUI(_root, _columnKey, $el, _editor, _$parent, _value);
            $el.on("blur", function () {
                GRID.body.inlineEdit.deActive.call(_root, "RETURN", _columnKey);
            });
            return $el;
        },
        bindUI: function bindUI(_root, _columnKey, _$el, _editor, _$parent, _value) {
            _$el.data("binded-ax5ui", "ax5formater");
            _$el.ax5formatter($.extend(true, {
                pattern: "money"
            }, _editor.config));
            _$el.focus().select();
        }
    };

    var edit_number = {
        useReturnToSave: true,
        editMode: "popup",
        getHtml: function getHtml(_root, _columnKey, _editor, _value) {
            var attributesText = "";
            if (typeof _editor.attributes !== "undefined") {
                for (var k in _editor.attributes) {
                    attributesText += " " + k + "='" + _editor.attributes[k] + "'";
                }
            }
            return '<input type="text" data-ax5grid-editor="number" value="' + _value + '" ' + attributesText + '" />';
        },
        init: function init(_root, _columnKey, _editor, _$parent, _value) {
            var $el;
            _$parent.append($el = jQuery(this.getHtml(_root, _columnKey, _editor, _value)));
            this.bindUI(_root, _columnKey, $el, _editor, _$parent, _value);
            $el.on("blur", function () {
                GRID.body.inlineEdit.deActive.call(_root, "RETURN", _columnKey);
            });
            return $el;
        },
        bindUI: function bindUI(_root, _columnKey, _$el, _editor, _$parent, _value) {
            _$el.data("binded-ax5ui", "ax5formater");
            _$el.ax5formatter($.extend(true, {
                pattern: "number"
            }, _editor.config));
            _$el.focus().select();
        }
    };

    var edit_date = {
        useReturnToSave: true,
        editMode: "popup",
        getHtml: function getHtml(_root, _columnKey, _editor, _value) {
            return '<input type="text" data-ax5grid-editor="calendar" value="' + _value + '" >';
        },
        init: function init(_root, _columnKey, _editor, _$parent, _value) {
            var $el;
            _$parent.append($el = jQuery(this.getHtml(_root, _columnKey, _editor, _value)));
            this.bindUI(_root, _columnKey, $el, _editor, _$parent, _value);
            return $el;
        },
        bindUI: function bindUI(_root, _columnKey, _$el, _editor, _$parent, _value) {
            var self = _root;
            _$el.data("binded-ax5ui", "ax5picker");

            _$el.ax5picker($.extend(true, {
                direction: "auto",
                content: {
                    type: 'date',
                    formatter: {
                        pattern: 'date'
                    }
                },
                onStateChanged: function onStateChanged() {
                    if (this.state == "open") {
                        this.self.activePicker.attr("data-ax5grid-inline-edit-picker", "date");
                    } else if (this.state == "close") {
                        GRID.body.inlineEdit.deActive.call(self, "RETURN", _columnKey);
                    }
                }
            }, _editor.config));

            _$el.focus().select();
        }
    };

    var edit_select = {
        useReturnToSave: false,
        editMode: "popup",
        getHtml: function getHtml(_root, _columnKey, _editor, _value) {
            var po = [];
            po.push('<div data-ax5select="ax5grid-editor" data-ax5select-config="{}">');
            po.push('</div>');

            return po.join('');
        },
        init: function init(_root, _columnKey, _editor, _$parent, _value) {
            var $el;
            _$parent.append($el = jQuery(this.getHtml(_root, _columnKey, _editor, _value)));
            this.bindUI(_root, _columnKey, $el, _editor, _$parent, _value);
            return $el;
        },
        bindUI: function bindUI(_root, _columnKey, _$el, _editor, _$parent, _value) {
            var eConfig = {
                columnKeys: {
                    optionValue: "value",
                    optionText: "text",
                    optionSelected: "selected"
                }
            };
            jQuery.extend(true, eConfig, _editor.config);

            eConfig.options.forEach(function (n) {
                if (n[eConfig.columnKeys.optionValue] == _value) n[eConfig.columnKeys.optionSelected] = true;
            });

            var self = _root;
            _$el.data("binded-ax5ui", "ax5select");
            _$el.ax5select($.extend(true, {
                tabIndex: 1,
                direction: "auto",
                columnKeys: eConfig.columnKeys,
                options: eConfig.options,
                onStateChanged: function onStateChanged() {
                    if (this.state == "open") {
                        this.self.activeSelectOptionGroup.attr("data-ax5grid-inline-edit-picker", "select");
                    } else if (this.state == "changeValue") {
                        GRID.body.inlineEdit.deActive.call(self, "RETURN", _columnKey, this.value[0][eConfig.columnKeys.optionValue]);
                    } else if (this.state == "close") {
                        GRID.body.inlineEdit.deActive.call(self, "ESC", _columnKey);
                    }
                }
            }, _editor.config));
            _$el.ax5select("open");
            _$el.ax5select("setValue", _value);
            _$el.find("a").focus();
        }
    };

    var edit_checkbox = {
        editMode: "inline",
        getHtml: function getHtml(_root, _editor, _value) {

            var lineHeight = _root.config.body.columnHeight - _root.config.body.columnPadding * 2 - _root.config.body.columnBorderWidth;
            var checked;
            if (_editor.config && _editor.config.trueValue) {
                checked = _value == _editor.config.trueValue ? "true" : "false";
            } else {
                checked = _value == false || _value == "false" || _value < "1" ? "false" : "true";
            }

            var eConfig = {
                marginTop: 2,
                height: lineHeight - 4
            };
            jQuery.extend(true, eConfig, _editor.config);
            eConfig.marginTop = (lineHeight - eConfig.height) / 2;

            return '<div data-ax5grid-editor="checkbox" data-ax5grid-checked="' + checked + '" style="height:' + eConfig.height + 'px;width:' + eConfig.height + 'px;margin-top:' + eConfig.marginTop + 'px;"></div>';
        }
    };

    var edit_textarea = {
        useReturnToSave: false,
        editMode: "popup",
        _getHtml: function _getHtml(_root, _columnKey, _editor, _value) {
            // init 에서 사용하게 될 HTML 태그를 만들어 줍니다.
            return "<div data-ax5grid-editor=\"textarea\"></div>";
        },
        _bindUI: function _bindUI(_root, _columnKey, _$el, _editor, _$parent, _value) {
            // 위치와 크기를 구합니다.
            var offset = _$el.offset();
            var box = {
                width: _$el.width()
            };
            var editorHeight = 150;
            var buttonHeight = 30;

            // 새로운 엘리먼트 생성
            var $newDiv = jQuery("<div data-ax5grid-popup=\"textarea\" style=\"z-index: 9999;\">\n    <textarea style=\"width:100%;height:" + (editorHeight - buttonHeight) + "px;\" class=\"form-control\">" + _value + "</textarea>\n    <div style=\"height:" + buttonHeight + "px;padding:5px;text-align: right;\">\n        <button class=\"btn btn-default\">OK</button>\n    </div>\n</div>");
            var $newTextarea = $newDiv.find("textarea");
            // 엘리먼트에 CSS 적용
            $newDiv.css({
                position: "absolute",
                left: offset.left,
                top: offset.top,
                width: box.width,
                height: editorHeight
            });
            $newDiv.find("textarea");

            // 새로운 엘리먼트를 document.body에 append
            jQuery(document.body).append($newDiv);
            $newTextarea.focus().select();

            $newTextarea.on("blur", function (e) {
                GRID.body.inlineEdit.deActive.call(_root, "RETURN", _columnKey, this.value);
                $newDiv.remove();
                ax5.util.stopEvent(e.originalEvent);
            });
            $newTextarea.on("keydown", function (e) {
                if (e.which == ax5.info.eventKeys.ESC) {
                    GRID.body.inlineEdit.deActive.call(_root, "ESC", _columnKey);
                    $newDiv.remove();
                    ax5.util.stopEvent(e.originalEvent);
                }
            });

            /// 값 변경
            /// GRID.body.inlineEdit.deActive.call(_root, "RETURN", _columnKey, this.value[0][eConfig.columnKeys.optionValue]);
            /// 에디팅 취소
            /// GRID.body.inlineEdit.deActive.call(_root, "ESC", _columnKey);
        },

        init: function init(_root, _columnKey, _editor, _$parent, _value) {
            // 인라인 에디팅 활성화 시작
            /**
             * _root : gridInstance
             * _columnKey : di + "_" + col.colIndex + "_" + col.rowIndex
             * _editor : col.editor
             * _$parent : 셀
             * _value : 값
             */
            var $el = void 0;
            _$parent.append($el = jQuery(this._getHtml(_root, _columnKey, _editor, _value)));
            // 셀에 HTML 컨텐츠 추가

            this._bindUI(_root, _columnKey, $el, _editor, _$parent, _value);
            // 이벤트 바인딩

            return $el;
        }
    };

    GRID.inlineEditor = {
        "text": edit_text,
        "money": edit_money,
        "number": edit_number,
        "date": edit_date,
        "select": edit_select,
        "checkbox": edit_checkbox,
        "textarea": edit_textarea
    };
})();
// ax5.ui.grid.page
(function () {

    var GRID = ax5.ui.grid;

    var U = ax5.util;

    var onclickPageMove = function onclickPageMove(_act) {
        var callback = function callback(_pageNo) {
            if (this.page.currentPage != _pageNo) {
                this.page.selectPage = _pageNo;
                if (this.config.page.onChange) {
                    this.config.page.onChange.call({
                        self: this,
                        page: this.page,
                        data: this.data
                    });
                }
            }
        };
        var processor = {
            "first": function first() {
                callback.call(this, 0);
            },
            "prev": function prev() {
                var pageNo = this.page.currentPage - 1;
                if (pageNo < 0) pageNo = 0;
                callback.call(this, pageNo);
            },
            "next": function next() {
                var pageNo = this.page.currentPage + 1;
                if (pageNo > this.page.totalPages - 1) pageNo = this.page.totalPages - 1;
                callback.call(this, pageNo);
            },
            "last": function last() {
                callback.call(this, this.page.totalPages - 1);
            }
        };

        if (_act in processor) {
            processor[_act].call(this);
        } else {
            callback.call(this, _act - 1);
        }
    };

    var navigationUpdate = function navigationUpdate() {
        var self = this;
        if (this.page) {
            var page = {
                hasPage: false,
                currentPage: this.page.currentPage,
                pageSize: this.page.pageSize,
                totalElements: this.page.totalElements,
                totalPages: this.page.totalPages,
                firstIcon: this.config.page.firstIcon,
                prevIcon: this.config.page.prevIcon || "«",
                nextIcon: this.config.page.nextIcon || "»",
                lastIcon: this.config.page.lastIcon
            };
            var navigationItemCount = this.config.page.navigationItemCount;

            page["@paging"] = function () {
                var returns = [],
                    startI = void 0,
                    endI = void 0;

                startI = page.currentPage - Math.floor(navigationItemCount / 2);
                if (startI < 0) startI = 0;
                endI = page.currentPage + navigationItemCount;
                if (endI > page.totalPages) endI = page.totalPages;

                if (endI - startI > navigationItemCount) {
                    endI = startI + navigationItemCount;
                }

                if (endI - startI < navigationItemCount) {
                    startI = endI - navigationItemCount;
                }
                if (startI < 0) startI = 0;

                for (var p = startI, l = endI; p < l; p++) {
                    returns.push({ 'pageNo': p + 1, 'selected': page.currentPage == p });
                }
                return returns;
            }();

            if (page["@paging"].length > 0) {
                page.hasPage = true;
            }

            this.$["page"]["navigation"].html(GRID.tmpl.get("page_navigation", page));
            this.$["page"]["navigation"].find("[data-ax5grid-page-move]").on("click", function () {
                onclickPageMove.call(self, this.getAttribute("data-ax5grid-page-move"));
            });
        } else {
            this.$["page"]["navigation"].empty();
        }
    };

    var statusUpdate = function statusUpdate() {
        if (!this.config.page.statusDisplay) {
            return;
        }

        var toRowIndex = void 0,
            rangeCount = Math.min(this.xvar.dataRowCount, this.xvar.virtualPaintRowCount);
        var data = {};

        toRowIndex = this.xvar.virtualPaintStartRowIndex + rangeCount;

        if (toRowIndex > this.xvar.dataRowCount) {
            toRowIndex = this.xvar.dataRowCount;
        }

        data.fromRowIndex = U.number(this.xvar.virtualPaintStartRowIndex + 1, { "money": true });
        data.toRowIndex = U.number(toRowIndex, { "money": true });
        data.totalElements = false;
        data.dataRealRowCount = this.xvar.dataRowCount !== this.xvar.dataRealRowCount ? U.number(this.xvar.dataRealRowCount, { "money": true }) : false;
        data.dataRowCount = U.number(this.xvar.dataRowCount, { "money": true });
        data.progress = this.appendProgress ? this.config.appendProgressIcon : "";

        if (this.page) {
            data.fromRowIndex_page = U.number(this.xvar.virtualPaintStartRowIndex + this.page.currentPage * this.page.pageSize + 1, { "money": true });
            data.toRowIndex_page = U.number(this.xvar.virtualPaintStartRowIndex + rangeCount + this.page.currentPage * this.page.pageSize, { "money": true });
            data.totalElements = U.number(this.page.totalElements, { "money": true });

            if (data.toRowIndex_page > this.page.totalElements) {
                data.toRowIndex_page = this.page.totalElements;
            }
        }

        this.$["page"]["status"].html(GRID.tmpl.get("page_status", data));
    };

    GRID.page = {
        navigationUpdate: navigationUpdate,
        statusUpdate: statusUpdate
    };
})();
// ax5.ui.grid.scroller
(function () {

    var GRID = ax5.ui.grid;

    var U = ax5.util;

    var convertScrollPosition = {
        "vertical": function vertical(css, _var) {
            var _content_height = _var._content_height - _var._panel_height,
                _scroller_height = _var._vertical_scroller_height - _var.verticalScrollBarHeight,
                top = _content_height * css.top / _scroller_height;

            if (top < 0) top = 0;else if (_content_height < top) {
                top = _content_height;
            }
            return {
                top: -top
            };
        },
        "horizontal": function horizontal(css, _var) {
            var _content_width = _var._content_width - _var._panel_width,
                _scroller_width = _var._horizontal_scroller_width - _var.horizontalScrollBarWidth,
                left = _content_width * css.left / _scroller_width;

            if (left < 0) left = 0;else if (_content_width < left) {
                left = _content_width;
            }
            return {
                left: -left
            };
        }
    };

    var convertScrollBarPosition = {
        "vertical": function vertical(_top, _var) {

            var self = this,
                type = "vertical",
                _content_height = _var._content_height - _var._panel_height,
                _scroller_height = _var._vertical_scroller_height - _var.verticalScrollBarHeight,
                top = _scroller_height * _top / _content_height,
                scrollPositon = void 0;

            if (-top > _scroller_height) {
                top = -_scroller_height;

                scrollPositon = convertScrollPosition[type].call(this, { top: -top }, {
                    _content_width: _var._content_width,
                    _content_height: _var._content_height,
                    _panel_width: _var._panel_width,
                    _panel_height: _var._panel_height,
                    _horizontal_scroller_width: _var._horizontal_scroller_width,
                    _vertical_scroller_height: _var._vertical_scroller_height,
                    verticalScrollBarHeight: _var.verticalScrollBarHeight,
                    horizontalScrollBarWidth: _var.horizontalScrollBarWidth
                });

                GRID.body.scrollTo.call(self, scrollPositon);
            }

            return -top;
        },
        "horizontal": function horizontal(_left, _var) {
            var self = this,
                type = "horizontal",
                _content_width = _var._content_width - _var._panel_width,
                _scroller_width = _var._horizontal_scroller_width - _var.horizontalScrollBarWidth,
                left = _scroller_width * _left / _content_width,
                scrollPositon = void 0;

            if (-left > _scroller_width) {
                left = -_scroller_width;
                scrollPositon = convertScrollPosition[type].call(this, { left: -left }, {
                    _content_width: _var._content_width,
                    _content_height: _var._content_height,
                    _panel_width: _var._panel_width,
                    _panel_height: _var._panel_height,
                    _horizontal_scroller_width: _var._horizontal_scroller_width,
                    _vertical_scroller_height: _var._vertical_scroller_height,
                    verticalScrollBarHeight: _var.verticalScrollBarHeight,
                    horizontalScrollBarWidth: _var.horizontalScrollBarWidth
                });

                GRID.header.scrollTo.call(self, scrollPositon);
                GRID.body.scrollTo.call(self, scrollPositon);
            }

            return -left;
        }
    };

    var scrollBarMover = {
        "click": function click(track, bar, type, e) {

            // 마우스 무브 완료 타임과 클릭타임 차이가 20 보다 작으면 클릭이벤트 막기.
            if (new Date().getTime() - GRID.scroller.moveout_timer < 20) {
                return false;
            }

            var self = this,
                trackOffset = track.offset(),
                barBox = {
                width: bar.outerWidth(), height: bar.outerHeight()
            },
                trackBox = {
                width: track.innerWidth(), height: track.innerHeight()
            },
                _vertical_scroller_height = self.$["scroller"]["vertical"].innerHeight(),
                _panel_height = self.$["panel"]["body"].height(),
                _horizontal_scroller_width = self.$["scroller"]["horizontal"].innerWidth(),
                _panel_width = self.$["panel"]["body"].width(),
                _content_height = self.xvar.scrollContentHeight,
                _content_width = self.xvar.scrollContentWidth,
                verticalScrollBarHeight = self.$["scroller"]["vertical-bar"].outerHeight(),
                horizontalScrollBarWidth = self.$["scroller"]["horizontal-bar"].outerWidth(),
                getScrollerPosition = {
                "vertical": function vertical(e) {
                    var mouseObj = GRID.util.getMousePosition(e);
                    // track을 벗어 나지 안도록 범위 체크
                    var newTop = mouseObj.clientY - trackOffset.top;
                    if (newTop < 0) {
                        newTop = 0;
                    } else if (newTop + barBox.height > trackBox.height) {
                        newTop = trackBox.height - barBox.height;
                    }
                    return { top: newTop };
                },
                "horizontal": function horizontal(e) {
                    var mouseObj = GRID.util.getMousePosition(e);
                    // track을 벗어 나지 안도록 범위 체크
                    var newLeft = mouseObj.clientX - trackOffset.left;
                    if (newLeft < 0) {
                        newLeft = 0;
                    } else if (newLeft + barBox.width > trackBox.width) {
                        newLeft = trackBox.width - barBox.width;
                    }
                    return { left: newLeft };
                }
            },
                css = getScrollerPosition[type](e);

            bar.css(css);

            var scrollPositon = convertScrollPosition[type].call(self, css, {
                _content_width: _content_width,
                _content_height: _content_height,
                _panel_width: _panel_width,
                _panel_height: _panel_height,
                _horizontal_scroller_width: _horizontal_scroller_width,
                _vertical_scroller_height: _vertical_scroller_height,
                verticalScrollBarHeight: verticalScrollBarHeight,
                horizontalScrollBarWidth: horizontalScrollBarWidth
            });
            if (type === "horizontal") GRID.header.scrollTo.call(self, scrollPositon);
            GRID.body.scrollTo.call(self, scrollPositon);

            scrollPositon = null;
        },
        "on": function on(track, bar, type, e) {
            var self = this,
                barOffset = bar.position(),
                barBox = {
                width: bar.outerWidth(), height: bar.outerHeight()
            },
                trackBox = {
                width: track.innerWidth(), height: track.innerHeight()
            },
                _vertical_scroller_height = self.$["scroller"]["vertical"].innerHeight(),
                _panel_height = self.$["panel"]["body"].height(),
                _horizontal_scroller_width = self.$["scroller"]["horizontal"].innerWidth(),
                _panel_width = self.$["panel"]["body"].width(),
                _content_height = self.xvar.scrollContentHeight,
                _content_width = self.xvar.scrollContentWidth,
                verticalScrollBarHeight = self.$["scroller"]["vertical-bar"].outerHeight(),
                horizontalScrollBarWidth = self.$["scroller"]["horizontal-bar"].outerWidth(),
                getScrollerPosition = {
                "vertical": function vertical(e) {
                    var mouseObj = GRID.util.getMousePosition(e);
                    self.xvar.__da = mouseObj.clientY - self.xvar.mousePosition.clientY;
                    // track을 벗어 나지 안도록 범위 체크
                    var newTop = barOffset.top + self.xvar.__da;
                    if (newTop < 0) {
                        newTop = 0;
                    } else if (newTop + barBox.height > trackBox.height) {
                        newTop = trackBox.height - barBox.height;
                    }
                    return { top: newTop };
                },
                "horizontal": function horizontal(e) {
                    var mouseObj = GRID.util.getMousePosition(e);
                    self.xvar.__da = mouseObj.clientX - self.xvar.mousePosition.clientX;
                    // track을 벗어 나지 안도록 범위 체크
                    var newLeft = barOffset.left + self.xvar.__da;
                    if (newLeft < 0) {
                        newLeft = 0;
                    } else if (newLeft + barBox.width > trackBox.width) {
                        newLeft = trackBox.width - barBox.width;
                    }
                    return { left: newLeft };
                }
            };

            self.xvar.__da = 0; // 이동량 변수 초기화 (계산이 잘못 될까바)

            jQuery(document.body).bind(GRID.util.ENM["mousemove"] + ".ax5grid-" + this.instanceId, function (e) {
                var css = getScrollerPosition[type](e);
                bar.css(css);

                var scrollPositon = convertScrollPosition[type].call(self, css, {
                    _content_width: _content_width,
                    _content_height: _content_height,
                    _panel_width: _panel_width,
                    _panel_height: _panel_height,
                    _horizontal_scroller_width: _horizontal_scroller_width,
                    _vertical_scroller_height: _vertical_scroller_height,
                    verticalScrollBarHeight: verticalScrollBarHeight,
                    horizontalScrollBarWidth: horizontalScrollBarWidth
                });

                if (type === "horizontal") GRID.header.scrollTo.call(self, scrollPositon);

                GRID.body.scrollTo.call(self, scrollPositon);
            }).bind(GRID.util.ENM["mouseup"] + ".ax5grid-" + this.instanceId, function (e) {
                scrollBarMover.off.call(self, e);
            }).bind("mouseleave.ax5grid-" + this.instanceId, function (e) {
                scrollBarMover.off.call(self, e);
            });

            jQuery(document.body).attr('unselectable', 'on').css('user-select', 'none').on('selectstart', false);
        },
        "off": function off(e) {
            ax5.util.stopEvent(e.originalEvent);
            GRID.scroller.moveout_timer = new Date().getTime();

            jQuery(document.body).unbind(GRID.util.ENM["mousemove"] + ".ax5grid-" + this.instanceId).unbind(GRID.util.ENM["mouseup"] + ".ax5grid-" + this.instanceId).unbind("mouseleave.ax5grid-" + this.instanceId);

            jQuery(document.body).removeAttr('unselectable').css('user-select', 'auto').off('selectstart');
        }
    };

    var scrollContentMover = {
        "wheel": function wheel(delta) {
            var self = this,
                _body_scroll_position = self.$["panel"]["body-scroll"].position(),
                _panel_height = self.xvar.body_panel_height,
                _panel_width = self.xvar.body_panel_width,
                _content_height = self.xvar.scrollContentHeight,
                _content_width = self.xvar.scrollContentWidth;

            if (isNaN(_content_height) || isNaN(_content_width)) {
                return false;
            }

            var newLeft = void 0,
                newTop = void 0,
                _top_is_end = false,
                _left_is_end = false;

            newLeft = _body_scroll_position.left - delta.x;
            newTop = _body_scroll_position.top - delta.y;

            // newTop이 범위를 넘었는지 체크
            if (newTop >= 0) {
                newTop = 0;
                _top_is_end = true;
            } else if (newTop <= _panel_height - _content_height) {
                newTop = _panel_height - _content_height;
                if (newTop >= 0) newTop = 0;
                _top_is_end = true;
            } else {
                if (delta.y == 0) _top_is_end = true;
            }

            // newLeft이 범위를 넘었는지 체크
            if (newLeft >= 0) {
                newLeft = 0;
                _left_is_end = true;
            } else if (newLeft <= _panel_width - _content_width) {
                newLeft = _panel_width - _content_width;
                if (newLeft >= 0) newLeft = 0;
                _left_is_end = true;
            } else {
                if (delta.x == 0) _left_is_end = true;
            }

            GRID.header.scrollTo.call(self, { left: newLeft });
            GRID.body.scrollTo.call(self, { left: newLeft, top: newTop }, {
                callback: function callback() {
                    resize.call(self);
                }
            });

            return !_top_is_end || !_left_is_end;
        },
        "on": function on() {
            var self = this,
                _body_scroll_position = self.$["panel"]["body-scroll"].position(),
                _panel_height = self.xvar.body_panel_height,
                _panel_width = self.xvar.body_panel_width,
                _content_height = self.xvar.scrollContentHeight,
                _content_width = self.xvar.scrollContentWidth,
                getContentPosition = function getContentPosition(e) {
                var mouseObj = GRID.util.getMousePosition(e),
                    newLeft = void 0,
                    newTop = void 0;

                self.xvar.__x_da = mouseObj.clientX - self.xvar.mousePosition.clientX;
                self.xvar.__y_da = mouseObj.clientY - self.xvar.mousePosition.clientY;

                newLeft = _body_scroll_position.left + self.xvar.__x_da;
                newTop = _body_scroll_position.top + self.xvar.__y_da;

                // newTop이 범위를 넘었는지 체크
                if (newTop >= 0) {
                    newTop = 0;
                } else if (newTop <= _panel_height - _content_height) {
                    newTop = _panel_height - _content_height;
                    if (newTop >= 0) newTop = 0;
                }

                // newLeft이 범위를 넘었는지 체크
                if (newLeft >= 0) {
                    newLeft = 0;
                } else if (newLeft <= _panel_width - _content_width) {
                    newLeft = _panel_width - _content_width;
                    if (newLeft >= 0) newLeft = 0;
                }

                return {
                    left: newLeft, top: newTop
                };
            };

            this.xvar.__x_da = 0; // 이동량 변수 초기화
            this.xvar.__y_da = 0; // 계산이 잘못 될까바
            this.xvar.touchmoved = false;

            jQuery(document.body).on("touchmove" + ".ax5grid-" + this.instanceId, function (e) {
                var css = getContentPosition(e);

                resize.call(self);
                GRID.header.scrollTo.call(self, { left: css.left });
                GRID.body.scrollTo.call(self, css, { noRepaint: "noRepaint" });
                U.stopEvent(e.originalEvent);
                self.xvar.touchmoved = true;
            }).on("touchend" + ".ax5grid-" + this.instanceId, function (e) {
                if (self.xvar.touchmoved) {
                    var css = getContentPosition(e);

                    resize.call(self);
                    GRID.header.scrollTo.call(self, { left: css.left });
                    GRID.body.scrollTo.call(self, css);
                    U.stopEvent(e.originalEvent);
                    scrollContentMover.off.call(self);
                }
            });

            jQuery(document.body).attr('unselectable', 'on').css('user-select', 'none').on('selectstart', false);
        },
        "off": function off() {

            jQuery(document.body).off("touchmove" + ".ax5grid-" + this.instanceId).off("touchend" + ".ax5grid-" + this.instanceId);

            jQuery(document.body).removeAttr('unselectable').css('user-select', 'auto').off('selectstart');
        }
    };

    var init = function init() {
        var self = this,
            margin = this.config.scroller.trackPadding;

        if (margin == 0) {
            this.$["scroller"]["vertical-bar"].css({ width: this.config.scroller.size, left: -1 });
            this.$["scroller"]["horizontal-bar"].css({ height: this.config.scroller.size, top: -1 });
        } else {
            this.$["scroller"]["vertical-bar"].css({ width: this.config.scroller.size - (margin + 1), left: margin / 2 });
            this.$["scroller"]["horizontal-bar"].css({ height: this.config.scroller.size - (margin + 1), top: margin / 2 });
        }

        this.$["scroller"]["vertical-bar"].on(GRID.util.ENM["mousedown"], function (e) {
            this.xvar.mousePosition = GRID.util.getMousePosition(e);
            scrollBarMover.on.call(this, this.$["scroller"]["vertical"], this.$["scroller"]["vertical-bar"], "vertical", e);
        }.bind(this)).on("dragstart", function (e) {
            U.stopEvent(e);
            return false;
        });

        this.$["scroller"]["vertical"].on("click", function (e) {
            if (e.target.getAttribute("data-ax5grid-scroller") == "vertical") {
                scrollBarMover.click.call(this, this.$["scroller"]["vertical"], this.$["scroller"]["vertical-bar"], "vertical", e);
            }
        }.bind(this));

        this.$["scroller"]["horizontal-bar"].on(GRID.util.ENM["mousedown"], function (e) {
            this.xvar.mousePosition = GRID.util.getMousePosition(e);
            scrollBarMover.on.call(this, this.$["scroller"]["horizontal"], this.$["scroller"]["horizontal-bar"], "horizontal", e);
        }.bind(this)).on("dragstart", function (e) {
            U.stopEvent(e);
            return false;
        });

        this.$["scroller"]["horizontal"].on("click", function (e) {
            if (e.target.getAttribute("data-ax5grid-scroller") == "horizontal") {
                scrollBarMover.click.call(this, this.$["scroller"]["horizontal"], this.$["scroller"]["horizontal-bar"], "horizontal", e);
            }
        }.bind(this));

        this.$["container"]["body"].on('mousewheel DOMMouseScroll', function (e) {
            var E = e.originalEvent,
                delta = { x: 0, y: 0 };

            if (E.detail) {
                delta.y = E.detail * 10;
            } else {
                if (typeof E.deltaY === "undefined") {
                    delta.y = -E.wheelDelta;
                    delta.x = 0;
                } else {
                    delta.y = E.deltaY;
                    delta.x = E.deltaX;
                }
            }

            if (scrollContentMover.wheel.call(this, delta)) {
                U.stopEvent(e);
            }
        }.bind(this));

        if (ax5.info.supportTouch) {
            this.$["container"]["body"].on("touchstart", '[data-ax5grid-panel]', function (e) {
                self.xvar.mousePosition = GRID.util.getMousePosition(e);
                scrollContentMover.on.call(self);
            });
        }

        this.xvar.body_panel_height = this.$["panel"]["body"].height();
        this.xvar.body_panel_width = this.$["panel"]["body"].width();
    };

    var resize = function resize() {
        var _vertical_scroller_height = this.$["scroller"]["vertical"].height(),
            _horizontal_scroller_width = this.$["scroller"]["horizontal"].width(),
            _panel_height = this.$["panel"]["body"].height(),
            _panel_width = this.$["panel"]["body"].width(),
            _content_height = this.xvar.scrollContentHeight,
            _content_width = this.xvar.scrollContentWidth,
            verticalScrollBarHeight = _panel_height * _vertical_scroller_height / _content_height,
            horizontalScrollBarWidth = _panel_width * _horizontal_scroller_width / _content_width;

        if (verticalScrollBarHeight < this.config.scroller.barMinSize) verticalScrollBarHeight = this.config.scroller.barMinSize;
        if (horizontalScrollBarWidth < this.config.scroller.barMinSize) horizontalScrollBarWidth = this.config.scroller.barMinSize;

        this.$["scroller"]["vertical-bar"].css({
            top: convertScrollBarPosition.vertical.call(this, this.$.panel["body-scroll"].position().top, {
                _content_width: _content_width,
                _content_height: _content_height,
                _panel_width: _panel_width,
                _panel_height: _panel_height,
                _horizontal_scroller_width: _horizontal_scroller_width,
                _vertical_scroller_height: _vertical_scroller_height,
                verticalScrollBarHeight: verticalScrollBarHeight,
                horizontalScrollBarWidth: horizontalScrollBarWidth
            }),
            height: verticalScrollBarHeight
        });

        this.$["scroller"]["horizontal-bar"].css({
            left: convertScrollBarPosition.horizontal.call(this, this.$.panel["body-scroll"].position().left, {
                _content_width: _content_width,
                _content_height: _content_height,
                _panel_width: _panel_width,
                _panel_height: _panel_height,
                _horizontal_scroller_width: _horizontal_scroller_width,
                _vertical_scroller_height: _vertical_scroller_height,
                verticalScrollBarHeight: verticalScrollBarHeight,
                horizontalScrollBarWidth: horizontalScrollBarWidth
            }),
            width: horizontalScrollBarWidth
        });

        this.xvar.body_panel_height = _panel_height;
        this.xvar.body_panel_width = _panel_width;

        _vertical_scroller_height = null;
        _horizontal_scroller_width = null;
        _panel_height = null;
        _panel_width = null;
        _content_height = null;
        _content_width = null;
        verticalScrollBarHeight = null;
        horizontalScrollBarWidth = null;
    };

    GRID.scroller = {
        // 타이머
        moveout_timer: new Date().getTime(),
        init: init,
        resize: resize
    };
})();
// ax5.ui.grid.tmpl
(function () {

    var GRID = ax5.ui.grid;

    var main = function main() {
        return "<div data-ax5grid-container=\"root\" data-ax5grid-instance=\"{{instanceId}}\">\n            <div data-ax5grid-container=\"hidden\">\n                <textarea data-ax5grid-form=\"clipboard\"></textarea>\n            </div>\n            <div data-ax5grid-container=\"header\">\n                <div data-ax5grid-panel=\"aside-header\"></div>\n                <div data-ax5grid-panel=\"left-header\"></div>\n                <div data-ax5grid-panel=\"header\">\n                    <div data-ax5grid-panel-scroll=\"header\"></div>\n                </div>\n                <div data-ax5grid-panel=\"right-header\"></div>\n            </div>\n            <div data-ax5grid-container=\"body\">\n                <div data-ax5grid-panel=\"top-aside-body\"></div>\n                <div data-ax5grid-panel=\"top-left-body\"></div>\n                <div data-ax5grid-panel=\"top-body\">\n                    <div data-ax5grid-panel-scroll=\"top-body\"></div>\n                </div>\n                <div data-ax5grid-panel=\"top-right-body\"></div>\n                <div data-ax5grid-panel=\"aside-body\">\n                    <div data-ax5grid-panel-scroll=\"aside-body\"></div>\n                </div>\n                <div data-ax5grid-panel=\"left-body\">\n                    <div data-ax5grid-panel-scroll=\"left-body\"></div>\n                </div>\n                <div data-ax5grid-panel=\"body\">\n                    <div data-ax5grid-panel-scroll=\"body\"></div>\n                </div>\n                <div data-ax5grid-panel=\"right-body\">\n                  <div data-ax5grid-panel-scroll=\"right-body\"></div>\n                </div>\n                <div data-ax5grid-panel=\"bottom-aside-body\"></div>\n                <div data-ax5grid-panel=\"bottom-left-body\"></div>\n                <div data-ax5grid-panel=\"bottom-body\">\n                    <div data-ax5grid-panel-scroll=\"bottom-body\"></div>\n                </div>\n                <div data-ax5grid-panel=\"bottom-right-body\"></div>\n            </div>\n            <div data-ax5grid-container=\"page\">\n                <div data-ax5grid-page=\"holder\">\n                    <div data-ax5grid-page=\"navigation\"></div>\n                    <div data-ax5grid-page=\"status\"></div>\n                </div>\n            </div>\n            <div data-ax5grid-container=\"scroller\">\n                <div data-ax5grid-scroller=\"vertical\">\n                    <div data-ax5grid-scroller=\"vertical-bar\"></div>    \n                </div>\n                <div data-ax5grid-scroller=\"horizontal\">\n                    <div data-ax5grid-scroller=\"horizontal-bar\"></div>\n                </div>\n                <div data-ax5grid-scroller=\"corner\"></div>\n            </div>\n            <div data-ax5grid-resizer=\"vertical\"></div>\n            <div data-ax5grid-resizer=\"horizontal\"></div>\n        </div>";
    };

    var page_navigation = function page_navigation() {
        return "<div data-ax5grid-page-navigation=\"holder\">\n            {{#hasPage}}\n            <div data-ax5grid-page-navigation=\"cell\">    \n                {{#firstIcon}}<button type=\"button\" data-ax5grid-page-move=\"first\">{{{firstIcon}}}</button>{{/firstIcon}}\n                <button type=\"button\" data-ax5grid-page-move=\"prev\">{{{prevIcon}}}</button>\n            </div>\n            <div data-ax5grid-page-navigation=\"cell-paging\">\n                {{#@paging}}\n                <button type=\"button\" data-ax5grid-page-move=\"{{pageNo}}\" data-ax5grid-page-selected=\"{{selected}}\">{{pageNo}}</button>\n                {{/@paging}}\n            </div>\n            <div data-ax5grid-page-navigation=\"cell\">\n                <button type=\"button\" data-ax5grid-page-move=\"next\">{{{nextIcon}}}</button>\n                {{#lastIcon}}<button type=\"button\" data-ax5grid-page-move=\"last\">{{{lastIcon}}}</button>{{/lastIcon}}\n            </div>\n            {{/hasPage}}\n        </div>";
    };

    var page_status = function page_status() {
        return "<span>{{{progress}}} {{fromRowIndex}} - {{toRowIndex}} of {{dataRowCount}} {{#totalElements}}&nbsp; Total {{.}}{{/totalElements}}</span>";
    };

    GRID.tmpl = {
        "main": main,
        "page_navigation": page_navigation,
        "page_status": page_status,

        get: function get(tmplName, data, columnKeys) {
            var template = GRID.tmpl[tmplName].call(this, columnKeys);
            ax5.mustache.parse(template);
            return ax5.mustache.render(template, data);
        }
    };
})();
// ax5.ui.grid.util
(function () {

    var GRID = ax5.ui.grid;

    var U = ax5.util;

    /**
     * @method ax5grid.util.divideTableByFrozenColumnIndex
     * @param _table
     * @param _frozenColumnIndex
     * @returns {{leftHeaderData: {rows: Array}, headerData: {rows: Array}}}
     */
    var divideTableByFrozenColumnIndex = function divideTableByFrozenColumnIndex(_table, _frozenColumnIndex) {

        var tempTable_l = { rows: [] },
            tempTable_r = { rows: [] };

        for (var r = 0, rl = _table.rows.length; r < rl; r++) {
            var row = _table.rows[r];

            tempTable_l.rows[r] = { cols: [] };
            tempTable_r.rows[r] = { cols: [] };

            for (var c = 0, cl = row.cols.length; c < cl; c++) {
                var col = jQuery.extend({}, row.cols[c]),
                    colStartIndex = col.colIndex,
                    colEndIndex = col.colIndex + col.colspan;

                if (colStartIndex < _frozenColumnIndex) {
                    if (colEndIndex <= _frozenColumnIndex) {
                        // 좌측편에 변형없이 추가
                        tempTable_l.rows[r].cols.push(col);
                    } else {
                        var leftCol = jQuery.extend({}, col),
                            rightCol = jQuery.extend({}, leftCol);

                        leftCol.colspan = _frozenColumnIndex - leftCol.colIndex;
                        rightCol.colIndex = _frozenColumnIndex;
                        rightCol.colspan = col.colspan - leftCol.colspan;

                        tempTable_l.rows[r].cols.push(leftCol);
                        if (rightCol.colspan) {
                            tempTable_r.rows[r].cols.push(rightCol);
                        }
                    }
                } else {
                    // 오른편
                    tempTable_r.rows[r].cols.push(col);
                }

                col = null;
                colStartIndex = null;
                colEndIndex = null;
            }

            row = null;
        }

        return {
            leftData: tempTable_l,
            rightData: tempTable_r
        };
    };

    var getTableByStartEndColumnIndex = function getTableByStartEndColumnIndex(_table, _startColumnIndex, _endColumnIndex) {

        var tempTable = { rows: [] };
        for (var r = 0, rl = _table.rows.length; r < rl; r++) {
            var row = _table.rows[r];

            tempTable.rows[r] = { cols: [] };
            for (var c = 0, cl = row.cols.length; c < cl; c++) {
                var col = jQuery.extend({}, row.cols[c]),
                    colStartIndex = col.colIndex,
                    colEndIndex = col.colIndex + col.colspan;

                if (_startColumnIndex <= colStartIndex || colEndIndex <= _endColumnIndex) {
                    if (_startColumnIndex <= colStartIndex && colEndIndex <= _endColumnIndex) {
                        // 변형없이 추가
                        tempTable.rows[r].cols.push(col);
                    } else if (_startColumnIndex > colStartIndex && colEndIndex > _startColumnIndex) {
                        // 앞에서 걸친경우
                        col.colspan = colEndIndex - _startColumnIndex;
                        tempTable.rows[r].cols.push(col);
                    } else if (colEndIndex > _endColumnIndex && colStartIndex <= _endColumnIndex) {
                        tempTable.rows[r].cols.push(col);
                    }
                }
            }
        }

        return tempTable;
    };

    var getMousePosition = function getMousePosition(e) {
        var mouseObj = void 0,
            originalEvent = e.originalEvent ? e.originalEvent : e;

        mouseObj = 'changedTouches' in originalEvent && originalEvent.changedTouches ? originalEvent.changedTouches[0] : originalEvent;
        // clientX, Y 쓰면 스크롤에서 문제 발생
        return {
            clientX: mouseObj.pageX,
            clientY: mouseObj.pageY
        };
    };

    var ENM = {
        "mousedown": ax5.info.supportTouch ? "touchstart" : "mousedown",
        "mousemove": ax5.info.supportTouch ? "touchmove" : "mousemove",
        "mouseup": ax5.info.supportTouch ? "touchend" : "mouseup"
    };

    var makeHeaderTable = function makeHeaderTable(_columns) {
        var columns = _columns,
            cfg = this.config,
            table = {
            rows: []
        },
            colIndex = 0,
            maekRows = function maekRows(_columns, depth, parentField) {
            var row = { cols: [] };
            var i = 0,
                l = _columns.length;
            var colspan = 1;

            for (; i < l; i++) {
                var field = jQuery.extend({}, _columns[i]);
                colspan = 1;

                if (!field.hidden) {
                    field.colspan = 1;
                    field.rowspan = 1;

                    field.rowIndex = depth;
                    field.colIndex = function () {
                        if (!parentField) {
                            return colIndex++;
                        } else {
                            colIndex = parentField.colIndex + i + 1;
                            return parentField.colIndex + i;
                        }
                    }();

                    row.cols.push(field); // 복제된 필드 삽입

                    if ('columns' in field) {
                        colspan = maekRows(field.columns, depth + 1, field);
                    } else {
                        field.width = 'width' in field ? field.width : cfg.columnMinWidth;
                    }
                    field.colspan = colspan;
                } else {}
            }

            if (row.cols.length > 0) {
                if (!table.rows[depth]) {
                    table.rows[depth] = { cols: [] };
                }
                table.rows[depth].cols = table.rows[depth].cols.concat(row.cols);
                return row.cols.length - 1 + colspan;
            } else {
                return colspan;
            }
        };

        maekRows(columns, 0);

        // set rowspan
        for (var r = 0, rl = table.rows.length; r < rl; r++) {
            for (var c = 0, cl = table.rows[r].cols.length; c < cl; c++) {
                if (!('columns' in table.rows[r].cols[c])) {
                    table.rows[r].cols[c].rowspan = rl - r;
                }
            }
        }

        return table;
    };

    var makeBodyRowTable = function makeBodyRowTable(_columns) {
        var columns = _columns,
            table = {
            rows: []
        },
            colIndex = 0,
            maekRows = function maekRows(_columns, depth, parentField) {
            var row = { cols: [] },
                i = 0,
                l = _columns.length,
                colspan = 1;

            var selfMakeRow = function selfMakeRow(__columns) {
                var i = 0,
                    l = __columns.length;
                for (; i < l; i++) {
                    var field = jQuery.extend({}, __columns[i]),
                        _colspan = 1;

                    if (!field.hidden) {

                        if ('key' in field) {
                            field.colspan = 1;
                            field.rowspan = 1;

                            field.rowIndex = depth;
                            field.colIndex = function () {
                                if (!parentField) {
                                    return colIndex++;
                                } else {
                                    colIndex = parentField.colIndex + i + 1;
                                    return parentField.colIndex + i;
                                }
                            }();

                            row.cols.push(field);
                            if ('columns' in field) {
                                _colspan = maekRows(field.columns, depth + 1, field);
                            }
                            field.colspan = _colspan;
                        } else {
                            if ('columns' in field) {
                                selfMakeRow(field.columns, depth);
                            }
                        }
                    } else {}
                }
            };

            for (; i < l; i++) {
                var field = jQuery.extend({}, _columns[i]);
                colspan = 1;

                if (!field.hidden) {

                    if ('key' in field) {
                        field.colspan = 1;
                        field.rowspan = 1;

                        field.rowIndex = depth;
                        field.colIndex = function () {
                            if (!parentField) {
                                return colIndex++;
                            } else {
                                colIndex = parentField.colIndex + i + 1;
                                return parentField.colIndex + i;
                            }
                        }();

                        row.cols.push(field);
                        if ('columns' in field) {
                            colspan = maekRows(field.columns, depth + 1, field);
                        }
                        field.colspan = colspan;
                    } else {
                        if ('columns' in field) {
                            selfMakeRow(field.columns, depth);
                        }
                    }
                } else {}

                field = null;
            }

            if (row.cols.length > 0) {
                if (!table.rows[depth]) {
                    table.rows[depth] = { cols: [] };
                }
                table.rows[depth].cols = table.rows[depth].cols.concat(row.cols);
                return row.cols.length - 1 + colspan;
            } else {
                return colspan;
            }
        };

        maekRows(columns, 0);

        (function (table) {
            // set rowspan
            for (var r = 0, rl = table.rows.length; r < rl; r++) {
                var row = table.rows[r];
                for (var c = 0, cl = row.cols.length; c < cl; c++) {
                    var col = row.cols[c];
                    if (!('columns' in col)) {
                        col.rowspan = rl - r;
                    }
                    col = null;
                }
                row = null;
            }
        })(table);

        return table;
    };

    var makeBodyRowMap = function makeBodyRowMap(_table) {
        var map = {};
        _table.rows.forEach(function (row) {
            row.cols.forEach(function (col) {
                map[col.rowIndex + "_" + col.colIndex] = jQuery.extend({}, col);
            });
        });
        return map;
    };

    var makeFootSumTable = function makeFootSumTable(_footSumColumns) {
        var table = {
            rows: []
        };

        for (var r = 0, rl = _footSumColumns.length; r < rl; r++) {
            var footSumRow = _footSumColumns[r],
                addC = 0;

            table.rows[r] = { cols: [] };

            for (var c = 0, cl = footSumRow.length; c < cl; c++) {
                if (addC > this.colGroup.length) break;
                var colspan = footSumRow[c].colspan || 1;
                if (footSumRow[c].label || footSumRow[c].key) {
                    table.rows[r].cols.push({
                        colspan: colspan,
                        rowspan: 1,
                        colIndex: addC,
                        columnAttr: "sum",
                        align: footSumRow[c].align,
                        label: footSumRow[c].label,
                        key: footSumRow[c].key,
                        collector: footSumRow[c].collector,
                        formatter: footSumRow[c].formatter
                    });
                } else {
                    table.rows[r].cols.push({
                        colIndex: addC,
                        colspan: colspan,
                        rowspan: 1,
                        label: "&nbsp;"
                    });
                }
                addC += colspan;
                colspan = null;
            }

            if (addC < this.colGroup.length) {
                for (var _c = addC; _c < this.colGroup.length; _c++) {
                    table.rows[r].cols.push({
                        colIndex: _c,
                        colspan: 1,
                        rowspan: 1,
                        label: "&nbsp;"
                    });
                }
            }
            footSumRow = null;
            addC = null;
        }

        return table;
    };

    var makeBodyGroupingTable = function makeBodyGroupingTable(_bodyGroupingColumns) {
        var table = {
            rows: []
        },
            r = 0,
            addC = 0;

        table.rows[r] = { cols: [] };
        for (var _c2 = 0, cl = _bodyGroupingColumns.length; _c2 < cl; _c2++) {
            if (addC > this.columns.length) break;
            var colspan = _bodyGroupingColumns[_c2].colspan || 1;
            if (_bodyGroupingColumns[_c2].label || _bodyGroupingColumns[_c2].key) {
                table.rows[r].cols.push({
                    colspan: colspan,
                    rowspan: 1,
                    rowIndex: 0,
                    colIndex: addC,
                    columnAttr: "default",
                    align: _bodyGroupingColumns[_c2].align,
                    label: _bodyGroupingColumns[_c2].label,
                    key: _bodyGroupingColumns[_c2].key,
                    collector: _bodyGroupingColumns[_c2].collector,
                    formatter: _bodyGroupingColumns[_c2].formatter
                });
            } else {
                table.rows[r].cols.push({
                    rowIndex: 0,
                    colIndex: addC,
                    colspan: colspan,
                    rowspan: 1,
                    label: "&nbsp;"
                });
            }
            addC += colspan;
        }

        if (addC < this.colGroup.length) {
            for (var c = addC; c < this.colGroup.length; c++) {
                table.rows[r].cols.push({
                    rowIndex: 0,
                    colIndex: c,
                    colspan: 1,
                    rowspan: 1,
                    label: "&nbsp;"
                });
            }
        }

        return table;
    };

    var findPanelByColumnIndex = function findPanelByColumnIndex(_dindex, _colIndex, _rowIndex) {
        var _containerPanelName = void 0,
            _isScrollPanel = false,
            _panels = [];

        if (this.xvar.frozenRowIndex > _dindex) _panels.push("top");
        if (this.xvar.frozenColumnIndex > _colIndex) _panels.push("left");
        _panels.push("body");

        if (this.xvar.frozenColumnIndex <= _colIndex || this.xvar.frozenRowIndex <= _dindex) {
            _containerPanelName = _panels.join("-");
            _panels.push("scroll");
            _isScrollPanel = true;
        }

        return {
            panelName: _panels.join("-"),
            containerPanelName: _containerPanelName,
            isScrollPanel: _isScrollPanel
        };
    };

    var getRealPathForDataItem = function getRealPathForDataItem(_dataPath) {
        var path = [],
            _path = [].concat(_dataPath.split(/[\.\[\]]/g));

        _path.forEach(function (n) {
            if (n !== "") path.push("[\"" + n.replace(/['\"]/g, "") + "\"]");
        });
        _path = null;
        return path.join("");
    };

    GRID.util = {
        divideTableByFrozenColumnIndex: divideTableByFrozenColumnIndex,
        getTableByStartEndColumnIndex: getTableByStartEndColumnIndex,
        getMousePosition: getMousePosition,
        ENM: ENM,
        makeHeaderTable: makeHeaderTable,
        makeBodyRowTable: makeBodyRowTable,
        makeBodyRowMap: makeBodyRowMap,
        makeFootSumTable: makeFootSumTable,
        makeBodyGroupingTable: makeBodyGroupingTable,
        findPanelByColumnIndex: findPanelByColumnIndex,
        getRealPathForDataItem: getRealPathForDataItem
    };
})();
'use strict';

// ax5.ui.combobox
(function () {

    var UI = ax5.ui;
    var U = ax5.util;
    var COMBOBOX = void 0;

    UI.addClass({
        className: "combobox"
    }, function () {
        /**
         * @class ax5combobox
         * @classdesc
         * @author tom@axisj.com
         * @example
         * ```js
         * var options = [];
         * options.push({value: "1", text: "string"});
         * options.push({value: "2", text: "number"});
         * options.push({value: "3", text: "substr"});
         * options.push({value: "4", text: "substring"});
         * options.push({value: "search", text: "search"});
         * options.push({value: "parseInt", text: "parseInt"});
         * options.push({value: "toFixed", text: "toFixed"});
         * options.push({value: "min", text: "min"});
         * options.push({value: "max", text: "max"});
         *
         * var myCombo = new ax5.ui.combobox({
         *     theme: "danger",
         *     removeIcon: '<i class="fa fa-times" aria-hidden="true"></i>'
         * });
         * ```
         */
        return function () {
            var self = this,
                cfg;

            this.instanceId = ax5.getGuid();
            this.config = {
                theme: 'default',
                animateTime: 250,
                removeIcon: 'X',
                lang: {
                    noSelected: '',
                    noOptions: 'no options',
                    loading: 'now loading..'
                },
                columnKeys: {
                    optionValue: 'value',
                    optionText: 'text',
                    optionSelected: 'selected'
                }
            };

            this.queue = [];
            this.activecomboboxOptionGroup = null;
            this.activecomboboxQueueIndex = -1;
            this.openTimer = null;
            this.closeTimer = null;
            this.waitOptionsCallback = null;
            this.keyUpTimer = null;

            cfg = this.config;

            var $window = jQuery(window);
            var ctrlKeys = {
                "18": "KEY_ALT",
                "8": "KEY_BACKSPACE",
                "17": "KEY_CONTROL",
                "46": "KEY_DELETE",
                "40": "KEY_DOWN",
                "35": "KEY_END",
                "187": "KEY_EQUAL",
                "27": "KEY_ESC",
                "36": "KEY_HOME",
                "45": "KEY_INSERT",
                "37": "KEY_LEFT",
                "189": "KEY_MINUS",
                "34": "KEY_PAGEDOWN",
                "33": "KEY_PAGEUP",
                // "190": "KEY_PERIOD",
                "13": "KEY_RETURN",
                "39": "KEY_RIGHT",
                "16": "KEY_SHIFT",
                // "32": "KEY_SPACE",
                "9": "KEY_TAB",
                "38": "KEY_UP",
                "91": "KEY_WINDOW"
                //"107" : "NUMPAD_ADD",
                //"194" : "NUMPAD_COMMA",
                //"110" : "NUMPAD_DECIMAL",
                //"111" : "NUMPAD_DIVIDE",
                //"12" : "NUMPAD_EQUAL",
                //"106" : "NUMPAD_MULTIPLY",
                //"109" : "NUMPAD_SUBTRACT"
            },
                onStateChanged = function onStateChanged(item, that) {
                if (item && item.onStateChanged) {
                    item.onStateChanged.call(that, that);
                } else if (this.onStateChanged) {
                    this.onStateChanged.call(that, that);
                }

                if (that.state == "changeValue") {
                    if (item && item.onChange) {
                        item.onChange.call(that, that);
                    } else if (this.onChange) {
                        this.onChange.call(that, that);
                    }
                }

                item = null;
                that = null;
                return true;
            },
                alignComboboxDisplay = function alignComboboxDisplay() {
                var i = this.queue.length,
                    w;

                while (i--) {
                    var item = this.queue[i];
                    if (item.$display) {
                        w = Math.max(item.$select.outerWidth(), U.number(item.minWidth));
                        item.$display.css({
                            "min-width": w
                        });
                        if (item.reset) {
                            item.$display.find(".addon-icon-reset").css({
                                "line-height": this.queue[i].$display.height() + "px"
                            });
                        }

                        // 높이조절 처리
                        if (item.multiple) {
                            var displayTableHeightAdjust = function () {
                                return U.number(item.$display.css("border-top-width")) + U.number(item.$display.css("border-bottom-width"));
                            }.call(this);
                            item.$target.height('');
                            item.$display.height('');

                            var displayTableHeight = item.$displayTable.outerHeight();
                            if (Math.abs(displayTableHeight - item.$target.height()) > displayTableHeightAdjust) {
                                item.$target.css({ height: displayTableHeight + displayTableHeightAdjust + 4 });
                                item.$display.css({ height: displayTableHeight + displayTableHeightAdjust + 4 });
                            }
                        }
                    }
                }

                i = null;
                w = null;
                return this;
            },
                alignComboboxOptionGroup = function alignComboboxOptionGroup(append) {
                if (!this.activecomboboxOptionGroup) return this;

                var item = this.queue[this.activecomboboxQueueIndex],
                    pos = {},
                    positionMargin = 0,
                    dim = {},
                    pickerDim = {},
                    pickerDirection;

                if (append) jQuery(document.body).append(this.activecomboboxOptionGroup);

                pos = item.$target.offset();
                dim = {
                    width: item.$target.outerWidth(),
                    height: item.$target.outerHeight()
                };
                pickerDim = {
                    winWidth: Math.max($window.width(), jQuery(document.body).width()),
                    winHeight: Math.max($window.height(), jQuery(document.body).height()),
                    width: this.activecomboboxOptionGroup.outerWidth(),
                    height: this.activecomboboxOptionGroup.outerHeight()
                };

                // picker css(width, left, top) & direction 결정
                if (!item.direction || item.direction === "" || item.direction === "auto") {
                    // set direction
                    pickerDirection = "top";

                    if (pos.top - pickerDim.height - positionMargin < 0) {
                        pickerDirection = "top";
                    } else if (pos.top + dim.height + pickerDim.height + positionMargin > pickerDim.winHeight) {
                        pickerDirection = "bottom";
                    }
                } else {
                    pickerDirection = item.direction;
                }

                if (append) {
                    this.activecomboboxOptionGroup.addClass("direction-" + pickerDirection);
                }
                this.activecomboboxOptionGroup.css(function () {
                    if (pickerDirection == "top") {
                        if (pos.top + dim.height + pickerDim.height + positionMargin > pickerDim.winHeight) {

                            var newTop = pos.top + dim.height / 2 - pickerDim.height / 2;
                            if (newTop + pickerDim.height + positionMargin > pickerDim.winHeight) {
                                newTop = 0;
                            }
                            if (newTop < 0) {
                                newTop = 0;
                            }

                            return {
                                left: pos.left,
                                top: newTop,
                                width: dim.width
                            };
                        }
                        return {
                            left: pos.left,
                            top: pos.top + dim.height + 1,
                            width: dim.width
                        };
                    } else if (pickerDirection == "bottom") {
                        return {
                            left: pos.left,
                            top: pos.top - pickerDim.height - 1,
                            width: dim.width
                        };
                    }
                }.call(this));
            },
                onBodyClick = function onBodyClick(e, target) {
                if (!this.activecomboboxOptionGroup) return this;

                var item = this.queue[this.activecomboboxQueueIndex],
                    clickEl = "display";

                target = U.findParentNode(e.target, function (target) {
                    if (target.getAttribute("data-option-value")) {
                        clickEl = "optionItem";
                        return true;
                    } else if (item.$target.get(0) == target) {
                        clickEl = "display";
                        return true;
                    }
                });

                if (!target) {
                    this.close();
                    return this;
                } else if (clickEl === "optionItem") {

                    setOptionSelect.call(this, item.id, {
                        index: {
                            gindex: target.getAttribute("data-option-group-index"),
                            index: target.getAttribute("data-option-index")
                        }
                    }, undefined, true);

                    alignComboboxDisplay.call(this);
                    alignComboboxOptionGroup.call(this);

                    if (!item.multiple) {
                        this.close();
                    }
                } else {}

                return this;
            },
                getLabel = function getLabel(queIdx) {
                var item = this.queue[queIdx];

                // 템플릿에 전달 해야할 데이터 선언
                var data = {};
                data.id = item.id;
                data.theme = item.theme;
                data.size = "ax5combobox-option-group-" + item.size;
                data.multiple = item.multiple;
                data.lang = item.lang;
                data.options = item.options;
                data.selected = item.selected;
                data.hasSelected = data.selected && data.selected.length > 0;
                data.removeIcon = item.removeIcon;

                return COMBOBOX.tmpl.get.call(this, "label", data, item.columnKeys);
            },
                printLabel = function printLabel(queIdx) {
                var item = this.queue[queIdx];

                item.$displayLabel.find('[data-ax5combobox-selected-label]').remove();
                item.$displayLabelInput.before(getLabel.call(this, queIdx));
            },
                focusLabel = function focusLabel(queIdx) {
                if (this.queue[queIdx].disabled) return this;

                this.queue[queIdx].$displayLabel.trigger("focus");
                this.queue[queIdx].$displayLabelInput.focus();
            },
                clearLabel = function clearLabel(queIdx) {
                this.queue[queIdx].$displayLabelInput.val('');
            },
                blurLabel = function blurLabel(queIdx) {
                this.queue[queIdx].$displayLabel.trigger("blur");
                this.queue[queIdx].$displayLabelInput.trigger("blur");
            },
                onSearch = function onSearch(queIdx, searchWord) {
                this.queue[queIdx].waitOptions = true;

                this.activecomboboxOptionGroup.find('[data-els="content"]').html(jQuery(COMBOBOX.tmpl.get.call(this, "option", this.queue[queIdx], this.queue[queIdx].columnKeys)));

                this.queue[queIdx].onSearch.call({
                    self: this,
                    item: this.queue[queIdx],
                    searchWord: searchWord
                }, function (O) {

                    var data = {};
                    var item = this.queue[this.activecomboboxQueueIndex];
                    if (!item) return false;

                    /// 현재 selected 검증후 처리
                    (function (item, O) {
                        var optionsMap = {};
                        O.options.forEach(function (_O, _OIndex) {
                            _O["@index"] = _OIndex;
                            optionsMap[_O[item.columnKeys.optionValue]] = _O;
                        });
                        if (U.isArray(item.selected)) {
                            item.selected.forEach(function (_O) {
                                if (optionsMap[_O[item.columnKeys.optionValue]]) {
                                    O.options[optionsMap[_O[item.columnKeys.optionValue]]["@index"]][item.columnKeys.optionSelected] = true;
                                }
                            });
                        }
                    })(item, O);

                    item.options = syncComboboxOptions.call(this, this.activecomboboxQueueIndex, O.options);

                    alignComboboxDisplay.call(this);

                    /// 템플릿에 전달할 오브젝트 선언
                    data.id = item.id;
                    data.theme = item.theme;
                    data.size = "ax5combobox-option-group-" + item.size;
                    data.multiple = item.multiple;
                    data.lang = item.lang;
                    data.options = item.options;

                    this.activecomboboxOptionGroup.find('[data-els="content"]').html(jQuery(COMBOBOX.tmpl.get.call(this, "options", data, item.columnKeys)));
                }.bind(this));
            },
                focusWord = function focusWord(queIdx, searchWord) {
                //console.log(searchWord);

                if (this.activecomboboxQueueIndex == -1) return this; // 옵션박스가 닫힌상태이면 진행안함.
                var options = [],
                    i = -1,
                    l = this.queue[queIdx].indexedOptions.length - 1,
                    n;

                if (searchWord != "") {
                    var regExp = /[\{\}\[\]\/?.,;:|\)*~`!^\-_+<>@\#$%&\\\=\(\'\"]/gi;
                    searchWord = searchWord.replace(regExp, "");
                    if (this.queue[queIdx].onSearch) {
                        onSearch.call(this, queIdx, searchWord);

                        try {
                            return options;
                        } finally {
                            options = null;
                            i = null;
                            l = null;
                            n = null;
                        }
                        // if there is a "onSearch", to end this process
                    }

                    while (l - i++) {
                        n = this.queue[queIdx].indexedOptions[i];

                        if (('' + n.text).toLowerCase() == searchWord.toLowerCase()) {
                            options = [{ '@findex': n['@findex'], optionsSort: 0 }];
                            break;
                        } else {
                            var sort = ('' + n.text).toLowerCase().search(searchWord.toLowerCase());
                            if (sort > -1) {
                                options.push({ '@findex': n['@findex'], optionsSort: sort });
                                if (options.length > 2) break;
                            }
                            sort = null;
                        }
                    }
                    options.sort(function (a, b) {
                        return a.optionsSort - b.optionsSort;
                    });
                }

                if (options && options.length > 0) {
                    focusMove.call(this, queIdx, undefined, options[0]['@findex']);
                } else {
                    focusClear.call(this, queIdx);
                }

                try {
                    return options;
                } finally {
                    options = null;
                    i = null;
                    l = null;
                    n = null;
                }
            },
                focusClear = function focusClear(queIdx) {
                if (this.activecomboboxOptionGroup) {
                    this.activecomboboxOptionGroup.find('[data-option-focus-index]').removeClass("hover").removeAttr("data-option-selected");
                }

                this.queue[queIdx].optionFocusIndex = -1;
            },
                focusMove = function focusMove(queIdx, direction, findex) {
                var _focusIndex, _prevFocusIndex, focusOptionEl, optionGroupScrollContainer;
                var item = this.queue[queIdx];

                if (this.activecomboboxOptionGroup && item.options && item.options.length > 0) {

                    if (typeof findex !== "undefined") {
                        _focusIndex = findex;
                    } else {
                        _prevFocusIndex = item.optionFocusIndex == -1 ? item.optionSelectedIndex || -1 : item.optionFocusIndex;
                        if (_prevFocusIndex == -1) {
                            _focusIndex = 0;
                            //_focusIndex = (direction > 0) ? 0 : item.optionItemLength - 1; // 맨 끝으로 보낼것인가 말 것인가.
                        } else {
                            _focusIndex = _prevFocusIndex + direction;
                            if (_focusIndex < 0) _focusIndex = 0;else if (_focusIndex > item.optionItemLength - 1) _focusIndex = item.optionItemLength - 1;
                        }
                    }

                    item.optionFocusIndex = _focusIndex;

                    // 포커스 인덱스가 hide아이템을 만나면 hide 아이템이 안나올 때까지 루프를 순회 합니다.
                    // todo : editable 로 추가된 options가 제거 되지 않으므로. 인덱스 검색을 좀 더 보강 해야함.
                    if (item.options[_focusIndex] && item.options[_focusIndex].hide) {
                        // 옵션이 없는 값이 선택된 경우
                        if (typeof direction === "undefined") {
                            return this;
                        } else {
                            var isStrop = false;
                            while (item.options[_focusIndex].hide) {
                                _focusIndex = _focusIndex + direction;
                                if (_focusIndex < 0) {
                                    _focusIndex = 0;
                                    break;
                                } else if (_focusIndex > item.optionItemLength - 1) {
                                    _focusIndex = item.optionItemLength - 1;
                                    break;
                                }
                            }
                        }
                    }

                    if (typeof _focusIndex !== "undefined") {
                        this.activecomboboxOptionGroup.find('[data-option-focus-index]').removeClass("hover");

                        focusOptionEl = this.activecomboboxOptionGroup.find('[data-option-focus-index="' + _focusIndex + '"]').addClass("hover");

                        optionGroupScrollContainer = this.activecomboboxOptionGroup.find('[data-els="content"]');

                        if (focusOptionEl.get(0)) {
                            var focusOptionElHeight = focusOptionEl.outerHeight(),
                                optionGroupScrollContainerHeight = optionGroupScrollContainer.innerHeight(),
                                optionGroupScrollContainerScrollTop = optionGroupScrollContainer.scrollTop(),
                                focusOptionElTop = focusOptionEl.position().top + optionGroupScrollContainer.scrollTop();

                            if (optionGroupScrollContainerHeight + optionGroupScrollContainerScrollTop < focusOptionElTop + focusOptionElHeight) {
                                optionGroupScrollContainer.scrollTop(focusOptionElTop + focusOptionElHeight - optionGroupScrollContainerHeight);
                            } else if (optionGroupScrollContainerScrollTop > focusOptionElTop) {
                                optionGroupScrollContainer.scrollTop(focusOptionElTop);
                            }
                            // optionGroup scroll check

                            if (typeof direction !== "undefined") {
                                item.$displayLabelInput.val(item.options[_focusIndex].text);
                            }
                        }
                    }
                }
            },
                syncComboboxOptions = function () {
                var setSelected = function setSelected(queIdx, O) {
                    if (!O) {
                        this.queue[queIdx].selected = [];
                    } else {
                        this.queue[queIdx].selected.push(jQuery.extend({}, O));
                        /*
                         콤보박스는 selected가 없을 때 options의 첫번째 아이템이 selected가 되지 않는다.
                         if (this.queue[queIdx].multiple) this.queue[queIdx].selected.push(jQuery.extend({}, O));
                         else this.queue[queIdx].selected[0] = jQuery.extend({}, O);
                         */
                    }
                };

                return function (queIdx, options) {
                    var item = this.queue[queIdx];
                    var po,
                        elementOptions,
                        newOptions,
                        focusIndex = 0;
                    setSelected.call(this, queIdx, false); // item.selected 초기화

                    if (options) {
                        item.options = options;
                        item.indexedOptions = [];

                        // combobox options 태그 생성
                        po = [];
                        po.push('<option value=""></option>');

                        item.options.forEach(function (O, OIndex) {
                            /// @gindex : index of optionGroup
                            /// @index : index of options (if you use optionGroup then the index is not unique)
                            if (O.optgroup) {
                                O['@gindex'] = OIndex;
                                O.options.forEach(function (OO, OOIndex) {
                                    OO['@index'] = OOIndex;
                                    OO['@findex'] = focusIndex;
                                    po.push('<option value="' + OO[item.columnKeys.optionValue] + '" ' + (OO[item.columnKeys.optionSelected] ? ' selected="selected"' : '') + '>' + OO[item.columnKeys.optionText] + '</option>');
                                    if (OO[item.columnKeys.optionSelected]) {
                                        setSelected.call(self, queIdx, OO);
                                    }

                                    item.indexedOptions.push({
                                        '@gindex': OIndex,
                                        '@index': OOIndex,
                                        '@findex': focusIndex,
                                        value: OO[item.columnKeys.optionValue],
                                        text: OO[item.columnKeys.optionText]
                                    });
                                    focusIndex++;
                                });
                            } else {
                                O['@index'] = OIndex;
                                O['@findex'] = focusIndex;
                                po.push('<option value="' + O[item.columnKeys.optionValue] + '" ' + (O[item.columnKeys.optionSelected] ? ' selected="selected"' : '') + '>' + O[item.columnKeys.optionText] + '</option>');
                                if (O[item.columnKeys.optionSelected]) {
                                    setSelected.call(self, queIdx, O);
                                }

                                item.indexedOptions.push({
                                    '@index': OIndex,
                                    '@findex': focusIndex,
                                    value: O[item.columnKeys.optionValue],
                                    text: O[item.columnKeys.optionText]
                                });
                                focusIndex++;
                            }
                        });
                        item.optionItemLength = focusIndex;
                        item.$select.html(po.join(''));
                    } else {
                        /// select > options 태그로 스크립트 options를 만들어주는 역할
                        if (item.$select.get(0).options && item.$select.get(0).options.length) item.$select.get(0).options[0].selected = false;
                        elementOptions = U.toArray(item.$select.get(0).options);

                        // select option 스크립트 생성
                        newOptions = [];
                        elementOptions.forEach(function (O, OIndex) {
                            var option = {};
                            option[item.columnKeys.optionValue] = O.value;
                            option[item.columnKeys.optionText] = O.text;
                            option[item.columnKeys.optionSelected] = O.selected;
                            option['@index'] = OIndex;
                            option['@findex'] = focusIndex;
                            if (O.selected) setSelected.call(self, queIdx, option);
                            newOptions.push(option);
                            focusIndex++;

                            option = null;
                        });
                        item.options = newOptions;
                        item.indexedOptions = newOptions;

                        item.$select.prepend('<option value=""></option>');
                        item.$select.get(0).options[0].selected = true;
                    }

                    po = null;
                    elementOptions = null;
                    newOptions = null;
                    return item.options;
                };
            }(),
                getQueIdx = function getQueIdx(boundID) {
                if (boundID instanceof jQuery) {
                    boundID = boundID.data("data-ax5combobox-id");
                } else if (!U.isString(boundID)) {
                    boundID = jQuery(boundID).data("data-ax5combobox-id");
                }
                if (!U.isString(boundID)) {
                    console.log(ax5.info.getError("ax5combobox", "402", "getQueIdx"));
                    return;
                }
                return U.search(this.queue, function () {
                    return this.id == boundID;
                });
            },
                getSelected = function getSelected(_item, o, selected) {
                if (typeof selected === "undefined") {
                    return _item.multiple ? !o : true;
                } else {
                    return selected;
                }
            },
                clearSelected = function clearSelected(queIdx) {
                this.queue[queIdx].options.forEach(function (n) {
                    if (n.optgroup) {
                        n.options.forEach(function (nn) {
                            nn.selected = false;
                        });
                    } else {
                        n.selected = false;
                    }
                });
            },
                setOptionSelect = function () {
                var processor = {
                    'index': function index(queIdx, value, selected, setValueType) {
                        // 클래스 내부에서 호출된 형태, 그런 이유로 옵션그룹에 대한 상태를 변경 하고 있다.
                        var item = this.queue[queIdx];

                        if (U.isString(value.index.gindex)) {
                            if (typeof item.options[value.index.gindex] !== "undefined") {

                                item.options[value.index.gindex].options[value.index.index][item.columnKeys.optionSelected] = getSelected(item, item.options[value.index.gindex].options[value.index.index][item.columnKeys.optionSelected], selected);

                                if (self.activecomboboxOptionGroup) {
                                    self.activecomboboxOptionGroup.find('[data-option-group-index="' + value.index.gindex + '"][data-option-index="' + value.index.index + '"]').attr("data-option-Selected", item.options[value.index.gindex].options[value.index.index][item.columnKeys.optionSelected].toString());
                                }
                            }
                        } else {
                            if (typeof item.options[value.index.index] !== "undefined") {

                                item.options[value.index.index][item.columnKeys.optionSelected] = getSelected(item, item.options[value.index.index][item.columnKeys.optionSelected], selected);

                                if (self.activecomboboxOptionGroup) {
                                    self.activecomboboxOptionGroup.find('[data-option-index="' + value.index.index + '"]').attr("data-option-Selected", item.options[value.index.index][item.columnKeys.optionSelected].toString());
                                }
                            }
                        }

                        if (typeof setValueType === "undefined" || setValueType !== "justSetValue") {
                            syncComboboxOptions.call(this, queIdx, item.options);
                            alignComboboxOptionGroup.call(this);
                        }
                    },
                    'arr': function arr(queIdx, values, selected, setValueType) {
                        values.forEach(function (value) {
                            if (U.isString(value) || U.isNumber(value)) {
                                processor.text.call(self, queIdx, value, selected, "justSetValue");
                            } else {
                                for (var key in processor) {
                                    if (value[key]) {
                                        processor[key].call(self, queIdx, value, selected, "justSetValue");
                                        break;
                                    }
                                }
                            }
                        });

                        syncComboboxOptions.call(this, queIdx, this.queue[queIdx].options);
                        alignComboboxOptionGroup.call(this);
                    },
                    'value': function value(queIdx, _value2, selected, setValueType) {
                        var item = this.queue[queIdx];
                        var addOptions;
                        var optionIndex = U.search(item.options, function () {
                            return this[item.columnKeys.optionValue] == _value2[item.columnKeys.optionValue];
                        });

                        if (optionIndex > -1) {
                            item.options[optionIndex][item.columnKeys.optionSelected] = getSelected(item, item.options[optionIndex][item.columnKeys.optionSelected], selected);
                        } else {
                            // 새로운 값 추가
                            optionIndex = item.options.length;
                            addOptions = {
                                "@index": optionIndex,
                                hide: true,
                                addedOption: true
                            };
                            addOptions[item.columnKeys.optionValue] = _value2;
                            addOptions[item.columnKeys.optionText] = _value2;
                            item.options.push(addOptions);
                            item.options[optionIndex][item.columnKeys.optionSelected] = getSelected(item, item.options[optionIndex][item.columnKeys.optionSelected], selected);
                        }
                        if (typeof setValueType === "undefined" || setValueType !== "justSetValue") {
                            syncComboboxOptions.call(this, queIdx, this.queue[queIdx].options);
                            alignComboboxOptionGroup.call(this);
                        }
                    },
                    'text': function text(queIdx, value, selected, setValueType) {
                        var item = this.queue[queIdx];
                        var addOptions;
                        var optionIndex = U.search(item.options, function () {
                            return this[item.columnKeys.optionText] == value;
                        });

                        if (optionIndex > -1) {
                            item.options[optionIndex][item.columnKeys.optionSelected] = getSelected(item, item.options[optionIndex][item.columnKeys.optionSelected], selected);
                        } else {
                            // 새로운 값 추가
                            optionIndex = item.options.length;
                            addOptions = {
                                "@index": optionIndex,
                                hide: true,
                                addedOption: true
                            };
                            addOptions[item.columnKeys.optionValue] = value;
                            addOptions[item.columnKeys.optionText] = value;
                            item.options.push(addOptions);
                            item.options[optionIndex][item.columnKeys.optionSelected] = getSelected(item, item.options[optionIndex][item.columnKeys.optionSelected], selected);
                        }
                        if (typeof setValueType === "undefined" || setValueType !== "justSetValue") {
                            syncComboboxOptions.call(this, queIdx, this.queue[queIdx].options);
                            alignComboboxOptionGroup.call(this);
                        }
                    },
                    'clear': function clear(queIdx) {
                        clearSelected.call(this, queIdx);
                        syncComboboxOptions.call(this, queIdx, this.queue[queIdx].options);
                        //focusLabel.call(this, queIdx);
                        focusClear.call(this, queIdx);

                        if (this.activecomboboxOptionGroup) {
                            this.activecomboboxOptionGroup.find('[data-option-index]').attr("data-option-Selected", "false");
                        }
                        this.queue[queIdx].optionSelectedIndex = -1;
                    }
                };
                return function (boundID, value, selected, _option) {
                    var queIdx = U.isNumber(boundID) ? boundID : getQueIdx.call(this, boundID);
                    if (queIdx === -1) {
                        console.log(ax5.info.getError("ax5combobox", "402", "val"));
                        return;
                    }

                    if (typeof value == "undefined") {
                        throw "error not found value";
                    } else if (U.isArray(value)) {
                        processor.clear.call(this, queIdx);
                        processor.arr.call(this, queIdx, this.queue[queIdx].multiple || value.length == 0 ? value : [value[value.length - 1]], selected);
                    } else if (U.isString(value) || U.isNumber(value)) {
                        if (typeof value !== "undefined" && value !== null && !this.queue[queIdx].multiple) {
                            clearSelected.call(this, queIdx);
                        }
                        processor.text.call(this, queIdx, value, selected, "justSetValue");
                    } else {
                        if (value === null) {
                            processor.clear.call(this, queIdx);
                        } else {
                            if (!this.queue[queIdx].multiple) {
                                clearSelected.call(this, queIdx);
                            }
                            for (var key in processor) {
                                if (value[key]) {
                                    processor[key].call(this, queIdx, value, selected, "justSetValue");
                                    break;
                                }
                            }
                        }
                    }

                    syncComboboxOptions.call(this, queIdx, this.queue[queIdx].options);
                    printLabel.call(this, queIdx);
                    focusLabel.call(this, queIdx);

                    if (typeof value !== "undefined") {
                        if (_option && !_option.noStateChange) {
                            onStateChanged.call(this, this.queue[queIdx], {
                                self: this,
                                item: this.queue[queIdx],
                                state: "changeValue",
                                value: this.queue[queIdx].selected
                            });
                        }
                    }

                    boundID = null;
                    return this;
                };
            }();

            /// private end

            /**
             * Preferences of combobox UI
             * @method ax5combobox.setConfig
             * @param {Object} config - 클래스 속성값
             * @returns {ax5combobox}
             * @example
             * ```
             * ```
             */
            this.init = function () {
                this.onStateChanged = cfg.onStateChanged;
                this.onChange = cfg.onChange;
                jQuery(window).on("resize.ax5combobox-display-" + this.instanceId, function () {
                    alignComboboxDisplay.call(this);
                }.bind(this));
            };

            /**
             * bind combobox
             * @method ax5combobox.bind
             * @param {Object} item
             * @param {String} [item.id]
             * @param {String} [item.theme]
             * @param {Boolean} [item.multiple]
             * @param {Element} item.target
             * @param {Object[]} item.options
             * @returns {ax5combobox}
             */
            this.bind = function (item) {
                var bindComboboxTarget = function () {
                    var debouncedFocusWord = U.debounce(function (queIdx) {
                        if (this.activecomboboxQueueIndex == -1) return this; // 옵션박스가 닫힌상태이면 진행안함.
                        focusWord.call(self, queIdx, this.queue[queIdx].$displayLabelInput.val());
                    }, 150);

                    var blurLabel = function blurLabel(queIdx) {
                        clearLabel.call(this, queIdx);
                    };

                    var comboboxEvent = {
                        'click': function click(queIdx, e) {
                            var clickEl;
                            var target = U.findParentNode(e.target, function (target) {
                                if (target.getAttribute("data-ax5combobox-remove")) {
                                    clickEl = "optionItemRemove";
                                    return true;
                                } else if (target.getAttribute("data-selected-clear")) {
                                    clickEl = "clear";
                                    return true;
                                }
                            });

                            if (target) {
                                if (clickEl === "optionItemRemove") {
                                    var selectedIndex = target.getAttribute("data-ax5combobox-remove-index");
                                    var option = this.queue[queIdx].selected[selectedIndex];
                                    setOptionSelect.call(this, queIdx, {
                                        index: {
                                            gindex: option['@gindex'],
                                            index: option['@index']
                                        }
                                    }, false, true);
                                    alignComboboxDisplay.call(this);
                                    alignComboboxOptionGroup.call(this);
                                    focusLabel.call(this, queIdx);
                                    U.stopEvent(e);
                                    return this;
                                } else if (clickEl === "clear") {
                                    setOptionSelect.call(this, queIdx, { clear: true });
                                    alignComboboxDisplay.call(this);
                                    alignComboboxOptionGroup.call(this);
                                    focusLabel.call(this, queIdx);
                                }
                            } else {
                                if (self.activecomboboxQueueIndex == queIdx) {
                                    if (this.queue[queIdx].optionFocusIndex == -1) {
                                        // 아이템에 포커스가 활성화 된 후, 마우스 이벤트 이면 무시
                                        self.close();
                                    }
                                } else {
                                    self.open(queIdx);
                                    focusLabel.call(this, queIdx);
                                }
                            }
                        },
                        'keyUp': function keyUp(queIdx, e) {
                            /// 약속된 키 이벤트가 발생하면 stopEvent를 통해 keyUp 이벤트가 발생되지 않도록 막아주는 센스
                            if (e.which == ax5.info.eventKeys.ESC && self.activecomboboxQueueIndex === -1) {
                                // ESC키를 누르고 옵션그룹이 열려있지 않은 경우
                                U.stopEvent(e);
                                return this;
                            }
                            if (self.activecomboboxQueueIndex != queIdx) {
                                // 닫힌 상태 인경우
                                self.open(queIdx);
                                U.stopEvent(e);
                            }

                            var disableCtrlKeys = {
                                "40": "KEY_DOWN",
                                "38": "KEY_UP"
                            };
                            if (!disableCtrlKeys[e.which]) {

                                // backspace 감지 하여 input 값이 없으면 스탑이벤트 처리 할 것
                                if (e.which == ax5.info.eventKeys.BACKSPACE && this.queue[queIdx].$displayLabelInput.val() == "") {
                                    // 마지막 아이템을 제거.
                                    if (this.queue[queIdx].selected.length > 0) {
                                        var option = this.queue[queIdx].selected[this.queue[queIdx].selected.length - 1];
                                        setOptionSelect.call(this, queIdx, {
                                            index: {
                                                gindex: option['@gindex'],
                                                index: option['@index']
                                            }
                                        }, false, true);
                                    }
                                    alignComboboxDisplay.call(this);
                                    alignComboboxOptionGroup.call(this);
                                    U.stopEvent(e);
                                } else {
                                    debouncedFocusWord.call(this, queIdx);
                                }
                            }
                        },
                        'keyDown': function keyDown(queIdx, e) {
                            if (e.which == ax5.info.eventKeys.ESC) {
                                clearLabel.call(this, queIdx);
                                this.close();
                                U.stopEvent(e);
                            } else if (e.which == ax5.info.eventKeys.RETURN) {

                                setOptionSelect.call(this, item.id, {
                                    index: {
                                        gindex: item.indexedOptions[item.optionFocusIndex]["@gindex"],
                                        index: item.indexedOptions[item.optionFocusIndex]["@index"]
                                    }
                                }, undefined, true);
                                clearLabel.call(this, queIdx);
                                alignComboboxDisplay.call(this);

                                this.close();
                                //alignComboboxOptionGroup.call(this);

                                U.stopEvent(e);
                            } else if (e.which == ax5.info.eventKeys.DOWN) {
                                focusMove.call(this, queIdx, 1);
                                U.stopEvent(e);
                            } else if (e.which == ax5.info.eventKeys.UP) {
                                focusMove.call(this, queIdx, -1);
                                U.stopEvent(e);
                            }
                        },
                        'focus': function focus(queIdx, e) {
                            //console.log(e);
                        },
                        'blur': function blur(queIdx, e) {
                            blurLabel.call(this, queIdx);
                            U.stopEvent(e);
                        },
                        'selectChange': function selectChange(queIdx, e) {
                            setOptionSelect.call(this, queIdx, { value: this.queue[queIdx].$select.val() }, true);
                        }
                    };

                    return function (queIdx) {
                        var item = this.queue[queIdx];
                        var data = {};
                        // 현재 선택된 값을 담아두는 저장소, syncComboboxOptions를 통해 options와 selected값을 동기화 처리 한다.
                        item.selected = [];

                        if (!item.$display) {
                            /// 템플릿에 전달할 오브젝트 선언
                            data.instanceId = this.instanceId;
                            data.id = item.id;
                            data.name = item.name;
                            data.theme = item.theme;
                            data.tabIndex = item.tabIndex;
                            data.multiple = item.multiple;
                            data.reset = item.reset;

                            data.label = getLabel.call(this, queIdx);
                            data.formSize = function () {
                                return item.size ? "input-" + item.size : "";
                            }();

                            //item.$display = jQuery(ax5.mustache.render(COMBOBOX.tmpl["comboboxDisplay"].call(this, queIdx), data));
                            item.$display = jQuery(COMBOBOX.tmpl.get.call(this, "comboboxDisplay", data, item.columnKeys));
                            item.$displayTable = item.$display.find('[data-els="display-table"]');
                            item.$displayLabel = item.$display.find('[data-ax5combobox-display="label"]');
                            item.$displayLabelInput = item.$display.find('[data-ax5combobox-display="input"]');

                            if (item.$target.find("select").get(0)) {
                                item.$select = item.$target.find("select");
                                item.$select.attr("tabindex", "-1").attr("class", "form-control " + data.formSize);
                                if (data.name) {
                                    item.$select.attr("name", "name");
                                }
                                if (data.multiple) {
                                    item.$select.attr("multiple", "multiple");
                                }
                            } else {
                                //item.$select = jQuery(ax5.mustache.render(COMBOBOX.tmpl["formSelect"].call(this, queIdx), data));
                                item.$select = jQuery(COMBOBOX.tmpl.get.call(this, "formSelect", data, item.columnKeys));
                                item.$target.append(item.$select);
                            }

                            item.$target.append(item.$display);
                            // 라벨에 사용자 입력 필드가 있으므로 displayInput은 필요 없음.
                            // select.options로 item.options를 만들어내거나 item.options로 select.options를 만들어냄
                            item.options = syncComboboxOptions.call(this, queIdx, item.options);
                        } else {
                            item.$displayLabel.html(getLabel.call(this, queIdx));
                            item.options = syncComboboxOptions.call(this, queIdx, item.options);
                        }

                        alignComboboxDisplay.call(this);

                        item.$display.off('click.ax5combobox').on('click.ax5combobox', comboboxEvent.click.bind(this, queIdx));

                        // combobox 태그에 대한 이벤트 감시


                        item.$displayLabelInput.off("focus.ax5combobox").on("focus.ax5combobox", comboboxEvent.focus.bind(this, queIdx)).off("blur.ax5combobox").on("blur.ax5combobox", comboboxEvent.blur.bind(this, queIdx)).off('keyup.ax5combobox').on('keyup.ax5combobox', comboboxEvent.keyUp.bind(this, queIdx)).off("keydown.ax5combobox").on("keydown.ax5combobox", comboboxEvent.keyDown.bind(this, queIdx));

                        // select 태그에 대한 change 이벤트 감시

                        item.$select.off('change.ax5combobox').on('change.ax5combobox', comboboxEvent.selectChange.bind(this, queIdx));

                        data = null;
                        item = null;
                        queIdx = null;
                        return this;
                    };
                }();

                var comboboxConfig = {},
                    queIdx;

                item = jQuery.extend(true, comboboxConfig, cfg, item);
                if (!item.target) {
                    console.log(ax5.info.getError("ax5combobox", "401", "bind"));
                    return this;
                }

                item.$target = jQuery(item.target);

                if (!item.id) item.id = item.$target.data("data-ax5combobox-id");
                if (!item.id) {
                    item.id = 'ax5combobox-' + ax5.getGuid();
                    item.$target.data("data-ax5combobox-id", item.id);
                }
                item.name = item.$target.attr("data-ax5combobox");
                if (item.options) {
                    item.options = JSON.parse(JSON.stringify(item.options));
                }

                // target attribute data
                (function (data) {
                    if (U.isObject(data) && !data.error) {
                        item = jQuery.extend(true, item, data);
                    }
                })(U.parseJson(item.$target.attr("data-ax5combobox-config"), true));

                queIdx = U.search(this.queue, function () {
                    return this.id == item.id;
                });

                if (queIdx === -1) {
                    this.queue.push(item);
                    bindComboboxTarget.call(this, this.queue.length - 1);
                } else {
                    this.queue[queIdx] = jQuery.extend(true, {}, this.queue[queIdx], item);
                    bindComboboxTarget.call(this, queIdx);
                }

                comboboxConfig = null;
                queIdx = null;
                return this;
            };

            /**
             * open the optionBox of combobox
             * @method ax5combobox.open
             * @param {(String|Number|Element)} boundID
             * @param {Number} [tryCount]
             * @returns {ax5combobox}
             */
            this.open = function () {
                var onExpand = function onExpand(item) {
                    item.onExpand.call({
                        self: this,
                        item: item
                    }, function (O) {
                        if (this.waitOptionsCallback) {
                            var data = {};
                            var item = this.queue[this.activecomboboxQueueIndex];

                            /// 현재 selected 검증후 처리
                            (function (item, O) {
                                var optionsMap = {};
                                O.options.forEach(function (_O, _OIndex) {
                                    _O["@index"] = _OIndex;
                                    optionsMap[_O[item.columnKeys.optionValue]] = _O;
                                });
                                if (U.isArray(item.selected)) {
                                    item.selected.forEach(function (_O) {
                                        if (optionsMap[_O[item.columnKeys.optionValue]]) {
                                            O.options[optionsMap[_O[item.columnKeys.optionValue]]["@index"]][item.columnKeys.optionSelected] = true;
                                        }
                                    });
                                }
                            })(item, O);

                            item.options = syncComboboxOptions.call(this, this.activecomboboxQueueIndex, O.options);
                            printLabel.call(this, this.activecomboboxQueueIndex);
                            alignComboboxDisplay.call(this);

                            /// 템플릿에 전달할 오브젝트 선언
                            data.id = item.id;
                            data.theme = item.theme;
                            data.size = "ax5combobox-option-group-" + item.size;
                            data.multiple = item.multiple;
                            data.lang = item.lang;
                            data.options = item.options;
                            /*
                             this.activecomboboxOptionGroup.find('[data-els="content"]').html(jQuery(
                             ax5.mustache.render(COMBOBOX.tmpl["options"].call(this, item.columnKeys), data)
                             ));
                             */
                            this.activecomboboxOptionGroup.find('[data-els="content"]').html(jQuery(COMBOBOX.tmpl.get.call(this, "options", data, item.columnKeys)));
                        }
                    }.bind(this));
                };
                return function (boundID, tryCount) {
                    this.waitOptionsCallback = null;

                    /**
                     * open combobox from the outside
                     */
                    var queIdx = U.isNumber(boundID) ? boundID : getQueIdx.call(this, boundID);
                    var item = this.queue[queIdx];
                    var data = {},
                        focusTop,
                        selectedOptionEl;

                    if (item.$display.attr("disabled")) return this;

                    if (this.openTimer) clearTimeout(this.openTimer);
                    if (this.activecomboboxOptionGroup) {
                        if (this.activecomboboxQueueIndex == queIdx) {
                            return this;
                        }

                        if (tryCount > 2) return this;
                        this.close();
                        this.openTimer = setTimeout(function () {
                            this.open(queIdx, (tryCount || 0) + 1);
                        }.bind(this), cfg.animateTime);

                        return this;
                    }

                    item.optionFocusIndex = -1; // optionGroup이 열리면 포커스 인덱스 초기화 -1로
                    if (item.selected && item.selected.length > 0) {
                        item.optionSelectedIndex = item.selected[0]["@findex"];
                    }

                    /// 템플릿에 전달할 오브젝트 선언
                    data.id = item.id;
                    data.theme = item.theme;
                    data.size = "ax5combobox-option-group-" + item.size;
                    data.multiple = item.multiple;

                    data.lang = item.lang;
                    item.$display.attr("data-combobox-option-group-opened", "true");

                    if (item.onExpand) {
                        // onExpand 인 경우 UI 대기모드 추가
                        data.waitOptions = true;
                    }
                    data.options = U.filter(item.options, function () {
                        return !this.hide;
                    });

                    //this.activecomboboxOptionGroup = jQuery(ax5.mustache.render(COMBOBOX.tmpl["optionGroup"].call(this, item.columnKeys), data));
                    this.activecomboboxOptionGroup = jQuery(COMBOBOX.tmpl.get.call(this, "optionGroup", data, item.columnKeys));
                    //this.activecomboboxOptionGroup.find('[data-els="content"]').html(jQuery(ax5.mustache.render(COMBOBOX.tmpl["options"].call(this, item.columnKeys), data)));
                    this.activecomboboxOptionGroup.find('[data-els="content"]').html(jQuery(COMBOBOX.tmpl.get.call(this, "options", data, item.columnKeys)));
                    this.activecomboboxQueueIndex = queIdx;

                    alignComboboxOptionGroup.call(this, "append"); // alignComboboxOptionGroup 에서 body append
                    jQuery(window).on("resize.ax5combobox-" + this.instanceId, function () {
                        alignComboboxOptionGroup.call(this);
                    }.bind(this));

                    if (item.selected && item.selected.length > 0) {
                        selectedOptionEl = this.activecomboboxOptionGroup.find('[data-option-index="' + item.selected[0]["@index"] + '"]');
                        if (selectedOptionEl.get(0)) {
                            focusTop = selectedOptionEl.position().top - this.activecomboboxOptionGroup.height() / 3;
                            this.activecomboboxOptionGroup.find('[data-els="content"]').stop().animate({ scrollTop: focusTop }, item.animateTime, 'swing', function () {});
                        }
                    }

                    jQuery(window).on("click.ax5combobox-" + this.instanceId, function (e) {
                        e = e || window.event;
                        onBodyClick.call(this, e);
                        U.stopEvent(e);
                    }.bind(this));

                    onStateChanged.call(this, item, {
                        self: this,
                        state: "open",
                        item: item
                    });

                    // waitOption timer
                    if (item.onExpand) {
                        this.waitOptionsCallback = true;
                        onExpand.call(this, item);
                    }

                    data = null;
                    focusTop = null;
                    selectedOptionEl = null;
                    return this;
                };
            }();

            /**
             * @method ax5combobox.update
             * @param {(Object|String)} item
             * @returns {ax5combobox}
             */
            this.update = function (_item) {
                this.bind(_item);
                return this;
            };

            /**
             * @method ax5combobox.setValue
             * @param {(jQueryObject|Element|Number)} _boundID
             * @param {(String|Array)} _value
             * @param {Boolean} [_selected]
             * @return {ax5combobox}
             * @example
             * ```js
             * myCombo.setValue($('[data-ax5combobox="combo1"]'), "1");
             * myCombo.setValue($('[data-ax5combobox="combo1"]'), ["1", "2"]);
             * ```
             */
            this.setValue = function (_boundID, _value, _selected) {
                var queIdx = U.isNumber(_boundID) ? _boundID : getQueIdx.call(this, _boundID);
                if (queIdx === -1) {
                    console.log(ax5.info.getError("ax5combobox", "402", "val"));
                    return;
                }

                clearSelected.call(this, queIdx);

                if (U.isArray(_value)) {
                    var _values = U.map(_value, function () {
                        return { value: this };
                    });
                    setOptionSelect.call(this, queIdx, _values, _selected || true, { noStateChange: true });
                } else if (U.isString(_value) || U.isNumber(_value)) {
                    setOptionSelect.call(this, queIdx, { value: _value }, _selected || true, { noStateChange: true });
                } else {
                    printLabel.call(this, queIdx);
                }

                blurLabel.call(this, queIdx);
                alignComboboxDisplay.call(this);

                return this;
            };

            /**
             * @method ax5combobox.setText
             * @param {(jQueryObject|Element|Number)} _boundID
             * @param {(String|Array)} _text
             * @param {Boolean} [_selected]
             * @returns {ax5combobox}
             * @example
             * ```js
             * myCombo.setText($('[data-ax5combobox="combo1"]'), "string");
             * myCombo.setText($('[data-ax5combobox="combo1"]'), ["substring", "search"]);
             * ```
             */
            this.setText = function (_boundID, _text, _selected) {
                var queIdx = U.isNumber(_boundID) ? _boundID : getQueIdx.call(this, _boundID);
                if (queIdx === -1) {
                    console.log(ax5.info.getError("ax5combobox", "402", "val"));
                    return;
                }
                clearSelected.call(this, queIdx);
                setOptionSelect.call(this, queIdx, _text, true, { noStateChange: true });
                blurLabel.call(this, queIdx);
                alignComboboxDisplay.call(this);

                return this;
            };

            /**
             * @method ax5combobox.getSelectedOption
             * @param {(jQueryObject|Element|Number)} _boundID
             * @returns {Array}
             */
            this.getSelectedOption = function (_boundID) {
                var queIdx = U.isNumber(_boundID) ? _boundID : getQueIdx.call(this, _boundID);
                if (queIdx === -1) {
                    console.log(ax5.info.getError("ax5combobox", "402", "val"));
                    return;
                }
                return U.deepCopy(this.queue[queIdx].selected);
            };

            /**
             * @method ax5combobox.close
             * @returns {ax5combobox}
             */
            this.close = function (item) {
                if (this.closeTimer) clearTimeout(this.closeTimer);
                if (!this.activecomboboxOptionGroup) return this;

                item = this.queue[this.activecomboboxQueueIndex];
                item.optionFocusIndex = -1;
                item.$display.removeAttr("data-combobox-option-group-opened").trigger("focus");
                item.$displayLabel.attr("contentEditable", "false");

                this.activecomboboxOptionGroup.addClass("destroy");

                jQuery(window).off("resize.ax5combobox-" + this.instanceId).off("click.ax5combobox-" + this.instanceId).off("keyup.ax5combobox-" + this.instanceId);

                this.closeTimer = setTimeout(function () {
                    if (this.activecomboboxOptionGroup) this.activecomboboxOptionGroup.remove();
                    this.activecomboboxOptionGroup = null;
                    this.activecomboboxQueueIndex = -1;

                    onStateChanged.call(this, item, {
                        self: this,
                        state: "close"
                    });
                }.bind(this), cfg.animateTime);
                this.waitOptionsCallback = null;
                return this;
            };

            /**
             * @method ax5combobox.blur
             * @param {(jQueryObject|Element|Number)} _boundID
             * @returns {ax5combobox}
             */
            this.blur = function (_boundID) {
                var queIdx = U.isNumber(_boundID) ? _boundID : getQueIdx.call(this, _boundID);
                if (queIdx === -1) {
                    console.log(ax5.info.getError("ax5combobox", "402", "val"));
                    return;
                }

                blurLabel.call(this, queIdx);
                return this;
            };

            /**
             * @method ax5combobox.enable
             * @param {(jQueryObject|Element|Number)} _boundID
             * @returns {ax5combobox}
             */
            this.enable = function (_boundID) {
                var queIdx = getQueIdx.call(this, _boundID);

                if (typeof queIdx !== "undefined") {
                    this.queue[queIdx].disabled = false;
                    if (this.queue[queIdx].$display[0]) {
                        this.queue[queIdx].$display.removeAttr("disabled");
                        this.queue[queIdx].$displayLabelInput.removeAttr("disabled");
                    }
                    if (this.queue[queIdx].$select[0]) {
                        this.queue[queIdx].$select.removeAttr("disabled");
                    }

                    onStateChanged.call(this, this.queue[queIdx], {
                        self: this,
                        state: "enable"
                    });
                }

                return this;
            };

            /**
             * @method ax5combobox.disable
             * @param {(jQueryObject|Element|Number)} _boundID
             * @returns {ax5combobox}
             */
            this.disable = function (_boundID) {
                var queIdx = getQueIdx.call(this, _boundID);

                if (typeof queIdx !== "undefined") {
                    this.queue[queIdx].disabled = true;
                    if (this.queue[queIdx].$display[0]) {
                        this.queue[queIdx].$display.attr("disabled", "disabled");
                        this.queue[queIdx].$displayLabelInput.attr("disabled", "disabled");
                    }
                    if (this.queue[queIdx].$select[0]) {
                        this.queue[queIdx].$select.attr("disabled", "disabled");
                    }

                    onStateChanged.call(this, this.queue[queIdx], {
                        self: this,
                        state: "disable"
                    });
                }
                return this;
            };

            /**
             * @method ax5combobox.align
             */
            this.align = function () {
                alignComboboxDisplay.call(this);
                return this;
            };

            /**
             * @method ax5combobox.clear
             * @param {(jQueryObject|Element|Number)} _boundID
             * @returns {ax5combobox}
             */
            this.clear = function (_boundID) {
                var queIdx = U.isNumber(_boundID) ? _boundID : getQueIdx.call(this, _boundID);
                if (queIdx === -1) {
                    console.log(ax5.info.getError("ax5combobox", "402", "clear"));
                    return;
                }

                clearSelected.call(this, queIdx);
                setOptionSelect.call(this, queIdx, [], false, { noStateChange: false });
                printLabel.call(this, queIdx);
                blurLabel.call(this, queIdx);
                alignComboboxDisplay.call(this);

                return this;
            };

            // 클래스 생성자
            this.main = function () {
                if (arguments && U.isObject(arguments[0])) {
                    this.setConfig(arguments[0]);
                } else {
                    this.init();
                }
            }.apply(this, arguments);
        };
    }());

    COMBOBOX = ax5.ui.combobox;
})();

// ax5.ui.combobox.tmpl
(function () {

    var COMBOBOX = ax5.ui.combobox;
    var U = ax5.util;

    var optionGroup = function optionGroup(columnKeys) {
        return '\n            <div class="ax5combobox-option-group {{theme}} {{size}}" data-ax5combobox-option-group="{{id}}">\n                <div class="ax-combobox-body">\n                    <div class="ax-combobox-option-group-content" data-els="content"></div>\n                </div>\n                <div class="ax-combobox-arrow"></div> \n            </div>\n        ';
    };

    var comboboxDisplay = function comboboxDisplay(columnKeys) {
        return '\n<div class="form-control {{formSize}} ax5combobox-display {{theme}}" \ndata-ax5combobox-display="{{id}}" data-ax5combobox-instance="{{instanceId}}">\n    <div class="ax5combobox-display-table" data-els="display-table">\n        <div data-ax5combobox-display="label-holder"> \n            <a {{^tabIndex}}{{/tabIndex}}{{#tabIndex}}tabindex="{{tabIndex}}" {{/tabIndex}}\n            data-ax5combobox-display="label"\n            spellcheck="false"><input type="text"data-ax5combobox-display="input" style="border:0 none;" /></a>\n        </div>\n        <div data-ax5combobox-display="addon"> \n            {{#multiple}}{{#reset}}\n            <span class="addon-icon-reset" data-selected-clear="true">{{{.}}}</span>\n            {{/reset}}{{/multiple}}\n            {{#icons}}\n            <span class="addon-icon-closed">{{clesed}}</span>\n            <span class="addon-icon-opened">{{opened}}</span>\n            {{/icons}}\n            {{^icons}}\n            <span class="addon-icon-closed"><span class="addon-icon-arrow"></span></span>\n            <span class="addon-icon-opened"><span class="addon-icon-arrow"></span></span>\n            {{/icons}}\n        </div>\n    </div>\n</div>\n        ';
    };

    var formSelect = function formSelect(columnKeys) {
        return '\n            <select tabindex="-1" class="form-control {{formSize}}" name="{{name}}" {{#multiple}}multiple="multiple"{{/multiple}}></select>\n        ';
    };

    var formSelectOptions = function formSelectOptions(columnKeys) {
        return '\n{{#selected}}\n<option value="{{' + columnKeys.optionValue + '}}" selected="true">{{' + columnKeys.optionText + '}}</option>\n{{/selected}}\n';
    };

    var options = function options(columnKeys) {
        return '\n            {{#waitOptions}}\n                <div class="ax-combobox-option-item">\n                        <div class="ax-combobox-option-item-holder">\n                            <span class="ax-combobox-option-item-cell ax-combobox-option-item-label">\n                                {{{lang.loading}}}\n                            </span>\n                        </div>\n                    </div>\n            {{/waitOptions}}\n            {{^waitOptions}}\n                {{#options}}\n                    {{#optgroup}}\n                        <div class="ax-combobox-option-group">\n                            <div class="ax-combobox-option-item-holder">\n                                <span class="ax-combobox-option-group-label">\n                                    {{{.}}}\n                                </span>\n                            </div>\n                            {{#options}}\n                            {{^hide}}\n                            <div class="ax-combobox-option-item" data-option-focus-index="{{@findex}}" data-option-group-index="{{@gindex}}" data-option-index="{{@index}}" \n                            data-option-value="{{' + columnKeys.optionValue + '}}" \n                            {{#' + columnKeys.optionSelected + '}}data-option-selected="true"{{/' + columnKeys.optionSelected + '}}>\n                                <div class="ax-combobox-option-item-holder">\n                                    {{#multiple}}\n                                    <span class="ax-combobox-option-item-cell ax-combobox-option-item-checkbox">\n                                        <span class="item-checkbox-wrap useCheckBox" data-option-checkbox-index="{{@i}}"></span>\n                                    </span>\n                                    {{/multiple}}\n                                    <span class="ax-combobox-option-item-cell ax-combobox-option-item-label">{{' + columnKeys.optionText + '}}</span>\n                                </div>\n                            </div>\n                            {{/hide}}\n                            {{/options}}\n                        </div>                            \n                    {{/optgroup}}\n                    {{^optgroup}}\n                    {{^hide}}\n                    <div class="ax-combobox-option-item" data-option-focus-index="{{@findex}}" data-option-index="{{@index}}" data-option-value="{{' + columnKeys.optionValue + '}}" {{#' + columnKeys.optionSelected + '}}data-option-selected="true"{{/' + columnKeys.optionSelected + '}}>\n                        <div class="ax-combobox-option-item-holder">\n                            {{#multiple}}\n                            <span class="ax-combobox-option-item-cell ax-combobox-option-item-checkbox">\n                                <span class="item-checkbox-wrap useCheckBox" data-option-checkbox-index="{{@i}}"></span>\n                            </span>\n                            {{/multiple}}\n                            <span class="ax-combobox-option-item-cell ax-combobox-option-item-label">{{' + columnKeys.optionText + '}}</span>\n                        </div>\n                    </div>\n                    {{/hide}}\n                    {{/optgroup}}\n                {{/options}}\n                {{^options}}\n                    <div class="ax-combobox-option-item">\n                        <div class="ax-combobox-option-item-holder">\n                            <span class="ax-combobox-option-item-cell ax-combobox-option-item-label">\n                                {{{lang.noOptions}}}\n                            </span>\n                        </div>\n                    </div>\n                {{/options}}\n            {{/waitOptions}}\n        ';
    };

    var label = function label(columnKeys) {
        return '{{#selected}}<div tabindex="-1" data-ax5combobox-selected-label="{{@i}}" data-ax5combobox-selected-text="{{text}}"><div data-ax5combobox-remove="true" \ndata-ax5combobox-remove-index="{{@i}}">{{{removeIcon}}}</div><span>{{' + columnKeys.optionText + '}}</span></div>{{/selected}}';
    };

    COMBOBOX.tmpl = {
        "comboboxDisplay": comboboxDisplay,
        "formSelect": formSelect,
        "formSelectOptions": formSelectOptions,
        "optionGroup": optionGroup,
        "options": options,
        "label": label,

        get: function get(tmplName, data, columnKeys) {
            return ax5.mustache.render(COMBOBOX.tmpl[tmplName].call(this, columnKeys), data);
        }
    };
})();
/*
 * Copyright (c) 2016. tom@axisj.com
 * - github.com/thomasjang
 * - www.axisj.com
 */

// ax5.ui.combobox.util
(function () {

    var COMBOBOX = ax5.ui.combobox;
    var U = ax5.util;

    var nodeTypeProcessor = {
        '1': function _(queIdx, node, editable) {
            var cfg = this.config;
            var textNode = node;

            if ($(node).find("span").get(0)) {
                textNode = $(node).find("span").get(0);
            }

            var text = (textNode.textContent || textNode.innerText).replace(/^[\s\r\n\t]*|[\s\r\n\t]*$/g, '');
            var item = this.queue[queIdx];

            var selectedIndex, option;
            if (item.selected && item.selected.length > 0 && node.getAttribute("data-ax5combobox-selected-text") == text) {
                selectedIndex = node.getAttribute("data-ax5combobox-selected-label");
                option = item.selected[selectedIndex];
                return {
                    index: {
                        gindex: option["@gindex"],
                        index: option["@index"],
                        value: option[cfg.columnKeys.optionValue]
                    }
                };
            } else if (!node.getAttribute("data-ax5combobox-selected-text")) {
                if (text != "") {
                    if (editable) {
                        return text;
                    } else {
                        var $option;
                        if (item.optionFocusIndex > -1) $option = this.activecomboboxOptionGroup.find('[data-option-focus-index="' + item.optionFocusIndex + '"]');
                        if (item.optionFocusIndex > -1 && $option.get(0) && $option.attr("data-option-value")) {
                            return {
                                index: {
                                    gindex: $option.attr("data-option-group-index"),
                                    index: $option.attr("data-option-index")
                                }
                            };
                        } else {
                            return item.editable ? text : undefined;
                        }
                    }
                } else {
                    return undefined;
                }
            } else {
                return text;
            }
        },
        '3': function _(queIdx, node, editable) {
            var cfg = this.config;
            var text = (node.textContent || node.innerText).replace(/^[\s\r\n\t]*|[\s\r\n\t]*$/g, '');
            var item = this.queue[queIdx];

            if (text != "") {
                if (editable) {
                    return text;
                } else {
                    var $option;
                    if (item.optionFocusIndex > -1) $option = this.activecomboboxOptionGroup.find('[data-option-focus-index="' + item.optionFocusIndex + '"]');
                    if (item.optionFocusIndex > -1 && $option.get(0) && $option.attr("data-option-value")) {
                        return {
                            index: {
                                gindex: $option.attr("data-option-group-index"),
                                index: $option.attr("data-option-index")
                            }
                        };
                    } else {
                        return item.editable ? text : undefined;
                    }
                }
            } else {
                return undefined;
            }
        }
    };

    COMBOBOX.util = {
        nodeTypeProcessor: nodeTypeProcessor
    };
})();

/*
 * Copyright (c) 2017. tom@axisj.com
 * - github.com/thomasjang
 * - www.axisj.com
 */

/**
 * ax5combobox jquery extends
 * @namespace jQueryExtends
 */

/**
 * @method jQueryExtends.ax5combobox
 * @param {String} methodName
 * @param [arguments]
 * @param [arguments]
 * @example
 * ```html
 * <div data-ax5combobox="ax1" data-ax5combobox-config='{
 *  multiple: true,
 *  editable: true,
 *  size: "",
 *  theme:""
 *  }'></div>
 * <script>
 * jQuery('[data-ax5combobox="ax1"]').ax5combobox();
 * $('[data-ax5combobox="ax1"]').ax5combobox("getSelectedOption");
 * $('[data-ax5combobox="ax1"]').ax5combobox("setValue", ["string", "number"]);
 * $('[data-ax5combobox="ax1"]').ax5combobox("enable");
 * $('[data-ax5combobox="ax1"]').ax5combobox("disable");
 * </script>
 * ```
 */

ax5.ui.combobox_instance = new ax5.ui.combobox();
jQuery.fn.ax5combobox = function () {
    return function (config) {
        if (ax5.util.isString(arguments[0])) {
            var methodName = arguments[0];

            switch (methodName) {
                case "open":
                    return ax5.ui.combobox_instance.open(this);
                    break;
                case "close":
                    return ax5.ui.combobox_instance.close(this);
                    break;
                case "setValue":
                    return ax5.ui.combobox_instance.setValue(this, arguments[1], arguments[2], arguments[3], arguments[4] || "justSetValue");
                    break;
                case "setText":
                    return ax5.ui.combobox_instance.setText(this, arguments[1], arguments[2], arguments[3], arguments[4] || "justSetValue");
                    break;
                case "getSelectedOption":
                    return ax5.ui.combobox_instance.getSelectedOption(this);
                    break;
                case "enable":
                    return ax5.ui.combobox_instance.enable(this);
                    break;
                case "disable":
                    return ax5.ui.combobox_instance.disable(this);
                    break;
                case "blur":
                    return ax5.ui.combobox_instance.blur(this);
                case "clear":
                    return ax5.ui.combobox_instance.clear(this);
                default:
                    return this;
            }
        } else {
            if (typeof config == "undefined") config = {};
            jQuery.each(this, function () {
                var defaultConfig = {
                    target: this
                };
                config = jQuery.extend({}, config, defaultConfig);
                ax5.ui.combobox_instance.bind(config);
            });
        }
        return this;
    };
}();
"use strict";

// ax5.ui.layout
(function () {
    var UI = ax5.ui;
    var U = ax5.util;

    UI.addClass({
        className: "layout"
    }, function () {
        /**
         * @class ax5layout
         * @alias ax5.ui.layout
         * @author tom@axisj.com
         */

        var ax5layout = function ax5layout() {
            var self = this,
                cfg,
                ENM = {
                "mousedown": ax5.info.supportTouch ? "touchstart" : "mousedown",
                "mousemove": ax5.info.supportTouch ? "touchmove" : "mousemove",
                "mouseup": ax5.info.supportTouch ? "touchend" : "mouseup"
            },
                getMousePosition = function getMousePosition(e) {
                var mouseObj = 'changedTouches' in e.originalEvent && e.changedTouches ? e.originalEvent.changedTouches[0] : e;
                return {
                    clientX: mouseObj.clientX,
                    clientY: mouseObj.clientY
                };
            };

            this.instanceId = ax5.getGuid();
            this.config = {
                theme: 'default',
                animateTime: 250,
                splitter: {
                    size: 1
                },
                autoResize: true
            };
            this.queue = [];

            this.openTimer = null;
            this.closeTimer = null;
            this.resizer = null;

            cfg = this.config;

            var onStateChanged = function onStateChanged(opts, that) {
                if (opts && opts.onStateChanged) {
                    opts.onStateChanged.call(that, that);
                } else if (this.onStateChanged) {
                    this.onStateChanged.call(that, that);
                }
                return true;
            },
                alignLayoutAll = function alignLayoutAll() {
                var i = this.queue.length;
                while (i--) {
                    if (typeof this.queue[i].parentQueIdx === "undefined" && this.queue[i].autoResize) {
                        alignLayout.call(this, i, null, "windowResize");
                    }
                }
            },
                getDockPanelOuterSize = {
                "width": function width(item, panel) {
                    return panel ? panel.__width + (panel.split ? item.splitter.size : 0) : 0;
                },
                "height": function height(item, panel) {
                    return panel ? panel.__height + (panel.split ? item.splitter.size : 0) : 0;
                }
            },
                getPixel = function getPixel(size, parentSize) {
                if (size == "*") {
                    return;
                } else if (U.right(size, 1) == "%") {
                    return parentSize * U.number(size) / 100;
                } else {
                    return Number(size);
                }
            },
                alignLayout = function () {

                var beforeSetCSS = {
                    "split": {
                        "horizontal": function horizontal(item, panel, panelIndex) {
                            if (panel.splitter) {
                                panel.__height = item.splitter.size;
                            } else {
                                if (panelIndex == item.splitPanel.length - 1) {
                                    if (item.splitPanel.asteriskLength == 0) {
                                        panel.height = "*";
                                        panel.__height = undefined;
                                        item.splitPanel.asteriskLength++;
                                    } else {
                                        if (panel.height == "*") {
                                            item.splitPanel.asteriskLength++;
                                        } else {
                                            //panel.__height = getPixel(panel.height, item.targetDimension.height);
                                        }
                                    }
                                } else {
                                    if (panel.height == "*") {
                                        item.splitPanel.asteriskLength++;
                                    } else {
                                        //panel.__height = getPixel(panel.height, item.targetDimension.height);
                                    }
                                }
                            }
                        },
                        "vertical": function vertical(item, panel, panelIndex) {
                            if (panel.splitter) {
                                panel.__width = item.splitter.size;
                            } else {
                                if (panelIndex == item.splitPanel.length - 1) {
                                    if (item.splitPanel.asteriskLength == 0) {
                                        panel.width = "*";
                                        panel.__width = undefined;
                                        item.splitPanel.asteriskLength++;
                                    } else {
                                        if (panel.width == "*") {
                                            item.splitPanel.asteriskLength++;
                                        }
                                    }
                                } else {
                                    if (panel.width == "*") {
                                        item.splitPanel.asteriskLength++;
                                    } else {
                                        //panel.__width = getPixel(panel.width, item.targetDimension.width);
                                    }
                                }
                            }
                        }
                    }
                };
                var setCSS = {
                    "top": function top(item, panel) {
                        panel.$target.css({ height: panel.__height || 0 });
                        if (panel.split) {
                            panel.$splitter.css({ height: item.splitter.size, top: panel.__height || 0 });
                        }
                    },
                    "bottom": function bottom(item, panel) {
                        panel.$target.css({ height: panel.__height || 0 });
                        if (panel.split) {
                            panel.$splitter.css({ height: item.splitter.size, bottom: panel.__height || 0 });
                        }
                    },
                    "left": function left(item, panel) {
                        var css = {
                            width: panel.__width || 0,
                            height: item.targetDimension.height
                        };

                        if (item.dockPanel.top) {
                            css.height -= item.dockPanel.top.__height;
                            css.top = item.dockPanel.top.__height;
                            if (item.dockPanel.top.split) {
                                css.height -= item.splitter.size;
                                css.top += item.splitter.size;
                            }
                        }
                        if (item.dockPanel.bottom) {
                            css.height -= item.dockPanel.bottom.__height;
                            if (item.dockPanel.bottom.split) {
                                css.height -= item.splitter.size;
                            }
                        }

                        panel.$target.css(css);

                        if (panel.split) {
                            panel.$splitter.css({ width: item.splitter.size, height: css.height, top: css.top, left: css.width });
                        }
                    },
                    "right": function right(item, panel) {
                        var css = {
                            width: panel.__width || 0,
                            height: item.targetDimension.height
                        };

                        if (item.dockPanel.top) {
                            css.height -= item.dockPanel.top.__height;
                            css.top = item.dockPanel.top.__height;
                            if (item.dockPanel.top.split) {
                                css.height -= item.splitter.size;
                                css.top += item.splitter.size;
                            }
                        }
                        if (item.dockPanel.bottom) {
                            css.height -= item.dockPanel.bottom.__height;
                            if (item.dockPanel.bottom.split) {
                                css.height -= item.splitter.size;
                            }
                        }

                        panel.$target.css(css);

                        if (panel.split) {
                            panel.$splitter.css({ width: item.splitter.size, height: css.height, top: css.top, right: css.width });
                        }
                    },
                    "center": function center(item, panel) {
                        var css = {
                            width: item.targetDimension.width,
                            height: item.targetDimension.height
                        };

                        if (item.dockPanel.top) {
                            css.height -= item.dockPanel.top.__height || 0;
                            css.top = item.dockPanel.top.__height || 0;
                            if (item.dockPanel.top.split) {
                                css.height -= item.splitter.size;
                                css.top += item.splitter.size;
                            }
                        }
                        if (item.dockPanel.bottom) {
                            css.height -= item.dockPanel.bottom.__height || 0;
                            if (item.dockPanel.bottom.split) {
                                css.height -= item.splitter.size;
                            }
                        }
                        if (item.dockPanel.left) {
                            css.width -= item.dockPanel.left.__width || 0;
                            css.left = item.dockPanel.left.__width || 0;
                            if (item.dockPanel.left.split) {
                                css.width -= item.splitter.size;
                                css.left += item.splitter.size;
                            }
                        }
                        if (item.dockPanel.right) {
                            css.width -= item.dockPanel.right.__width || 0;
                            if (item.dockPanel.right.split) {
                                css.width -= item.splitter.size;
                            }
                        }

                        var minWidth = panel.minWidth || 0;
                        var minHeight = panel.minHeight || 0;

                        // 레이아웃의 최소 너비 높이를 주어 레이아웃 덕패널이 겹치는 일이 없게 합니다
                        if (css.width < minWidth) {
                            css.width = minWidth;
                            item.$target.css({ minWidth: minWidth + getDockPanelOuterSize["width"](item.dockPanel.left, item.splitter.size) + getDockPanelOuterSize["width"](item.dockPanel.right, item.splitter.size) });
                        }
                        if (css.height < minHeight) {
                            css.height = minHeight;
                            item.$target.css({ minHeight: minHeight + getDockPanelOuterSize["height"](item.dockPanel.top, item.splitter.size) + getDockPanelOuterSize["height"](item.dockPanel.bottom, item.splitter.size) });
                        }

                        panel.$target.css(css);
                    },
                    "split": {
                        "horizontal": function horizontal(item, panel, panelIndex, withoutAsteriskSize, windowResize) {
                            var css = {
                                display: "block"
                            };
                            var prevPosition = panelIndex ? Number(item.splitPanel[panelIndex - 1].offsetEnd) : 0;
                            if (panel.splitter) {
                                css.height = item.splitter.size;
                            } else {
                                if (panel.height == "*" && (typeof panel.__height === "undefined" || windowResize)) {
                                    // 남은 전체 공간을 사용
                                    css.height = panel.__height = (item.targetDimension.height - withoutAsteriskSize) / item.splitPanel.asteriskLength;
                                } else {
                                    css.height = panel.__height || 0;
                                }
                            }
                            css.top = prevPosition;
                            panel.offsetStart = prevPosition;
                            panel.offsetEnd = Number(prevPosition) + Number(css.height);
                            panel.$target.css(css);
                        },
                        "vertical": function vertical(item, panel, panelIndex, withoutAsteriskSize, windowResize) {
                            var css = {
                                display: "block"
                            };
                            var prevPosition = panelIndex ? Number(item.splitPanel[panelIndex - 1].offsetEnd) : 0;

                            if (panel.splitter) {
                                css.width = item.splitter.size;
                            } else {
                                if (panel.width == "*" && (typeof panel.__width === "undefined" || windowResize)) {
                                    // 남은 전체 공간을 사용
                                    css.width = panel.__width = (item.targetDimension.width - withoutAsteriskSize) / item.splitPanel.asteriskLength;
                                } else {
                                    css.width = panel.__width || 0;
                                }
                            }
                            css.left = prevPosition;
                            panel.offsetStart = prevPosition;
                            panel.offsetEnd = Number(prevPosition) + Number(css.width);

                            panel.$target.css(css);
                        }
                    }
                };
                var layoutProcessor = {
                    "dock-panel": function dockPanel(item) {
                        for (var panel in item.dockPanel) {
                            if (item.dockPanel[panel].$target && item.dockPanel[panel].$target.get(0)) {
                                if (panel in setCSS) {
                                    setCSS[panel].call(this, item, item.dockPanel[panel]);
                                }
                            }
                        }
                    },
                    "split-panel": function splitPanel(item, windowResize) {
                        //console.log(item.splitPanel);
                        var withoutAsteriskSize;
                        item.splitPanel.asteriskLength = 0;
                        item.splitPanel.forEach(function (panel, panelIndex) {
                            beforeSetCSS["split"][item.orientation].call(this, item, panel, panelIndex);
                        });

                        if (item.orientation == "horizontal") {
                            withoutAsteriskSize = U.sum(item.splitPanel, function (n) {
                                if (n.height != "*") return U.number(n.__height);
                            });
                        } else {
                            withoutAsteriskSize = U.sum(item.splitPanel, function (n) {
                                if (n.width != "*") return U.number(n.__width);
                            });
                        }

                        item.splitPanel.forEach(function (panel, panelIndex) {
                            setCSS["split"][item.orientation].call(this, item, panel, panelIndex, withoutAsteriskSize, windowResize);
                        });
                    }
                };
                var childResize = function childResize(item) {
                    var i = item.childQueIdxs.length;
                    while (i--) {
                        alignLayout.call(this, item.childQueIdxs[i]);
                    }
                };

                return function (queIdx, callback, windowResize) {
                    var item = this.queue[queIdx];

                    // 레이아웃 타겟의 CSS속성을 미리 저장해 둡니다. 왜? 패널별로 크기 계산 할 때 쓰려고
                    item.targetDimension = {
                        height: item.$target.innerHeight(),
                        width: item.$target.innerWidth()
                    };

                    if (item.layout in layoutProcessor) {
                        layoutProcessor[item.layout].call(this, item, windowResize);
                    }

                    if (item.childQueIdxs) childResize.call(this, item, windowResize);
                    if (item.onResize) {
                        setTimeout(function () {
                            this.onResize.call(this, this);
                        }.bind(item), 1);
                    }
                    if (callback) {
                        callback.call(item, item);
                    }
                };
            }(),
                resizeSplitter = {
                "on": function on(queIdx, panel, $splitter) {
                    var item = this.queue[queIdx];
                    var splitterOffset = $splitter.position();
                    var splitterBox = {
                        width: $splitter.width(), height: $splitter.height()
                    };
                    var getResizerPosition = {
                        "left": function left(e) {
                            var mouseObj = 'changedTouches' in e.originalEvent && e.changedTouches ? e.originalEvent.changedTouches[0] : e;

                            panel.__da = mouseObj.clientX - panel.mousePosition.clientX;
                            var minWidth = panel.minWidth || 0;
                            var maxWidth = panel.maxWidth || item.targetDimension.width - getDockPanelOuterSize["width"](item, item.dockPanel.left) - getDockPanelOuterSize["width"](item, item.dockPanel.right);

                            if (panel.__width + panel.__da < minWidth) {
                                panel.__da = -panel.__width + minWidth;
                            } else if (maxWidth < panel.__width + panel.__da) {
                                panel.__da = maxWidth - panel.__width;
                            }
                            return { left: panel.$splitter.position().left + panel.__da };
                        },
                        "right": function right(e) {
                            var mouseObj = 'changedTouches' in e.originalEvent && e.changedTouches ? e.originalEvent.changedTouches[0] : e;

                            panel.__da = mouseObj.clientX - panel.mousePosition.clientX;
                            var minWidth = panel.minWidth || 0;
                            var maxWidth = panel.maxWidth || item.targetDimension.width - getDockPanelOuterSize["width"](item, item.dockPanel.left) - getDockPanelOuterSize["width"](item, item.dockPanel.right);

                            if (panel.__width - panel.__da < minWidth) {
                                panel.__da = panel.__width - minWidth;
                            } else if (maxWidth < panel.__width - panel.__da) {
                                panel.__da = -maxWidth + panel.__width;
                            }
                            return { left: panel.$splitter.position().left + panel.__da };
                        },
                        "top": function top(e) {
                            var mouseObj = 'changedTouches' in e.originalEvent && e.changedTouches ? e.originalEvent.changedTouches[0] : e;

                            panel.__da = mouseObj.clientY - panel.mousePosition.clientY;
                            var minHeight = panel.minHeight || 0;
                            var maxHeight = panel.maxHeight || item.targetDimension.height - getDockPanelOuterSize["height"](item, item.dockPanel.top) - getDockPanelOuterSize["height"](item, item.dockPanel.bottom);

                            if (panel.__height + panel.__da < minHeight) {
                                panel.__da = -panel.__height + minHeight;
                            } else if (maxHeight < panel.__height + panel.__da) {
                                panel.__da = maxHeight - panel.__height;
                            }
                            return { top: panel.$splitter.position().top + panel.__da };
                        },
                        "bottom": function bottom(e) {
                            var mouseObj = 'changedTouches' in e.originalEvent && e.changedTouches ? e.originalEvent.changedTouches[0] : e;

                            panel.__da = mouseObj.clientY - panel.mousePosition.clientY;
                            var minHeight = panel.minHeight || 0;
                            var maxHeight = panel.maxHeight || item.targetDimension.height - getDockPanelOuterSize["height"](item, item.dockPanel.top) - getDockPanelOuterSize["height"](item, item.dockPanel.bottom);

                            if (panel.__height - panel.__da < minHeight) {
                                panel.__da = panel.__height - minHeight;
                            } else if (maxHeight < panel.__height - panel.__da) {
                                panel.__da = -maxHeight + panel.__height;
                            }
                            return { top: panel.$splitter.position().top + panel.__da };
                        },
                        "split": function split(e) {
                            var mouseObj = 'changedTouches' in e.originalEvent && e.changedTouches ? e.originalEvent.changedTouches[0] : e;

                            if (item.orientation == "horizontal") {
                                panel.__da = mouseObj.clientY - panel.mousePosition.clientY;

                                var prevPanel = item.splitPanel[panel.panelIndex - 1];
                                var nextPanel = item.splitPanel[panel.panelIndex + 1];

                                var prePanelMinHeight = prevPanel.minHeight || 0;
                                var nextPanelMinHeight = nextPanel.minHeight || 0;

                                if (panel.offsetStart + panel.__da < prevPanel.offsetStart + prePanelMinHeight) {
                                    panel.__da = prevPanel.offsetStart - panel.offsetStart + prePanelMinHeight;
                                } else if (panel.offsetStart + panel.__da > nextPanel.offsetEnd - nextPanelMinHeight) {
                                    panel.__da = nextPanel.offsetEnd - panel.offsetEnd - nextPanelMinHeight;
                                }

                                return { top: panel.$target.position().top + panel.__da };
                            } else {
                                /// todo : min & max 범위 정하기
                                panel.__da = mouseObj.clientX - panel.mousePosition.clientX;

                                var prevPanel = item.splitPanel[panel.panelIndex - 1];
                                var nextPanel = item.splitPanel[panel.panelIndex + 1];
                                var prePanelMinWidth = prevPanel.minWidth || 0;
                                var nextPanelMinWidth = nextPanel.minWidth || 0;

                                if (panel.offsetStart + panel.__da < prevPanel.offsetStart + prePanelMinWidth) {
                                    panel.__da = prevPanel.offsetStart - panel.offsetStart + prePanelMinWidth;
                                } else if (panel.offsetStart + panel.__da > nextPanel.offsetEnd - nextPanelMinWidth) {
                                    panel.__da = nextPanel.offsetEnd - panel.offsetEnd - nextPanelMinWidth;
                                }
                                return { left: Number(panel.$target.position().left) + Number(panel.__da) };
                            }
                        }
                    };
                    panel.__da = 0; // 패널의 변화량

                    jQuery(document.body).bind(ENM["mousemove"] + ".ax5layout-" + this.instanceId, function (e) {
                        if (!self.resizer) {

                            self.resizer = jQuery('<div class="ax5layout-resizer panel-' + panel.resizerType + '" ondragstart="return false;"></div>');
                            self.resizer.css({
                                left: splitterOffset.left,
                                top: splitterOffset.top,
                                width: splitterBox.width,
                                height: splitterBox.height
                            });
                            item.$target.append(self.resizer);
                        }
                        self.resizer.css(getResizerPosition[panel.resizerType](e));
                    }).bind(ENM["mouseup"] + ".ax5layout-" + this.instanceId, function (e) {
                        resizeSplitter.off.call(self, queIdx, panel, $splitter);
                    }).bind("mouseleave.ax5layout-" + this.instanceId, function (e) {
                        resizeSplitter.off.call(self, queIdx, panel, $splitter);
                    });

                    jQuery(document.body).attr('unselectable', 'on').css('user-select', 'none').on('selectstart', false);
                },
                "off": function off(queIdx, panel, $splitter) {
                    var item = this.queue[queIdx];
                    var setPanelSize = {
                        "dock-panel": {
                            "left": function left(queIdx, panel) {
                                panel.__width += panel.__da;
                            },
                            "right": function right() {
                                panel.__width -= panel.__da;
                            },
                            "top": function top() {
                                panel.__height += panel.__da;
                            },
                            "bottom": function bottom() {
                                panel.__height -= panel.__da;
                            }
                        },
                        "split-panel": {
                            "split": function split() {
                                if (item.orientation == "horizontal") {
                                    // 앞과 뒤의 높이 조절
                                    item.splitPanel[panel.panelIndex - 1].__height += panel.__da;
                                    item.splitPanel[panel.panelIndex + 1].__height -= panel.__da;
                                } else {
                                    // 앞과 뒤의 높이 조절

                                    item.splitPanel[panel.panelIndex - 1].__width += panel.__da;
                                    item.splitPanel[panel.panelIndex + 1].__width -= panel.__da;
                                }
                            }
                        },
                        "tab-panel": {}
                    };

                    if (self.resizer) {
                        self.resizer.remove();
                        self.resizer = null;
                        setPanelSize[this.queue[queIdx].layout][panel.resizerType].call(this, queIdx, panel);
                        alignLayout.call(this, queIdx);
                    }

                    jQuery(document.body).unbind(ENM["mousemove"] + ".ax5layout-" + this.instanceId).unbind(ENM["mouseup"] + ".ax5layout-" + this.instanceId).unbind("mouseleave.ax5layout-" + this.instanceId);

                    jQuery(document.body).removeAttr('unselectable').css('user-select', 'auto').off('selectstart');
                }
            },
                tabControl = {
                "open": function open(queIdx, layout, panelIndex) {
                    if (layout.activePanelIndex != panelIndex) {
                        layout.tabPanel[panelIndex].active = true;
                        layout.tabPanel[layout.activePanelIndex].active = false;
                        layout.$target.find('[data-tab-panel-label="' + panelIndex + '"]').attr("data-tab-active", "true");
                        layout.$target.find('[data-tab-panel-label="' + layout.activePanelIndex + '"]').removeAttr("data-tab-active");
                        layout.tabPanel[panelIndex].$target.attr("data-tab-active", "true");
                        layout.tabPanel[layout.activePanelIndex].$target.removeAttr("data-tab-active");
                        layout.activePanelIndex = panelIndex;

                        if (layout.onOpenTab) {
                            var that = {
                                '$target': layout.$target,
                                name: layout.name,
                                id: layout.id,
                                layout: layout.layout,
                                activePanelIndex: layout.activePanelIndex,
                                activePanel: layout.tabPanel[layout.activePanelIndex],
                                tabPanel: layout.tabPanel
                            };
                            layout.onOpenTab.call(that);
                        }
                    }
                }
            },
                getTabLabesTmpl = function getTabLabesTmpl() {
                return "\n<div data-tab-panel-label-holder=\"{{id}}\">\n    <div data-tab-panel-label-border=\"{{id}}\"></div>\n    <div data-tab-panel-label-table=\"{{id}}\">\n        <div data-tab-panel-aside=\"left\"></div>\n    {{#tabPanel}}\n        <div data-tab-panel-label=\"{{panelIndex}}\" data-tab-active=\"{{active}}\">\n            <div data-tab-label=\"{{panelIndex}}\">{{{label}}}</div>\n        </div>\n    {{/tabPanel}}\n        <div data-tab-panel-aside=\"right\"></div>\n    </div>\n</div>\n";
            },
                bindLayoutTarget = function () {

                var applyLayout = {
                    "dock-panel": function dockPanel(queIdx) {
                        var item = this.queue[queIdx];
                        item.dockPanel = {};
                        item.$target.find('>[data-dock-panel]').each(function () {

                            var panelInfo = {};
                            (function (data) {
                                if (U.isObject(data) && !data.error) {
                                    panelInfo = jQuery.extend(true, panelInfo, data);
                                }
                            })(U.parseJson(this.getAttribute("data-dock-panel"), true));

                            if ('dock' in panelInfo) {
                                panelInfo.$target = jQuery(this);
                                panelInfo.$target.addClass("dock-panel-" + panelInfo.dock);

                                if (panelInfo.split = panelInfo.split && panelInfo.split.toString() == "true") {
                                    panelInfo.$splitter = jQuery('<div data-splitter="" class="dock-panel-' + panelInfo.dock + '"></div>');
                                    panelInfo.$splitter.bind(ENM["mousedown"], function (e) {
                                        panelInfo.mousePosition = getMousePosition(e);
                                        resizeSplitter.on.call(self, queIdx, panelInfo, panelInfo.$splitter);
                                    }).bind("dragstart", function (e) {
                                        U.stopEvent(e);
                                        return false;
                                    });
                                    item.$target.append(panelInfo.$splitter);
                                }

                                if (panelInfo.dock == "top" || panelInfo.dock == "bottom") {
                                    panelInfo.__height = getPixel(panelInfo.height, item.targetDimension.height);
                                } else {
                                    panelInfo.__width = getPixel(panelInfo.width, item.targetDimension.width);
                                }
                                panelInfo.resizerType = panelInfo.dock;
                                item.dockPanel[panelInfo.dock] = panelInfo;
                            }
                        });
                    },
                    "split-panel": function splitPanel(queIdx) {
                        var item = this.queue[queIdx];
                        item.splitPanel = [];
                        item.$target.find('>[data-split-panel], >[data-splitter]').each(function (ELIndex) {

                            var panelInfo = {};
                            (function (data) {
                                if (U.isObject(data) && !data.error) {
                                    panelInfo = jQuery.extend(true, panelInfo, data);
                                }
                            })(U.parseJson(this.getAttribute("data-split-panel") || this.getAttribute("data-splitter"), true));

                            panelInfo.$target = jQuery(this);
                            panelInfo.$target.addClass("split-panel-" + item.orientation);
                            panelInfo.panelIndex = ELIndex;

                            if (this.getAttribute("data-splitter")) {

                                panelInfo.splitter = true;
                                panelInfo.$target.bind(ENM["mousedown"], function (e) {
                                    if (panelInfo.panelIndex > 0 && panelInfo.panelIndex < item.splitPanel.length - 1) {
                                        panelInfo.mousePosition = getMousePosition(e);
                                        resizeSplitter.on.call(self, queIdx, panelInfo, panelInfo.$target);
                                    }
                                }).bind("dragstart", function (e) {
                                    U.stopEvent(e);
                                    return false;
                                });
                                panelInfo.resizerType = "split";
                            } else {

                                if (item.orientation == "horizontal") {
                                    panelInfo.__height = getPixel(panelInfo.height, item.targetDimension.height);
                                } else {
                                    item.orientation = "vertical";
                                    panelInfo.__width = getPixel(panelInfo.width, item.targetDimension.width);
                                }
                            }

                            item.splitPanel.push(panelInfo);
                        });
                    },
                    "tab-panel": function tabPanel(queIdx) {
                        var item = this.queue[queIdx];

                        var hasActivePanel = false;
                        var activePanelIndex = -1;
                        item.tabPanel = [];
                        item.$target.find('>[data-tab-panel]').each(function (ELIndex) {
                            var panelInfo = {};
                            (function (data) {
                                if (U.isObject(data) && !data.error) {
                                    panelInfo = jQuery.extend(true, panelInfo, data);
                                }
                            })(U.parseJson(this.getAttribute("data-tab-panel"), true));

                            if (hasActivePanel) {
                                panelInfo.active = false;
                            }

                            panelInfo.$target = jQuery(this);

                            if (panelInfo.active && panelInfo.active != "false") {
                                hasActivePanel = true;
                                item.activePanelIndex = ELIndex;
                                panelInfo.$target.attr("data-tab-active", "true");
                            }

                            panelInfo.panelIndex = ELIndex;
                            item.tabPanel.push(panelInfo);
                        });

                        if (!hasActivePanel) {
                            item.tabPanel[0].active = true;
                            item.tabPanel[0].$target.attr("data-tab-active", "true");
                            item.activePanelIndex = 0;
                        }

                        // make tabLabel
                        item.$target.append(jQuery(ax5.mustache.render(getTabLabesTmpl.call(this, queIdx), item)));
                        item.$target.on("click", '[data-tab-panel-label]', function (e) {
                            var index = this.getAttribute("data-tab-panel-label");
                            tabControl.open.call(self, queIdx, item, index);
                        });
                    }
                };

                return function (queIdx) {
                    var item = this.queue[queIdx];
                    var data = {};

                    // 레이아웃 타겟의 CSS속성을 미리 저장해 둡니다. 왜? 패널별로 크기 계산 할 때 쓰려고
                    item.targetDimension = {
                        height: item.$target.innerHeight(),
                        width: item.$target.innerWidth()
                    };

                    // 부모 컨테이너가 ax5layout인지 판단 필요.
                    if (item.$target.parents("[data-ax5layout]").get(0)) {
                        hooksResizeLayout.call(this, item.$target.parents("[data-ax5layout]").get(0), queIdx);
                    }

                    if (item.layout in applyLayout) {
                        applyLayout[item.layout].call(this, queIdx);
                    }
                    alignLayout.call(this, queIdx);
                };
            }(),
                getQueIdx = function getQueIdx(boundID) {
                if (!U.isString(boundID)) {
                    boundID = jQuery(boundID).data("data-ax5layout-id");
                }
                if (!U.isString(boundID)) {
                    //console.log(ax5.info.getError("ax5layout", "402", "getQueIdx"));
                    return -1;
                }
                return U.search(this.queue, function () {
                    return this.id == boundID;
                });
            },
                hooksResizeLayout = function hooksResizeLayout(boundID, childQueIdx) {
                var queIdx = U.isNumber(boundID) ? boundID : getQueIdx.call(this, boundID);
                if (!this.queue[queIdx].childQueIdxs) this.queue[queIdx].childQueIdxs = [];
                this.queue[queIdx].childQueIdxs.push(childQueIdx);
                this.queue[childQueIdx].parentQueIdx = queIdx;
            };

            /**
             * Preferences of layout UI
             * @method ax5layout.setConfig
             * @param {Object} config - 클래스 속성값
             * @param {Number} [config.animateTime=250]
             * @param {Object} [config.splitter]
             * @param {Number} [config.splitter.size=4]
             * @param {Boolean} [config.autoResize=true]
             * @returns {ax5layout}
             * @example
             * ```js
             * jQuery('[data-ax5layout="ax1"]').ax5layout({
             *     onResize: function () {
             *     }
             * });
             *
             * jQuery('[data-ax5layout="ax1"]').ax5layout("resize", {
             *     top: {height: 100},
             *     bottom: 100,
             *     left: 100,
             *     right: 100
             * });
             * ```
             */
            this.init = function () {
                this.onStateChanged = cfg.onStateChanged;
                this.onClick = cfg.onClick;
                jQuery(window).bind("resize.ax5layout-" + this.instanceId, function () {
                    alignLayoutAll.call(this);
                }.bind(this));
            };

            /**
             * @method ax5layout.bind
             * @param {Object} item
             * @param {String} [item.layout]
             * @param {String} [item.theme]
             * @param {Element} item.target
             * @param {Object[]} item.options
             * @param {Object} [item.splitter]
             * @param {Number} [item.splitter.size=4]
             * @param {Boolean} [item.autoResize=true]
             * @returns {ax5layout}
             */
            this.bind = function (item) {
                var UIConfig = {},
                    queIdx;

                item = jQuery.extend(true, UIConfig, cfg, item);
                if (!item.target) {
                    console.log(ax5.info.getError("ax5layout", "401", "bind"));
                    return this;
                }

                item.$target = jQuery(item.target);

                if (!item.id) item.id = item.$target.data("data-ax5layout-id");
                if (!item.id) {
                    item.id = 'ax5layout-' + ax5.getGuid();
                    item.$target.data("data-ax5layout-id", item.id);
                }
                item.name = item.$target.attr("data-ax5layout");
                if (item.options) {
                    item.options = JSON.parse(JSON.stringify(item.options));
                }

                // target attribute data
                (function (data) {
                    if (U.isObject(data) && !data.error) {
                        item = jQuery.extend(true, item, data);
                    }
                })(U.parseJson(item.$target.attr("data-config"), true));

                queIdx = U.search(this.queue, function () {
                    return this.id == item.id;
                });

                if (queIdx === -1) {
                    this.queue.push(item);
                    bindLayoutTarget.call(this, this.queue.length - 1);
                } else {
                    this.queue[queIdx] = jQuery.extend(true, {}, this.queue[queIdx], item);
                    bindLayoutTarget.call(this, queIdx);
                }

                UIConfig = null;
                queIdx = null;
                return this;
            };

            /**
             * @method ax5layout.align
             * @param boundID
             * @param {Function} [callback]
             * @param {String} [windowResize]
             * @returns {ax5layout}
             */
            this.align = function (boundID, windowResize) {
                var queIdx = U.isNumber(boundID) ? boundID : getQueIdx.call(this, boundID);

                if (queIdx === -1) {
                    var i = this.queue.length;
                    while (i--) {
                        alignLayout.call(this, i, null, windowResize);
                    }
                } else {
                    alignLayout.call(this, queIdx, null, windowResize);
                }
                return this;
            };

            /**
             * @method ax5layout.onResize
             * @param boundID
             * @param fn
             * @returns {ax5layout}
             */
            this.onResize = function (boundID, fn) {
                var queIdx = U.isNumber(boundID) ? boundID : getQueIdx.call(this, boundID);
                if (queIdx === -1) {
                    console.log(ax5.info.getError("ax5layout", "402", "onResize"));
                    return;
                }
                this.queue[queIdx].onResize = fn;
                return this;
            };

            /**
             * @method ax5layout.resize
             * @param boundID
             * @param {Object} resizeOption
             * @param {Function} [callback]
             * @returns {ax5layout}
             */
            this.resize = function () {

                var resizeLayoutPanel = {
                    "dock-panel": function dockPanel(item, resizeOption) {
                        ["top", "bottom", "left", "right"].forEach(function (dock) {
                            if (resizeOption[dock] && item.dockPanel[dock]) {
                                if (dock == "top" || dock == "bottom") {
                                    item.dockPanel[dock].__height = U.isObject(resizeOption[dock]) ? resizeOption[dock].height : resizeOption[dock];
                                } else if (dock == "left" || dock == "right") {
                                    item.dockPanel[dock].__width = U.isObject(resizeOption[dock]) ? resizeOption[dock].width : resizeOption[dock];
                                }
                            }
                        });
                    },
                    "split-panel": function splitPanel() {},
                    "tab-panel": function tabPanel() {}
                };

                return function (boundID, resizeOption, callback) {
                    var queIdx = U.isNumber(boundID) ? boundID : getQueIdx.call(this, boundID);
                    if (queIdx === -1) {
                        var i = this.queue.length;
                        while (i--) {
                            resizeLayoutPanel[this.queue[i].layout].call(this, this.queue[i], resizeOption);
                            alignLayout.call(this, i, callback);
                        }
                    } else {
                        if (this.queue[queIdx]) {
                            resizeLayoutPanel[this.queue[queIdx].layout].call(this, this.queue[queIdx], resizeOption);
                            alignLayout.call(this, queIdx, callback);
                        }
                    }

                    return this;
                };
            }();

            this.reset = function () {

                var resetLayoutPanel = {
                    "dock-panel": function dockPanel(item) {
                        ["top", "bottom", "left", "right"].forEach(function (dock) {
                            if (item.dockPanel[dock]) {
                                if (dock == "top" || dock == "bottom") {
                                    item.dockPanel[dock].__height = item.dockPanel[dock].height;
                                } else if (dock == "left" || dock == "right") {
                                    item.dockPanel[dock].__width = item.dockPanel[dock].width;
                                }
                            }
                        });
                    },
                    "split-panel": function splitPanel(item) {
                        item.splitPanel.forEach(function (panel) {
                            if (item.orientation == "vertical") {
                                panel.__width = getPixel(panel.width, item.targetDimension.width);
                            } else if (item.orientation == "horizontal") {
                                panel.__height = getPixel(panel.height, item.targetDimension.height);
                            }
                        });
                    },
                    "tab-panel": function tabPanel() {}
                };

                return function (boundID, callback) {
                    var queIdx = U.isNumber(boundID) ? boundID : getQueIdx.call(this, boundID);
                    if (queIdx === -1) {} else {
                        resetLayoutPanel[this.queue[queIdx].layout].call(this, this.queue[queIdx]);
                        alignLayout.call(this, queIdx, callback);
                    }

                    return this;
                };
            }();

            this.hide = function () {};

            /**
             * @method ax5layout.tabOpen
             * @param boundID
             * @param tabIndex
             * @returns {ax5.ui.ax5layout}
             */
            this.tabOpen = function () {
                return function (boundID, tabIndex) {
                    var queIdx = U.isNumber(boundID) ? boundID : getQueIdx.call(this, boundID);
                    if (queIdx === -1) {
                        console.log(ax5.info.getError("ax5layout", "402", "tabOpen"));
                        return;
                    }

                    tabControl.open.call(this, queIdx, this.queue[queIdx], tabIndex);
                    return this;
                };
            }();

            this.getActiveTab = function (boundID) {
                var queIdx = U.isNumber(boundID) ? boundID : getQueIdx.call(this, boundID);
                if (queIdx === -1) {
                    console.log(ax5.info.getError("ax5layout", "402", "tabOpen"));
                    return;
                }

                if (typeof this.queue[queIdx].activePanelIndex != "undefined") {
                    return this.queue[queIdx].tabPanel[this.queue[queIdx].activePanelIndex];
                }
            };

            /// 클래스 생성자
            this.main = function () {
                if (arguments && U.isObject(arguments[0])) {
                    this.setConfig(arguments[0]);
                } else {
                    this.init();
                }
            }.apply(this, arguments);
        };
        return ax5layout;
    }());
})();

/*
 * Copyright (c) 2017. tom@axisj.com
 * - github.com/thomasjang
 * - www.axisj.com
 */

ax5.ui.layout_instance = new ax5.ui.layout();

/**
 * ax5layout jquery extends
 * @namespace jQueryExtends
 */

/**
 * @method jQueryExtends.ax5layout
 * @param {String} methodName
 * @example
 * ```js
 * jQuery('[data-ax5layout="ax1"]').ax5layout("align");
 * jQuery('[data-ax5layout="ax1"]').ax5layout("resize");
 * jQuery('[data-ax5layout="ax1"]').ax5layout("reset");
 * jQuery('[data-ax5layout="ax1"]').ax5layout("hide");
 * jQuery('[data-ax5layout="ax1"]').ax5layout("onResize");
 * jQuery('[data-ax5layout="ax1"]').ax5layout("tabOpen", 1);
 * ```
 */
jQuery.fn.ax5layout = function () {
    return function (config) {
        if (ax5.util.isString(arguments[0])) {
            var methodName = arguments[0];

            switch (methodName) {
                case "align":
                    return ax5.ui.layout_instance.align(this, arguments[1]);
                    break;
                case "resize":
                    return ax5.ui.layout_instance.resize(this, arguments[1], arguments[2]);
                    break;
                case "reset":
                    return ax5.ui.layout_instance.reset(this, arguments[1]);
                    break;
                case "hide":
                    return ax5.ui.layout_instance.hide(this, arguments[1]);
                    break;
                case "onResize":
                    return ax5.ui.layout_instance.onResize(this, arguments[1]);
                    break;
                case "tabOpen":
                    return ax5.ui.layout_instance.tabOpen(this, arguments[1]);
                    break;
                case "getActiveTab":
                    return ax5.ui.layout_instance.getActiveTab(this, arguments[1]);
                    break;
                default:
                    return this;
            }
        } else {
            if (typeof config == "undefined") config = {};
            jQuery.each(this, function () {
                var defaultConfig = {
                    target: this
                };
                config = jQuery.extend({}, config, defaultConfig);
                ax5.ui.layout_instance.bind(config);
            });
        }
        return this;
    };
}();
"use strict";

// ax5.ui.binder
(function () {

    var UI = ax5.ui;
    var U = ax5.util;

    UI.addClass({
        className: "binder"
    }, function () {

        /**
         * @class ax5binder
         * @classdesc
         * @author tom@axisj.com
         * @example
         * ```js
         * var obj = {
         *     name: "Thomas Jang",
         *     alias: "tom",
         *     tel: "010-8881-9137",
         *     email: "tom@axisj.com",
         *     sex: "M",
         *     hobby: ["sport"],
         *     useYn: "N",
         *     description: "http://www.axisj.com",
         *     list: [
         *         {
         *             name: "thomas",
         *             tel: "010-8881-9000",
         *             email: "tom@axisj.com",
         *             sex: "M",
         *             description: "",
         *             child: [{name:"값1"},{name:"값2"}],
         *             qty: 10,
         *             cost: 100
         *         },
         *         {
         *             name: "thomas",
         *             tel: "010-8881-9000",
         *             email: "tom@axisj.com",
         *             sex: "M",
         *             description: "",
         *             child: [{name:"값1"},{name:"값2"}],
         *             qty: 20,
         *             cost: 100
         *         }
         * ]
         * };
         *
         * var myBinder = new ax5.ui.binder();
         * myBinder.setModel(obj, $('#form-target'));
         * ```
         */
        return function () {

            var self = this,
                cfg;

            this.instanceId = ax5.getGuid();
            this.config = {};
            cfg = this.config;

            this.model = {};
            this.tmpl = {};
            this.view_target = null;
            this.change_trigger = {};
            this.click_trigger = {};
            this.update_trigger = {};
            this.onerror = null;

            var _toString = Object.prototype.toString,
                get_type = function get_type(O) {
                var typeName;
                if (O != null && O == O.window) {
                    typeName = "window";
                } else if (!!(O && O.nodeType == 1)) {
                    typeName = "element";
                } else if (!!(O && O.nodeType == 11)) {
                    typeName = "fragment";
                } else if (typeof O === "undefined") {
                    typeName = "undefined";
                } else if (_toString.call(O) == "[object Object]") {
                    typeName = "object";
                } else if (_toString.call(O) == "[object Array]") {
                    typeName = "array";
                } else if (_toString.call(O) == "[object String]") {
                    typeName = "string";
                } else if (_toString.call(O) == "[object Number]") {
                    typeName = "number";
                } else if (_toString.call(O) == "[object NodeList]") {
                    typeName = "nodelist";
                } else if (typeof O === "function") {
                    typeName = "function";
                }
                return typeName;
            },
                get_mix_path = function get_mix_path(dataPath, index, item_path) {
                return dataPath + "[" + index + "]" + (item_path == "." ? "" : "." + item_path);
            },
                get_real_path = function get_real_path(_dataPath) {
                var path = [];
                var _path = [].concat(_dataPath.split(/[\.\[\]]/g));
                _path.forEach(function (n) {
                    if (n !== "") path.push("[\"" + n.replace(/['\"]/g, "") + "\"]");
                });
                _path = null;
                return path.join("");
                /*
                var path = [];
                var _path = [].concat(dataPath.split(/[\.\[\]]/g));
                 _path.forEach(function (n) {
                    if (n !== "") path.push(n);
                });
                _path = null;
                return "'" + path.join("']['") + "'";
                */
            };

            /**
             * 바인딩할 자바스크립트 오브젝트로 제이쿼리로 검색된 HTML dom 엘리먼트 에 바인딩합니다. 바인딩된 모델을 반환합니다.
             * @method ax5binder.setModel
             * @param {Object} model
             * @param {jQueryObject} [view_target]
             * @returns {ax5binder}
             * @example
             * ```js
             * var myModel = new ax5.ui.binder();
             * myModel.setModel({}, $("#..."));
             * ```
             */
            this.setModel = function (model, view_target) {
                this.model = model;
                if (!this.view_target && view_target) {
                    this.view_target = view_target;
                    this._binding();
                } else {
                    this._binding("update");
                }
                return this;
            };

            /**
             * data_path에 값을 변경한다. value의 타입은 (String, Number, Array, Object)를 지원.
             * @method ax5binder.set
             * @param {String} dataPath
             * @param {Object} value
             * @returns {ax5binder}
             * @example
             * ```js
             * myModel.set("name", "Seowoo");
             * myModel.set("obj.path", {a:1});
             * ```
             */
            this.set = function (dataPath, value) {
                var _this = this,
                    obj_type,
                    i,
                    this_type;

                Function("val", "this" + get_real_path(dataPath) + " = val;").call(this.model, value);
                obj_type = get_type(value);

                if (obj_type == "object") {
                    for (var k in value) {
                        this.set(dataPath + "." + k, value[k]);
                    }
                } else if (obj_type == "array") {
                    this.view_target.find('[data-ax-path="' + dataPath + '"]').each(function () {
                        this_type = (this.type || "").toLowerCase();
                        if (this_type == "checkbox" || this_type == "radio") _this.set_els_value(this, this.tagName.toLowerCase(), this_type, value, dataPath, "change");
                    });
                    i = value.length;
                    while (i--) {
                        this.set(dataPath + "[" + i + "]", value[i]);
                    }
                } else {
                    // apply data value to els
                    this.view_target.find('[data-ax-path="' + dataPath + '"]').each(function () {
                        _this.set_els_value(this, this.tagName.toLowerCase(), (this.type || "").toLowerCase(), value, dataPath, "change");
                    });
                }
                return this;
            };

            /**
             * data_path에 값을 반환한다. data_path 가 없으면 전체 Object를 반환한다.
             * @method ax5binder.get
             * @param dataPath
             * @returns {*}
             */
            this.get = function (dataPath) {
                if (typeof dataPath == "undefined") {
                    return this.model;
                } else {
                    return Function("", "return this" + get_real_path(dataPath) + ";").call(this.model);
                }
            };

            /**
             * data_path에 값이 변경되는 이벤트 발생하면 callback을 실행합니다.
             * @method ax5binder.onChange
             * @param dataPath
             * @param callback
             * @returns {ax5binder}
             * @example
             * ```js
             * myModel.onChange("name", function () {
             *       console.log(this);
             *       // el: domElement - 변경이 발생한 엘리먼트, 엘리먼트로 부터 다양한 속성을 추출할 수 있다.
             *       // jquery : jQueryObject
             *       // tagname: "input"
             *       // value: "changed value"
             *       console.log(this.el.id);
             *   });
             *   myModel.onChange("*", function (n) {
             *       console.log(n);
             *       // console.log(this); 와 동일
             *   });
             * ```
             */
            this.onChange = function (dataPath, callback) {
                this.change_trigger[dataPath || "*"] = callback;
                return this;
            };

            /**
             * data-ax-repeat="list" 속성이 부여된 엘리먼트 하위에 태그중에 data-ax-repeat-click 속성을 가진 아이템에 대해 클릭 이벤트 발생하면 callback을 실행합니다.
             * @method ax5binder.onClick
             * @param dataPath
             * @param callback
             * @returns {ax5binder}
             * @example
             * ```js
             * myModel.onclick("list", function () {
             *       console.log(this);
             *       // el: domElement
             *       // jquery: jQueryObject
             *       // item: Object - repeat item
             *       // item_index: "0" - index of item
             *       // item_path: "list[0]" - repeat data_path
             *       // repeat_path: "list"
             *       // tagname: "button"
             *       // value: "add"
             *   });
             * ```
             */
            this.onClick = function (dataPath, callback) {
                this.click_trigger[dataPath] = callback;
                return this;
            };

            /**
             * data-ax-repeat="list" 하위아이템을 추가합니다.
             * @method ax5binder.add
             * @param dataPath
             * @param item
             * @returns {ax5binder}
             * @example
             * ```js
             * myModel.add("list", {a:1});
             * ```
             */
            this.add = function (dataPath, item) {
                var list = Function("", "return this" + get_real_path(dataPath) + ";").call(this.model);
                var tmpl = this.tmpl[dataPath];
                item['@i'] = list.length;
                item['@r'] = list.length;
                item.__ADDED__ = true;
                Function("val", "this" + get_real_path(dataPath) + ".push(val);").call(this.model, item);

                // 다중 템플릿 처리
                for (var t in tmpl) {
                    var fragdom = $(ax5.mustache.render(tmpl[t].content, item));
                    fragdom.attr("data-ax-repeat-path", dataPath);
                    fragdom.attr("data-ax-repeat-i", item['@i']);
                    this.bind_event_tmpl(fragdom, dataPath);
                    tmpl[t].container.append(fragdom);
                }

                this.change("*");

                var callback = this.update_trigger[dataPath];
                if (callback) {
                    var that = {
                        repeat_path: dataPath,
                        tmpl: tmpl,
                        list: list
                    };
                    callback.call(that, that);
                }

                return this;
            };

            /**
             * data-ax-repeat="list" 하위 아이템을 제거합니다. 단! 이 때 ADDED 값을 가진 아이템은 제거하고 그렇지 않은 아이템은 DELETED 값을 아이템에 추가합니다.
             * @method ax5binder.remove
             * @param dataPath
             * @param index
             * @returns {ax5binder}
             * @example
             * ```js
             * myModel.remove("list", 0);
             * ```
             */
            this.remove = function (dataPath, index) {
                var list = Function("", "return this" + get_real_path(dataPath) + ";").call(this.model);
                var tmpl = this.tmpl[dataPath];
                if (typeof index == "undefined") index = list.length - 1;
                var remove_item = list[index];
                if (remove_item.__ADDED__) {
                    list.splice(index, 1);
                } else {
                    list[index].__DELETED__ = true;
                }

                for (var t in tmpl) {
                    tmpl[t].container.empty();
                    this.print_tmpl(dataPath, tmpl[t]);
                }

                this.change("*");

                var callback = this.update_trigger[dataPath];
                if (callback) {
                    var that = {
                        repeat_path: dataPath,
                        tmpl: tmpl,
                        list: list
                    };
                    callback.call(that, that);
                }

                return this;
            };

            /**
             * data-ax-repeat="list" 하위 아이템을 교체합니다.
             * @method ax5binder.update
             * @param dataPath
             * @param index
             * @param item
             * @returns {ax5binder}
             * @example
             * ```js
             * myModel.update("list", 0, {a:1});
             * ```
             */
            this.update = function (dataPath, index, item) {
                var list = Function("", "return this" + get_real_path(dataPath) + ";").call(this.model);
                var tmpl = this.tmpl[dataPath];
                if (typeof index != "undefined" && item) list.splice(index, 1, item);

                for (var t in tmpl) {
                    tmpl[t].container.empty();
                    this.print_tmpl(dataPath, tmpl[t]);
                }

                this.change("*");

                var callback = this.update_trigger[dataPath];
                if (callback) {
                    var that = {
                        repeat_path: dataPath,
                        tmpl: tmpl,
                        list: list
                    };
                    callback.call(that, that);
                }

                return this;
            };

            /**
             * @method ax5binder.childAdd
             * @param dataPath
             * @param index
             * @param child_path
             * @param child_item
             */
            this.childAdd = function (dataPath, index, child_path, child_item) {
                var _list = Function("", "return this" + get_real_path(dataPath) + ";").call(this.model);
                var list = Function("", "return this" + get_real_path(dataPath) + "[" + index + "]." + child_path + ";").call(this.model);
                child_item.__ADDED__ = true;
                list.push(child_item);
                this.update(dataPath, index, _list[index]);
            };

            /**
             * ax5binder.childRemove
             * @param dataPath
             * @param index
             * @param child_path
             * @param child_index
             */
            this.childRemove = function (dataPath, index, child_path, child_index) {
                var _list = Function("", "return this" + get_real_path(dataPath) + ";").call(this.model);
                var list = Function("", "return this" + get_real_path(dataPath) + "[" + index + "]." + child_path + ";").call(this.model);
                var remove_item = list[child_index];
                if (remove_item.__ADDED__) {
                    list.splice(child_index, 1);
                } else {
                    list[child_index].__DELETED__ = true;
                }
                this.update(dataPath, index, _list[index]);
            };

            /**
             * @method ax5binder.childUpdate
             * @param dataPath
             * @param index
             * @param child_path
             * @param child_index
             * @param child_item
             */
            this.childUpdate = function (dataPath, index, child_path, child_index, child_item) {
                var _list = Function("", "return this" + get_real_path(dataPath) + ";").call(this.model);
                var list = Function("", "return this" + get_real_path(dataPath) + "[" + index + "]." + child_path + ";").call(this.model);
                list[child_index] = child_item;
                this.update(dataPath, index, _list[index]);
            };

            /**
             * @method ax5binder.childSet
             * @param dataPath
             * @param index
             * @param child_path
             * @param value
             * @returns {ax5binder}
             */
            this.childSet = function (dataPath, index, child_path, value) {
                var _this = this,
                    i;
                Function("val", "this" + get_real_path(dataPath) + "[" + index + "]." + child_path + " = val;").call(this.model, value);

                // apply data value to els
                this.view_target.find('[data-ax-repeat="' + dataPath + '"]').find('[data-ax-repeat-i="' + index + '"]').find('[data-ax-item-path="' + child_path + '"]').each(function () {
                    _this.set_els_value(this, this.tagName.toLowerCase(), (this.type || "").toLowerCase(), value, dataPath + "[" + index + "]." + child_path);
                });
                return this;
            };

            /**
             * @method ax5binder.onUpdate
             * @param dataPath
             * @param callback
             * @returns {ax5binder}
             * @example
             * ```js
             *  this.model.onupdate("moderator", function () {
             *      $('#moderator-add').val('');
             *      $moderator.find('[data-role-user-btn]')
             *          .unbind("click")
             *          .bind("click", role_user_btn_onclick);
             *  });
             * ```
             */
            this.onUpdate = function (dataPath, callback) {
                this.update_trigger[dataPath] = callback;
                return this;
            };

            this._binding = function (isupdate) {
                var _this = this;

                // apply data value to els
                this.view_target.find('[data-ax-path]').each(function () {
                    var dom = $(this),
                        dataPath = dom.attr("data-ax-path"),
                        this_type = (this.type || "").toLowerCase();

                    var val;
                    try {
                        val = Function("", "return this" + get_real_path(dataPath) + ";").call(_this.model);
                    } catch (e) {
                        /**
                         * onerror를 선언 한 경우에만 에러 출력
                         * */
                        if (_this.onerror) _this.onerror("not found target [model." + get_real_path(dataPath) + "]");
                    }

                    _this.set_els_value(this, this.tagName.toLowerCase(), this_type, val || "", dataPath);
                });

                if (typeof isupdate == "undefined") {
                    // collect tmpl
                    this.view_target.find('[data-ax-repeat]').each(function () {
                        var dom = $(this),
                            dataPath = dom.attr("data-ax-repeat"),
                            repeat_idx = dom.attr("data-ax-repeat-idx");

                        if (typeof _this.tmpl[dataPath] == "undefined") _this.tmpl[dataPath] = {};
                        if (typeof repeat_idx != "undefined") {
                            _this.tmpl[dataPath][repeat_idx] = {
                                container: dom, content: dom.find("script").html()
                            };
                        } else {
                            _this.tmpl[dataPath]["0"] = {
                                container: dom, content: dom.find("script").html()
                            };
                        }
                        //dom.empty().show();
                        dom.empty();
                    });
                } else {
                    this.view_target.find('[data-ax-repeat]').each(function () {
                        var dom = $(this);
                        dom.empty().show();
                    });
                }

                // binding event to els
                this.view_target.find('[data-ax-path]').off("change.axbinder").on("change.axbinder", function (e) {

                    var i,
                        hasItem = false,
                        checked,
                        new_value = [],
                        dom = $(e.target),
                        dataPath = dom.attr("data-ax-path"),
                        origin_value = Function("", "return this" + get_real_path(dataPath) + ";").call(_this.model),
                        this_type = (this.type || "").toLowerCase(),
                        value_type = get_type(origin_value),
                        setAllow = true;

                    if (value_type == "object" || value_type == "array") {
                        setAllow = false;
                    }

                    if (this_type == "checkbox") {
                        // 동일한 체크박스가 여러개 인지 판단합니다.
                        if (_this.view_target.find('[data-ax-path="' + dataPath + '"]').length > 1) {

                            if (get_type(origin_value) != "array") {
                                if (typeof origin_value === "undefined" || origin_value == "") origin_value = [];else origin_value = [].concat(origin_value);
                            }
                            i = origin_value.length, hasItem = false, checked = this.checked;
                            while (i--) {
                                if (origin_value[i] == this.value) {
                                    hasItem = true;
                                }
                            }

                            if (checked) {
                                if (!hasItem) origin_value.push(this.value);
                            } else {
                                i = origin_value.length;
                                while (i--) {
                                    if (origin_value[i] == this.value) {
                                        //hasItemIndex = i;
                                    } else {
                                        new_value.push(origin_value[i]);
                                    }
                                }
                                origin_value = new_value;
                            }
                        } else {
                            origin_value = this.checked ? this.value : "";
                        }

                        Function("val", "this" + get_real_path(dataPath) + " = val;").call(_this.model, origin_value);
                        _this.change(dataPath, {
                            el: this, jquery: dom, tagname: this.tagName.toLowerCase(), value: origin_value
                        });
                    } else {
                        if (setAllow) {
                            Function("val", "this" + get_real_path(dataPath) + " = val;").call(_this.model, this.value);
                            _this.change(dataPath, {
                                el: this, jquery: dom, tagname: this.tagName.toLowerCase(), value: this.value
                            });
                        }
                    }

                    dom.data("changedTime", new Date().getTime());
                });
                /*
                 this.view_target.find('[data-ax-path]').unbind("blur.axbinder").bind("blur.axbinder", function (e) {
                 var dom = $(e.target);
                 if (typeof dom.data("changedTime") == "undefined" || dom.data("changedTime") < (new Date()).getTime() - 10) dom.trigger("change");
                 });
                 */

                //_this.tmpl
                var callback;
                for (var tk in _this.tmpl) {
                    for (var ix in _this.tmpl[tk]) {
                        // console.log(_this.tmpl[tk][ix].content);
                        this.print_tmpl(tk, _this.tmpl[tk][ix], "isInit");
                    }

                    if (callback = this.update_trigger[tk]) {
                        var that = {
                            repeat_path: tk,
                            tmpl: _this.tmpl[tk],
                            list: Function("", "return this." + tk + ";").call(this.model)
                        };
                        callback.call(that, that);
                    }
                }
            };

            this.set_els_value = function (el, tagname, type, value, dataPath, callChange) {
                if (typeof value === "undefined") value = [];else value = [].concat(value);
                var options, i;

                if (tagname == "input") {
                    if (type == "checkbox" || type == "radio") {
                        i = value.length;
                        var checked = false;
                        try {
                            if (i > 0) {
                                while (i--) {
                                    if (typeof value[i] !== "undefined" && el.value === value[i].toString()) {
                                        checked = true;
                                    }
                                }
                            }
                        } catch (e) {
                            console.log(e);
                        }

                        el.checked = checked;
                    } else {
                        el.value = value.join('');
                    }
                } else if (tagname == "select") {
                    options = el.options, i = options.length;
                    var vi,
                        option_matched = false;

                    while (i--) {
                        vi = value.length;
                        while (vi--) {
                            if (typeof value[vi] !== "undefined" && options[i].value === value[vi].toString()) {
                                options[i].selected = true;
                                option_matched = true;
                                break;
                            }
                        }
                        if (option_matched) break;
                    }
                    if (!option_matched) {
                        if (options[0]) {
                            options[0].selected = true;
                            Function("val", "this" + get_real_path(dataPath) + " = val;").call(this.model, options[0].value);
                        } else {
                            Function("val", "this" + get_real_path(dataPath) + " = val;").call(this.model, "");
                        }
                    }

                    if (window.AXSelect) {
                        // AXISJ 사용가능
                        $(typeof value !== "undefined" && el).bindSelectSetValue(value[value.length - 1]);
                    }
                } else if (tagname == "textarea") {
                    el.value = value.join('') || "";
                } else {
                    if (el.innerText) {
                        el.innerText = value.join("");
                    } else {
                        el.innerHTML = value.join("");
                    }
                }

                if (callChange) {
                    this.change(dataPath, { el: el, tagname: tagname, value: value });
                }
                return this;
            };

            this.change = function (dataPath, that) {
                var callback = this.change_trigger[dataPath];
                if (callback) {
                    callback.call(that, that);
                }
                if (dataPath != "*" && this.change_trigger["*"]) {
                    this.change_trigger["*"].call(that, that);
                }
            };

            this.click = function (dataPath, that) {
                var callback = this.click_trigger[dataPath];
                if (callback) {
                    callback.call(that, that);
                }
            };

            this.sync_model = function () {};

            this.print_tmpl = function (dataPath, tmpl, isInit) {
                var list = Function("", "return this" + get_real_path(dataPath) + ";").call(this.model);
                if (list && get_type(list) == "array") {
                    for (var i = 0, l = list.length; i < l; i++) {
                        var item = list[i];
                        if (jQuery.isPlainObject(item)) {
                            item['@i'] = i;
                            item['@r'] = i;
                            if (i === 0) item['@first'] = true;
                        } else {
                            item = {
                                "@i": i,
                                "@value": item
                            };
                            if (i === 0) item['@first'] = true;
                            console.log(item);
                        }

                        if (!item.__DELETED__) {
                            var fragdom = $(ax5.mustache.render(tmpl.content, item));
                            fragdom.attr("data-ax-repeat-path", dataPath);
                            fragdom.attr("data-ax-repeat-i", item['@i']);
                            this.bind_event_tmpl(fragdom, dataPath);
                            tmpl.container.append(fragdom);
                        }
                    }
                }
            };

            this.bind_event_tmpl = function (target, dataPath) {
                var _this = this,
                    index = target.attr("data-ax-repeat-i");
                var list = Function("", "return this" + get_real_path(dataPath) + ";").call(this.model);

                target.find('[data-ax-repeat-click]').off("click.axbinder").on("click.axbinder", function (e) {
                    var target = ax5.util.findParentNode(e.target, function (el) {
                        return el.getAttribute("data-ax-repeat-click");
                    });
                    if (target) {
                        var dom = $(target),
                            value = dom.attr("data-ax-repeat-click"),
                            repeat_path = dom.attr("data-ax-repeat-path");
                        var that = {
                            el: target,
                            jquery: dom,
                            tagname: target.tagName.toLowerCase(),
                            value: value,
                            repeat_path: dataPath,
                            item: list[index],
                            item_index: index,
                            item_path: dataPath + "[" + index + "]"
                        };
                        _this.click(dataPath, that);
                    }
                });

                // apply data value to els
                target.find('[data-ax-item-path]').each(function () {
                    var dom = $(this),
                        item_path = dom.attr("data-ax-item-path"),
                        mix_path = get_mix_path(dataPath, index, item_path),
                        val,
                        this_type = (this.type || "").toLowerCase();

                    try {
                        val = Function("", "return this." + mix_path + ";").call(_this.model);
                    } catch (e) {
                        /**
                         * onerror를 선언 한 경우에만 에러 출력
                         * */
                        if (_this.onerror) _this.onerror("not found target [model." + mix_path + "]");
                    }
                    //if (typeof val !== "undefined") _this.set_els_value(this, this.tagName.toLowerCase(), this_type, val, mix_path);
                    _this.set_els_value(this, this.tagName.toLowerCase(), this_type, val || "", mix_path);
                });

                // binding event to els
                target.find('[data-ax-item-path]').off("change.axbinder").on("change.axbinder", function (e) {
                    var i,
                        hasItem = false,
                        checked,
                        new_value = [],
                        this_type = (this.type || "").toLowerCase(),
                        dom = $(e.target),
                        item_path = dom.attr("data-ax-item-path"),
                        mix_path = get_mix_path(dataPath, index, item_path),
                        origin_value = Function("", "return this." + mix_path + ";").call(_this.model),
                        value_type = get_type(origin_value),
                        setAllow = true;

                    if (value_type == "object" || value_type == "array") {
                        setAllow = false;
                    }

                    if (this_type == "checkbox") {
                        if (target.find('[data-ax-item-path="' + item_path + '"]').length > 1) {
                            if (get_type(origin_value) != "array") {
                                if (typeof origin_value === "undefined" || origin_value == "") origin_value = [];else origin_value = [].concat(origin_value);
                            }
                            i = origin_value.length, hasItem = false, checked = this.checked;
                            while (i--) {
                                if (origin_value[i] == this.value) {
                                    hasItem = true;
                                }
                            }

                            if (checked) {
                                if (!hasItem) origin_value.push(this.value);
                            } else {
                                i = origin_value.length;
                                while (i--) {
                                    if (origin_value[i] == this.value) {
                                        //hasItemIndex = i;
                                    } else {
                                        new_value.push(origin_value[i]);
                                    }
                                }
                                origin_value = new_value;
                            }
                        } else {
                            origin_value = this.checked ? this.value : "";
                        }

                        Function("val", "this." + mix_path + " = val;").call(_this.model, origin_value);
                        _this.change(mix_path, {
                            el: this, jquery: dom, tagname: this.tagName.toLowerCase(), value: origin_value
                        });
                    } else {
                        if (setAllow) {
                            Function("val", "this." + mix_path + " = val;").call(_this.model, this.value);
                            _this.change(mix_path, {
                                el: this, jquery: dom, tagname: this.tagName.toLowerCase(), value: this.value
                            });
                        }
                    }

                    dom.data("changedTime", new Date().getTime());
                });

                target.find('[data-ax-item-path]').off("blur.axbinder").on("blur.axbinder", function (e) {
                    var dom = $(e.target);
                    if (typeof dom.data("changedTime") == "undefined" || dom.data("changedTime") < new Date().getTime() - 10) dom.trigger("change");
                });
            };

            /**
             * @method ax5binder.focus
             * @param dataPath
             * @returns {ax5binder}
             */
            this.focus = function (dataPath) {
                this.view_target.find('[data-ax-path="' + dataPath + '"]').focus();
                //this.view_target.find('[data-ax-item-path="' + get_real_path(dataPath) + '"]').focus();
                return this;
            };

            /**
             * @method ax5binder.validate
             * @returns {*}
             * @example
             * ```html
             * <input type="text" data-ax-path="q" data-ax-validate="required" title="이름" maxlength="8" value=""/>
             * ```
             * ```js
             * var rs = myModel.validate(), _s;
             * console.log(rs); // 결과를 체크 해보세요
             * if(rs.error) {
             *      _s = rs.error[0].jquery.attr("title");
             *      alert("" + _s + "(은)는 필수 입력사항입니다." + _s + "(을)를 입력하세요");
             *      rs.error[0].el.focus();
             *      return;
             *  }
             * ```
             */
            this.validate = function () {
                var _this = this;
                var errors = [];
                this.view_target.find('[data-ax-path]').each(function () {
                    var dom = $(this),
                        dataPath = dom.attr("data-ax-path"),
                        is_validate = dom.attr("data-ax-validate"),
                        pattern = dom.attr("pattern");

                    if (is_validate) {
                        var val, _val, is_error;

                        val = Function("", "return this" + get_real_path(dataPath) + ";").call(_this.model);
                        if (typeof val === "undefined" || val === null) val = "";
                        _val = val.toString();
                        is_error = false;

                        if (is_validate == "required" && _val.trim() == "") {
                            is_error = true;
                        } else if (is_validate == "pattern") {
                            is_error = !new RegExp(pattern).test(_val);
                        } else if (is_validate == "email") {
                            is_error = !/[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/.test(_val);
                        } else if (!/\D.?/g.test(is_validate) && _val.trim().length < is_validate.number()) {
                            is_error = true;
                        }

                        if (is_error) {
                            errors.push({
                                type: is_validate,
                                dataPath: dataPath,
                                el: this,
                                jquery: dom,
                                value: val
                            });
                        }
                    }
                });
                this.view_target.find('[data-ax-repeat-path]').each(function () {

                    var dom = $(this),
                        dataPath = dom.attr("data-ax-repeat-path"),
                        repeat_idx = dom.attr("data-ax-repeat-i");

                    dom.find('[data-ax-validate]').each(function () {
                        var dom = $(this),
                            is_validate = dom.attr("data-ax-validate"),
                            item_path = dom.attr("data-ax-item-path");
                        var val = Function("", "return this" + get_real_path(dataPath) + "[" + repeat_idx + "]." + item_path + ";").call(_this.model);
                        if (typeof val === "undefined") val = "";
                        var _val = val.toString();

                        if (is_validate) {
                            var is_error = false;
                            if (is_validate == "required" && _val.trim() == "") {
                                is_error = true;
                            } else if (is_validate == "pattern") {
                                is_error = !new RegExp(pattern).test(_val);
                            } else if (is_validate == "email") {
                                is_error = !/[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/.test(_val);
                            } else if (!/\D.?/g.test(is_validate) && _val.trim().length < is_validate.number()) {
                                is_error = true;
                            }

                            if (is_error) {
                                errors.push({
                                    type: is_validate,
                                    dataPath: dataPath,
                                    el: this,
                                    jquery: dom,
                                    value: val
                                });
                            }
                        }
                    });
                });

                if (errors.length > 0) {
                    return {
                        error: errors
                    };
                } else {
                    return {};
                }
            };

            // 클래스 생성자
            this.main = function () {
                if (arguments && U.isObject(arguments[0])) {
                    this.setConfig(arguments[0]);
                }
            }.apply(this, arguments);
        };
    }());
})();
'use strict';

// ax5.ui.autocomplete
(function () {

    var UI = ax5.ui;
    var U = ax5.util;
    var AUTOCOMPLETE = void 0;

    UI.addClass({
        className: "autocomplete"
    }, function () {
        /**
         * @class ax5autocomplete
         * @classdesc
         * @author tom@axisj.com
         * @example
         * ```js
         * var options = [];
         * options.push({value: "1", text: "string"});
         * options.push({value: "2", text: "number"});
         * options.push({value: "3", text: "substr"});
         * options.push({value: "4", text: "substring"});
         * options.push({value: "5", text: "search"});
         * options.push({value: "6", text: "parseInt"});
         * options.push({value: "7", text: "toFixed"});
         * options.push({value: "8", text: "min"});
         * options.push({value: "9", text: "max"});
         * options.push({value: "10", text: "장기영"});
         * options.push({value: "11", text: "장서우"});
         * options.push({value: "12", text: "이영희"});
         * options.push({value: "13", text: "황인서"});
         * options.push({value: "14", text: "황세진"});
         * options.push({value: "15", text: "이서연"});
         * options.push({value: "16", text: "액시스제이"});
         * options.push({value: "17", text: "ax5"});
         * options.push({value: "18", text: "ax5grid"});
         * options.push({value: "19", text: "ax5combobox"});
         * options.push({value: "20", text: "ax5autocomplete"});
         * options.push({value: "21", text: "ax5binder"});
         * options.push({value: "22", text: "ax5select"});
         * options.push({value: "23", text: "ax5mask"});
         * options.push({value: "24", text: "ax5toast"});
         * options.push({value: "25", text: "ax5dialog"});
         * options.push({value: "26", text: "ax5modal"});
          * var myUI = new ax5.ui.autocomplete({
         *      theme: "danger",
         *      removeIcon: '<i class="fa fa-times" aria-hidden="true"></i>'
         * });
         * ```
         */
        return function () {
            var self = this,
                cfg;

            this.instanceId = ax5.getGuid();
            this.config = {
                theme: 'default',
                animateTime: 250,
                removeIcon: 'X',
                lang: {
                    noSelected: '',
                    noOptions: 'no options',
                    loading: 'Now Processing'
                },
                columnKeys: {
                    optionValue: 'value',
                    optionText: 'text',
                    optionSelected: 'selected'
                }
            };

            this.queue = [];
            this.activeautocompleteOptionGroup = null;
            this.activeautocompleteQueueIndex = -1;
            this.openTimer = null;
            this.closeTimer = null;
            this.waitOptionsCallback = null;
            this.keyUpTimer = null;

            cfg = this.config;

            var $window = jQuery(window);
            var ctrlKeys = {
                "18": "KEY_ALT",
                //"8": "KEY_BACKSPACE",
                "17": "KEY_CONTROL",
                "46": "KEY_DELETE",
                "40": "KEY_DOWN",
                "35": "KEY_END",
                "187": "KEY_EQUAL",
                //"27": "KEY_ESC",
                "36": "KEY_HOME",
                "45": "KEY_INSERT",
                "37": "KEY_LEFT",
                "189": "KEY_MINUS",
                "34": "KEY_PAGEDOWN",
                "33": "KEY_PAGEUP",
                // "190": "KEY_PERIOD",
                //"13": "KEY_RETURN",
                "39": "KEY_RIGHT",
                "16": "KEY_SHIFT",
                // "32": "KEY_SPACE",
                "9": "KEY_TAB",
                "38": "KEY_UP",
                "91": "KEY_WINDOW"
                //"107" : "NUMPAD_ADD",
                //"194" : "NUMPAD_COMMA",
                //"110" : "NUMPAD_DECIMAL",
                //"111" : "NUMPAD_DIVIDE",
                //"12" : "NUMPAD_EQUAL",
                //"106" : "NUMPAD_MULTIPLY",
                //"109" : "NUMPAD_SUBTRACT"
            },
                onStateChanged = function onStateChanged(item, that) {
                if (item && item.onStateChanged) {
                    item.onStateChanged.call(that, that);
                } else if (this.onStateChanged) {
                    this.onStateChanged.call(that, that);
                }

                if (that.state == "changeValue") {
                    if (item && item.onChange) {
                        item.onChange.call(that, that);
                    } else if (this.onChange) {
                        this.onChange.call(that, that);
                    }
                }

                item = null;
                that = null;
                return true;
            },
                alignAutocompleteDisplay = function alignAutocompleteDisplay() {
                var i = this.queue.length,
                    w;

                while (i--) {
                    var item = this.queue[i];
                    if (item.$display) {
                        w = Math.max(item.$select.outerWidth(), U.number(item.minWidth));
                        item.$display.css({
                            "min-width": w
                        });
                        if (item.reset) {
                            item.$display.find(".addon-icon-reset").css({
                                "line-height": this.queue[i].$display.height() + "px"
                            });
                        }

                        // 높이조절 처리
                        if (item.multiple) {
                            var displayTableHeightAdjust = function () {
                                return U.number(item.$display.css("border-top-width")) + U.number(item.$display.css("border-bottom-width"));
                            }.call(this);
                            item.$target.height('');
                            item.$display.height('');

                            var displayTableHeight = item.$displayTable.outerHeight();
                            //debugger;
                            if (Math.abs(displayTableHeight - item.$target.height()) >= displayTableHeightAdjust) {
                                item.$target.css({ height: displayTableHeight + displayTableHeightAdjust + 4 });
                                item.$display.css({ height: displayTableHeight + displayTableHeightAdjust + 4 });
                            }
                        }
                    }
                }

                i = null;
                w = null;
                return this;
            },
                alignAutocompleteOptionGroup = function alignAutocompleteOptionGroup(append) {
                if (append && !this.activeautocompleteOptionGroup) return this;

                var item = this.queue[this.activeautocompleteQueueIndex],
                    pos = {},
                    positionMargin = 0,
                    dim = {},
                    pickerDim = {},
                    pickerDirection;

                if (!item) return this;
                if (append) jQuery(document.body).append(this.activeautocompleteOptionGroup);

                pos = item.$target.offset();
                dim = {
                    width: item.$target.outerWidth(),
                    height: item.$target.outerHeight()
                };
                pickerDim = {
                    winWidth: Math.max($window.width(), jQuery(document.body).width()),
                    winHeight: Math.max($window.height(), jQuery(document.body).height()),
                    width: this.activeautocompleteOptionGroup.outerWidth(),
                    height: this.activeautocompleteOptionGroup.outerHeight()
                };

                // picker css(width, left, top) & direction 결정
                if (!item.direction || item.direction === "" || item.direction === "auto") {
                    // set direction
                    pickerDirection = "top";

                    if (pos.top - pickerDim.height - positionMargin < 0) {
                        pickerDirection = "top";
                    } else if (pos.top + dim.height + pickerDim.height + positionMargin > pickerDim.winHeight) {
                        pickerDirection = "bottom";
                    }
                } else {
                    pickerDirection = item.direction;
                }

                if (append) {
                    this.activeautocompleteOptionGroup.addClass("direction-" + pickerDirection);
                }
                this.activeautocompleteOptionGroup.css(function () {
                    if (pickerDirection == "top") {
                        if (pos.top + dim.height + pickerDim.height + positionMargin > pickerDim.winHeight) {

                            var newTop = pos.top + pickerDim.height;
                            if (newTop + pickerDim.height + positionMargin > pickerDim.winHeight) {
                                newTop = 0;
                            }
                            if (newTop < 0) {
                                newTop = 0;
                            }

                            return {
                                left: pos.left,
                                top: newTop,
                                width: dim.width
                            };
                        }
                        return {
                            left: pos.left,
                            top: pos.top + dim.height + 1,
                            width: dim.width
                        };
                    } else if (pickerDirection == "bottom") {
                        return {
                            left: pos.left,
                            top: pos.top - pickerDim.height - 1,
                            width: dim.width
                        };
                    }
                }.call(this));
            },
                onBodyClick = function onBodyClick(e, target) {
                if (!this.activeautocompleteOptionGroup) return this;

                var item = this.queue[this.activeautocompleteQueueIndex],
                    clickEl = "display";

                target = U.findParentNode(e.target, function (target) {
                    if (target.getAttribute("data-option-value")) {
                        clickEl = "optionItem";
                        return true;
                    } else if (item.$target.get(0) == target) {
                        clickEl = "display";
                        return true;
                    }
                });

                if (!target) {
                    this.close();
                    return this;
                } else if (clickEl === "optionItem") {
                    setSelected.call(this, item.id, {
                        optionIndex: {
                            index: target.getAttribute("data-option-index")
                        }
                    }, undefined, "optionItemClick");
                    alignAutocompleteDisplay.call(this);
                    alignAutocompleteOptionGroup.call(this);
                    if (!item.multiple) {
                        this.close();
                    }
                } else {}

                return this;
            },
                getLabel = function getLabel(queIdx) {
                var item = this.queue[queIdx];

                // 템플릿에 전달 해야할 데이터 선언
                var data = {};
                data.id = item.id;
                data.theme = item.theme;
                data.size = "ax5autocomplete-option-group-" + item.size;
                data.multiple = item.multiple;
                data.lang = item.lang;
                data.options = item.options;
                data.selected = item.selected;
                data.hasSelected = data.selected && data.selected.length > 0;
                data.removeIcon = item.removeIcon;

                return AUTOCOMPLETE.tmpl.get.call(this, "label", data, item.columnKeys);
            },
                syncLabel = function syncLabel(queIdx) {
                var item = this.queue[queIdx];

                if (!item.multiple && item.selected && item.selected.length > 0) {
                    item.selected = [].concat(item.selected[item.selected.length - 1]);
                }

                item.selected.forEach(function (n, nindex) {
                    n["@index"] = nindex;
                });

                item.$select.html(AUTOCOMPLETE.tmpl.get.call(this, "formSelectOptions", {
                    selected: item.selected
                }, item.columnKeys));
            },
                printLabel = function printLabel(queIdx) {
                var item = this.queue[queIdx];

                item.$displayLabel.find('[data-ax5autocomplete-selected-label]').remove();
                item.$displayLabelInput.before(getLabel.call(this, queIdx));
            },
                focusLabel = function focusLabel(queIdx) {
                if (this.queue[queIdx].disabled) return this;

                this.queue[queIdx].$displayLabel.trigger("focus");
                this.queue[queIdx].$displayLabelInput.focus();
            },
                clearLabel = function clearLabel(queIdx) {
                this.queue[queIdx].$displayLabelInput.val('');
            },
                blurLabel = function blurLabel(queIdx) {
                this.queue[queIdx].$displayLabel.trigger("blur");
            },
                onSearch = function onSearch(queIdx, searchWord) {
                if (this.activeautocompleteQueueIndex == -1) return this; // 옵션박스가 닫힌상태이면 진행안함.
                var regExp = /[\{\}\[\]\/?.,;:|\)*~`!^\-_+<>@\#$%&\\\=\(\'\"]/gi;
                searchWord = searchWord.replace(regExp, "");

                this.queue[queIdx].waitOptions = true;
                this.queue[queIdx].onSearch.call({
                    self: this,
                    item: this.queue[queIdx],
                    searchWord: searchWord
                }, function (O) {

                    var data = {};
                    var item = this.queue[this.activeautocompleteQueueIndex];
                    if (!item) return false;

                    /// 현재 selected 검증후 처리
                    (function (item, O) {
                        var optionsMap = {};
                        O.options.forEach(function (_O, _OIndex) {
                            _O["@index"] = _OIndex;
                            _O["@findex"] = _OIndex;
                            optionsMap[_O[item.columnKeys.optionValue]] = _O;
                        });
                        if (U.isArray(item.selected)) {
                            item.selected.forEach(function (_O) {
                                if (optionsMap[_O[item.columnKeys.optionValue]]) {
                                    O.options[optionsMap[_O[item.columnKeys.optionValue]]["@index"]][item.columnKeys.optionSelected] = true;
                                }
                            });
                        }
                    })(item, O);

                    item.options = O.options;

                    alignAutocompleteDisplay.call(this);

                    /// 템플릿에 전달할 오브젝트 선언
                    data.id = item.id;
                    data.theme = item.theme;
                    data.size = "ax5autocomplete-option-group-" + item.size;
                    data.multiple = item.multiple;
                    data.lang = item.lang;
                    data.options = item.options;
                    this.activeautocompleteOptionGroup.find('[data-els="content"]').html(AUTOCOMPLETE.tmpl.get.call(this, "options", data, item.columnKeys));

                    focusWord.call(this, this.activeautocompleteQueueIndex, searchWord);
                    alignAutocompleteOptionGroup.call(this);

                    setTimeout(function () {
                        alignAutocompleteOptionGroup.call(this);
                    }.bind(this));
                }.bind(this));
            },
                focusWord = function focusWord(queIdx, searchWord) {
                if (this.activeautocompleteQueueIndex == -1) return this; // 옵션박스가 닫힌상태이면 진행안함.
                var collect_options = [],
                    i = -1,
                    l = this.queue[queIdx].options.length - 1,
                    n;
                if (searchWord != "") {
                    while (l - i++) {
                        n = this.queue[queIdx].options[i];

                        if (('' + n.text).toLowerCase() == searchWord.toLowerCase()) {
                            collect_options = [{ '@findex': n['@findex'], optionsSort: 0 }];
                            break;
                        } else {
                            var sort = ('' + n.text).toLowerCase().search(searchWord.toLowerCase());
                            if (sort > -1) {
                                collect_options.push({ '@findex': n['@findex'], optionsSort: sort });
                                if (collect_options.length > 2) break;
                            }
                            sort = null;
                        }
                    }
                    collect_options.sort(function (a, b) {
                        return a.optionsSort - b.optionsSort;
                    });
                }

                if (collect_options && collect_options.length > 0) {
                    focusMove.call(this, queIdx, undefined, collect_options[0]['@findex']);
                } else {
                    focusClear.call(this, queIdx);
                }
            },
                focusClear = function focusClear(queIdx) {
                if (this.activeautocompleteOptionGroup) {
                    this.activeautocompleteOptionGroup.find('[data-option-focus-index]').removeClass("hover").removeAttr("data-option-selected");
                }

                this.queue[queIdx].optionFocusIndex = -1;
            },
                focusMove = function focusMove(queIdx, direction, findex) {
                var _focusIndex, _prevFocusIndex, focusOptionEl, optionGroupScrollContainer;
                var item = this.queue[queIdx];

                if (this.activeautocompleteOptionGroup && item.options && item.options.length > 0) {

                    if (typeof findex !== "undefined") {
                        _focusIndex = findex;
                    } else {
                        _prevFocusIndex = item.optionFocusIndex == -1 ? item.optionSelectedIndex || -1 : item.optionFocusIndex;
                        if (_prevFocusIndex == -1) {
                            _focusIndex = 0;
                            //_focusIndex = (direction > 0) ? 0 : item.optionItemLength - 1; // 맨 끝으로 보낼것인가 말 것인가.
                        } else {
                            _focusIndex = _prevFocusIndex + direction;
                            if (_focusIndex < 0) _focusIndex = 0;else if (_focusIndex > item.optionItemLength - 1) _focusIndex = item.optionItemLength - 1;
                        }
                    }

                    item.optionFocusIndex = _focusIndex;

                    // 포커스 인덱스가 hide아이템을 만나면 hide 아이템이 안나올 때까지 루프를 순회 합니다.
                    if (item.options[_focusIndex] && item.options[_focusIndex].hide) {
                        // 옵션이 없는 값이 선택된 경우
                        if (typeof direction === "undefined") {
                            return this;
                        } else {
                            var isStrop = false;
                            while (item.options[_focusIndex].hide) {
                                _focusIndex = _focusIndex + direction;
                                if (_focusIndex < 0) {
                                    _focusIndex = 0;
                                    break;
                                } else if (_focusIndex > item.optionItemLength - 1) {
                                    _focusIndex = item.optionItemLength - 1;
                                    break;
                                }
                            }
                        }
                    }

                    if (typeof _focusIndex !== "undefined") {
                        this.activeautocompleteOptionGroup.find('[data-option-focus-index]').removeClass("hover");

                        focusOptionEl = this.activeautocompleteOptionGroup.find('[data-option-focus-index="' + _focusIndex + '"]').addClass("hover");

                        optionGroupScrollContainer = this.activeautocompleteOptionGroup.find('[data-els="content"]');

                        if (focusOptionEl.get(0)) {
                            var focusOptionElHeight = focusOptionEl.outerHeight(),
                                optionGroupScrollContainerHeight = optionGroupScrollContainer.innerHeight(),
                                optionGroupScrollContainerScrollTop = optionGroupScrollContainer.scrollTop(),
                                focusOptionElTop = focusOptionEl.position().top + optionGroupScrollContainer.scrollTop();

                            if (optionGroupScrollContainerHeight + optionGroupScrollContainerScrollTop < focusOptionElTop + focusOptionElHeight) {
                                optionGroupScrollContainer.scrollTop(focusOptionElTop + focusOptionElHeight - optionGroupScrollContainerHeight);
                            } else if (optionGroupScrollContainerScrollTop > focusOptionElTop) {
                                optionGroupScrollContainer.scrollTop(focusOptionElTop);
                            }
                            // optionGroup scroll check

                            if (typeof direction !== "undefined") {
                                item.$displayLabelInput.val(item.options[_focusIndex].text);
                            }
                        }
                    }
                }
            },
                getQueIdx = function getQueIdx(boundID) {
                if (boundID instanceof jQuery) {
                    boundID = boundID.data("data-ax5autocomplete-id");
                } else if (!U.isString(boundID)) {
                    boundID = jQuery(boundID).data("data-ax5autocomplete-id");
                }
                if (!U.isString(boundID)) {
                    console.log(ax5.info.getError("ax5autocomplete", "402", "getQueIdx"));
                    return;
                }
                return U.search(this.queue, function () {
                    return this.id == boundID;
                });
            },
                getSelected = function getSelected(_item, o, selected) {
                if (typeof selected === "undefined") {
                    return _item.multiple ? !o : true;
                } else {
                    return selected;
                }
            },
                clearSelected = function clearSelected(queIdx) {
                this.queue[queIdx].options.forEach(function (n) {
                    if (n.optgroup) {
                        n.options.forEach(function (nn) {
                            nn.selected = false;
                        });
                    } else {
                        n.selected = false;
                    }
                });

                this.queue[queIdx].selected = [];
                this.queue[queIdx].$select.html(AUTOCOMPLETE.tmpl.get.call(this, "formSelectOptions", {
                    selected: this.queue[queIdx].selected
                }, this.queue[queIdx].columnKeys));
            },
                setSelected = function () {
                var processor = {
                    'selectedIndex': function selectedIndex(queIdx, value, selected, setValueType) {},
                    'removeSelectedIndex': function removeSelectedIndex(queIdx, value, selected, setValueType) {
                        var item = this.queue[queIdx],
                            addOptions = {};
                        var newSelectedArray = [],
                            optionIndex = 0;
                        for (var i = 0; i < item.selected.length; i++) {
                            if (item.selected[i]['@index'] != value.removeSelectedIndex.index) {
                                addOptions = { '@index': optionIndex, '@findex': optionIndex };
                                addOptions[item.columnKeys.optionValue] = item.selected[i][item.columnKeys.optionValue];
                                addOptions[item.columnKeys.optionText] = item.selected[i][item.columnKeys.optionText];
                                newSelectedArray.push(addOptions);
                                optionIndex++;
                            }
                        }
                        item.selected = newSelectedArray;
                    },
                    'optionIndex': function optionIndex(queIdx, value, selected, setValueType) {
                        var item = this.queue[queIdx],
                            addOptions = {};
                        var optionIndex = item.selected.length;
                        var pushOk = true;

                        addOptions = {
                            '@index': optionIndex, '@findex': optionIndex
                        };
                        addOptions[item.columnKeys.optionValue] = item.options[value.optionIndex.index][item.columnKeys.optionValue];
                        addOptions[item.columnKeys.optionText] = item.options[value.optionIndex.index][item.columnKeys.optionText];

                        for (var i = 0; i < item.selected.length; i++) {
                            if (item.selected[i][item.columnKeys.optionValue] == addOptions[item.columnKeys.optionValue]) {
                                pushOk = false;
                            }
                        }
                        if (pushOk) item.selected.push(addOptions);
                    },
                    'arr': function arr(queIdx, values, selected, setValueType) {
                        values.forEach(function (value) {
                            if (U.isString(value) || U.isNumber(value)) {
                                processor.text.call(self, queIdx, value, selected, "justSetValue");
                            } else {
                                for (var key in processor) {
                                    if (value[key]) {
                                        processor[key].call(self, queIdx, value, selected, "justSetValue");
                                        break;
                                    }
                                }
                            }
                        });
                    },
                    'value': function value(queIdx, _value2, selected, setValueType) {
                        var item = this.queue[queIdx];
                        var addOptions;
                        var optionIndex = U.search(item.options, function () {
                            return this[item.columnKeys.optionValue] == _value2.value[item.columnKeys.optionValue];
                        });

                        if (optionIndex > -1) {
                            item.options[optionIndex][item.columnKeys.optionSelected] = getSelected(item, item.options[optionIndex][item.columnKeys.optionSelected], selected);

                            if (item.options[optionIndex][item.columnKeys.optionSelected]) {
                                var appendOk = true;
                                for (var i = 0; i < item.selected.length; i++) {
                                    if (item.selected[i][cfg.columnKeys.optionValue] == item.options[optionIndex][cfg.columnKeys.optionValue]) {
                                        appendOk = false;
                                        break;
                                    }
                                }
                                if (appendOk) {
                                    addOptions = {};
                                    addOptions[item.columnKeys.optionValue] = item.options[optionIndex][item.columnKeys.optionValue];
                                    addOptions[item.columnKeys.optionText] = item.options[optionIndex][item.columnKeys.optionText];
                                    item.selected.push(addOptions);
                                }
                            } else {
                                var newSelectedArray = [];
                                for (var i = 0; i < item.selected.length; i++) {
                                    if (item.selected[i][cfg.columnKeys.optionValue] == item.options[optionIndex][cfg.columnKeys.optionValue]) {} else {
                                        addOptions = {};
                                        addOptions[item.columnKeys.optionValue] = item.selected[i][item.columnKeys.optionValue];
                                        addOptions[item.columnKeys.optionText] = item.selected[i][item.columnKeys.optionText];
                                        newSelectedArray.push(addOptions);
                                    }
                                }
                                item.selected = newSelectedArray;
                            }
                        } else {
                            // 새로운 값 추가
                            var appendOk = true;
                            for (var i = 0; i < item.selected.length; i++) {
                                if (item.selected[i][cfg.columnKeys.optionValue] == _value2.value[cfg.columnKeys.optionValue]) {
                                    appendOk = false;
                                    break;
                                }
                            }

                            if (appendOk) {
                                addOptions = {};
                                addOptions[item.columnKeys.optionValue] = _value2.value[cfg.columnKeys.optionValue];
                                addOptions[item.columnKeys.optionText] = _value2.value[cfg.columnKeys.optionText];
                                item.selected.push(addOptions);
                            }
                        }
                    },
                    'text': function text(queIdx, value, selected, setValueType) {
                        var item = this.queue[queIdx];
                        var addOptions;
                        var optionIndex = U.search(item.options, function () {
                            return this[item.columnKeys.optionText] == value;
                        });

                        if (optionIndex > -1) {
                            item.options[optionIndex][item.columnKeys.optionSelected] = getSelected(item, item.options[optionIndex][item.columnKeys.optionSelected], selected);

                            if (item.options[optionIndex][item.columnKeys.optionSelected]) {
                                var appendOk = true;
                                for (var i = 0; i < item.selected.length; i++) {
                                    if (item.selected[i][cfg.columnKeys.optionText] == item.options[optionIndex][cfg.columnKeys.optionText]) {
                                        appendOk = false;
                                        break;
                                    }
                                }
                                if (appendOk) {
                                    addOptions = {};
                                    addOptions[item.columnKeys.optionValue] = item.options[optionIndex][item.columnKeys.optionValue];
                                    addOptions[item.columnKeys.optionText] = item.options[optionIndex][item.columnKeys.optionText];
                                    item.selected.push(addOptions);
                                }
                            } else {
                                var newSelectedArray = [];
                                for (var i = 0; i < item.selected.length; i++) {
                                    if (item.selected[i][cfg.columnKeys.optionText] == item.options[optionIndex][cfg.columnKeys.optionText]) {} else {
                                        addOptions = {};
                                        addOptions[item.columnKeys.optionValue] = item.selected[i][item.columnKeys.optionValue];
                                        addOptions[item.columnKeys.optionText] = item.selected[i][item.columnKeys.optionText];
                                        newSelectedArray.push(addOptions);
                                    }
                                }
                                item.selected = newSelectedArray;
                            }
                        } else {
                            // 새로운 값 추가
                            var appendOk = true;
                            for (var i = 0; i < item.selected.length; i++) {
                                if (item.selected[i][cfg.columnKeys.optionText] == value) {
                                    appendOk = false;
                                    break;
                                }
                            }

                            if (appendOk) {
                                addOptions = {};
                                addOptions[item.columnKeys.optionValue] = value;
                                addOptions[item.columnKeys.optionText] = value;
                                item.selected.push(addOptions);
                            }
                        }
                    },
                    'clear': function clear(queIdx) {
                        clearSelected.call(this, queIdx);
                        focusClear.call(this, queIdx);

                        if (this.activeautocompleteOptionGroup) {
                            this.activeautocompleteOptionGroup.find('[data-option-index]').attr("data-option-Selected", "false");
                        }
                        this.queue[queIdx].optionSelectedIndex = -1;
                    }
                };
                return function (boundID, value, selected, _option) {

                    var queIdx = U.isNumber(boundID) ? boundID : getQueIdx.call(this, boundID);
                    if (queIdx === -1) {
                        console.log(ax5.info.getError("ax5autocomplete", "402", "val"));
                        return;
                    }

                    if (typeof value == "undefined") {
                        throw "error not found value";
                    } else if (U.isArray(value)) {
                        processor.clear.call(this, queIdx);
                        processor.arr.call(this, queIdx, this.queue[queIdx].multiple || value.length == 0 ? value : [value[value.length - 1]], selected);
                    } else if (U.isString(value) || U.isNumber(value)) {
                        if (typeof value !== "undefined" && value !== null && !this.queue[queIdx].multiple) {
                            clearSelected.call(this, queIdx);
                        }
                        processor.text.call(this, queIdx, value, selected);
                    } else {
                        if (value === null) {
                            processor.clear.call(this, queIdx);
                        } else {
                            if (!this.queue[queIdx].multiple) {
                                clearSelected.call(this, queIdx);
                            }
                            for (var key in processor) {
                                if (value[key]) {
                                    processor[key].call(this, queIdx, value, selected);
                                    break;
                                }
                            }
                        }
                    }

                    syncLabel.call(this, queIdx);
                    printLabel.call(this, queIdx);
                    focusLabel.call(this, queIdx);
                    alignAutocompleteOptionGroup.call(this);

                    if (typeof value !== "undefined") {
                        if (_option && !_option.noStateChange) {
                            onStateChanged.call(this, this.queue[queIdx], {
                                self: this,
                                item: this.queue[queIdx],
                                state: "changeValue",
                                value: this.queue[queIdx].selected
                            });
                        }
                    }

                    boundID = null;
                    return this;
                };
            }();

            /// private end

            /**
             * Preferences of autocomplete UI
             * @method ax5autocomplete.setConfig
             * @param {Object} config - 클래스 속성값
             * @returns {ax5autocomplete}
             * @example
             * ```
             * ```
             */
            this.init = function () {
                this.onStateChanged = cfg.onStateChanged;
                this.onChange = cfg.onChange;
                jQuery(window).on("resize.ax5autocomplete-display-" + this.instanceId, function () {
                    alignAutocompleteDisplay.call(this);
                }.bind(this));
            };

            /**
             * bind autocomplete
             * @method ax5autocomplete.bind
             * @param {Object} item
             * @param {String} [item.id]
             * @param {String} [item.theme]
             * @param {Boolean} [item.multiple]
             * @param {Element} item.target
             * @returns {ax5autocomplete}
             */
            this.bind = function (item) {
                var bindAutocompleteTarget = function () {
                    var debouncedFocusWord = U.debounce(function (queIdx) {
                        if (this.activeautocompleteQueueIndex == -1) return this; // 옵션박스가 닫힌상태이면 진행안함.
                        onSearch.call(self, queIdx, this.queue[queIdx].$displayLabelInput.val());
                    }, 150);

                    var blurLabel = function blurLabel(queIdx) {
                        clearLabel.call(this, queIdx);
                    };

                    var autocompleteEvent = {
                        'click': function click(queIdx, e) {
                            var clickEl;
                            var target = U.findParentNode(e.target, function (target) {
                                if (target.getAttribute("data-ax5autocomplete-remove")) {
                                    clickEl = "optionItemRemove";
                                    return true;
                                } else if (target.getAttribute("data-selected-clear")) {
                                    clickEl = "clear";
                                    return true;
                                }
                            });

                            if (target) {
                                if (clickEl === "optionItemRemove") {
                                    var removeIndex = target.getAttribute("data-ax5autocomplete-remove-index");
                                    this.queue[queIdx].selected.splice(removeIndex, 1);
                                    syncLabel.call(this, queIdx);
                                    printLabel.call(this, queIdx);
                                    focusLabel.call(this, queIdx);
                                    alignAutocompleteDisplay.call(this);
                                    alignAutocompleteOptionGroup.call(this);
                                    U.stopEvent(e);
                                    return this;
                                } else if (clickEl === "clear") {
                                    setSelected.call(this, queIdx, { clear: true });
                                    alignAutocompleteDisplay.call(this);
                                    alignAutocompleteOptionGroup.call(this);
                                }
                            } else {
                                if (self.activeautocompleteQueueIndex == queIdx) {
                                    if (this.queue[queIdx].optionFocusIndex == -1) {
                                        // 아이템에 포커스가 활성화 된 후, 마우스 이벤트 이면 무시
                                        self.close();
                                    }
                                } else {
                                    focusLabel.call(this, queIdx);
                                }
                            }
                        },
                        'keyUp': function keyUp(queIdx, e) {
                            /// 약속된 키 이벤트가 발생하면 stopEvent를 통해 keyUp 이벤트가 발생되지 않도록 막아주는 센스
                            if (e.which == ax5.info.eventKeys.ESC && this.activeautocompleteQueueIndex === -1) {
                                // ESC키를 누르고 옵션그룹이 열려있지 않은 경우
                                U.stopEvent(e);
                                return this;
                            }
                            if (e.which == ax5.info.eventKeys.TAB) {
                                // nothing

                                this.close();
                                return this;
                            }
                            if (this.activeautocompleteQueueIndex != queIdx) {
                                // 닫힌 상태 인경우
                                this.open(queIdx); // open and align
                            }
                            if (ctrlKeys[e.which]) {
                                U.stopEvent(e);
                            } else {
                                // backspace 감지 하여 input 값이 없으면 스탑이벤트 처리 할 것
                                if (e.which == ax5.info.eventKeys.BACKSPACE && this.queue[queIdx].$displayLabelInput.val() == "") {
                                    // 마지막 아이템을 제거.
                                    this.queue[queIdx].selected.pop();
                                    syncLabel.call(this, queIdx);
                                    printLabel.call(this, queIdx);
                                    focusLabel.call(this, queIdx);
                                    alignAutocompleteDisplay.call(this);
                                    alignAutocompleteOptionGroup.call(this);
                                    U.stopEvent(e);
                                } else {
                                    debouncedFocusWord.call(this, queIdx);
                                }
                            }
                        },
                        'keyDown': function keyDown(queIdx, e) {
                            if (e.which == ax5.info.eventKeys.ESC) {
                                clearLabel.call(this, queIdx);
                                this.close();
                                U.stopEvent(e);
                            } else if (e.which == ax5.info.eventKeys.RETURN) {
                                var inputValue = this.queue[queIdx].$displayLabelInput.val();
                                if (item.optionFocusIndex > -1) {
                                    setSelected.call(this, item.id, {
                                        optionIndex: {
                                            index: item.optionFocusIndex
                                        }
                                    }, undefined, "optionItemClick");
                                } else if (inputValue != "") {
                                    setSelected.call(this, queIdx, inputValue, true);
                                }
                                clearLabel.call(this, queIdx);
                                alignAutocompleteDisplay.call(this);
                                this.close();

                                U.stopEvent(e);
                            } else if (e.which == ax5.info.eventKeys.DOWN) {
                                focusMove.call(this, queIdx, 1);
                                U.stopEvent(e);
                            } else if (e.which == ax5.info.eventKeys.UP) {
                                focusMove.call(this, queIdx, -1);
                                U.stopEvent(e);
                            }
                        },
                        'focus': function focus(queIdx, e) {
                            // console.log(e);

                        },
                        'blur': function blur(queIdx, e) {
                            blurLabel.call(this, queIdx);
                            U.stopEvent(e);
                        },
                        'selectChange': function selectChange(queIdx, e) {
                            setSelected.call(this, queIdx, { value: this.queue[queIdx].$select.val() }, true);
                        }
                    };

                    return function (queIdx) {
                        var item = this.queue[queIdx];
                        var data = {};

                        if (!item.$display) {
                            /// 템플릿에 전달할 오브젝트 선언
                            data.instanceId = this.instanceId;
                            data.id = item.id;
                            data.name = item.name;
                            data.theme = item.theme;
                            data.tabIndex = item.tabIndex;
                            data.multiple = item.multiple;
                            data.reset = item.reset;

                            data.label = getLabel.call(this, queIdx);
                            data.formSize = function () {
                                return item.size ? "input-" + item.size : "";
                            }();

                            item.$display = jQuery(AUTOCOMPLETE.tmpl.get.call(this, "autocompleteDisplay", data, item.columnKeys));
                            item.$displayTable = item.$display.find('[data-els="display-table"]');
                            item.$displayLabel = item.$display.find('[data-ax5autocomplete-display="label"]');
                            item.$displayLabelInput = item.$display.find('[data-ax5autocomplete-display="input"]');

                            if (item.$target.find("select").get(0)) {
                                item.$select = item.$target.find("select");
                                item.$select.attr("tabindex", "-1").attr("class", "form-control " + data.formSize);

                                if (data.name) {
                                    item.$select.attr("name", "name");
                                }
                                item.$select.attr("multiple", "multiple");
                            } else {
                                item.$select = jQuery(AUTOCOMPLETE.tmpl.get.call(this, "formSelect", data, item.columnKeys));
                                item.$target.append(item.$select);
                            }

                            item.$target.append(item.$display);
                        } else {
                            printLabel.call(this, queIdx);
                        }

                        alignAutocompleteDisplay.call(this);

                        item.$display.off('click.ax5autocomplete').on('click.ax5autocomplete', autocompleteEvent.click.bind(this, queIdx));

                        // autocomplete 태그에 대한 이벤트 감시
                        item.$displayLabelInput.off("focus.ax5autocomplete").on("focus.ax5autocomplete", autocompleteEvent.focus.bind(this, queIdx)).off("blur.ax5autocomplete").on("blur.ax5autocomplete", autocompleteEvent.blur.bind(this, queIdx)).off("keydown.ax5autocomplete").on("keydown.ax5autocomplete", autocompleteEvent.keyDown.bind(this, queIdx)).off("keyup.ax5autocomplete").on("keyup.ax5autocomplete", autocompleteEvent.keyUp.bind(this, queIdx));

                        // select 태그에 대한 change 이벤트 감시

                        item.$select.off('change.ax5autocomplete').on('change.ax5autocomplete', autocompleteEvent.selectChange.bind(this, queIdx));

                        data = null;
                        item = null;
                        queIdx = null;
                        return this;
                    };
                }();

                var autocompleteConfig = {},
                    queIdx;

                item = jQuery.extend(true, autocompleteConfig, cfg, item);
                if (!item.target) {
                    console.log(ax5.info.getError("ax5autocomplete", "401", "bind"));
                    return this;
                }

                item.$target = jQuery(item.target);

                if (!item.id) item.id = item.$target.data("data-ax5autocomplete-id");
                if (!item.id) {
                    item.id = 'ax5autocomplete-' + ax5.getGuid();
                    item.$target.data("data-ax5autocomplete-id", item.id);
                }
                item.name = item.$target.attr("data-ax5autocomplete");

                item.options = [];
                item.selected = [];

                // target attribute data
                (function (data) {
                    if (U.isObject(data) && !data.error) {
                        item = jQuery.extend(true, item, data);
                    }
                })(U.parseJson(item.$target.attr("data-ax5autocomplete-config"), true));

                queIdx = U.search(this.queue, function () {
                    return this.id == item.id;
                });

                if (queIdx === -1) {
                    this.queue.push(item);
                    bindAutocompleteTarget.call(this, this.queue.length - 1);
                } else {
                    this.queue[queIdx] = jQuery.extend(true, {}, this.queue[queIdx], item);
                    bindAutocompleteTarget.call(this, queIdx);
                }

                autocompleteConfig = null;
                queIdx = null;
                return this;
            };

            /**
             * open the optionBox of autocomplete
             * @method ax5autocomplete.open
             * @param {(String|Number|Element)} boundID
             * @param {Number} [tryCount]
             * @returns {ax5autocomplete}
             */
            this.open = function () {

                return function (boundID, tryCount) {
                    this.waitOptionsCallback = null;

                    /**
                     * open autocomplete from the outside
                     */
                    var queIdx = U.isNumber(boundID) ? boundID : getQueIdx.call(this, boundID);
                    var item = this.queue[queIdx];
                    var data = {},
                        focusTop,
                        selectedOptionEl;

                    if (item.$display.attr("disabled")) return this;

                    if (this.openTimer) clearTimeout(this.openTimer);
                    if (this.activeautocompleteOptionGroup) {
                        if (this.activeautocompleteQueueIndex == queIdx) {
                            return this;
                        }

                        if (tryCount > 2) return this;
                        this.close();
                        this.openTimer = setTimeout(function () {
                            this.open(queIdx, (tryCount || 0) + 1);
                        }.bind(this), cfg.animateTime);

                        return this;
                    }

                    item.optionFocusIndex = -1; // optionGroup이 열리면 포커스 인덱스 초기화 -1로
                    if (item.selected && item.selected.length > 0) {
                        item.optionSelectedIndex = item.selected[0]["@findex"];
                    }

                    /// 템플릿에 전달할 오브젝트 선언
                    data.id = item.id;
                    data.theme = item.theme;
                    data.size = "ax5autocomplete-option-group-" + item.size;
                    data.multiple = item.multiple;

                    data.lang = item.lang;
                    item.$display.attr("data-autocomplete-option-group-opened", "true");

                    data.waitOptions = true;
                    data.options = [];

                    this.activeautocompleteOptionGroup = jQuery(AUTOCOMPLETE.tmpl.get.call(this, "optionGroup", data, item.columnKeys));
                    this.activeautocompleteOptionGroup.find('[data-els="content"]').html(AUTOCOMPLETE.tmpl.get.call(this, "options", data, item.columnKeys));
                    this.activeautocompleteQueueIndex = queIdx;

                    alignAutocompleteOptionGroup.call(this, "append"); // alignAutocompleteOptionGroup 에서 body append
                    jQuery(window).on("resize.ax5autocomplete-" + this.instanceId, function () {
                        alignAutocompleteOptionGroup.call(this);
                    }.bind(this));

                    if (item.selected && item.selected.length > 0) {
                        selectedOptionEl = this.activeautocompleteOptionGroup.find('[data-option-index="' + item.selected[0]["@index"] + '"]');
                        if (selectedOptionEl.get(0)) {
                            focusTop = selectedOptionEl.position().top - this.activeautocompleteOptionGroup.height() / 3;
                            this.activeautocompleteOptionGroup.find('[data-els="content"]').stop().animate({ scrollTop: focusTop }, item.animateTime, 'swing', function () {});
                        }
                    }

                    jQuery(window).on("click.ax5autocomplete-" + this.instanceId, function (e) {
                        e = e || window.event;
                        onBodyClick.call(this, e);
                        U.stopEvent(e);
                    }.bind(this));

                    onStateChanged.call(this, item, {
                        self: this,
                        state: "open",
                        item: item
                    });

                    data = null;
                    focusTop = null;
                    selectedOptionEl = null;
                    return this;
                };
            }();

            /**
             * @method ax5autocomplete.setValue
             * @param {(jQueryObject|Element|Number)} _boundID
             * @param {(String|Array)} _value
             * @return {ax5autocomplete}
             * @example
             * ```js
             * myAutocomplete.setValue($('[data-ax5autocomplete="autocomplete1"]'), {value:"test", text:"test"});
             * myAutocomplete.setValue($('[data-ax5autocomplete="autocomplete1"]'), [{value:"test1", text:"test1"}, {value:"test2", text:"test2"}]);
             * myAutocomplete.setValue($('[data-ax5autocomplete="autocomplete1"]'), null);
             * ```
             */
            this.setValue = function (_boundID, _value) {
                var queIdx = U.isNumber(_boundID) ? _boundID : getQueIdx.call(this, _boundID);
                if (queIdx === -1) {
                    console.log(ax5.info.getError("ax5autocomplete", "402", "val"));
                    return;
                }

                clearSelected.call(this, queIdx);

                if (U.isArray(_value)) {
                    var _values = U.map(_value, function () {
                        return { value: this };
                    });
                    setSelected.call(this, queIdx, _values, true, { noStateChange: true });
                } else if (U.isObject(_value)) {
                    setSelected.call(this, queIdx, { value: _value }, true, { noStateChange: true });
                } else {
                    printLabel.call(this, queIdx);
                }

                blurLabel.call(this, queIdx);
                alignAutocompleteDisplay.call(this);

                return this;
            };

            /**
             * @method ax5autocomplete.setText
             * @param {(jQueryObject|Element|Number)} _boundID
             * @param {(String|Array)} _text
             * @returns {ax5autocomplete}
             * @example
             * ```js
             * myAutocomplete.setText($('[data-ax5autocomplete="autocomplete1"]'), "string");
             * myAutocomplete.setText($('[data-ax5autocomplete="autocomplete1"]'), ["substring", "search"]);
             * ```
             */
            this.setText = function (_boundID, _text) {
                var queIdx = U.isNumber(_boundID) ? _boundID : getQueIdx.call(this, _boundID);
                if (queIdx === -1) {
                    console.log(ax5.info.getError("ax5autocomplete", "402", "val"));
                    return;
                }
                this.queue[queIdx].selected = [];
                clearSelected.call(this, queIdx);
                setSelected.call(this, queIdx, _text, true, { noStateChange: true });
                blurLabel.call(this, queIdx);
                alignAutocompleteDisplay.call(this);

                return this;
            };

            /**
             * @method ax5autocomplete.getSelectedOption
             * @param {(jQueryObject|Element|Number)} _boundID
             * @returns {Array}
             */
            this.getSelectedOption = function (_boundID) {
                var queIdx = U.isNumber(_boundID) ? _boundID : getQueIdx.call(this, _boundID);
                if (queIdx === -1) {
                    console.log(ax5.info.getError("ax5autocomplete", "402", "val"));
                    return;
                }
                return U.deepCopy(this.queue[queIdx].selected);
            };

            /**
             * @method ax5autocomplete.close
             * @returns {ax5autocomplete}
             */
            this.close = function (item) {
                if (this.closeTimer) clearTimeout(this.closeTimer);
                if (!this.activeautocompleteOptionGroup) return this;

                item = this.queue[this.activeautocompleteQueueIndex];
                item.optionFocusIndex = -1;
                item.$display.removeAttr("data-autocomplete-option-group-opened").trigger("focus");

                this.activeautocompleteOptionGroup.addClass("destroy");

                jQuery(window).off("resize.ax5autocomplete-" + this.instanceId).off("click.ax5autocomplete-" + this.instanceId).off("keyup.ax5autocomplete-" + this.instanceId);

                this.closeTimer = setTimeout(function () {
                    if (this.activeautocompleteOptionGroup) this.activeautocompleteOptionGroup.remove();
                    this.activeautocompleteOptionGroup = null;
                    this.activeautocompleteQueueIndex = -1;

                    onStateChanged.call(this, item, {
                        self: this,
                        state: "close"
                    });
                }.bind(this), cfg.animateTime);
                this.waitOptionsCallback = null;
                return this;
            };

            /**
             * @method ax5autocomplete.blur
             * @param {(jQueryObject|Element|Number)} _boundID
             * @returns {ax5autocomplete}
             */
            this.blur = function (_boundID) {
                var queIdx = U.isNumber(_boundID) ? _boundID : getQueIdx.call(this, _boundID);
                if (queIdx === -1) {
                    console.log(ax5.info.getError("ax5autocomplete", "402", "val"));
                    return;
                }

                blurLabel.call(this, queIdx);
                return this;
            };

            /**
             * @method ax5autocomplete.enable
             * @param {(jQueryObject|Element|Number)} _boundID
             * @returns {ax5autocomplete}
             */
            this.enable = function (_boundID) {
                var queIdx = getQueIdx.call(this, _boundID);

                if (typeof queIdx !== "undefined") {
                    this.queue[queIdx].disable = false;
                    if (this.queue[queIdx].$display[0]) {
                        this.queue[queIdx].$display.removeAttr("disabled");
                        this.queue[queIdx].$displayLabelInput.removeAttr("disabled");
                    }
                    if (this.queue[queIdx].$select[0]) {
                        this.queue[queIdx].$select.removeAttr("disabled");
                    }

                    onStateChanged.call(this, this.queue[queIdx], {
                        self: this,
                        state: "enable"
                    });
                }

                return this;
            };

            /**
             * @method ax5autocomplete.disable
             * @param {(jQueryObject|Element|Number)} _boundID
             * @returns {ax5autocomplete}
             */
            this.disable = function (_boundID) {
                var queIdx = getQueIdx.call(this, _boundID);

                if (typeof queIdx !== "undefined") {
                    this.queue[queIdx].disable = true;
                    if (this.queue[queIdx].$display[0]) {
                        this.queue[queIdx].$display.attr("disabled", "disabled");
                        this.queue[queIdx].$displayLabelInput.attr("disabled", "disabled");
                    }
                    if (this.queue[queIdx].$select[0]) {
                        this.queue[queIdx].$select.attr("disabled", "disabled");
                    }

                    onStateChanged.call(this, this.queue[queIdx], {
                        self: this,
                        state: "disable"
                    });
                }

                return this;
            };

            /**
             * @method ax5autocomplete.align
             */
            this.align = function () {
                alignAutocompleteDisplay.call(this);
                return this;
            };

            // 클래스 생성자
            this.main = function () {
                if (arguments && U.isObject(arguments[0])) {
                    this.setConfig(arguments[0]);
                } else {
                    this.init();
                }
            }.apply(this, arguments);
        };
    }());

    AUTOCOMPLETE = ax5.ui.autocomplete;
})();

// todo : editable 지원.
// 아이템 박스 안에서 제거 할때 디스플레이 정렬

// ax5.ui.autocomplete.tmpl
(function () {
    var AUTOCOMPLETE = ax5.ui.autocomplete;
    var U = ax5.util;

    var optionGroup = function optionGroup(columnKeys) {
        return '\n<div class="ax5autocomplete-option-group {{theme}} {{size}}" data-ax5autocomplete-option-group="{{id}}">\n    <div class="ax-autocomplete-body">\n        <div class="ax-autocomplete-option-group-content" data-els="content"></div>\n    </div>\n    <div class="ax-autocomplete-arrow"></div> \n</div>\n';
    };

    var autocompleteDisplay = function autocompleteDisplay(columnKeys) {
        return ' \n<input tabindex="-1" type="text" data-input-dummy="" style="display: none;" />\n<div class="form-control {{formSize}} ax5autocomplete-display {{theme}}" \ndata-ax5autocomplete-display="{{id}}" data-ax5autocomplete-instance="{{instanceId}}">\n    <div class="ax5autocomplete-display-table" data-els="display-table">\n        <div data-ax5autocomplete-display="label-holder"> \n        <a {{^tabIndex}}{{/tabIndex}}{{#tabIndex}}tabindex="{{tabIndex}}" {{/tabIndex}}\n        data-ax5autocomplete-display="label"\n        spellcheck="false"><input type="text"data-ax5autocomplete-display="input" style="border:0px none;" /></a>\n        </div>\n        <div data-ax5autocomplete-display="addon"> \n            {{#multiple}}{{#reset}}\n            <span class="addon-icon-reset" data-selected-clear="true">{{{.}}}</span>\n            {{/reset}}{{/multiple}}\n        </div>\n    </div>\n</a>\n';
    };

    var formSelect = function formSelect(columnKeys) {
        return '\n<select tabindex="-1" class="form-control {{formSize}}" name="{{name}}" multiple="multiple"></select>\n';
    };

    var formSelectOptions = function formSelectOptions(columnKeys) {
        return '\n{{#selected}}\n<option value="{{' + columnKeys.optionValue + '}}" selected="true">{{' + columnKeys.optionText + '}}</option>\n{{/selected}}\n';
    };

    var options = function options(columnKeys) {
        return '\n{{#waitOptions}}\n    <div class="ax-autocomplete-option-item">\n            <div class="ax-autocomplete-option-item-holder">\n                <span class="ax-autocomplete-option-item-cell ax-autocomplete-option-item-label">\n                    {{{lang.loading}}}\n                </span>\n            </div>\n        </div>\n{{/waitOptions}}\n{{^waitOptions}}\n    {{#options}}\n        {{^hide}}\n        <div class="ax-autocomplete-option-item" data-option-focus-index="{{@findex}}" data-option-index="{{@index}}" data-option-value="{{' + columnKeys.optionValue + '}}" {{#' + columnKeys.optionSelected + '}}data-option-selected="true"{{/' + columnKeys.optionSelected + '}}>\n            <div class="ax-autocomplete-option-item-holder">\n                <span class="ax-autocomplete-option-item-cell ax-autocomplete-option-item-label">{{' + columnKeys.optionText + '}}</span>\n            </div>\n        </div>\n        {{/hide}}\n    {{/options}}\n    {{^options}}\n        <div class="ax-autocomplete-option-item">\n            <div class="ax-autocomplete-option-item-holder">\n                <span class="ax-autocomplete-option-item-cell ax-autocomplete-option-item-label">\n                    {{{lang.noOptions}}}\n                </span>\n            </div>\n        </div>\n    {{/options}}\n{{/waitOptions}}\n';
    };

    var label = function label(columnKeys) {
        return '{{#selected}}\n<div tabindex="-1" data-ax5autocomplete-selected-label="{{@i}}" data-ax5autocomplete-selected-text="{{text}}">\n<div data-ax5autocomplete-remove="true" data-ax5autocomplete-remove-index="{{@i}}">{{{removeIcon}}}</div>\n<span>{{' + columnKeys.optionText + '}}</span>\n</div>{{/selected}}';
    };

    AUTOCOMPLETE.tmpl = {
        "autocompleteDisplay": autocompleteDisplay,
        "formSelect": formSelect,
        "formSelectOptions": formSelectOptions,
        "optionGroup": optionGroup,
        "options": options,
        "label": label,

        get: function get(tmplName, data, columnKeys) {
            return ax5.mustache.render(AUTOCOMPLETE.tmpl[tmplName].call(this, columnKeys), data);
        }
    };
})();
/**
 * autocomplete jquery extends
 * @namespace jQueryExtends
 */

/**
 * @method jQueryExtends.ax5autocomplete
 * @param {String} methodName
 * @param [arguments]
 * @param [arguments]
 * @example
 * ```html
 * <div data-ax5autocomplete="ax1" data-ax5autocomplete-config='{
 *  multiple: true,
 *  editable: true,
 *  size: "",
 *  theme:""
 *  }'></div>
 * <script>
 * jQuery('[data-ax5autocomplete="ax1"]').ax5autocomplete();
 * $('[data-ax5autocomplete="ax1"]').ax5autocomplete("getSelectedOption");
 * $('[data-ax5autocomplete="ax1"]').ax5autocomplete("setValue", {value:"test", text:"test"});
 * $('[data-ax5autocomplete="ax1"]').ax5autocomplete("enable");
 * $('[data-ax5autocomplete="ax1"]').ax5autocomplete("disable");
 * </script>
 * ```
 */
ax5.ui.autocomplete_instance = new ax5.ui.autocomplete();
jQuery.fn.ax5autocomplete = function () {
    return function (config) {
        if (ax5.util.isString(arguments[0])) {
            var methodName = arguments[0];

            switch (methodName) {
                case "open":
                    return ax5.ui.autocomplete_instance.open(this);
                    break;
                case "close":
                    return ax5.ui.autocomplete_instance.close(this);
                    break;
                case "setValue":
                    return ax5.ui.autocomplete_instance.setValue(this, arguments[1], arguments[2], arguments[3], arguments[4] || "justSetValue");
                    break;
                case "setText":
                    return ax5.ui.autocomplete_instance.setText(this, arguments[1], arguments[2], arguments[3], arguments[4] || "justSetValue");
                    break;
                case "getSelectedOption":
                    return ax5.ui.autocomplete_instance.getSelectedOption(this);
                    break;
                case "enable":
                    return ax5.ui.autocomplete_instance.enable(this);
                    break;
                case "disable":
                    return ax5.ui.autocomplete_instance.disable(this);
                    break;
                case "blur":
                    return ax5.ui.autocomplete_instance.blur(this);
                case "align":
                    return ax5.ui.autocomplete_instance.align(this);
                default:
                    return this;
            }
        } else {
            if (typeof config == "undefined") config = {};
            jQuery.each(this, function () {
                var defaultConfig = {
                    target: this
                };
                config = jQuery.extend({}, config, defaultConfig);
                ax5.ui.autocomplete_instance.bind(config);
            });
        }
        return this;
    };
}();