mirror of
https://github.com/piskelapp/piskel.git
synced 2023-08-10 21:12:52 +03:00
16583 lines
492 KiB
JavaScript
16583 lines
492 KiB
JavaScript
/*!
|
|
* jQuery JavaScript Library v1.8.0
|
|
* http://jquery.com/
|
|
*
|
|
* Includes Sizzle.js
|
|
* http://sizzlejs.com/
|
|
*
|
|
* Copyright 2012 jQuery Foundation and other contributors
|
|
* Released under the MIT license
|
|
* http://jquery.org/license
|
|
*
|
|
* Date: Thu Aug 09 2012 16:24:48 GMT-0400 (Eastern Daylight Time)
|
|
*/
|
|
(function( window, undefined ) {
|
|
var
|
|
// A central reference to the root jQuery(document)
|
|
rootjQuery,
|
|
|
|
// The deferred used on DOM ready
|
|
readyList,
|
|
|
|
// Use the correct document accordingly with window argument (sandbox)
|
|
document = window.document,
|
|
location = window.location,
|
|
navigator = window.navigator,
|
|
|
|
// Map over jQuery in case of overwrite
|
|
_jQuery = window.jQuery,
|
|
|
|
// Map over the $ in case of overwrite
|
|
_$ = window.$,
|
|
|
|
// Save a reference to some core methods
|
|
core_push = Array.prototype.push,
|
|
core_slice = Array.prototype.slice,
|
|
core_indexOf = Array.prototype.indexOf,
|
|
core_toString = Object.prototype.toString,
|
|
core_hasOwn = Object.prototype.hasOwnProperty,
|
|
core_trim = String.prototype.trim,
|
|
|
|
// Define a local copy of jQuery
|
|
jQuery = function( selector, context ) {
|
|
// The jQuery object is actually just the init constructor 'enhanced'
|
|
return new jQuery.fn.init( selector, context, rootjQuery );
|
|
},
|
|
|
|
// Used for matching numbers
|
|
core_pnum = /[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,
|
|
|
|
// Used for detecting and trimming whitespace
|
|
core_rnotwhite = /\S/,
|
|
core_rspace = /\s+/,
|
|
|
|
// IE doesn't match non-breaking spaces with \s
|
|
rtrim = core_rnotwhite.test("\xA0") ? (/^[\s\xA0]+|[\s\xA0]+$/g) : /^\s+|\s+$/g,
|
|
|
|
// A simple way to check for HTML strings
|
|
// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
|
|
rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
|
|
|
|
// Match a standalone tag
|
|
rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
|
|
|
|
// JSON RegExp
|
|
rvalidchars = /^[\],:{}\s]*$/,
|
|
rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
|
|
rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
|
|
rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/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();
|
|
},
|
|
|
|
// The ready event handler and self cleanup method
|
|
DOMContentLoaded = function() {
|
|
if ( document.addEventListener ) {
|
|
document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
|
|
jQuery.ready();
|
|
} else if ( document.readyState === "complete" ) {
|
|
// we're here because readyState === "complete" in oldIE
|
|
// which is good enough for us to call the dom ready!
|
|
document.detachEvent( "onreadystatechange", DOMContentLoaded );
|
|
jQuery.ready();
|
|
}
|
|
},
|
|
|
|
// [[Class]] -> type pairs
|
|
class2type = {};
|
|
|
|
jQuery.fn = jQuery.prototype = {
|
|
constructor: jQuery,
|
|
init: function( selector, context, rootjQuery ) {
|
|
var match, elem, ret, doc;
|
|
|
|
// Handle $(""), $(null), $(undefined), $(false)
|
|
if ( !selector ) {
|
|
return this;
|
|
}
|
|
|
|
// Handle $(DOMElement)
|
|
if ( selector.nodeType ) {
|
|
this.context = this[0] = selector;
|
|
this.length = 1;
|
|
return this;
|
|
}
|
|
|
|
// 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;
|
|
doc = ( context && context.nodeType ? context.ownerDocument || context : document );
|
|
|
|
// scripts is true for back-compat
|
|
selector = jQuery.parseHTML( match[1], doc, true );
|
|
if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
|
|
this.attr.call( selector, context, true );
|
|
}
|
|
|
|
return jQuery.merge( this, selector );
|
|
|
|
// HANDLE: $(#id)
|
|
} else {
|
|
elem = document.getElementById( match[2] );
|
|
|
|
// Check parentNode to catch when Blackberry 4.6 returns
|
|
// nodes that are no longer in the document #6963
|
|
if ( elem && elem.parentNode ) {
|
|
// Handle the case where IE and Opera return items
|
|
// by name instead of ID
|
|
if ( elem.id !== match[2] ) {
|
|
return rootjQuery.find( selector );
|
|
}
|
|
|
|
// Otherwise, we inject the element directly into the jQuery object
|
|
this.length = 1;
|
|
this[0] = elem;
|
|
}
|
|
|
|
this.context = document;
|
|
this.selector = selector;
|
|
return this;
|
|
}
|
|
|
|
// HANDLE: $(expr, $(...))
|
|
} else if ( !context || context.jquery ) {
|
|
return ( context || rootjQuery ).find( selector );
|
|
|
|
// HANDLE: $(expr, context)
|
|
// (which is just equivalent to: $(context).find(expr)
|
|
} else {
|
|
return this.constructor( context ).find( selector );
|
|
}
|
|
|
|
// HANDLE: $(function)
|
|
// Shortcut for document ready
|
|
} else if ( jQuery.isFunction( selector ) ) {
|
|
return rootjQuery.ready( selector );
|
|
}
|
|
|
|
if ( selector.selector !== undefined ) {
|
|
this.selector = selector.selector;
|
|
this.context = selector.context;
|
|
}
|
|
|
|
return jQuery.makeArray( selector, this );
|
|
},
|
|
|
|
// Start with an empty selector
|
|
selector: "",
|
|
|
|
// The current version of jQuery being used
|
|
jquery: "1.8.0",
|
|
|
|
// The default length of a jQuery object is 0
|
|
length: 0,
|
|
|
|
// The number of elements contained in the matched element set
|
|
size: function() {
|
|
return this.length;
|
|
},
|
|
|
|
toArray: function() {
|
|
return core_slice.call( this );
|
|
},
|
|
|
|
// Get the Nth element in the matched element set OR
|
|
// Get the whole matched element set as a clean array
|
|
get: function( num ) {
|
|
return num == null ?
|
|
|
|
// Return a 'clean' array
|
|
this.toArray() :
|
|
|
|
// Return just the object
|
|
( num < 0 ? this[ this.length + num ] : this[ num ] );
|
|
},
|
|
|
|
// Take an array of elements and push it onto the stack
|
|
// (returning the new matched element set)
|
|
pushStack: function( elems, name, selector ) {
|
|
|
|
// Build a new jQuery matched element set
|
|
var ret = jQuery.merge( this.constructor(), elems );
|
|
|
|
// Add the old object onto the stack (as a reference)
|
|
ret.prevObject = this;
|
|
|
|
ret.context = this.context;
|
|
|
|
if ( name === "find" ) {
|
|
ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
|
|
} else if ( name ) {
|
|
ret.selector = this.selector + "." + name + "(" + selector + ")";
|
|
}
|
|
|
|
// Return the newly-formed element set
|
|
return ret;
|
|
},
|
|
|
|
// Execute a callback for every element in the matched set.
|
|
// (You can seed the arguments with an array of args, but this is
|
|
// only used internally.)
|
|
each: function( callback, args ) {
|
|
return jQuery.each( this, callback, args );
|
|
},
|
|
|
|
ready: function( fn ) {
|
|
// Add the callback
|
|
jQuery.ready.promise().done( fn );
|
|
|
|
return this;
|
|
},
|
|
|
|
eq: function( i ) {
|
|
i = +i;
|
|
return i === -1 ?
|
|
this.slice( i ) :
|
|
this.slice( i, i + 1 );
|
|
},
|
|
|
|
first: function() {
|
|
return this.eq( 0 );
|
|
},
|
|
|
|
last: function() {
|
|
return this.eq( -1 );
|
|
},
|
|
|
|
slice: function() {
|
|
return this.pushStack( core_slice.apply( this, arguments ),
|
|
"slice", core_slice.call(arguments).join(",") );
|
|
},
|
|
|
|
map: function( callback ) {
|
|
return this.pushStack( jQuery.map(this, function( elem, i ) {
|
|
return callback.call( elem, i, elem );
|
|
}));
|
|
},
|
|
|
|
end: function() {
|
|
return this.prevObject || this.constructor(null);
|
|
},
|
|
|
|
// For internal use only.
|
|
// Behaves like an Array's method, not like a jQuery method.
|
|
push: core_push,
|
|
sort: [].sort,
|
|
splice: [].splice
|
|
};
|
|
|
|
// Give the init function the jQuery prototype for later instantiation
|
|
jQuery.fn.init.prototype = jQuery.fn;
|
|
|
|
jQuery.extend = jQuery.fn.extend = function() {
|
|
var options, name, src, copy, copyIsArray, clone,
|
|
target = arguments[0] || {},
|
|
i = 1,
|
|
length = arguments.length,
|
|
deep = false;
|
|
|
|
// Handle a deep copy situation
|
|
if ( typeof target === "boolean" ) {
|
|
deep = target;
|
|
target = arguments[1] || {};
|
|
// skip the boolean and the target
|
|
i = 2;
|
|
}
|
|
|
|
// Handle case when target is a string or something (possible in deep copy)
|
|
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
|
|
target = {};
|
|
}
|
|
|
|
// extend jQuery itself if only one argument is passed
|
|
if ( length === i ) {
|
|
target = this;
|
|
--i;
|
|
}
|
|
|
|
for ( ; i < length; i++ ) {
|
|
// Only deal with non-null/undefined values
|
|
if ( (options = arguments[ i ]) != null ) {
|
|
// Extend the base object
|
|
for ( name in options ) {
|
|
src = target[ name ];
|
|
copy = options[ name ];
|
|
|
|
// Prevent never-ending loop
|
|
if ( target === copy ) {
|
|
continue;
|
|
}
|
|
|
|
// Recurse if we're merging plain objects or arrays
|
|
if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
|
|
if ( copyIsArray ) {
|
|
copyIsArray = false;
|
|
clone = src && jQuery.isArray(src) ? src : [];
|
|
|
|
} else {
|
|
clone = src && jQuery.isPlainObject(src) ? src : {};
|
|
}
|
|
|
|
// Never move original objects, clone them
|
|
target[ name ] = jQuery.extend( deep, clone, copy );
|
|
|
|
// Don't bring in undefined values
|
|
} else if ( copy !== undefined ) {
|
|
target[ name ] = copy;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return the modified object
|
|
return target;
|
|
};
|
|
|
|
jQuery.extend({
|
|
noConflict: function( deep ) {
|
|
if ( window.$ === jQuery ) {
|
|
window.$ = _$;
|
|
}
|
|
|
|
if ( deep && window.jQuery === jQuery ) {
|
|
window.jQuery = _jQuery;
|
|
}
|
|
|
|
return jQuery;
|
|
},
|
|
|
|
// Is the DOM ready to be used? Set to true once it occurs.
|
|
isReady: false,
|
|
|
|
// A counter to track how many items to wait for before
|
|
// the ready event fires. See #6781
|
|
readyWait: 1,
|
|
|
|
// Hold (or release) the ready event
|
|
holdReady: function( hold ) {
|
|
if ( hold ) {
|
|
jQuery.readyWait++;
|
|
} else {
|
|
jQuery.ready( true );
|
|
}
|
|
},
|
|
|
|
// Handle when the DOM is ready
|
|
ready: function( wait ) {
|
|
|
|
// Abort if there are pending holds or we're already ready
|
|
if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
|
|
return;
|
|
}
|
|
|
|
// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
|
|
if ( !document.body ) {
|
|
return setTimeout( jQuery.ready, 1 );
|
|
}
|
|
|
|
// Remember that the DOM is ready
|
|
jQuery.isReady = true;
|
|
|
|
// If a normal DOM Ready event fired, decrement, and wait if need be
|
|
if ( wait !== true && --jQuery.readyWait > 0 ) {
|
|
return;
|
|
}
|
|
|
|
// If there are functions bound, to execute
|
|
readyList.resolveWith( document, [ jQuery ] );
|
|
|
|
// Trigger any bound ready events
|
|
if ( jQuery.fn.trigger ) {
|
|
jQuery( document ).trigger("ready").off("ready");
|
|
}
|
|
},
|
|
|
|
// 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 ) {
|
|
return obj != null && obj == obj.window;
|
|
},
|
|
|
|
isNumeric: function( obj ) {
|
|
return !isNaN( parseFloat(obj) ) && isFinite( obj );
|
|
},
|
|
|
|
type: function( obj ) {
|
|
return obj == null ?
|
|
String( obj ) :
|
|
class2type[ core_toString.call(obj) ] || "object";
|
|
},
|
|
|
|
isPlainObject: function( obj ) {
|
|
// Must be an Object.
|
|
// Because of IE, we also have to check the presence of the constructor property.
|
|
// Make sure that DOM nodes and window objects don't pass through, as well
|
|
if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
// Not own constructor property must be Object
|
|
if ( obj.constructor &&
|
|
!core_hasOwn.call(obj, "constructor") &&
|
|
!core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
|
|
return false;
|
|
}
|
|
} catch ( e ) {
|
|
// IE8,9 Will throw exceptions on certain host objects #9897
|
|
return false;
|
|
}
|
|
|
|
// Own properties are enumerated firstly, so to speed up,
|
|
// if last one is own, then all properties are own.
|
|
|
|
var key;
|
|
for ( key in obj ) {}
|
|
|
|
return key === undefined || core_hasOwn.call( obj, key );
|
|
},
|
|
|
|
isEmptyObject: function( obj ) {
|
|
var name;
|
|
for ( name in obj ) {
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
|
|
error: function( msg ) {
|
|
throw new Error( msg );
|
|
},
|
|
|
|
// data: string of html
|
|
// context (optional): If specified, the fragment will be created in this context, defaults to document
|
|
// scripts (optional): If true, will include scripts passed in the html string
|
|
parseHTML: function( data, context, scripts ) {
|
|
var parsed;
|
|
if ( !data || typeof data !== "string" ) {
|
|
return null;
|
|
}
|
|
if ( typeof context === "boolean" ) {
|
|
scripts = context;
|
|
context = 0;
|
|
}
|
|
context = context || document;
|
|
|
|
// Single tag
|
|
if ( (parsed = rsingleTag.exec( data )) ) {
|
|
return [ context.createElement( parsed[1] ) ];
|
|
}
|
|
|
|
parsed = jQuery.buildFragment( [ data ], context, scripts ? null : [] );
|
|
return jQuery.merge( [],
|
|
(parsed.cacheable ? jQuery.clone( parsed.fragment ) : parsed.fragment).childNodes );
|
|
},
|
|
|
|
parseJSON: function( data ) {
|
|
if ( !data || typeof data !== "string") {
|
|
return null;
|
|
}
|
|
|
|
// Make sure leading/trailing whitespace is removed (IE can't handle it)
|
|
data = jQuery.trim( data );
|
|
|
|
// Attempt to parse using the native JSON parser first
|
|
if ( window.JSON && window.JSON.parse ) {
|
|
return window.JSON.parse( data );
|
|
}
|
|
|
|
// Make sure the incoming data is actual JSON
|
|
// Logic borrowed from http://json.org/json2.js
|
|
if ( rvalidchars.test( data.replace( rvalidescape, "@" )
|
|
.replace( rvalidtokens, "]" )
|
|
.replace( rvalidbraces, "")) ) {
|
|
|
|
return ( new Function( "return " + data ) )();
|
|
|
|
}
|
|
jQuery.error( "Invalid JSON: " + data );
|
|
},
|
|
|
|
// Cross-browser xml parsing
|
|
parseXML: function( data ) {
|
|
var xml, tmp;
|
|
if ( !data || typeof data !== "string" ) {
|
|
return null;
|
|
}
|
|
try {
|
|
if ( window.DOMParser ) { // Standard
|
|
tmp = new DOMParser();
|
|
xml = tmp.parseFromString( data , "text/xml" );
|
|
} else { // IE
|
|
xml = new ActiveXObject( "Microsoft.XMLDOM" );
|
|
xml.async = "false";
|
|
xml.loadXML( data );
|
|
}
|
|
} catch( e ) {
|
|
xml = undefined;
|
|
}
|
|
if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
|
|
jQuery.error( "Invalid XML: " + data );
|
|
}
|
|
return xml;
|
|
},
|
|
|
|
noop: function() {},
|
|
|
|
// Evaluates a script in a global context
|
|
// Workarounds based on findings by Jim Driscoll
|
|
// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
|
|
globalEval: function( data ) {
|
|
if ( data && core_rnotwhite.test( data ) ) {
|
|
// We use execScript on Internet Explorer
|
|
// We use an anonymous function so that context is window
|
|
// rather than jQuery in Firefox
|
|
( window.execScript || function( data ) {
|
|
window[ "eval" ].call( window, data );
|
|
} )( data );
|
|
}
|
|
},
|
|
|
|
// Convert dashed to camelCase; used by the css and data modules
|
|
// Microsoft forgot to hump their vendor prefix (#9572)
|
|
camelCase: function( string ) {
|
|
return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
|
|
},
|
|
|
|
nodeName: function( elem, name ) {
|
|
return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
|
|
},
|
|
|
|
// args is for internal usage only
|
|
each: function( obj, callback, args ) {
|
|
var name,
|
|
i = 0,
|
|
length = obj.length,
|
|
isObj = length === undefined || jQuery.isFunction( obj );
|
|
|
|
if ( args ) {
|
|
if ( isObj ) {
|
|
for ( name in obj ) {
|
|
if ( callback.apply( obj[ name ], args ) === false ) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
for ( ; i < length; ) {
|
|
if ( callback.apply( obj[ i++ ], args ) === false ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// A special, fast, case for the most common use of each
|
|
} else {
|
|
if ( isObj ) {
|
|
for ( name in obj ) {
|
|
if ( callback.call( obj[ name ], name, obj[ name ] ) === false ) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
for ( ; i < length; ) {
|
|
if ( callback.call( obj[ i ], i, obj[ i++ ] ) === false ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return obj;
|
|
},
|
|
|
|
// Use native String.trim function wherever possible
|
|
trim: core_trim ?
|
|
function( text ) {
|
|
return text == null ?
|
|
"" :
|
|
core_trim.call( text );
|
|
} :
|
|
|
|
// Otherwise use our own trimming functionality
|
|
function( text ) {
|
|
return text == null ?
|
|
"" :
|
|
text.toString().replace( rtrim, "" );
|
|
},
|
|
|
|
// results is for internal usage only
|
|
makeArray: function( arr, results ) {
|
|
var type,
|
|
ret = results || [];
|
|
|
|
if ( arr != null ) {
|
|
// The window, strings (and functions) also have 'length'
|
|
// Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
|
|
type = jQuery.type( arr );
|
|
|
|
if ( arr.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( arr ) ) {
|
|
core_push.call( ret, arr );
|
|
} else {
|
|
jQuery.merge( ret, arr );
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
},
|
|
|
|
inArray: function( elem, arr, i ) {
|
|
var len;
|
|
|
|
if ( arr ) {
|
|
if ( core_indexOf ) {
|
|
return core_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 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;
|
|
},
|
|
|
|
grep: function( elems, callback, inv ) {
|
|
var retVal,
|
|
ret = [],
|
|
i = 0,
|
|
length = elems.length;
|
|
inv = !!inv;
|
|
|
|
// Go through the array, only saving the items
|
|
// that pass the validator function
|
|
for ( ; i < length; i++ ) {
|
|
retVal = !!callback( elems[ i ], i );
|
|
if ( inv !== retVal ) {
|
|
ret.push( elems[ i ] );
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
},
|
|
|
|
// arg is for internal usage only
|
|
map: function( elems, callback, arg ) {
|
|
var value, key,
|
|
ret = [],
|
|
i = 0,
|
|
length = elems.length,
|
|
// jquery objects are treated as arrays
|
|
isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
|
|
|
|
// Go through the array, translating each of the items to their
|
|
if ( isArray ) {
|
|
for ( ; i < length; i++ ) {
|
|
value = callback( elems[ i ], i, arg );
|
|
|
|
if ( value != null ) {
|
|
ret[ ret.length ] = value;
|
|
}
|
|
}
|
|
|
|
// Go through every key on the object,
|
|
} else {
|
|
for ( key in elems ) {
|
|
value = callback( elems[ key ], key, arg );
|
|
|
|
if ( value != null ) {
|
|
ret[ ret.length ] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Flatten any nested arrays
|
|
return ret.concat.apply( [], ret );
|
|
},
|
|
|
|
// A global GUID counter for objects
|
|
guid: 1,
|
|
|
|
// Bind a function to a context, optionally partially applying any
|
|
// arguments.
|
|
proxy: function( fn, context ) {
|
|
var tmp, args, proxy;
|
|
|
|
if ( typeof context === "string" ) {
|
|
tmp = fn[ context ];
|
|
context = fn;
|
|
fn = tmp;
|
|
}
|
|
|
|
// Quick check to determine if target is callable, in the spec
|
|
// this throws a TypeError, but we will just return undefined.
|
|
if ( !jQuery.isFunction( fn ) ) {
|
|
return undefined;
|
|
}
|
|
|
|
// Simulated bind
|
|
args = core_slice.call( arguments, 2 );
|
|
proxy = function() {
|
|
return fn.apply( context, args.concat( core_slice.call( arguments ) ) );
|
|
};
|
|
|
|
// Set the guid of unique handler to the same of original handler, so it can be removed
|
|
proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
|
|
|
|
return proxy;
|
|
},
|
|
|
|
// Multifunctional method to get and set values of a collection
|
|
// The value/s can optionally be executed if it's a function
|
|
access: function( elems, fn, key, value, chainable, emptyGet, pass ) {
|
|
var exec,
|
|
bulk = key == null,
|
|
i = 0,
|
|
length = elems.length;
|
|
|
|
// Sets many values
|
|
if ( key && typeof key === "object" ) {
|
|
for ( i in key ) {
|
|
jQuery.access( elems, fn, i, key[i], 1, emptyGet, value );
|
|
}
|
|
chainable = 1;
|
|
|
|
// Sets one value
|
|
} else if ( value !== undefined ) {
|
|
// Optionally, function values get executed if exec is true
|
|
exec = pass === undefined && jQuery.isFunction( value );
|
|
|
|
if ( bulk ) {
|
|
// Bulk operations only iterate when executing function values
|
|
if ( exec ) {
|
|
exec = fn;
|
|
fn = function( elem, key, value ) {
|
|
return exec.call( jQuery( elem ), value );
|
|
};
|
|
|
|
// Otherwise they run against the entire set
|
|
} else {
|
|
fn.call( elems, value );
|
|
fn = null;
|
|
}
|
|
}
|
|
|
|
if ( fn ) {
|
|
for (; i < length; i++ ) {
|
|
fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
|
|
}
|
|
}
|
|
|
|
chainable = 1;
|
|
}
|
|
|
|
return chainable ?
|
|
elems :
|
|
|
|
// Gets
|
|
bulk ?
|
|
fn.call( elems ) :
|
|
length ? fn( elems[0], key ) : emptyGet;
|
|
},
|
|
|
|
now: function() {
|
|
return ( new Date() ).getTime();
|
|
}
|
|
});
|
|
|
|
jQuery.ready.promise = function( obj ) {
|
|
if ( !readyList ) {
|
|
|
|
readyList = jQuery.Deferred();
|
|
|
|
// Catch cases where $(document).ready() is called after the
|
|
// browser event has already occurred.
|
|
if ( document.readyState === "complete" || ( document.readyState !== "loading" && document.addEventListener ) ) {
|
|
// Handle it asynchronously to allow scripts the opportunity to delay ready
|
|
setTimeout( jQuery.ready, 1 );
|
|
|
|
// Standards-based browsers support DOMContentLoaded
|
|
} else if ( document.addEventListener ) {
|
|
// Use the handy event callback
|
|
document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
|
|
|
|
// A fallback to window.onload, that will always work
|
|
window.addEventListener( "load", jQuery.ready, false );
|
|
|
|
// If IE event model is used
|
|
} else {
|
|
// Ensure firing before onload, maybe late but safe also for iframes
|
|
document.attachEvent( "onreadystatechange", DOMContentLoaded );
|
|
|
|
// A fallback to window.onload, that will always work
|
|
window.attachEvent( "onload", jQuery.ready );
|
|
|
|
// If IE and not a frame
|
|
// continually check to see if the document is ready
|
|
var 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 setTimeout( doScrollCheck, 50 );
|
|
}
|
|
|
|
// and execute any waiting functions
|
|
jQuery.ready();
|
|
}
|
|
})();
|
|
}
|
|
}
|
|
}
|
|
return readyList.promise( obj );
|
|
};
|
|
|
|
// Populate the class2type map
|
|
jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
|
|
class2type[ "[object " + name + "]" ] = name.toLowerCase();
|
|
});
|
|
|
|
// All jQuery objects should point back to these
|
|
rootjQuery = jQuery(document);
|
|
// String to Object options format cache
|
|
var optionsCache = {};
|
|
|
|
// Convert String-formatted options into Object-formatted ones and store in cache
|
|
function createOptions( options ) {
|
|
var object = optionsCache[ options ] = {};
|
|
jQuery.each( options.split( core_rspace ), function( _, flag ) {
|
|
object[ flag ] = true;
|
|
});
|
|
return object;
|
|
}
|
|
|
|
/*
|
|
* Create a callback list using the following parameters:
|
|
*
|
|
* options: an optional list of space-separated options that will change how
|
|
* the callback list behaves or a more traditional option object
|
|
*
|
|
* By default a callback list will act like an event callback list and can be
|
|
* "fired" multiple times.
|
|
*
|
|
* Possible options:
|
|
*
|
|
* once: will ensure the callback list can only be fired once (like a Deferred)
|
|
*
|
|
* memory: will keep track of previous values and will call any callback added
|
|
* after the list has been fired right away with the latest "memorized"
|
|
* values (like a Deferred)
|
|
*
|
|
* unique: will ensure a callback can only be added once (no duplicate in the list)
|
|
*
|
|
* stopOnFalse: interrupt callings when a callback returns false
|
|
*
|
|
*/
|
|
jQuery.Callbacks = function( options ) {
|
|
|
|
// Convert options from String-formatted to Object-formatted if needed
|
|
// (we check in cache first)
|
|
options = typeof options === "string" ?
|
|
( optionsCache[ options ] || createOptions( options ) ) :
|
|
jQuery.extend( {}, options );
|
|
|
|
var // Last fire value (for non-forgettable lists)
|
|
memory,
|
|
// Flag to know if list was already fired
|
|
fired,
|
|
// Flag to know if list is currently firing
|
|
firing,
|
|
// First callback to fire (used internally by add and fireWith)
|
|
firingStart,
|
|
// End of the loop when firing
|
|
firingLength,
|
|
// Index of currently firing callback (modified by remove if needed)
|
|
firingIndex,
|
|
// Actual callback list
|
|
list = [],
|
|
// Stack of fire calls for repeatable lists
|
|
stack = !options.once && [],
|
|
// Fire callbacks
|
|
fire = function( data ) {
|
|
memory = options.memory && data;
|
|
fired = true;
|
|
firingIndex = firingStart || 0;
|
|
firingStart = 0;
|
|
firingLength = list.length;
|
|
firing = true;
|
|
for ( ; list && firingIndex < firingLength; firingIndex++ ) {
|
|
if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
|
|
memory = false; // To prevent further calls using add
|
|
break;
|
|
}
|
|
}
|
|
firing = false;
|
|
if ( list ) {
|
|
if ( stack ) {
|
|
if ( stack.length ) {
|
|
fire( stack.shift() );
|
|
}
|
|
} else if ( memory ) {
|
|
list = [];
|
|
} else {
|
|
self.disable();
|
|
}
|
|
}
|
|
},
|
|
// Actual Callbacks object
|
|
self = {
|
|
// Add a callback or a collection of callbacks to the list
|
|
add: function() {
|
|
if ( list ) {
|
|
// First, we save the current length
|
|
var start = list.length;
|
|
(function add( args ) {
|
|
jQuery.each( args, function( _, arg ) {
|
|
if ( jQuery.isFunction( arg ) && ( !options.unique || !self.has( arg ) ) ) {
|
|
list.push( arg );
|
|
} else if ( arg && arg.length ) {
|
|
// Inspect recursively
|
|
add( arg );
|
|
}
|
|
});
|
|
})( arguments );
|
|
// Do we need to add the callbacks to the
|
|
// current firing batch?
|
|
if ( firing ) {
|
|
firingLength = list.length;
|
|
// With memory, if we're not firing then
|
|
// we should call right away
|
|
} else if ( memory ) {
|
|
firingStart = start;
|
|
fire( memory );
|
|
}
|
|
}
|
|
return this;
|
|
},
|
|
// Remove a callback from the list
|
|
remove: function() {
|
|
if ( list ) {
|
|
jQuery.each( arguments, function( _, arg ) {
|
|
var index;
|
|
while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
|
|
list.splice( index, 1 );
|
|
// Handle firing indexes
|
|
if ( firing ) {
|
|
if ( index <= firingLength ) {
|
|
firingLength--;
|
|
}
|
|
if ( index <= firingIndex ) {
|
|
firingIndex--;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
return this;
|
|
},
|
|
// Control if a given callback is in the list
|
|
has: function( fn ) {
|
|
return jQuery.inArray( fn, list ) > -1;
|
|
},
|
|
// Remove all callbacks from the list
|
|
empty: function() {
|
|
list = [];
|
|
return this;
|
|
},
|
|
// Have the list do nothing anymore
|
|
disable: function() {
|
|
list = stack = memory = undefined;
|
|
return this;
|
|
},
|
|
// Is it disabled?
|
|
disabled: function() {
|
|
return !list;
|
|
},
|
|
// Lock the list in its current state
|
|
lock: function() {
|
|
stack = undefined;
|
|
if ( !memory ) {
|
|
self.disable();
|
|
}
|
|
return this;
|
|
},
|
|
// Is it locked?
|
|
locked: function() {
|
|
return !stack;
|
|
},
|
|
// Call all callbacks with the given context and arguments
|
|
fireWith: function( context, args ) {
|
|
args = args || [];
|
|
args = [ context, args.slice ? args.slice() : args ];
|
|
if ( list && ( !fired || stack ) ) {
|
|
if ( firing ) {
|
|
stack.push( args );
|
|
} else {
|
|
fire( args );
|
|
}
|
|
}
|
|
return this;
|
|
},
|
|
// Call all the callbacks with the given arguments
|
|
fire: function() {
|
|
self.fireWith( this, arguments );
|
|
return this;
|
|
},
|
|
// To know if the callbacks have already been called at least once
|
|
fired: function() {
|
|
return !!fired;
|
|
}
|
|
};
|
|
|
|
return self;
|
|
};
|
|
jQuery.extend({
|
|
|
|
Deferred: function( func ) {
|
|
var tuples = [
|
|
// action, add listener, listener list, final state
|
|
[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
|
|
[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
|
|
[ "notify", "progress", jQuery.Callbacks("memory") ]
|
|
],
|
|
state = "pending",
|
|
promise = {
|
|
state: function() {
|
|
return state;
|
|
},
|
|
always: function() {
|
|
deferred.done( arguments ).fail( arguments );
|
|
return this;
|
|
},
|
|
then: function( /* fnDone, fnFail, fnProgress */ ) {
|
|
var fns = arguments;
|
|
return jQuery.Deferred(function( newDefer ) {
|
|
jQuery.each( tuples, function( i, tuple ) {
|
|
var action = tuple[ 0 ],
|
|
fn = fns[ i ];
|
|
// deferred[ done | fail | progress ] for forwarding actions to newDefer
|
|
deferred[ tuple[1] ]( jQuery.isFunction( fn ) ?
|
|
function() {
|
|
var returned = fn.apply( this, arguments );
|
|
if ( returned && jQuery.isFunction( returned.promise ) ) {
|
|
returned.promise()
|
|
.done( newDefer.resolve )
|
|
.fail( newDefer.reject )
|
|
.progress( newDefer.notify );
|
|
} else {
|
|
newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
|
|
}
|
|
} :
|
|
newDefer[ action ]
|
|
);
|
|
});
|
|
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 typeof obj === "object" ? 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 ] = list.fire
|
|
deferred[ tuple[0] ] = list.fire;
|
|
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 = core_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 ? core_slice.call( arguments ) : value;
|
|
if( values === progressValues ) {
|
|
deferred.notifyWith( contexts, values );
|
|
} else if ( !( --remaining ) ) {
|
|
deferred.resolveWith( contexts, values );
|
|
}
|
|
};
|
|
},
|
|
|
|
progressValues, progressContexts, resolveContexts;
|
|
|
|
// add listeners to Deferred subordinates; treat others as resolved
|
|
if ( length > 1 ) {
|
|
progressValues = new Array( length );
|
|
progressContexts = new Array( length );
|
|
resolveContexts = new Array( length );
|
|
for ( ; i < length; i++ ) {
|
|
if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
|
|
resolveValues[ i ].promise()
|
|
.done( updateFunc( i, resolveContexts, resolveValues ) )
|
|
.fail( deferred.reject )
|
|
.progress( updateFunc( i, progressContexts, progressValues ) );
|
|
} else {
|
|
--remaining;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we're not waiting on anything, resolve the master
|
|
if ( !remaining ) {
|
|
deferred.resolveWith( resolveContexts, resolveValues );
|
|
}
|
|
|
|
return deferred.promise();
|
|
}
|
|
});
|
|
jQuery.support = (function() {
|
|
|
|
var support,
|
|
all,
|
|
a,
|
|
select,
|
|
opt,
|
|
input,
|
|
fragment,
|
|
eventName,
|
|
i,
|
|
isSupported,
|
|
clickFn,
|
|
div = document.createElement("div");
|
|
|
|
// Preliminary tests
|
|
div.setAttribute( "className", "t" );
|
|
div.innerHTML = " <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
|
|
|
|
all = div.getElementsByTagName("*");
|
|
a = div.getElementsByTagName("a")[ 0 ];
|
|
a.style.cssText = "top:1px;float:left;opacity:.5";
|
|
|
|
// Can't get basic test support
|
|
if ( !all || !all.length || !a ) {
|
|
return {};
|
|
}
|
|
|
|
// First batch of supports tests
|
|
select = document.createElement("select");
|
|
opt = select.appendChild( document.createElement("option") );
|
|
input = div.getElementsByTagName("input")[ 0 ];
|
|
|
|
support = {
|
|
// IE strips leading whitespace when .innerHTML is used
|
|
leadingWhitespace: ( div.firstChild.nodeType === 3 ),
|
|
|
|
// Make sure that tbody elements aren't automatically inserted
|
|
// IE will insert them into empty tables
|
|
tbody: !div.getElementsByTagName("tbody").length,
|
|
|
|
// Make sure that link elements get serialized correctly by innerHTML
|
|
// This requires a wrapper element in IE
|
|
htmlSerialize: !!div.getElementsByTagName("link").length,
|
|
|
|
// Get the style information from getAttribute
|
|
// (IE uses .cssText instead)
|
|
style: /top/.test( a.getAttribute("style") ),
|
|
|
|
// Make sure that URLs aren't manipulated
|
|
// (IE normalizes it by default)
|
|
hrefNormalized: ( a.getAttribute("href") === "/a" ),
|
|
|
|
// Make sure that element opacity exists
|
|
// (IE uses filter instead)
|
|
// Use a regex to work around a WebKit issue. See #5145
|
|
opacity: /^0.5/.test( a.style.opacity ),
|
|
|
|
// Verify style float existence
|
|
// (IE uses styleFloat instead of cssFloat)
|
|
cssFloat: !!a.style.cssFloat,
|
|
|
|
// Make sure that if no value is specified for a checkbox
|
|
// that it defaults to "on".
|
|
// (WebKit defaults to "" instead)
|
|
checkOn: ( input.value === "on" ),
|
|
|
|
// Make sure that a selected-by-default option has a working selected property.
|
|
// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
|
|
optSelected: opt.selected,
|
|
|
|
// Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
|
|
getSetAttribute: div.className !== "t",
|
|
|
|
// Tests for enctype support on a form(#6743)
|
|
enctype: !!document.createElement("form").enctype,
|
|
|
|
// Makes sure cloning an html5 element does not cause problems
|
|
// Where outerHTML is undefined, this still works
|
|
html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>",
|
|
|
|
// jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode
|
|
boxModel: ( document.compatMode === "CSS1Compat" ),
|
|
|
|
// Will be defined later
|
|
submitBubbles: true,
|
|
changeBubbles: true,
|
|
focusinBubbles: false,
|
|
deleteExpando: true,
|
|
noCloneEvent: true,
|
|
inlineBlockNeedsLayout: false,
|
|
shrinkWrapBlocks: false,
|
|
reliableMarginRight: true,
|
|
boxSizingReliable: true,
|
|
pixelPosition: false
|
|
};
|
|
|
|
// Make sure checked status is properly cloned
|
|
input.checked = true;
|
|
support.noCloneChecked = input.cloneNode( true ).checked;
|
|
|
|
// Make sure that the options inside disabled selects aren't marked as disabled
|
|
// (WebKit marks them as disabled)
|
|
select.disabled = true;
|
|
support.optDisabled = !opt.disabled;
|
|
|
|
// Test to see if it's possible to delete an expando from an element
|
|
// Fails in Internet Explorer
|
|
try {
|
|
delete div.test;
|
|
} catch( e ) {
|
|
support.deleteExpando = false;
|
|
}
|
|
|
|
if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
|
|
div.attachEvent( "onclick", clickFn = function() {
|
|
// Cloning a node shouldn't copy over any
|
|
// bound event handlers (IE does this)
|
|
support.noCloneEvent = false;
|
|
});
|
|
div.cloneNode( true ).fireEvent("onclick");
|
|
div.detachEvent( "onclick", clickFn );
|
|
}
|
|
|
|
// Check if a radio maintains its value
|
|
// after being appended to the DOM
|
|
input = document.createElement("input");
|
|
input.value = "t";
|
|
input.setAttribute( "type", "radio" );
|
|
support.radioValue = input.value === "t";
|
|
|
|
input.setAttribute( "checked", "checked" );
|
|
|
|
// #11217 - WebKit loses check when the name is after the checked attribute
|
|
input.setAttribute( "name", "t" );
|
|
|
|
div.appendChild( input );
|
|
fragment = document.createDocumentFragment();
|
|
fragment.appendChild( div.lastChild );
|
|
|
|
// WebKit doesn't clone checked state correctly in fragments
|
|
support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
|
|
|
|
// Check if a disconnected checkbox will retain its checked
|
|
// value of true after appended to the DOM (IE6/7)
|
|
support.appendChecked = input.checked;
|
|
|
|
fragment.removeChild( input );
|
|
fragment.appendChild( div );
|
|
|
|
// Technique from Juriy Zaytsev
|
|
// http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
|
|
// We only care about the case where non-standard event systems
|
|
// are used, namely in IE. Short-circuiting here helps us to
|
|
// avoid an eval call (in setAttribute) which can cause CSP
|
|
// to go haywire. See: https://developer.mozilla.org/en/Security/CSP
|
|
if ( div.attachEvent ) {
|
|
for ( i in {
|
|
submit: true,
|
|
change: true,
|
|
focusin: true
|
|
}) {
|
|
eventName = "on" + i;
|
|
isSupported = ( eventName in div );
|
|
if ( !isSupported ) {
|
|
div.setAttribute( eventName, "return;" );
|
|
isSupported = ( typeof div[ eventName ] === "function" );
|
|
}
|
|
support[ i + "Bubbles" ] = isSupported;
|
|
}
|
|
}
|
|
|
|
// Run tests that need a body at doc ready
|
|
jQuery(function() {
|
|
var container, div, tds, marginDiv,
|
|
divReset = "padding:0;margin:0;border:0;display:block;overflow:hidden;",
|
|
body = document.getElementsByTagName("body")[0];
|
|
|
|
if ( !body ) {
|
|
// Return for frameset docs that don't have a body
|
|
return;
|
|
}
|
|
|
|
container = document.createElement("div");
|
|
container.style.cssText = "visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px";
|
|
body.insertBefore( container, body.firstChild );
|
|
|
|
// Construct the test element
|
|
div = document.createElement("div");
|
|
container.appendChild( div );
|
|
|
|
// Check if table cells still have offsetWidth/Height when they are set
|
|
// to display:none and there are still other visible table cells in a
|
|
// table row; if so, offsetWidth/Height are not reliable for use when
|
|
// determining if an element has been hidden directly using
|
|
// display:none (it is still safe to use offsets if a parent element is
|
|
// hidden; don safety goggles and see bug #4512 for more information).
|
|
// (only IE 8 fails this test)
|
|
div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>";
|
|
tds = div.getElementsByTagName("td");
|
|
tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none";
|
|
isSupported = ( tds[ 0 ].offsetHeight === 0 );
|
|
|
|
tds[ 0 ].style.display = "";
|
|
tds[ 1 ].style.display = "none";
|
|
|
|
// Check if empty table cells still have offsetWidth/Height
|
|
// (IE <= 8 fail this test)
|
|
support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
|
|
|
|
// Check box-sizing and margin behavior
|
|
div.innerHTML = "";
|
|
div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";
|
|
support.boxSizing = ( div.offsetWidth === 4 );
|
|
support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 );
|
|
|
|
// NOTE: To any future maintainer, window.getComputedStyle was used here
|
|
// instead of getComputedStyle because it gave a better gzip size.
|
|
// The difference between window.getComputedStyle and getComputedStyle is
|
|
// 7 bytes
|
|
if ( window.getComputedStyle ) {
|
|
support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
|
|
support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
|
|
|
|
// Check if div with explicit width and no margin-right incorrectly
|
|
// gets computed margin-right based on width of container. For more
|
|
// info see bug #3333
|
|
// Fails in WebKit before Feb 2011 nightlies
|
|
// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
|
|
marginDiv = document.createElement("div");
|
|
marginDiv.style.cssText = div.style.cssText = divReset;
|
|
marginDiv.style.marginRight = marginDiv.style.width = "0";
|
|
div.style.width = "1px";
|
|
div.appendChild( marginDiv );
|
|
support.reliableMarginRight =
|
|
!parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
|
|
}
|
|
|
|
if ( typeof div.style.zoom !== "undefined" ) {
|
|
// Check if natively block-level elements act like inline-block
|
|
// elements when setting their display to 'inline' and giving
|
|
// them layout
|
|
// (IE < 8 does this)
|
|
div.innerHTML = "";
|
|
div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1";
|
|
support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
|
|
|
|
// Check if elements with layout shrink-wrap their children
|
|
// (IE 6 does this)
|
|
div.style.display = "block";
|
|
div.style.overflow = "visible";
|
|
div.innerHTML = "<div></div>";
|
|
div.firstChild.style.width = "5px";
|
|
support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
|
|
|
|
container.style.zoom = 1;
|
|
}
|
|
|
|
// Null elements to avoid leaks in IE
|
|
body.removeChild( container );
|
|
container = div = tds = marginDiv = null;
|
|
});
|
|
|
|
// Null elements to avoid leaks in IE
|
|
fragment.removeChild( div );
|
|
all = a = select = opt = input = fragment = div = null;
|
|
|
|
return support;
|
|
})();
|
|
var rbrace = /^(?:\{.*\}|\[.*\])$/,
|
|
rmultiDash = /([A-Z])/g;
|
|
|
|
jQuery.extend({
|
|
cache: {},
|
|
|
|
deletedIds: [],
|
|
|
|
// Please use with caution
|
|
uuid: 0,
|
|
|
|
// Unique for each copy of jQuery on the page
|
|
// Non-digits removed to match rinlinejQuery
|
|
expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
|
|
|
|
// The following elements throw uncatchable exceptions if you
|
|
// attempt to add expando properties to them.
|
|
noData: {
|
|
"embed": true,
|
|
// Ban all objects except for Flash (which handle expandos)
|
|
"object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
|
|
"applet": true
|
|
},
|
|
|
|
hasData: function( elem ) {
|
|
elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
|
|
return !!elem && !isEmptyDataObject( elem );
|
|
},
|
|
|
|
data: function( elem, name, data, pvt /* Internal Use Only */ ) {
|
|
if ( !jQuery.acceptData( elem ) ) {
|
|
return;
|
|
}
|
|
|
|
var thisCache, ret,
|
|
internalKey = jQuery.expando,
|
|
getByName = typeof name === "string",
|
|
|
|
// We have to handle DOM nodes and JS objects differently because IE6-7
|
|
// can't GC object references properly across the DOM-JS boundary
|
|
isNode = elem.nodeType,
|
|
|
|
// Only DOM nodes need the global jQuery cache; JS object data is
|
|
// attached directly to the object so GC can occur automatically
|
|
cache = isNode ? jQuery.cache : elem,
|
|
|
|
// Only defining an ID for JS objects if its cache already exists allows
|
|
// the code to shortcut on the same path as a DOM node with no cache
|
|
id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
|
|
|
|
// 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)) && getByName && data === undefined ) {
|
|
return;
|
|
}
|
|
|
|
if ( !id ) {
|
|
// Only DOM nodes need a new unique ID for each element since their data
|
|
// ends up in the global cache
|
|
if ( isNode ) {
|
|
elem[ internalKey ] = id = jQuery.deletedIds.pop() || ++jQuery.uuid;
|
|
} else {
|
|
id = internalKey;
|
|
}
|
|
}
|
|
|
|
if ( !cache[ id ] ) {
|
|
cache[ id ] = {};
|
|
|
|
// Avoids exposing jQuery metadata on plain JS objects when the object
|
|
// is serialized using JSON.stringify
|
|
if ( !isNode ) {
|
|
cache[ id ].toJSON = jQuery.noop;
|
|
}
|
|
}
|
|
|
|
// An object can be passed to jQuery.data instead of a key/value pair; this gets
|
|
// shallow copied over onto the existing cache
|
|
if ( typeof name === "object" || typeof name === "function" ) {
|
|
if ( pvt ) {
|
|
cache[ id ] = jQuery.extend( cache[ id ], name );
|
|
} else {
|
|
cache[ id ].data = jQuery.extend( cache[ id ].data, name );
|
|
}
|
|
}
|
|
|
|
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 ( getByName ) {
|
|
|
|
// First Try to find as-is property data
|
|
ret = thisCache[ name ];
|
|
|
|
// Test for null|undefined property data
|
|
if ( ret == null ) {
|
|
|
|
// Try to find the camelCased property
|
|
ret = thisCache[ jQuery.camelCase( name ) ];
|
|
}
|
|
} else {
|
|
ret = thisCache;
|
|
}
|
|
|
|
return ret;
|
|
},
|
|
|
|
removeData: function( elem, name, pvt /* Internal Use Only */ ) {
|
|
if ( !jQuery.acceptData( elem ) ) {
|
|
return;
|
|
}
|
|
|
|
var thisCache, i, l,
|
|
|
|
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(" ");
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( i = 0, l = name.length; i < l; i++ ) {
|
|
delete thisCache[ name[i] ];
|
|
}
|
|
|
|
// If there is no data left in the cache, we want to continue
|
|
// and let the cache object itself get destroyed
|
|
if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// See jQuery.data for more information
|
|
if ( !pvt ) {
|
|
delete cache[ id ].data;
|
|
|
|
// Don't destroy the parent cache unless the internal data object
|
|
// had been the only thing left in it
|
|
if ( !isEmptyDataObject( cache[ id ] ) ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Destroy the cache
|
|
if ( isNode ) {
|
|
jQuery.cleanData( [ elem ], true );
|
|
|
|
// Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
|
|
} else if ( jQuery.support.deleteExpando || cache != cache.window ) {
|
|
delete cache[ id ];
|
|
|
|
// When all else fails, null
|
|
} else {
|
|
cache[ id ] = null;
|
|
}
|
|
},
|
|
|
|
// For internal use only.
|
|
_data: function( elem, name, data ) {
|
|
return jQuery.data( elem, name, data, true );
|
|
},
|
|
|
|
// A method for determining if a DOM node can handle the data expando
|
|
acceptData: function( elem ) {
|
|
var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
|
|
|
|
// nodes accept data unless otherwise specified; rejection can be conditional
|
|
return !noData || noData !== true && elem.getAttribute("classid") === noData;
|
|
}
|
|
});
|
|
|
|
jQuery.fn.extend({
|
|
data: function( key, value ) {
|
|
var parts, part, attr, name, l,
|
|
elem = this[0],
|
|
i = 0,
|
|
data = null;
|
|
|
|
// Gets all values
|
|
if ( key === undefined ) {
|
|
if ( this.length ) {
|
|
data = jQuery.data( elem );
|
|
|
|
if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
|
|
attr = elem.attributes;
|
|
for ( l = attr.length; i < l; i++ ) {
|
|
name = attr[i].name;
|
|
|
|
if ( name.indexOf( "data-" ) === 0 ) {
|
|
name = jQuery.camelCase( name.substring(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 );
|
|
});
|
|
}
|
|
|
|
parts = key.split( ".", 2 );
|
|
parts[1] = parts[1] ? "." + parts[1] : "";
|
|
part = parts[1] + "!";
|
|
|
|
return jQuery.access( this, function( value ) {
|
|
|
|
if ( value === undefined ) {
|
|
data = this.triggerHandler( "getData" + part, [ parts[0] ] );
|
|
|
|
// Try to fetch any internally stored data first
|
|
if ( data === undefined && elem ) {
|
|
data = jQuery.data( elem, key );
|
|
data = dataAttr( elem, key, data );
|
|
}
|
|
|
|
return data === undefined && parts[1] ?
|
|
this.data( parts[0] ) :
|
|
data;
|
|
}
|
|
|
|
parts[1] = value;
|
|
this.each(function() {
|
|
var self = jQuery( this );
|
|
|
|
self.triggerHandler( "setData" + part, parts );
|
|
jQuery.data( this, key, value );
|
|
self.triggerHandler( "changeData" + part, parts );
|
|
});
|
|
}, null, value, arguments.length > 1, null, false );
|
|
},
|
|
|
|
removeData: function( key ) {
|
|
return this.each(function() {
|
|
jQuery.removeData( this, key );
|
|
});
|
|
}
|
|
});
|
|
|
|
function dataAttr( elem, key, data ) {
|
|
// If nothing was found internally, try to fetch any
|
|
// data from the HTML5 data-* attribute
|
|
if ( data === undefined && elem.nodeType === 1 ) {
|
|
|
|
var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
|
|
|
|
data = elem.getAttribute( name );
|
|
|
|
if ( typeof data === "string" ) {
|
|
try {
|
|
data = data === "true" ? true :
|
|
data === "false" ? false :
|
|
data === "null" ? null :
|
|
// 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;
|
|
}
|
|
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 ),
|
|
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();
|
|
}
|
|
|
|
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 ( !queue.length && 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", true );
|
|
jQuery.removeData( elem, key, true );
|
|
})
|
|
});
|
|
}
|
|
});
|
|
|
|
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 );
|
|
});
|
|
},
|
|
// Based off of the plugin by Clint Helfers, with permission.
|
|
// http://blindsignals.com/index.php/2009/07/jquery-delay/
|
|
delay: function( time, type ) {
|
|
time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
|
|
type = type || "fx";
|
|
|
|
return this.queue( type, function( next, hooks ) {
|
|
var timeout = setTimeout( next, time );
|
|
hooks.stop = function() {
|
|
clearTimeout( timeout );
|
|
};
|
|
});
|
|
},
|
|
clearQueue: function( type ) {
|
|
return this.queue( type || "fx", [] );
|
|
},
|
|
// Get a promise resolved when queues of a certain type
|
|
// are emptied (fx is the type by default)
|
|
promise: function( type, 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-- ) {
|
|
if ( (tmp = jQuery._data( elements[ i ], type + "queueHooks" )) && tmp.empty ) {
|
|
count++;
|
|
tmp.empty.add( resolve );
|
|
}
|
|
}
|
|
resolve();
|
|
return defer.promise( obj );
|
|
}
|
|
});
|
|
var nodeHook, boolHook, fixSpecified,
|
|
rclass = /[\t\r\n]/g,
|
|
rreturn = /\r/g,
|
|
rtype = /^(?:button|input)$/i,
|
|
rfocusable = /^(?:button|input|object|select|textarea)$/i,
|
|
rclickable = /^a(?:rea|)$/i,
|
|
rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
|
|
getSetAttribute = jQuery.support.getSetAttribute;
|
|
|
|
jQuery.fn.extend({
|
|
attr: function( name, value ) {
|
|
return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
|
|
},
|
|
|
|
removeAttr: function( name ) {
|
|
return this.each(function() {
|
|
jQuery.removeAttr( this, name );
|
|
});
|
|
},
|
|
|
|
prop: function( name, value ) {
|
|
return jQuery.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 ) {}
|
|
});
|
|
},
|
|
|
|
addClass: function( value ) {
|
|
var classNames, i, l, elem,
|
|
setClass, c, cl;
|
|
|
|
if ( jQuery.isFunction( value ) ) {
|
|
return this.each(function( j ) {
|
|
jQuery( this ).addClass( value.call(this, j, this.className) );
|
|
});
|
|
}
|
|
|
|
if ( value && typeof value === "string" ) {
|
|
classNames = value.split( core_rspace );
|
|
|
|
for ( i = 0, l = this.length; i < l; i++ ) {
|
|
elem = this[ i ];
|
|
|
|
if ( elem.nodeType === 1 ) {
|
|
if ( !elem.className && classNames.length === 1 ) {
|
|
elem.className = value;
|
|
|
|
} else {
|
|
setClass = " " + elem.className + " ";
|
|
|
|
for ( c = 0, cl = classNames.length; c < cl; c++ ) {
|
|
if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
|
|
setClass += classNames[ c ] + " ";
|
|
}
|
|
}
|
|
elem.className = jQuery.trim( setClass );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
removeClass: function( value ) {
|
|
var removes, className, elem, c, cl, i, l;
|
|
|
|
if ( jQuery.isFunction( value ) ) {
|
|
return this.each(function( j ) {
|
|
jQuery( this ).removeClass( value.call(this, j, this.className) );
|
|
});
|
|
}
|
|
if ( (value && typeof value === "string") || value === undefined ) {
|
|
removes = ( value || "" ).split( core_rspace );
|
|
|
|
for ( i = 0, l = this.length; i < l; i++ ) {
|
|
elem = this[ i ];
|
|
if ( elem.nodeType === 1 && elem.className ) {
|
|
|
|
className = (" " + elem.className + " ").replace( rclass, " " );
|
|
|
|
// loop over each item in the removal list
|
|
for ( c = 0, cl = removes.length; c < cl; c++ ) {
|
|
// Remove until there is nothing to remove,
|
|
while ( className.indexOf(" " + removes[ c ] + " ") > -1 ) {
|
|
className = className.replace( " " + removes[ c ] + " " , " " );
|
|
}
|
|
}
|
|
elem.className = value ? jQuery.trim( className ) : "";
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
toggleClass: function( value, stateVal ) {
|
|
var type = typeof value,
|
|
isBool = typeof stateVal === "boolean";
|
|
|
|
if ( jQuery.isFunction( value ) ) {
|
|
return this.each(function( i ) {
|
|
jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
|
|
});
|
|
}
|
|
|
|
return this.each(function() {
|
|
if ( type === "string" ) {
|
|
// toggle individual class names
|
|
var className,
|
|
i = 0,
|
|
self = jQuery( this ),
|
|
state = stateVal,
|
|
classNames = value.split( core_rspace );
|
|
|
|
while ( (className = classNames[ i++ ]) ) {
|
|
// check each className given, space separated list
|
|
state = isBool ? state : !self.hasClass( className );
|
|
self[ state ? "addClass" : "removeClass" ]( className );
|
|
}
|
|
|
|
} else if ( type === "undefined" || type === "boolean" ) {
|
|
if ( this.className ) {
|
|
// store className if set
|
|
jQuery._data( this, "__className__", this.className );
|
|
}
|
|
|
|
// toggle whole className
|
|
this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
|
|
}
|
|
});
|
|
},
|
|
|
|
hasClass: function( selector ) {
|
|
var className = " " + selector + " ",
|
|
i = 0,
|
|
l = this.length;
|
|
for ( ; i < l; i++ ) {
|
|
if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
val: function( value ) {
|
|
var hooks, ret, isFunction,
|
|
elem = this[0];
|
|
|
|
if ( !arguments.length ) {
|
|
if ( elem ) {
|
|
hooks = jQuery.valHooks[ elem.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,
|
|
self = jQuery(this);
|
|
|
|
if ( this.nodeType !== 1 ) {
|
|
return;
|
|
}
|
|
|
|
if ( isFunction ) {
|
|
val = value.call( this, i, self.val() );
|
|
} else {
|
|
val = value;
|
|
}
|
|
|
|
// Treat null/undefined as ""; convert numbers to string
|
|
if ( val == null ) {
|
|
val = "";
|
|
} else if ( typeof val === "number" ) {
|
|
val += "";
|
|
} else if ( jQuery.isArray( val ) ) {
|
|
val = jQuery.map(val, function ( value ) {
|
|
return value == null ? "" : value + "";
|
|
});
|
|
}
|
|
|
|
hooks = jQuery.valHooks[ this.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 ) {
|
|
// attributes.value is undefined in Blackberry 4.7 but
|
|
// uses .value. See #6932
|
|
var val = elem.attributes.value;
|
|
return !val || val.specified ? elem.value : elem.text;
|
|
}
|
|
},
|
|
select: {
|
|
get: function( elem ) {
|
|
var value, i, max, option,
|
|
index = elem.selectedIndex,
|
|
values = [],
|
|
options = elem.options,
|
|
one = elem.type === "select-one";
|
|
|
|
// Nothing was selected
|
|
if ( index < 0 ) {
|
|
return null;
|
|
}
|
|
|
|
// Loop through all the selected options
|
|
i = one ? index : 0;
|
|
max = one ? index + 1 : options.length;
|
|
for ( ; i < max; i++ ) {
|
|
option = options[ i ];
|
|
|
|
// Don't return options that are disabled or in a disabled optgroup
|
|
if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
|
|
(!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
|
|
|
|
// Get the specific value for the option
|
|
value = jQuery( option ).val();
|
|
|
|
// We don't need an array for one selects
|
|
if ( one ) {
|
|
return value;
|
|
}
|
|
|
|
// Multi-Selects return an array
|
|
values.push( value );
|
|
}
|
|
}
|
|
|
|
// Fixes Bug #2551 -- select.val() broken in IE after form.reset()
|
|
if ( one && !values.length && options.length ) {
|
|
return jQuery( options[ index ] ).val();
|
|
}
|
|
|
|
return values;
|
|
},
|
|
|
|
set: function( elem, value ) {
|
|
var values = jQuery.makeArray( value );
|
|
|
|
jQuery(elem).find("option").each(function() {
|
|
this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
|
|
});
|
|
|
|
if ( !values.length ) {
|
|
elem.selectedIndex = -1;
|
|
}
|
|
return values;
|
|
}
|
|
}
|
|
},
|
|
|
|
// Unused in 1.8, left in so attrFn-stabbers won't die; remove in 1.9
|
|
attrFn: {},
|
|
|
|
attr: function( elem, name, value, pass ) {
|
|
var ret, hooks, notxml,
|
|
nType = elem.nodeType;
|
|
|
|
// don't get/set attributes on text, comment and attribute nodes
|
|
if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
|
|
return;
|
|
}
|
|
|
|
if ( pass && jQuery.isFunction( jQuery.fn[ name ] ) ) {
|
|
return jQuery( elem )[ name ]( value );
|
|
}
|
|
|
|
// Fallback to prop when attributes are not supported
|
|
if ( typeof elem.getAttribute === "undefined" ) {
|
|
return jQuery.prop( elem, name, value );
|
|
}
|
|
|
|
notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
|
|
|
|
// All attributes are lowercase
|
|
// Grab necessary hook if one is defined
|
|
if ( notxml ) {
|
|
name = name.toLowerCase();
|
|
hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
|
|
}
|
|
|
|
if ( value !== undefined ) {
|
|
|
|
if ( value === null ) {
|
|
jQuery.removeAttr( elem, name );
|
|
return;
|
|
|
|
} else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
|
|
return ret;
|
|
|
|
} else {
|
|
elem.setAttribute( name, "" + value );
|
|
return value;
|
|
}
|
|
|
|
} else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
|
|
return ret;
|
|
|
|
} else {
|
|
|
|
ret = elem.getAttribute( name );
|
|
|
|
// Non-existent attributes return null, we normalize to undefined
|
|
return ret === null ?
|
|
undefined :
|
|
ret;
|
|
}
|
|
},
|
|
|
|
removeAttr: function( elem, value ) {
|
|
var propName, attrNames, name, isBool,
|
|
i = 0;
|
|
|
|
if ( value && elem.nodeType === 1 ) {
|
|
|
|
attrNames = value.split( core_rspace );
|
|
|
|
for ( ; i < attrNames.length; i++ ) {
|
|
name = attrNames[ i ];
|
|
|
|
if ( name ) {
|
|
propName = jQuery.propFix[ name ] || name;
|
|
isBool = rboolean.test( name );
|
|
|
|
// See #9699 for explanation of this approach (setting first, then removal)
|
|
// Do not do this for boolean attributes (see #10870)
|
|
if ( !isBool ) {
|
|
jQuery.attr( elem, name, "" );
|
|
}
|
|
elem.removeAttribute( getSetAttribute ? name : propName );
|
|
|
|
// Set corresponding property to false for boolean attributes
|
|
if ( isBool && propName in elem ) {
|
|
elem[ propName ] = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
attrHooks: {
|
|
type: {
|
|
set: function( elem, value ) {
|
|
// We can't allow the type property to be changed (since it causes problems in IE)
|
|
if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
|
|
jQuery.error( "type property can't be changed" );
|
|
} else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
|
|
// Setting the type on a radio button after the value resets the value in IE6-9
|
|
// Reset value to it's default in case type is set after value
|
|
// This is for element creation
|
|
var val = elem.value;
|
|
elem.setAttribute( "type", value );
|
|
if ( val ) {
|
|
elem.value = val;
|
|
}
|
|
return value;
|
|
}
|
|
}
|
|
},
|
|
// Use the value property for back compat
|
|
// Use the nodeHook for button elements in IE6/7 (#1954)
|
|
value: {
|
|
get: function( elem, name ) {
|
|
if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
|
|
return nodeHook.get( elem, name );
|
|
}
|
|
return name in elem ?
|
|
elem.value :
|
|
null;
|
|
},
|
|
set: function( elem, value, name ) {
|
|
if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
|
|
return nodeHook.set( elem, value, name );
|
|
}
|
|
// Does not return so that setAttribute is also used
|
|
elem.value = value;
|
|
}
|
|
}
|
|
},
|
|
|
|
propFix: {
|
|
tabindex: "tabIndex",
|
|
readonly: "readOnly",
|
|
"for": "htmlFor",
|
|
"class": "className",
|
|
maxlength: "maxLength",
|
|
cellspacing: "cellSpacing",
|
|
cellpadding: "cellPadding",
|
|
rowspan: "rowSpan",
|
|
colspan: "colSpan",
|
|
usemap: "useMap",
|
|
frameborder: "frameBorder",
|
|
contenteditable: "contentEditable"
|
|
},
|
|
|
|
prop: function( elem, name, value ) {
|
|
var ret, hooks, notxml,
|
|
nType = elem.nodeType;
|
|
|
|
// don't get/set properties on text, comment and attribute nodes
|
|
if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
|
|
return;
|
|
}
|
|
|
|
notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
|
|
|
|
if ( notxml ) {
|
|
// Fix name and attach hooks
|
|
name = jQuery.propFix[ name ] || name;
|
|
hooks = jQuery.propHooks[ name ];
|
|
}
|
|
|
|
if ( value !== undefined ) {
|
|
if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
|
|
return ret;
|
|
|
|
} else {
|
|
return ( elem[ name ] = value );
|
|
}
|
|
|
|
} else {
|
|
if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
|
|
return ret;
|
|
|
|
} else {
|
|
return elem[ name ];
|
|
}
|
|
}
|
|
},
|
|
|
|
propHooks: {
|
|
tabIndex: {
|
|
get: function( elem ) {
|
|
// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
|
|
// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
|
|
var attributeNode = elem.getAttributeNode("tabindex");
|
|
|
|
return attributeNode && attributeNode.specified ?
|
|
parseInt( attributeNode.value, 10 ) :
|
|
rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
|
|
0 :
|
|
undefined;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Hook for boolean attributes
|
|
boolHook = {
|
|
get: function( elem, name ) {
|
|
// Align boolean attributes with corresponding properties
|
|
// Fall back to attribute presence where some booleans are not supported
|
|
var attrNode,
|
|
property = jQuery.prop( elem, name );
|
|
return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
|
|
name.toLowerCase() :
|
|
undefined;
|
|
},
|
|
set: function( elem, value, name ) {
|
|
var propName;
|
|
if ( value === false ) {
|
|
// Remove boolean attributes when set to false
|
|
jQuery.removeAttr( elem, name );
|
|
} else {
|
|
// value is true since we know at this point it's type boolean and not false
|
|
// Set boolean attributes to the same name and set the DOM property
|
|
propName = jQuery.propFix[ name ] || name;
|
|
if ( propName in elem ) {
|
|
// Only set the IDL specifically if it already exists on the element
|
|
elem[ propName ] = true;
|
|
}
|
|
|
|
elem.setAttribute( name, name.toLowerCase() );
|
|
}
|
|
return name;
|
|
}
|
|
};
|
|
|
|
// IE6/7 do not support getting/setting some attributes with get/setAttribute
|
|
if ( !getSetAttribute ) {
|
|
|
|
fixSpecified = {
|
|
name: true,
|
|
id: true,
|
|
coords: true
|
|
};
|
|
|
|
// Use this for any attribute in IE6/7
|
|
// This fixes almost every IE6/7 issue
|
|
nodeHook = jQuery.valHooks.button = {
|
|
get: function( elem, name ) {
|
|
var ret;
|
|
ret = elem.getAttributeNode( name );
|
|
return ret && ( fixSpecified[ name ] ? ret.value !== "" : ret.specified ) ?
|
|
ret.value :
|
|
undefined;
|
|
},
|
|
set: function( elem, value, name ) {
|
|
// Set the existing or create a new attribute node
|
|
var ret = elem.getAttributeNode( name );
|
|
if ( !ret ) {
|
|
ret = document.createAttribute( name );
|
|
elem.setAttributeNode( ret );
|
|
}
|
|
return ( ret.value = value + "" );
|
|
}
|
|
};
|
|
|
|
// Set width and height to auto instead of 0 on empty string( Bug #8150 )
|
|
// This is for removals
|
|
jQuery.each([ "width", "height" ], function( i, name ) {
|
|
jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
|
|
set: function( elem, value ) {
|
|
if ( value === "" ) {
|
|
elem.setAttribute( name, "auto" );
|
|
return value;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// Set contenteditable to false on removals(#10429)
|
|
// Setting to empty string throws an error as an invalid value
|
|
jQuery.attrHooks.contenteditable = {
|
|
get: nodeHook.get,
|
|
set: function( elem, value, name ) {
|
|
if ( value === "" ) {
|
|
value = "false";
|
|
}
|
|
nodeHook.set( elem, value, name );
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
// Some attributes require a special call on IE
|
|
if ( !jQuery.support.hrefNormalized ) {
|
|
jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
|
|
jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
|
|
get: function( elem ) {
|
|
var ret = elem.getAttribute( name, 2 );
|
|
return ret === null ? undefined : ret;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
if ( !jQuery.support.style ) {
|
|
jQuery.attrHooks.style = {
|
|
get: function( elem ) {
|
|
// Return undefined in the case of empty string
|
|
// Normalize to lowercase since IE uppercases css property names
|
|
return elem.style.cssText.toLowerCase() || undefined;
|
|
},
|
|
set: function( elem, value ) {
|
|
return ( elem.style.cssText = "" + value );
|
|
}
|
|
};
|
|
}
|
|
|
|
// Safari mis-reports the default selected property of an option
|
|
// Accessing the parent's selectedIndex property fixes it
|
|
if ( !jQuery.support.optSelected ) {
|
|
jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
|
|
get: function( elem ) {
|
|
var parent = elem.parentNode;
|
|
|
|
if ( parent ) {
|
|
parent.selectedIndex;
|
|
|
|
// Make sure that it also works with optgroups, see #5701
|
|
if ( parent.parentNode ) {
|
|
parent.parentNode.selectedIndex;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
|
|
// IE6/7 call enctype encoding
|
|
if ( !jQuery.support.enctype ) {
|
|
jQuery.propFix.enctype = "encoding";
|
|
}
|
|
|
|
// Radios and checkboxes getter/setter
|
|
if ( !jQuery.support.checkOn ) {
|
|
jQuery.each([ "radio", "checkbox" ], function() {
|
|
jQuery.valHooks[ this ] = {
|
|
get: function( elem ) {
|
|
// Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
|
|
return elem.getAttribute("value") === null ? "on" : elem.value;
|
|
}
|
|
};
|
|
});
|
|
}
|
|
jQuery.each([ "radio", "checkbox" ], function() {
|
|
jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
|
|
set: function( elem, value ) {
|
|
if ( jQuery.isArray( value ) ) {
|
|
return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
|
|
}
|
|
}
|
|
});
|
|
});
|
|
var rformElems = /^(?:textarea|input|select)$/i,
|
|
rtypenamespace = /^([^\.]*|)(?:\.(.+)|)$/,
|
|
rhoverHack = /(?:^|\s)hover(\.\S+|)\b/,
|
|
rkeyEvent = /^key/,
|
|
rmouseEvent = /^(?:mouse|contextmenu)|click/,
|
|
rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
|
|
hoverHack = function( events ) {
|
|
return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
|
|
};
|
|
|
|
/*
|
|
* Helper functions for managing events -- not part of the public interface.
|
|
* Props to Dean Edwards' addEvent library for many of the ideas.
|
|
*/
|
|
jQuery.event = {
|
|
|
|
add: function( elem, types, handler, data, selector ) {
|
|
|
|
var elemData, eventHandle, events,
|
|
t, tns, type, namespaces, handleObj,
|
|
handleObjIn, handlers, special;
|
|
|
|
// Don't attach events to noData or text/comment nodes (allow plain objects tho)
|
|
if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
|
|
return;
|
|
}
|
|
|
|
// Caller can pass in an object of custom data in lieu of the handler
|
|
if ( handler.handler ) {
|
|
handleObjIn = handler;
|
|
handler = handleObjIn.handler;
|
|
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
|
|
events = elemData.events;
|
|
if ( !events ) {
|
|
elemData.events = events = {};
|
|
}
|
|
eventHandle = elemData.handle;
|
|
if ( !eventHandle ) {
|
|
elemData.handle = eventHandle = function( e ) {
|
|
// Discard the second event of a jQuery.event.trigger() and
|
|
// when an event is called after a page has unloaded
|
|
return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
|
|
jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
|
|
undefined;
|
|
};
|
|
// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
|
|
eventHandle.elem = elem;
|
|
}
|
|
|
|
// Handle multiple events separated by a space
|
|
// jQuery(...).bind("mouseover mouseout", fn);
|
|
types = jQuery.trim( hoverHack(types) ).split( " " );
|
|
for ( t = 0; t < types.length; t++ ) {
|
|
|
|
tns = rtypenamespace.exec( types[t] ) || [];
|
|
type = tns[1];
|
|
namespaces = ( tns[2] || "" ).split( "." ).sort();
|
|
|
|
// If event changes its type, use the special event handlers for the changed type
|
|
special = jQuery.event.special[ type ] || {};
|
|
|
|
// If selector defined, determine special event api type, otherwise given type
|
|
type = ( selector ? special.delegateType : special.bindType ) || type;
|
|
|
|
// Update special based on newly reset type
|
|
special = jQuery.event.special[ type ] || {};
|
|
|
|
// handleObj is passed to all event handlers
|
|
handleObj = jQuery.extend({
|
|
type: type,
|
|
origType: tns[1],
|
|
data: data,
|
|
handler: handler,
|
|
guid: handler.guid,
|
|
selector: selector,
|
|
namespace: namespaces.join(".")
|
|
}, handleObjIn );
|
|
|
|
// Init the event handler queue if we're the first
|
|
handlers = events[ type ];
|
|
if ( !handlers ) {
|
|
handlers = events[ type ] = [];
|
|
handlers.delegateCount = 0;
|
|
|
|
// Only use addEventListener/attachEvent if the special events handler returns false
|
|
if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
|
|
// Bind the global event handler to the element
|
|
if ( elem.addEventListener ) {
|
|
elem.addEventListener( type, eventHandle, false );
|
|
|
|
} else if ( elem.attachEvent ) {
|
|
elem.attachEvent( "on" + type, eventHandle );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( special.add ) {
|
|
special.add.call( elem, handleObj );
|
|
|
|
if ( !handleObj.handler.guid ) {
|
|
handleObj.handler.guid = handler.guid;
|
|
}
|
|
}
|
|
|
|
// Add to the element's handler list, delegates in front
|
|
if ( selector ) {
|
|
handlers.splice( handlers.delegateCount++, 0, handleObj );
|
|
} else {
|
|
handlers.push( handleObj );
|
|
}
|
|
|
|
// Keep track of which events have ever been used, for event optimization
|
|
jQuery.event.global[ type ] = true;
|
|
}
|
|
|
|
// Nullify elem to prevent memory leaks in IE
|
|
elem = null;
|
|
},
|
|
|
|
global: {},
|
|
|
|
// Detach an event or set of events from an element
|
|
remove: function( elem, types, handler, selector, mappedTypes ) {
|
|
|
|
var t, tns, type, origType, namespaces, origCount,
|
|
j, events, special, eventType, handleObj,
|
|
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 = jQuery.trim( hoverHack( types || "" ) ).split(" ");
|
|
for ( t = 0; t < types.length; t++ ) {
|
|
tns = rtypenamespace.exec( types[t] ) || [];
|
|
type = origType = tns[1];
|
|
namespaces = tns[2];
|
|
|
|
// Unbind all events (on this namespace, if provided) for the element
|
|
if ( !type ) {
|
|
for ( type in events ) {
|
|
jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
|
|
}
|
|
continue;
|
|
}
|
|
|
|
special = jQuery.event.special[ type ] || {};
|
|
type = ( selector? special.delegateType : special.bindType ) || type;
|
|
eventType = events[ type ] || [];
|
|
origCount = eventType.length;
|
|
namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
|
|
|
|
// Remove matching events
|
|
for ( j = 0; j < eventType.length; j++ ) {
|
|
handleObj = eventType[ j ];
|
|
|
|
if ( ( mappedTypes || origType === handleObj.origType ) &&
|
|
( !handler || handler.guid === handleObj.guid ) &&
|
|
( !namespaces || namespaces.test( handleObj.namespace ) ) &&
|
|
( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
|
|
eventType.splice( j--, 1 );
|
|
|
|
if ( handleObj.selector ) {
|
|
eventType.delegateCount--;
|
|
}
|
|
if ( special.remove ) {
|
|
special.remove.call( elem, handleObj );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove generic event handler if we removed something and no more handlers exist
|
|
// (avoids potential for endless recursion during removal of special event handlers)
|
|
if ( eventType.length === 0 && origCount !== eventType.length ) {
|
|
if ( !special.teardown || special.teardown.call( elem, namespaces, 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", true );
|
|
}
|
|
},
|
|
|
|
// Events that are safe to short-circuit if no handlers are attached.
|
|
// Native DOM events should not be added, they may have inline handlers.
|
|
customEvent: {
|
|
"getData": true,
|
|
"setData": true,
|
|
"changeData": true
|
|
},
|
|
|
|
trigger: function( event, data, elem, onlyHandlers ) {
|
|
// Don't do events on text and comment nodes
|
|
if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
|
|
return;
|
|
}
|
|
|
|
// Event object or event type
|
|
var cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType,
|
|
type = event.type || event,
|
|
namespaces = [];
|
|
|
|
// focus/blur morphs to focusin/out; ensure we're not firing them right now
|
|
if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( type.indexOf( "!" ) >= 0 ) {
|
|
// Exclusive events trigger only for the exact event (no namespaces)
|
|
type = type.slice(0, -1);
|
|
exclusive = true;
|
|
}
|
|
|
|
if ( type.indexOf( "." ) >= 0 ) {
|
|
// Namespaced trigger; create a regexp to match event type in handle()
|
|
namespaces = type.split(".");
|
|
type = namespaces.shift();
|
|
namespaces.sort();
|
|
}
|
|
|
|
if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
|
|
// No jQuery handlers for this event type, and it can't have inline handlers
|
|
return;
|
|
}
|
|
|
|
// Caller can pass in an Event, Object, or just an event type string
|
|
event = typeof event === "object" ?
|
|
// jQuery.Event object
|
|
event[ jQuery.expando ] ? event :
|
|
// Object literal
|
|
new jQuery.Event( type, event ) :
|
|
// Just the event type (string)
|
|
new jQuery.Event( type );
|
|
|
|
event.type = type;
|
|
event.isTrigger = true;
|
|
event.exclusive = exclusive;
|
|
event.namespace = namespaces.join( "." );
|
|
event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
|
|
ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
|
|
|
|
// Handle a global trigger
|
|
if ( !elem ) {
|
|
|
|
// TODO: Stop taunting the data cache; remove global events and always attach to document
|
|
cache = jQuery.cache;
|
|
for ( i in cache ) {
|
|
if ( cache[ i ].events && cache[ i ].events[ type ] ) {
|
|
jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Clean up the event in case it is being reused
|
|
event.result = undefined;
|
|
if ( !event.target ) {
|
|
event.target = elem;
|
|
}
|
|
|
|
// Clone any incoming data and prepend the event, creating the handler arg list
|
|
data = data != null ? jQuery.makeArray( data ) : [];
|
|
data.unshift( event );
|
|
|
|
// Allow special events to draw outside the lines
|
|
special = jQuery.event.special[ type ] || {};
|
|
if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
|
|
return;
|
|
}
|
|
|
|
// Determine event propagation path in advance, per W3C events spec (#9951)
|
|
// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
|
|
eventPath = [[ elem, special.bindType || type ]];
|
|
if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
|
|
|
|
bubbleType = special.delegateType || type;
|
|
cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
|
|
for ( old = elem; cur; cur = cur.parentNode ) {
|
|
eventPath.push([ cur, bubbleType ]);
|
|
old = cur;
|
|
}
|
|
|
|
// Only add window if we got to document (e.g., not plain obj or detached DOM)
|
|
if ( old === (elem.ownerDocument || document) ) {
|
|
eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
|
|
}
|
|
}
|
|
|
|
// Fire handlers on the event path
|
|
for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {
|
|
|
|
cur = eventPath[i][0];
|
|
event.type = eventPath[i][1];
|
|
|
|
handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
|
|
if ( handle ) {
|
|
handle.apply( cur, data );
|
|
}
|
|
// Note that this is a bare JS function and not a jQuery handler
|
|
handle = ontype && cur[ ontype ];
|
|
if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) {
|
|
event.preventDefault();
|
|
}
|
|
}
|
|
event.type = type;
|
|
|
|
// If nobody prevented the default action, do it now
|
|
if ( !onlyHandlers && !event.isDefaultPrevented() ) {
|
|
|
|
if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
|
|
!(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
|
|
|
|
// Call a native DOM method on the target with the same name name as the event.
|
|
// Can't use an .isFunction() check here because IE6/7 fails that test.
|
|
// Don't do default actions on window, that's where global variables be (#6170)
|
|
// IE<9 dies on focus/blur to hidden element (#1486)
|
|
if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {
|
|
|
|
// Don't re-trigger an onFOO event when we call its FOO() method
|
|
old = elem[ ontype ];
|
|
|
|
if ( old ) {
|
|
elem[ ontype ] = null;
|
|
}
|
|
|
|
// Prevent re-triggering of the same event, since we already bubbled it above
|
|
jQuery.event.triggered = type;
|
|
elem[ type ]();
|
|
jQuery.event.triggered = undefined;
|
|
|
|
if ( old ) {
|
|
elem[ ontype ] = old;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return event.result;
|
|
},
|
|
|
|
dispatch: function( event ) {
|
|
|
|
// Make a writable jQuery.Event from the native event object
|
|
event = jQuery.event.fix( event || window.event );
|
|
|
|
var i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related,
|
|
handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
|
|
delegateCount = handlers.delegateCount,
|
|
args = [].slice.call( arguments ),
|
|
run_all = !event.exclusive && !event.namespace,
|
|
special = jQuery.event.special[ event.type ] || {},
|
|
handlerQueue = [];
|
|
|
|
// 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 that should run if there are delegated events
|
|
// Avoid non-left-click bubbling in Firefox (#3861)
|
|
if ( delegateCount && !(event.button && event.type === "click") ) {
|
|
|
|
// Pregenerate a single jQuery object for reuse with .is()
|
|
jqcur = jQuery(this);
|
|
jqcur.context = this;
|
|
|
|
for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
|
|
|
|
// Don't process clicks (ONLY) on disabled elements (#6911, #8165, #xxxx)
|
|
if ( cur.disabled !== true || event.type !== "click" ) {
|
|
selMatch = {};
|
|
matches = [];
|
|
jqcur[0] = cur;
|
|
for ( i = 0; i < delegateCount; i++ ) {
|
|
handleObj = handlers[ i ];
|
|
sel = handleObj.selector;
|
|
|
|
if ( selMatch[ sel ] === undefined ) {
|
|
selMatch[ sel ] = jqcur.is( sel );
|
|
}
|
|
if ( selMatch[ sel ] ) {
|
|
matches.push( handleObj );
|
|
}
|
|
}
|
|
if ( matches.length ) {
|
|
handlerQueue.push({ elem: cur, matches: matches });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the remaining (directly-bound) handlers
|
|
if ( handlers.length > delegateCount ) {
|
|
handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
|
|
}
|
|
|
|
// Run delegates first; they may want to stop propagation beneath us
|
|
for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
|
|
matched = handlerQueue[ i ];
|
|
event.currentTarget = matched.elem;
|
|
|
|
for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
|
|
handleObj = matched.matches[ j ];
|
|
|
|
// Triggered event must either 1) be non-exclusive and have no namespace, or
|
|
// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
|
|
if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
|
|
|
|
event.data = handleObj.data;
|
|
event.handleObj = handleObj;
|
|
|
|
ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
|
|
.apply( matched.elem, args );
|
|
|
|
if ( ret !== undefined ) {
|
|
event.result = ret;
|
|
if ( ret === false ) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Call the postDispatch hook for the mapped type
|
|
if ( special.postDispatch ) {
|
|
special.postDispatch.call( this, event );
|
|
}
|
|
|
|
return event.result;
|
|
},
|
|
|
|
// Includes some event props shared by KeyEvent and MouseEvent
|
|
// *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 ***
|
|
props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
|
|
|
|
fixHooks: {},
|
|
|
|
keyHooks: {
|
|
props: "char charCode key keyCode".split(" "),
|
|
filter: function( event, original ) {
|
|
|
|
// Add which for key events
|
|
if ( event.which == null ) {
|
|
event.which = original.charCode != null ? original.charCode : original.keyCode;
|
|
}
|
|
|
|
return event;
|
|
}
|
|
},
|
|
|
|
mouseHooks: {
|
|
props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
|
|
filter: function( event, original ) {
|
|
var eventDoc, doc, body,
|
|
button = original.button,
|
|
fromElement = original.fromElement;
|
|
|
|
// Calculate pageX/Y if missing and clientX/Y available
|
|
if ( event.pageX == null && original.clientX != null ) {
|
|
eventDoc = event.target.ownerDocument || document;
|
|
doc = eventDoc.documentElement;
|
|
body = eventDoc.body;
|
|
|
|
event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
|
|
event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
|
|
}
|
|
|
|
// Add relatedTarget, if necessary
|
|
if ( !event.relatedTarget && fromElement ) {
|
|
event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
|
|
}
|
|
|
|
// Add which for click: 1 === left; 2 === middle; 3 === right
|
|
// Note: button is not normalized, so don't use it
|
|
if ( !event.which && button !== undefined ) {
|
|
event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
|
|
}
|
|
|
|
return event;
|
|
}
|
|
},
|
|
|
|
fix: function( event ) {
|
|
if ( event[ jQuery.expando ] ) {
|
|
return event;
|
|
}
|
|
|
|
// Create a writable copy of the event object and normalize some properties
|
|
var i, prop,
|
|
originalEvent = event,
|
|
fixHook = jQuery.event.fixHooks[ event.type ] || {},
|
|
copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
|
|
|
|
event = jQuery.Event( originalEvent );
|
|
|
|
for ( i = copy.length; i; ) {
|
|
prop = copy[ --i ];
|
|
event[ prop ] = originalEvent[ prop ];
|
|
}
|
|
|
|
// Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
|
|
if ( !event.target ) {
|
|
event.target = originalEvent.srcElement || document;
|
|
}
|
|
|
|
// Target should not be a text node (#504, Safari)
|
|
if ( event.target.nodeType === 3 ) {
|
|
event.target = event.target.parentNode;
|
|
}
|
|
|
|
// For mouse/key events, metaKey==false if it's undefined (#3368, #11328; IE6/7/8)
|
|
event.metaKey = !!event.metaKey;
|
|
|
|
return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
|
|
},
|
|
|
|
special: {
|
|
ready: {
|
|
// Make sure the ready event is setup
|
|
setup: jQuery.bindReady
|
|
},
|
|
|
|
load: {
|
|
// Prevent triggered image.load events from bubbling to window.load
|
|
noBubble: true
|
|
},
|
|
|
|
focus: {
|
|
delegateType: "focusin"
|
|
},
|
|
blur: {
|
|
delegateType: "focusout"
|
|
},
|
|
|
|
beforeunload: {
|
|
setup: function( data, namespaces, eventHandle ) {
|
|
// We only want to do this special case on windows
|
|
if ( jQuery.isWindow( this ) ) {
|
|
this.onbeforeunload = eventHandle;
|
|
}
|
|
},
|
|
|
|
teardown: function( namespaces, eventHandle ) {
|
|
if ( this.onbeforeunload === eventHandle ) {
|
|
this.onbeforeunload = null;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
simulate: function( type, elem, event, bubble ) {
|
|
// Piggyback on a donor event to simulate a different one.
|
|
// Fake originalEvent to avoid donor's stopPropagation, but if the
|
|
// simulated event prevents default then we do the same on the donor.
|
|
var e = jQuery.extend(
|
|
new jQuery.Event(),
|
|
event,
|
|
{ type: type,
|
|
isSimulated: true,
|
|
originalEvent: {}
|
|
}
|
|
);
|
|
if ( bubble ) {
|
|
jQuery.event.trigger( e, null, elem );
|
|
} else {
|
|
jQuery.event.dispatch.call( elem, e );
|
|
}
|
|
if ( e.isDefaultPrevented() ) {
|
|
event.preventDefault();
|
|
}
|
|
}
|
|
};
|
|
|
|
// Some plugins are using, but it's undocumented/deprecated and will be removed.
|
|
// The 1.7 special event interface should provide all the hooks needed now.
|
|
jQuery.event.handle = jQuery.event.dispatch;
|
|
|
|
jQuery.removeEvent = document.removeEventListener ?
|
|
function( elem, type, handle ) {
|
|
if ( elem.removeEventListener ) {
|
|
elem.removeEventListener( type, handle, false );
|
|
}
|
|
} :
|
|
function( elem, type, handle ) {
|
|
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.returnValue === false ||
|
|
src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
|
|
|
|
// Event type
|
|
} else {
|
|
this.type = src;
|
|
}
|
|
|
|
// Put explicitly provided properties onto the event object
|
|
if ( props ) {
|
|
jQuery.extend( this, props );
|
|
}
|
|
|
|
// Create a timestamp if incoming event doesn't have one
|
|
this.timeStamp = src && src.timeStamp || jQuery.now();
|
|
|
|
// Mark it as fixed
|
|
this[ jQuery.expando ] = true;
|
|
};
|
|
|
|
function returnFalse() {
|
|
return false;
|
|
}
|
|
function returnTrue() {
|
|
return true;
|
|
}
|
|
|
|
// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
|
|
// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
|
|
jQuery.Event.prototype = {
|
|
preventDefault: function() {
|
|
this.isDefaultPrevented = returnTrue;
|
|
|
|
var e = this.originalEvent;
|
|
if ( !e ) {
|
|
return;
|
|
}
|
|
|
|
// if preventDefault exists run it on the original event
|
|
if ( e.preventDefault ) {
|
|
e.preventDefault();
|
|
|
|
// otherwise set the returnValue property of the original event to false (IE)
|
|
} else {
|
|
e.returnValue = false;
|
|
}
|
|
},
|
|
stopPropagation: function() {
|
|
this.isPropagationStopped = returnTrue;
|
|
|
|
var e = this.originalEvent;
|
|
if ( !e ) {
|
|
return;
|
|
}
|
|
// if stopPropagation exists run it on the original event
|
|
if ( e.stopPropagation ) {
|
|
e.stopPropagation();
|
|
}
|
|
// otherwise set the cancelBubble property of the original event to true (IE)
|
|
e.cancelBubble = true;
|
|
},
|
|
stopImmediatePropagation: function() {
|
|
this.isImmediatePropagationStopped = returnTrue;
|
|
this.stopPropagation();
|
|
},
|
|
isDefaultPrevented: returnFalse,
|
|
isPropagationStopped: returnFalse,
|
|
isImmediatePropagationStopped: returnFalse
|
|
};
|
|
|
|
// Create mouseenter/leave events using mouseover/out and event-time checks
|
|
jQuery.each({
|
|
mouseenter: "mouseover",
|
|
mouseleave: "mouseout"
|
|
}, function( orig, fix ) {
|
|
jQuery.event.special[ orig ] = {
|
|
delegateType: fix,
|
|
bindType: fix,
|
|
|
|
handle: function( event ) {
|
|
var ret,
|
|
target = this,
|
|
related = event.relatedTarget,
|
|
handleObj = event.handleObj,
|
|
selector = handleObj.selector;
|
|
|
|
// For mousenter/leave call the handler if related is outside the target.
|
|
// NB: No relatedTarget if the mouse left/entered the browser window
|
|
if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
|
|
event.type = handleObj.origType;
|
|
ret = handleObj.handler.apply( this, arguments );
|
|
event.type = fix;
|
|
}
|
|
return ret;
|
|
}
|
|
};
|
|
});
|
|
|
|
// IE submit delegation
|
|
if ( !jQuery.support.submitBubbles ) {
|
|
|
|
jQuery.event.special.submit = {
|
|
setup: function() {
|
|
// Only need this for delegated form submit events
|
|
if ( jQuery.nodeName( this, "form" ) ) {
|
|
return false;
|
|
}
|
|
|
|
// Lazy-add a submit handler when a descendant form may potentially be submitted
|
|
jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
|
|
// Node name check avoids a VML-related crash in IE (#9807)
|
|
var elem = e.target,
|
|
form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
|
|
if ( form && !jQuery._data( form, "_submit_attached" ) ) {
|
|
jQuery.event.add( form, "submit._submit", function( event ) {
|
|
event._submit_bubble = true;
|
|
});
|
|
jQuery._data( form, "_submit_attached", 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._submit_bubble ) {
|
|
delete event._submit_bubble;
|
|
if ( this.parentNode && !event.isTrigger ) {
|
|
jQuery.event.simulate( "submit", this.parentNode, event, true );
|
|
}
|
|
}
|
|
},
|
|
|
|
teardown: function() {
|
|
// Only need this for delegated form submit events
|
|
if ( jQuery.nodeName( this, "form" ) ) {
|
|
return false;
|
|
}
|
|
|
|
// Remove delegated handlers; cleanData eventually reaps submit handlers attached above
|
|
jQuery.event.remove( this, "._submit" );
|
|
}
|
|
};
|
|
}
|
|
|
|
// IE change delegation and checkbox/radio fix
|
|
if ( !jQuery.support.changeBubbles ) {
|
|
|
|
jQuery.event.special.change = {
|
|
|
|
setup: function() {
|
|
|
|
if ( rformElems.test( this.nodeName ) ) {
|
|
// IE doesn't fire change on a check/radio until blur; trigger it on click
|
|
// after a propertychange. Eat the blur-change in special.change.handle.
|
|
// This still fires onchange a second time for check/radio after blur.
|
|
if ( this.type === "checkbox" || this.type === "radio" ) {
|
|
jQuery.event.add( this, "propertychange._change", function( event ) {
|
|
if ( event.originalEvent.propertyName === "checked" ) {
|
|
this._just_changed = true;
|
|
}
|
|
});
|
|
jQuery.event.add( this, "click._change", function( event ) {
|
|
if ( this._just_changed && !event.isTrigger ) {
|
|
this._just_changed = false;
|
|
}
|
|
// Allow triggered, simulated change events (#11500)
|
|
jQuery.event.simulate( "change", this, event, true );
|
|
});
|
|
}
|
|
return false;
|
|
}
|
|
// Delegated event; lazy-add a change handler on descendant inputs
|
|
jQuery.event.add( this, "beforeactivate._change", function( e ) {
|
|
var elem = e.target;
|
|
|
|
if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "_change_attached" ) ) {
|
|
jQuery.event.add( elem, "change._change", function( event ) {
|
|
if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
|
|
jQuery.event.simulate( "change", this.parentNode, event, true );
|
|
}
|
|
});
|
|
jQuery._data( elem, "_change_attached", true );
|
|
}
|
|
});
|
|
},
|
|
|
|
handle: function( event ) {
|
|
var elem = event.target;
|
|
|
|
// Swallow native change events from checkbox/radio, we already triggered them above
|
|
if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
|
|
return event.handleObj.handler.apply( this, arguments );
|
|
}
|
|
},
|
|
|
|
teardown: function() {
|
|
jQuery.event.remove( this, "._change" );
|
|
|
|
return rformElems.test( this.nodeName );
|
|
}
|
|
};
|
|
}
|
|
|
|
// Create "bubbling" focus and blur events
|
|
if ( !jQuery.support.focusinBubbles ) {
|
|
jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
|
|
|
|
// Attach a single capturing handler while someone wants focusin/focusout
|
|
var attaches = 0,
|
|
handler = function( event ) {
|
|
jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
|
|
};
|
|
|
|
jQuery.event.special[ fix ] = {
|
|
setup: function() {
|
|
if ( attaches++ === 0 ) {
|
|
document.addEventListener( orig, handler, true );
|
|
}
|
|
},
|
|
teardown: function() {
|
|
if ( --attaches === 0 ) {
|
|
document.removeEventListener( orig, handler, true );
|
|
}
|
|
}
|
|
};
|
|
});
|
|
}
|
|
|
|
jQuery.fn.extend({
|
|
|
|
on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
|
|
var origFn, type;
|
|
|
|
// Types can be a map of types/handlers
|
|
if ( typeof types === "object" ) {
|
|
// ( types-Object, selector, data )
|
|
if ( typeof selector !== "string" ) { // && selector != null
|
|
// ( types-Object, data )
|
|
data = data || selector;
|
|
selector = undefined;
|
|
}
|
|
for ( type in types ) {
|
|
this.on( type, selector, data, types[ type ], one );
|
|
}
|
|
return this;
|
|
}
|
|
|
|
if ( data == null && fn == null ) {
|
|
// ( types, fn )
|
|
fn = selector;
|
|
data = selector = undefined;
|
|
} else if ( fn == null ) {
|
|
if ( typeof selector === "string" ) {
|
|
// ( types, selector, fn )
|
|
fn = data;
|
|
data = undefined;
|
|
} else {
|
|
// ( types, data, fn )
|
|
fn = data;
|
|
data = selector;
|
|
selector = undefined;
|
|
}
|
|
}
|
|
if ( fn === false ) {
|
|
fn = returnFalse;
|
|
} else if ( !fn ) {
|
|
return this;
|
|
}
|
|
|
|
if ( one === 1 ) {
|
|
origFn = fn;
|
|
fn = function( event ) {
|
|
// Can use an empty set, since event contains the info
|
|
jQuery().off( event );
|
|
return origFn.apply( this, arguments );
|
|
};
|
|
// Use same guid so caller can remove using origFn
|
|
fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
|
|
}
|
|
return this.each( function() {
|
|
jQuery.event.add( this, types, fn, data, selector );
|
|
});
|
|
},
|
|
one: function( types, selector, data, fn ) {
|
|
return this.on( types, selector, data, fn, 1 );
|
|
},
|
|
off: function( types, selector, fn ) {
|
|
var handleObj, type;
|
|
if ( types && types.preventDefault && types.handleObj ) {
|
|
// ( event ) dispatched jQuery.Event
|
|
handleObj = types.handleObj;
|
|
jQuery( types.delegateTarget ).off(
|
|
handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
|
|
handleObj.selector,
|
|
handleObj.handler
|
|
);
|
|
return this;
|
|
}
|
|
if ( typeof types === "object" ) {
|
|
// ( types-object [, selector] )
|
|
for ( type in types ) {
|
|
this.off( type, selector, types[ type ] );
|
|
}
|
|
return this;
|
|
}
|
|
if ( selector === false || typeof selector === "function" ) {
|
|
// ( types [, fn] )
|
|
fn = selector;
|
|
selector = undefined;
|
|
}
|
|
if ( fn === false ) {
|
|
fn = returnFalse;
|
|
}
|
|
return this.each(function() {
|
|
jQuery.event.remove( this, types, fn, selector );
|
|
});
|
|
},
|
|
|
|
bind: function( types, data, fn ) {
|
|
return this.on( types, null, data, fn );
|
|
},
|
|
unbind: function( types, fn ) {
|
|
return this.off( types, null, fn );
|
|
},
|
|
|
|
live: function( types, data, fn ) {
|
|
jQuery( this.context ).on( types, this.selector, data, fn );
|
|
return this;
|
|
},
|
|
die: function( types, fn ) {
|
|
jQuery( this.context ).off( types, this.selector || "**", fn );
|
|
return this;
|
|
},
|
|
|
|
delegate: function( selector, types, data, fn ) {
|
|
return this.on( types, selector, data, fn );
|
|
},
|
|
undelegate: function( selector, types, fn ) {
|
|
// ( namespace ) or ( selector, types [, fn] )
|
|
return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
|
|
},
|
|
|
|
trigger: function( type, data ) {
|
|
return this.each(function() {
|
|
jQuery.event.trigger( type, data, this );
|
|
});
|
|
},
|
|
triggerHandler: function( type, data ) {
|
|
if ( this[0] ) {
|
|
return jQuery.event.trigger( type, data, this[0], true );
|
|
}
|
|
},
|
|
|
|
toggle: function( fn ) {
|
|
// Save reference to arguments for access in closure
|
|
var args = arguments,
|
|
guid = fn.guid || jQuery.guid++,
|
|
i = 0,
|
|
toggler = function( event ) {
|
|
// Figure out which function to execute
|
|
var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
|
|
jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
|
|
|
|
// Make sure that clicks stop
|
|
event.preventDefault();
|
|
|
|
// and execute the function
|
|
return args[ lastToggle ].apply( this, arguments ) || false;
|
|
};
|
|
|
|
// link all the functions, so any of them can unbind this click handler
|
|
toggler.guid = guid;
|
|
while ( i < args.length ) {
|
|
args[ i++ ].guid = guid;
|
|
}
|
|
|
|
return this.click( toggler );
|
|
},
|
|
|
|
hover: function( fnOver, fnOut ) {
|
|
return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
|
|
}
|
|
});
|
|
|
|
jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
|
|
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
|
|
"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
|
|
|
|
// Handle event binding
|
|
jQuery.fn[ name ] = function( data, fn ) {
|
|
if ( fn == null ) {
|
|
fn = data;
|
|
data = null;
|
|
}
|
|
|
|
return arguments.length > 0 ?
|
|
this.on( name, null, data, fn ) :
|
|
this.trigger( name );
|
|
};
|
|
|
|
if ( rkeyEvent.test( name ) ) {
|
|
jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
|
|
}
|
|
|
|
if ( rmouseEvent.test( name ) ) {
|
|
jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
|
|
}
|
|
});
|
|
/*!
|
|
* Sizzle CSS Selector Engine
|
|
* Copyright 2012 jQuery Foundation and other contributors
|
|
* Released under the MIT license
|
|
* http://sizzlejs.com/
|
|
*/
|
|
(function( window, undefined ) {
|
|
|
|
var cachedruns,
|
|
dirruns,
|
|
sortOrder,
|
|
siblingCheck,
|
|
assertGetIdNotName,
|
|
|
|
document = window.document,
|
|
docElem = document.documentElement,
|
|
|
|
strundefined = "undefined",
|
|
hasDuplicate = false,
|
|
baseHasDuplicate = true,
|
|
done = 0,
|
|
slice = [].slice,
|
|
push = [].push,
|
|
|
|
expando = ( "sizcache" + Math.random() ).replace( ".", "" ),
|
|
|
|
// Regex
|
|
|
|
// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
|
|
whitespace = "[\\x20\\t\\r\\n\\f]",
|
|
// http://www.w3.org/TR/css3-syntax/#characters
|
|
characterEncoding = "(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",
|
|
|
|
// Loosely modeled on CSS identifier characters
|
|
// An unquoted value should be a CSS identifier (http://www.w3.org/TR/css3-selectors/#attribute-selectors)
|
|
// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
|
|
identifier = characterEncoding.replace( "w", "w#" ),
|
|
|
|
// Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
|
|
operators = "([*^$|!~]?=)",
|
|
attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
|
|
"*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
|
|
pseudos = ":(" + characterEncoding + ")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|((?:[^,]|\\\\,|(?:,(?=[^\\[]*\\]))|(?:,(?=[^\\(]*\\))))*))\\)|)",
|
|
pos = ":(nth|eq|gt|lt|first|last|even|odd)(?:\\((\\d*)\\)|)(?=[^-]|$)",
|
|
combinators = whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*",
|
|
groups = "(?=[^\\x20\\t\\r\\n\\f])(?:\\\\.|" + attributes + "|" + pseudos.replace( 2, 7 ) + "|[^\\\\(),])+",
|
|
|
|
// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
|
|
rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
|
|
|
|
rcombinators = new RegExp( "^" + combinators ),
|
|
|
|
// All simple (non-comma) selectors, excluding insignifant trailing whitespace
|
|
rgroups = new RegExp( groups + "?(?=" + whitespace + "*,|$)", "g" ),
|
|
|
|
// A selector, or everything after leading whitespace
|
|
// Optionally followed in either case by a ")" for terminating sub-selectors
|
|
rselector = new RegExp( "^(?:(?!,)(?:(?:^|,)" + whitespace + "*" + groups + ")*?|" + whitespace + "*(.*?))(\\)|$)" ),
|
|
|
|
// All combinators and selector components (attribute test, tag, pseudo, etc.), the latter appearing together when consecutive
|
|
rtokens = new RegExp( groups.slice( 19, -6 ) + "\\x20\\t\\r\\n\\f>+~])+|" + combinators, "g" ),
|
|
|
|
// Easily-parseable/retrievable ID or TAG or CLASS selectors
|
|
rquickExpr = /^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,
|
|
|
|
rsibling = /[\x20\t\r\n\f]*[+~]/,
|
|
rendsWithNot = /:not\($/,
|
|
|
|
rheader = /h\d/i,
|
|
rinputs = /input|select|textarea|button/i,
|
|
|
|
rbackslash = /\\(?!\\)/g,
|
|
|
|
matchExpr = {
|
|
"ID": new RegExp( "^#(" + characterEncoding + ")" ),
|
|
"CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
|
|
"NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ),
|
|
"TAG": new RegExp( "^(" + characterEncoding.replace( "[-", "[-\\*" ) + ")" ),
|
|
"ATTR": new RegExp( "^" + attributes ),
|
|
"PSEUDO": new RegExp( "^" + pseudos ),
|
|
"CHILD": new RegExp( "^:(only|nth|last|first)-child(?:\\(" + whitespace +
|
|
"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
|
|
"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
|
|
"POS": new RegExp( pos, "ig" ),
|
|
// For use in libraries implementing .is()
|
|
"needsContext": new RegExp( "^" + whitespace + "*[>+~]|" + pos, "i" )
|
|
},
|
|
|
|
classCache = {},
|
|
cachedClasses = [],
|
|
compilerCache = {},
|
|
cachedSelectors = [],
|
|
|
|
// Mark a function for use in filtering
|
|
markFunction = function( fn ) {
|
|
fn.sizzleFilter = true;
|
|
return fn;
|
|
},
|
|
|
|
// Returns a function to use in pseudos for input types
|
|
createInputFunction = function( type ) {
|
|
return function( elem ) {
|
|
// Check the input's nodeName and type
|
|
return elem.nodeName.toLowerCase() === "input" && elem.type === type;
|
|
};
|
|
},
|
|
|
|
// Returns a function to use in pseudos for buttons
|
|
createButtonFunction = function( type ) {
|
|
return function( elem ) {
|
|
var name = elem.nodeName.toLowerCase();
|
|
return (name === "input" || name === "button") && elem.type === type;
|
|
};
|
|
},
|
|
|
|
// Used for testing something on an element
|
|
assert = function( fn ) {
|
|
var pass = false,
|
|
div = document.createElement("div");
|
|
try {
|
|
pass = fn( div );
|
|
} catch (e) {}
|
|
// release memory in IE
|
|
div = null;
|
|
return pass;
|
|
},
|
|
|
|
// Check if attributes should be retrieved by attribute nodes
|
|
assertAttributes = assert(function( div ) {
|
|
div.innerHTML = "<select></select>";
|
|
var type = typeof div.lastChild.getAttribute("multiple");
|
|
// IE8 returns a string for some attributes even when not present
|
|
return type !== "boolean" && type !== "string";
|
|
}),
|
|
|
|
// Check if getElementById returns elements by name
|
|
// Check if getElementsByName privileges form controls or returns elements by ID
|
|
assertUsableName = assert(function( div ) {
|
|
// Inject content
|
|
div.id = expando + 0;
|
|
div.innerHTML = "<a name='" + expando + "'></a><div name='" + expando + "'></div>";
|
|
docElem.insertBefore( div, docElem.firstChild );
|
|
|
|
// Test
|
|
var pass = document.getElementsByName &&
|
|
// buggy browsers will return fewer than the correct 2
|
|
document.getElementsByName( expando ).length ===
|
|
// buggy browsers will return more than the correct 0
|
|
2 + document.getElementsByName( expando + 0 ).length;
|
|
assertGetIdNotName = !document.getElementById( expando );
|
|
|
|
// Cleanup
|
|
docElem.removeChild( div );
|
|
|
|
return pass;
|
|
}),
|
|
|
|
// Check if the browser returns only elements
|
|
// when doing getElementsByTagName("*")
|
|
assertTagNameNoComments = assert(function( div ) {
|
|
div.appendChild( document.createComment("") );
|
|
return div.getElementsByTagName("*").length === 0;
|
|
}),
|
|
|
|
// Check if getAttribute returns normalized href attributes
|
|
assertHrefNotNormalized = assert(function( div ) {
|
|
div.innerHTML = "<a href='#'></a>";
|
|
return div.firstChild && typeof div.firstChild.getAttribute !== strundefined &&
|
|
div.firstChild.getAttribute("href") === "#";
|
|
}),
|
|
|
|
// Check if getElementsByClassName can be trusted
|
|
assertUsableClassName = assert(function( div ) {
|
|
// Opera can't find a second classname (in 9.6)
|
|
div.innerHTML = "<div class='hidden e'></div><div class='hidden'></div>";
|
|
if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
|
|
return false;
|
|
}
|
|
|
|
// Safari caches class attributes, doesn't catch changes (in 3.2)
|
|
div.lastChild.className = "e";
|
|
return div.getElementsByClassName("e").length !== 1;
|
|
});
|
|
|
|
var Sizzle = function( selector, context, results, seed ) {
|
|
results = results || [];
|
|
context = context || document;
|
|
var match, elem, xml, m,
|
|
nodeType = context.nodeType;
|
|
|
|
if ( nodeType !== 1 && nodeType !== 9 ) {
|
|
return [];
|
|
}
|
|
|
|
if ( !selector || typeof selector !== "string" ) {
|
|
return results;
|
|
}
|
|
|
|
xml = isXML( context );
|
|
|
|
if ( !xml && !seed ) {
|
|
if ( (match = rquickExpr.exec( selector )) ) {
|
|
// Speed-up: Sizzle("#ID")
|
|
if ( (m = match[1]) ) {
|
|
if ( nodeType === 9 ) {
|
|
elem = context.getElementById( m );
|
|
// Check parentNode to catch when Blackberry 4.6 returns
|
|
// nodes that are no longer in the document #6963
|
|
if ( elem && elem.parentNode ) {
|
|
// Handle the case where IE, Opera, and Webkit return items
|
|
// by name instead of ID
|
|
if ( elem.id === m ) {
|
|
results.push( elem );
|
|
return results;
|
|
}
|
|
} else {
|
|
return results;
|
|
}
|
|
} else {
|
|
// Context is not a document
|
|
if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
|
|
contains( context, elem ) && elem.id === m ) {
|
|
results.push( elem );
|
|
return results;
|
|
}
|
|
}
|
|
|
|
// Speed-up: Sizzle("TAG")
|
|
} else if ( match[2] ) {
|
|
push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) );
|
|
return results;
|
|
|
|
// Speed-up: Sizzle(".CLASS")
|
|
} else if ( (m = match[3]) && assertUsableClassName && context.getElementsByClassName ) {
|
|
push.apply( results, slice.call(context.getElementsByClassName( m ), 0) );
|
|
return results;
|
|
}
|
|
}
|
|
}
|
|
|
|
// All others
|
|
return select( selector, context, results, seed, xml );
|
|
};
|
|
|
|
var Expr = Sizzle.selectors = {
|
|
|
|
// Can be adjusted by the user
|
|
cacheLength: 50,
|
|
|
|
match: matchExpr,
|
|
|
|
order: [ "ID", "TAG" ],
|
|
|
|
attrHandle: {},
|
|
|
|
createPseudo: markFunction,
|
|
|
|
find: {
|
|
"ID": assertGetIdNotName ?
|
|
function( id, context, xml ) {
|
|
if ( typeof context.getElementById !== strundefined && !xml ) {
|
|
var m = context.getElementById( id );
|
|
// Check parentNode to catch when Blackberry 4.6 returns
|
|
// nodes that are no longer in the document #6963
|
|
return m && m.parentNode ? [m] : [];
|
|
}
|
|
} :
|
|
function( id, context, xml ) {
|
|
if ( typeof context.getElementById !== strundefined && !xml ) {
|
|
var m = context.getElementById( id );
|
|
|
|
return m ?
|
|
m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ?
|
|
[m] :
|
|
undefined :
|
|
[];
|
|
}
|
|
},
|
|
|
|
"TAG": assertTagNameNoComments ?
|
|
function( tag, context ) {
|
|
if ( typeof context.getElementsByTagName !== strundefined ) {
|
|
return context.getElementsByTagName( tag );
|
|
}
|
|
} :
|
|
function( tag, context ) {
|
|
var results = context.getElementsByTagName( tag );
|
|
|
|
// Filter out possible comments
|
|
if ( tag === "*" ) {
|
|
var elem,
|
|
tmp = [],
|
|
i = 0;
|
|
|
|
for ( ; (elem = results[i]); i++ ) {
|
|
if ( elem.nodeType === 1 ) {
|
|
tmp.push( elem );
|
|
}
|
|
}
|
|
|
|
return tmp;
|
|
}
|
|
return results;
|
|
}
|
|
},
|
|
|
|
relative: {
|
|
">": { dir: "parentNode", first: true },
|
|
" ": { dir: "parentNode" },
|
|
"+": { dir: "previousSibling", first: true },
|
|
"~": { dir: "previousSibling" }
|
|
},
|
|
|
|
preFilter: {
|
|
"ATTR": function( match ) {
|
|
match[1] = match[1].replace( rbackslash, "" );
|
|
|
|
// Move the given value to match[3] whether quoted or unquoted
|
|
match[3] = ( match[4] || match[5] || "" ).replace( rbackslash, "" );
|
|
|
|
if ( match[2] === "~=" ) {
|
|
match[3] = " " + match[3] + " ";
|
|
}
|
|
|
|
return match.slice( 0, 4 );
|
|
},
|
|
|
|
"CHILD": function( match ) {
|
|
/* matches from matchExpr.CHILD
|
|
1 type (only|nth|...)
|
|
2 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
|
|
3 xn-component of xn+y argument ([+-]?\d*n|)
|
|
4 sign of xn-component
|
|
5 x of xn-component
|
|
6 sign of y-component
|
|
7 y of y-component
|
|
*/
|
|
match[1] = match[1].toLowerCase();
|
|
|
|
if ( match[1] === "nth" ) {
|
|
// nth-child requires argument
|
|
if ( !match[2] ) {
|
|
Sizzle.error( match[0] );
|
|
}
|
|
|
|
// numeric x and y parameters for Expr.filter.CHILD
|
|
// remember that false/true cast respectively to 0/1
|
|
match[3] = +( match[3] ? match[4] + (match[5] || 1) : 2 * ( match[2] === "even" || match[2] === "odd" ) );
|
|
match[4] = +( ( match[6] + match[7] ) || match[2] === "odd" );
|
|
|
|
// other types prohibit arguments
|
|
} else if ( match[2] ) {
|
|
Sizzle.error( match[0] );
|
|
}
|
|
|
|
return match;
|
|
},
|
|
|
|
"PSEUDO": function( match ) {
|
|
var argument,
|
|
unquoted = match[4];
|
|
|
|
if ( matchExpr["CHILD"].test( match[0] ) ) {
|
|
return null;
|
|
}
|
|
|
|
// Relinquish our claim on characters in `unquoted` from a closing parenthesis on
|
|
if ( unquoted && (argument = rselector.exec( unquoted )) && argument.pop() ) {
|
|
|
|
match[0] = match[0].slice( 0, argument[0].length - unquoted.length - 1 );
|
|
unquoted = argument[0].slice( 0, -1 );
|
|
}
|
|
|
|
// Quoted or unquoted, we have the full argument
|
|
// Return only captures needed by the pseudo filter method (type and argument)
|
|
match.splice( 2, 3, unquoted || match[3] );
|
|
return match;
|
|
}
|
|
},
|
|
|
|
filter: {
|
|
"ID": assertGetIdNotName ?
|
|
function( id ) {
|
|
id = id.replace( rbackslash, "" );
|
|
return function( elem ) {
|
|
return elem.getAttribute("id") === id;
|
|
};
|
|
} :
|
|
function( id ) {
|
|
id = id.replace( rbackslash, "" );
|
|
return function( elem ) {
|
|
var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
|
|
return node && node.value === id;
|
|
};
|
|
},
|
|
|
|
"TAG": function( nodeName ) {
|
|
if ( nodeName === "*" ) {
|
|
return function() { return true; };
|
|
}
|
|
nodeName = nodeName.replace( rbackslash, "" ).toLowerCase();
|
|
|
|
return function( elem ) {
|
|
return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
|
|
};
|
|
},
|
|
|
|
"CLASS": function( className ) {
|
|
var pattern = classCache[ className ];
|
|
if ( !pattern ) {
|
|
pattern = classCache[ className ] = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" );
|
|
cachedClasses.push( className );
|
|
// Avoid too large of a cache
|
|
if ( cachedClasses.length > Expr.cacheLength ) {
|
|
delete classCache[ cachedClasses.shift() ];
|
|
}
|
|
}
|
|
return function( elem ) {
|
|
return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" );
|
|
};
|
|
},
|
|
|
|
"ATTR": function( name, operator, check ) {
|
|
if ( !operator ) {
|
|
return function( elem ) {
|
|
return Sizzle.attr( elem, name ) != null;
|
|
};
|
|
}
|
|
|
|
return function( elem ) {
|
|
var result = Sizzle.attr( elem, name ),
|
|
value = result + "";
|
|
|
|
if ( result == null ) {
|
|
return operator === "!=";
|
|
}
|
|
|
|
switch ( operator ) {
|
|
case "=":
|
|
return value === check;
|
|
case "!=":
|
|
return value !== check;
|
|
case "^=":
|
|
return check && value.indexOf( check ) === 0;
|
|
case "*=":
|
|
return check && value.indexOf( check ) > -1;
|
|
case "$=":
|
|
return check && value.substr( value.length - check.length ) === check;
|
|
case "~=":
|
|
return ( " " + value + " " ).indexOf( check ) > -1;
|
|
case "|=":
|
|
return value === check || value.substr( 0, check.length + 1 ) === check + "-";
|
|
}
|
|
};
|
|
},
|
|
|
|
"CHILD": function( type, argument, first, last ) {
|
|
|
|
if ( type === "nth" ) {
|
|
var doneName = done++;
|
|
|
|
return function( elem ) {
|
|
var parent, diff,
|
|
count = 0,
|
|
node = elem;
|
|
|
|
if ( first === 1 && last === 0 ) {
|
|
return true;
|
|
}
|
|
|
|
parent = elem.parentNode;
|
|
|
|
if ( parent && (parent[ expando ] !== doneName || !elem.sizset) ) {
|
|
for ( node = parent.firstChild; node; node = node.nextSibling ) {
|
|
if ( node.nodeType === 1 ) {
|
|
node.sizset = ++count;
|
|
if ( node === elem ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
parent[ expando ] = doneName;
|
|
}
|
|
|
|
diff = elem.sizset - last;
|
|
|
|
if ( first === 0 ) {
|
|
return diff === 0;
|
|
|
|
} else {
|
|
return ( diff % first === 0 && diff / first >= 0 );
|
|
}
|
|
};
|
|
}
|
|
|
|
return function( elem ) {
|
|
var node = elem;
|
|
|
|
switch ( type ) {
|
|
case "only":
|
|
case "first":
|
|
while ( (node = node.previousSibling) ) {
|
|
if ( node.nodeType === 1 ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( type === "first" ) {
|
|
return true;
|
|
}
|
|
|
|
node = elem;
|
|
|
|
/* falls through */
|
|
case "last":
|
|
while ( (node = node.nextSibling) ) {
|
|
if ( node.nodeType === 1 ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
},
|
|
|
|
"PSEUDO": function( pseudo, argument, context, xml ) {
|
|
// 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
|
|
var fn = Expr.pseudos[ pseudo ] || Expr.pseudos[ pseudo.toLowerCase() ];
|
|
|
|
if ( !fn ) {
|
|
Sizzle.error( "unsupported pseudo: " + pseudo );
|
|
}
|
|
|
|
// The user may set fn.sizzleFilter to indicate
|
|
// that arguments are needed to create the filter function
|
|
// just as Sizzle does
|
|
if ( !fn.sizzleFilter ) {
|
|
return fn;
|
|
}
|
|
|
|
return fn( argument, context, xml );
|
|
}
|
|
},
|
|
|
|
pseudos: {
|
|
"not": markFunction(function( selector, context, xml ) {
|
|
// Trim the selector passed to compile
|
|
// to avoid treating leading and trailing
|
|
// spaces as combinators
|
|
var matcher = compile( selector.replace( rtrim, "$1" ), context, xml );
|
|
return function( elem ) {
|
|
return !matcher( elem );
|
|
};
|
|
}),
|
|
|
|
"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;
|
|
},
|
|
|
|
"parent": function( elem ) {
|
|
return !Expr.pseudos["empty"]( elem );
|
|
},
|
|
|
|
"empty": function( elem ) {
|
|
// http://www.w3.org/TR/selectors/#empty-pseudo
|
|
// :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),
|
|
// not comment, processing instructions, or others
|
|
// Thanks to Diego Perini for the nodeName shortcut
|
|
// Greater than "@" means alpha characters (specifically not starting with "#" or "?")
|
|
var nodeType;
|
|
elem = elem.firstChild;
|
|
while ( elem ) {
|
|
if ( elem.nodeName > "@" || (nodeType = elem.nodeType) === 3 || nodeType === 4 ) {
|
|
return false;
|
|
}
|
|
elem = elem.nextSibling;
|
|
}
|
|
return true;
|
|
},
|
|
|
|
"contains": markFunction(function( text ) {
|
|
return function( elem ) {
|
|
return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
|
|
};
|
|
}),
|
|
|
|
"has": markFunction(function( selector ) {
|
|
return function( elem ) {
|
|
return Sizzle( selector, elem ).length > 0;
|
|
};
|
|
}),
|
|
|
|
"header": function( elem ) {
|
|
return rheader.test( elem.nodeName );
|
|
},
|
|
|
|
"text": function( elem ) {
|
|
var type, attr;
|
|
// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
|
|
// use getAttribute instead to test this case
|
|
return elem.nodeName.toLowerCase() === "input" &&
|
|
(type = elem.type) === "text" &&
|
|
( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === type );
|
|
},
|
|
|
|
// Input types
|
|
"radio": createInputFunction("radio"),
|
|
"checkbox": createInputFunction("checkbox"),
|
|
"file": createInputFunction("file"),
|
|
"password": createInputFunction("password"),
|
|
"image": createInputFunction("image"),
|
|
|
|
"submit": createButtonFunction("submit"),
|
|
"reset": createButtonFunction("reset"),
|
|
|
|
"button": function( elem ) {
|
|
var name = elem.nodeName.toLowerCase();
|
|
return name === "input" && elem.type === "button" || name === "button";
|
|
},
|
|
|
|
"input": function( elem ) {
|
|
return rinputs.test( elem.nodeName );
|
|
},
|
|
|
|
"focus": function( elem ) {
|
|
var doc = elem.ownerDocument;
|
|
return elem === doc.activeElement && (!doc.hasFocus || doc.hasFocus()) && !!(elem.type || elem.href);
|
|
},
|
|
|
|
"active": function( elem ) {
|
|
return elem === elem.ownerDocument.activeElement;
|
|
}
|
|
},
|
|
|
|
setFilters: {
|
|
"first": function( elements, argument, not ) {
|
|
return not ? elements.slice( 1 ) : [ elements[0] ];
|
|
},
|
|
|
|
"last": function( elements, argument, not ) {
|
|
var elem = elements.pop();
|
|
return not ? elements : [ elem ];
|
|
},
|
|
|
|
"even": function( elements, argument, not ) {
|
|
var results = [],
|
|
i = not ? 1 : 0,
|
|
len = elements.length;
|
|
for ( ; i < len; i = i + 2 ) {
|
|
results.push( elements[i] );
|
|
}
|
|
return results;
|
|
},
|
|
|
|
"odd": function( elements, argument, not ) {
|
|
var results = [],
|
|
i = not ? 0 : 1,
|
|
len = elements.length;
|
|
for ( ; i < len; i = i + 2 ) {
|
|
results.push( elements[i] );
|
|
}
|
|
return results;
|
|
},
|
|
|
|
"lt": function( elements, argument, not ) {
|
|
return not ? elements.slice( +argument ) : elements.slice( 0, +argument );
|
|
},
|
|
|
|
"gt": function( elements, argument, not ) {
|
|
return not ? elements.slice( 0, +argument + 1 ) : elements.slice( +argument + 1 );
|
|
},
|
|
|
|
"eq": function( elements, argument, not ) {
|
|
var elem = elements.splice( +argument, 1 );
|
|
return not ? elements : elem;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Deprecated
|
|
Expr.setFilters["nth"] = Expr.setFilters["eq"];
|
|
|
|
// Back-compat
|
|
Expr.filters = Expr.pseudos;
|
|
|
|
// IE6/7 return a modified href
|
|
if ( !assertHrefNotNormalized ) {
|
|
Expr.attrHandle = {
|
|
"href": function( elem ) {
|
|
return elem.getAttribute( "href", 2 );
|
|
},
|
|
"type": function( elem ) {
|
|
return elem.getAttribute("type");
|
|
}
|
|
};
|
|
}
|
|
|
|
// Add getElementsByName if usable
|
|
if ( assertUsableName ) {
|
|
Expr.order.push("NAME");
|
|
Expr.find["NAME"] = function( name, context ) {
|
|
if ( typeof context.getElementsByName !== strundefined ) {
|
|
return context.getElementsByName( name );
|
|
}
|
|
};
|
|
}
|
|
|
|
// Add getElementsByClassName if usable
|
|
if ( assertUsableClassName ) {
|
|
Expr.order.splice( 1, 0, "CLASS" );
|
|
Expr.find["CLASS"] = function( className, context, xml ) {
|
|
if ( typeof context.getElementsByClassName !== strundefined && !xml ) {
|
|
return context.getElementsByClassName( className );
|
|
}
|
|
};
|
|
}
|
|
|
|
// If slice is not available, provide a backup
|
|
try {
|
|
slice.call( docElem.childNodes, 0 )[0].nodeType;
|
|
} catch ( e ) {
|
|
slice = function( i ) {
|
|
var elem, results = [];
|
|
for ( ; (elem = this[i]); i++ ) {
|
|
results.push( elem );
|
|
}
|
|
return results;
|
|
};
|
|
}
|
|
|
|
var 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;
|
|
};
|
|
|
|
// Element contains another
|
|
var contains = Sizzle.contains = docElem.compareDocumentPosition ?
|
|
function( a, b ) {
|
|
return !!( a.compareDocumentPosition( b ) & 16 );
|
|
} :
|
|
docElem.contains ?
|
|
function( a, b ) {
|
|
var adown = a.nodeType === 9 ? a.documentElement : a,
|
|
bup = b.parentNode;
|
|
return a === bup || !!( bup && bup.nodeType === 1 && adown.contains && adown.contains(bup) );
|
|
} :
|
|
function( a, b ) {
|
|
while ( (b = b.parentNode) ) {
|
|
if ( b === a ) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Utility function for retrieving the text value of an array of DOM nodes
|
|
* @param {Array|Element} elem
|
|
*/
|
|
var getText = Sizzle.getText = function( elem ) {
|
|
var node,
|
|
ret = "",
|
|
i = 0,
|
|
nodeType = elem.nodeType;
|
|
|
|
if ( nodeType ) {
|
|
if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
|
|
// Use textContent for elements
|
|
// innerText usage removed for consistency of new lines (see #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
|
|
} else {
|
|
|
|
// If no nodeType, this is expected to be an array
|
|
for ( ; (node = elem[i]); i++ ) {
|
|
// Do not traverse comment nodes
|
|
ret += getText( node );
|
|
}
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
Sizzle.attr = function( elem, name ) {
|
|
var attr,
|
|
xml = isXML( elem );
|
|
|
|
if ( !xml ) {
|
|
name = name.toLowerCase();
|
|
}
|
|
if ( Expr.attrHandle[ name ] ) {
|
|
return Expr.attrHandle[ name ]( elem );
|
|
}
|
|
if ( assertAttributes || xml ) {
|
|
return elem.getAttribute( name );
|
|
}
|
|
attr = elem.getAttributeNode( name );
|
|
return attr ?
|
|
typeof elem[ name ] === "boolean" ?
|
|
elem[ name ] ? name : null :
|
|
attr.specified ? attr.value : null :
|
|
null;
|
|
};
|
|
|
|
Sizzle.error = function( msg ) {
|
|
throw new Error( "Syntax error, unrecognized expression: " + msg );
|
|
};
|
|
|
|
// Check if the JavaScript engine is using some sort of
|
|
// optimization where it does not always call our comparision
|
|
// function. If that is the case, discard the hasDuplicate value.
|
|
// Thus far that includes Google Chrome.
|
|
[0, 0].sort(function() {
|
|
return (baseHasDuplicate = 0);
|
|
});
|
|
|
|
|
|
if ( docElem.compareDocumentPosition ) {
|
|
sortOrder = function( a, b ) {
|
|
if ( a === b ) {
|
|
hasDuplicate = true;
|
|
return 0;
|
|
}
|
|
|
|
return ( !a.compareDocumentPosition || !b.compareDocumentPosition ?
|
|
a.compareDocumentPosition :
|
|
a.compareDocumentPosition(b) & 4
|
|
) ? -1 : 1;
|
|
};
|
|
|
|
} else {
|
|
sortOrder = function( a, b ) {
|
|
// The nodes are identical, we can exit early
|
|
if ( a === b ) {
|
|
hasDuplicate = true;
|
|
return 0;
|
|
|
|
// Fallback to using sourceIndex (in IE) if it's available on both nodes
|
|
} else if ( a.sourceIndex && b.sourceIndex ) {
|
|
return a.sourceIndex - b.sourceIndex;
|
|
}
|
|
|
|
var al, bl,
|
|
ap = [],
|
|
bp = [],
|
|
aup = a.parentNode,
|
|
bup = b.parentNode,
|
|
cur = aup;
|
|
|
|
// If the nodes are siblings (or identical) we can do a quick check
|
|
if ( aup === bup ) {
|
|
return siblingCheck( a, b );
|
|
|
|
// If no parents were found then the nodes are disconnected
|
|
} else if ( !aup ) {
|
|
return -1;
|
|
|
|
} else if ( !bup ) {
|
|
return 1;
|
|
}
|
|
|
|
// Otherwise they're somewhere else in the tree so we need
|
|
// to build up a full list of the parentNodes for comparison
|
|
while ( cur ) {
|
|
ap.unshift( cur );
|
|
cur = cur.parentNode;
|
|
}
|
|
|
|
cur = bup;
|
|
|
|
while ( cur ) {
|
|
bp.unshift( cur );
|
|
cur = cur.parentNode;
|
|
}
|
|
|
|
al = ap.length;
|
|
bl = bp.length;
|
|
|
|
// Start walking down the tree looking for a discrepancy
|
|
for ( var i = 0; i < al && i < bl; i++ ) {
|
|
if ( ap[i] !== bp[i] ) {
|
|
return siblingCheck( ap[i], bp[i] );
|
|
}
|
|
}
|
|
|
|
// We ended someplace up the tree so do a sibling check
|
|
return i === al ?
|
|
siblingCheck( a, bp[i], -1 ) :
|
|
siblingCheck( ap[i], b, 1 );
|
|
};
|
|
|
|
siblingCheck = function( a, b, ret ) {
|
|
if ( a === b ) {
|
|
return ret;
|
|
}
|
|
|
|
var cur = a.nextSibling;
|
|
|
|
while ( cur ) {
|
|
if ( cur === b ) {
|
|
return -1;
|
|
}
|
|
|
|
cur = cur.nextSibling;
|
|
}
|
|
|
|
return 1;
|
|
};
|
|
}
|
|
|
|
// Document sorting and removing duplicates
|
|
Sizzle.uniqueSort = function( results ) {
|
|
var elem,
|
|
i = 1;
|
|
|
|
if ( sortOrder ) {
|
|
hasDuplicate = baseHasDuplicate;
|
|
results.sort( sortOrder );
|
|
|
|
if ( hasDuplicate ) {
|
|
for ( ; (elem = results[i]); i++ ) {
|
|
if ( elem === results[ i - 1 ] ) {
|
|
results.splice( i--, 1 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return results;
|
|
};
|
|
|
|
function multipleContexts( selector, contexts, results, seed ) {
|
|
var i = 0,
|
|
len = contexts.length;
|
|
for ( ; i < len; i++ ) {
|
|
Sizzle( selector, contexts[i], results, seed );
|
|
}
|
|
}
|
|
|
|
function handlePOSGroup( selector, posfilter, argument, contexts, seed, not ) {
|
|
var results,
|
|
fn = Expr.setFilters[ posfilter.toLowerCase() ];
|
|
|
|
if ( !fn ) {
|
|
Sizzle.error( posfilter );
|
|
}
|
|
|
|
if ( selector || !(results = seed) ) {
|
|
multipleContexts( selector || "*", contexts, (results = []), seed );
|
|
}
|
|
|
|
return results.length > 0 ? fn( results, argument, not ) : [];
|
|
}
|
|
|
|
function handlePOS( selector, context, results, seed, groups ) {
|
|
var match, not, anchor, ret, elements, currentContexts, part, lastIndex,
|
|
i = 0,
|
|
len = groups.length,
|
|
rpos = matchExpr["POS"],
|
|
// This is generated here in case matchExpr["POS"] is extended
|
|
rposgroups = new RegExp( "^" + rpos.source + "(?!" + whitespace + ")", "i" ),
|
|
// This is for making sure non-participating
|
|
// matching groups are represented cross-browser (IE6-8)
|
|
setUndefined = function() {
|
|
var i = 1,
|
|
len = arguments.length - 2;
|
|
for ( ; i < len; i++ ) {
|
|
if ( arguments[i] === undefined ) {
|
|
match[i] = undefined;
|
|
}
|
|
}
|
|
};
|
|
|
|
for ( ; i < len; i++ ) {
|
|
// Reset regex index to 0
|
|
rpos.exec("");
|
|
selector = groups[i];
|
|
ret = [];
|
|
anchor = 0;
|
|
elements = seed;
|
|
while ( (match = rpos.exec( selector )) ) {
|
|
lastIndex = rpos.lastIndex = match.index + match[0].length;
|
|
if ( lastIndex > anchor ) {
|
|
part = selector.slice( anchor, match.index );
|
|
anchor = lastIndex;
|
|
currentContexts = [ context ];
|
|
|
|
if ( rcombinators.test(part) ) {
|
|
if ( elements ) {
|
|
currentContexts = elements;
|
|
}
|
|
elements = seed;
|
|
}
|
|
|
|
if ( (not = rendsWithNot.test( part )) ) {
|
|
part = part.slice( 0, -5 ).replace( rcombinators, "$&*" );
|
|
}
|
|
|
|
if ( match.length > 1 ) {
|
|
match[0].replace( rposgroups, setUndefined );
|
|
}
|
|
elements = handlePOSGroup( part, match[1], match[2], currentContexts, elements, not );
|
|
}
|
|
}
|
|
|
|
if ( elements ) {
|
|
ret = ret.concat( elements );
|
|
|
|
if ( (part = selector.slice( anchor )) && part !== ")" ) {
|
|
if ( rcombinators.test(part) ) {
|
|
multipleContexts( part, ret, results, seed );
|
|
} else {
|
|
Sizzle( part, context, results, seed ? seed.concat(elements) : elements );
|
|
}
|
|
} else {
|
|
push.apply( results, ret );
|
|
}
|
|
} else {
|
|
Sizzle( selector, context, results, seed );
|
|
}
|
|
}
|
|
|
|
// Do not sort if this is a single filter
|
|
return len === 1 ? results : Sizzle.uniqueSort( results );
|
|
}
|
|
|
|
function tokenize( selector, context, xml ) {
|
|
var tokens, soFar, type,
|
|
groups = [],
|
|
i = 0,
|
|
|
|
// Catch obvious selector issues: terminal ")"; nonempty fallback match
|
|
// rselector never fails to match *something*
|
|
match = rselector.exec( selector ),
|
|
matched = !match.pop() && !match.pop(),
|
|
selectorGroups = matched && selector.match( rgroups ) || [""],
|
|
|
|
preFilters = Expr.preFilter,
|
|
filters = Expr.filter,
|
|
checkContext = !xml && context !== document;
|
|
|
|
for ( ; (soFar = selectorGroups[i]) != null && matched; i++ ) {
|
|
groups.push( tokens = [] );
|
|
|
|
// Need to make sure we're within a narrower context if necessary
|
|
// Adding a descendant combinator will generate what is needed
|
|
if ( checkContext ) {
|
|
soFar = " " + soFar;
|
|
}
|
|
|
|
while ( soFar ) {
|
|
matched = false;
|
|
|
|
// Combinators
|
|
if ( (match = rcombinators.exec( soFar )) ) {
|
|
soFar = soFar.slice( match[0].length );
|
|
|
|
// Cast descendant combinators to space
|
|
matched = tokens.push({ part: match.pop().replace( rtrim, " " ), captures: match });
|
|
}
|
|
|
|
// Filters
|
|
for ( type in filters ) {
|
|
if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
|
|
(match = preFilters[ type ]( match, context, xml )) ) ) {
|
|
|
|
soFar = soFar.slice( match.shift().length );
|
|
matched = tokens.push({ part: type, captures: match });
|
|
}
|
|
}
|
|
|
|
if ( !matched ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !matched ) {
|
|
Sizzle.error( selector );
|
|
}
|
|
|
|
return groups;
|
|
}
|
|
|
|
function addCombinator( matcher, combinator, context ) {
|
|
var dir = combinator.dir,
|
|
doneName = done++;
|
|
|
|
if ( !matcher ) {
|
|
// If there is no matcher to check, check against the context
|
|
matcher = function( elem ) {
|
|
return elem === context;
|
|
};
|
|
}
|
|
return combinator.first ?
|
|
function( elem, context ) {
|
|
while ( (elem = elem[ dir ]) ) {
|
|
if ( elem.nodeType === 1 ) {
|
|
return matcher( elem, context ) && elem;
|
|
}
|
|
}
|
|
} :
|
|
function( elem, context ) {
|
|
var cache,
|
|
dirkey = doneName + "." + dirruns,
|
|
cachedkey = dirkey + "." + cachedruns;
|
|
while ( (elem = elem[ dir ]) ) {
|
|
if ( elem.nodeType === 1 ) {
|
|
if ( (cache = elem[ expando ]) === cachedkey ) {
|
|
return elem.sizset;
|
|
} else if ( typeof cache === "string" && cache.indexOf(dirkey) === 0 ) {
|
|
if ( elem.sizset ) {
|
|
return elem;
|
|
}
|
|
} else {
|
|
elem[ expando ] = cachedkey;
|
|
if ( matcher( elem, context ) ) {
|
|
elem.sizset = true;
|
|
return elem;
|
|
}
|
|
elem.sizset = false;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
function addMatcher( higher, deeper ) {
|
|
return higher ?
|
|
function( elem, context ) {
|
|
var result = deeper( elem, context );
|
|
return result && higher( result === true ? elem : result, context );
|
|
} :
|
|
deeper;
|
|
}
|
|
|
|
// ["TAG", ">", "ID", " ", "CLASS"]
|
|
function matcherFromTokens( tokens, context, xml ) {
|
|
var token, matcher,
|
|
i = 0;
|
|
|
|
for ( ; (token = tokens[i]); i++ ) {
|
|
if ( Expr.relative[ token.part ] ) {
|
|
matcher = addCombinator( matcher, Expr.relative[ token.part ], context );
|
|
} else {
|
|
token.captures.push( context, xml );
|
|
matcher = addMatcher( matcher, Expr.filter[ token.part ].apply( null, token.captures ) );
|
|
}
|
|
}
|
|
|
|
return matcher;
|
|
}
|
|
|
|
function matcherFromGroupMatchers( matchers ) {
|
|
return function( elem, context ) {
|
|
var matcher,
|
|
j = 0;
|
|
for ( ; (matcher = matchers[j]); j++ ) {
|
|
if ( matcher(elem, context) ) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
}
|
|
|
|
var compile = Sizzle.compile = function( selector, context, xml ) {
|
|
var tokens, group, i,
|
|
cached = compilerCache[ selector ];
|
|
|
|
// Return a cached group function if already generated (context dependent)
|
|
if ( cached && cached.context === context ) {
|
|
return cached;
|
|
}
|
|
|
|
// Generate a function of recursive functions that can be used to check each element
|
|
group = tokenize( selector, context, xml );
|
|
for ( i = 0; (tokens = group[i]); i++ ) {
|
|
group[i] = matcherFromTokens( tokens, context, xml );
|
|
}
|
|
|
|
// Cache the compiled function
|
|
cached = compilerCache[ selector ] = matcherFromGroupMatchers( group );
|
|
cached.context = context;
|
|
cached.runs = cached.dirruns = 0;
|
|
cachedSelectors.push( selector );
|
|
// Ensure only the most recent are cached
|
|
if ( cachedSelectors.length > Expr.cacheLength ) {
|
|
delete compilerCache[ cachedSelectors.shift() ];
|
|
}
|
|
return cached;
|
|
};
|
|
|
|
Sizzle.matches = function( expr, elements ) {
|
|
return Sizzle( expr, null, null, elements );
|
|
};
|
|
|
|
Sizzle.matchesSelector = function( elem, expr ) {
|
|
return Sizzle( expr, null, null, [ elem ] ).length > 0;
|
|
};
|
|
|
|
var select = function( selector, context, results, seed, xml ) {
|
|
// Remove excessive whitespace
|
|
selector = selector.replace( rtrim, "$1" );
|
|
var elements, matcher, i, len, elem, token,
|
|
type, findContext, notTokens,
|
|
match = selector.match( rgroups ),
|
|
tokens = selector.match( rtokens ),
|
|
contextNodeType = context.nodeType;
|
|
|
|
// POS handling
|
|
if ( matchExpr["POS"].test(selector) ) {
|
|
return handlePOS( selector, context, results, seed, match );
|
|
}
|
|
|
|
if ( seed ) {
|
|
elements = slice.call( seed, 0 );
|
|
|
|
// To maintain document order, only narrow the
|
|
// set if there is one group
|
|
} else if ( match && match.length === 1 ) {
|
|
|
|
// Take a shortcut and set the context if the root selector is an ID
|
|
if ( tokens.length > 1 && contextNodeType === 9 && !xml &&
|
|
(match = matchExpr["ID"].exec( tokens[0] )) ) {
|
|
|
|
context = Expr.find["ID"]( match[1], context, xml )[0];
|
|
if ( !context ) {
|
|
return results;
|
|
}
|
|
|
|
selector = selector.slice( tokens.shift().length );
|
|
}
|
|
|
|
findContext = ( (match = rsibling.exec( tokens[0] )) && !match.index && context.parentNode ) || context;
|
|
|
|
// Get the last token, excluding :not
|
|
notTokens = tokens.pop();
|
|
token = notTokens.split(":not")[0];
|
|
|
|
for ( i = 0, len = Expr.order.length; i < len; i++ ) {
|
|
type = Expr.order[i];
|
|
|
|
if ( (match = matchExpr[ type ].exec( token )) ) {
|
|
elements = Expr.find[ type ]( (match[1] || "").replace( rbackslash, "" ), findContext, xml );
|
|
|
|
if ( elements == null ) {
|
|
continue;
|
|
}
|
|
|
|
if ( token === notTokens ) {
|
|
selector = selector.slice( 0, selector.length - notTokens.length ) +
|
|
token.replace( matchExpr[ type ], "" );
|
|
|
|
if ( !selector ) {
|
|
push.apply( results, slice.call(elements, 0) );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only loop over the given elements once
|
|
// If selector is empty, we're already done
|
|
if ( selector ) {
|
|
matcher = compile( selector, context, xml );
|
|
dirruns = matcher.dirruns++;
|
|
|
|
if ( elements == null ) {
|
|
elements = Expr.find["TAG"]( "*", (rsibling.test( selector ) && context.parentNode) || context );
|
|
}
|
|
for ( i = 0; (elem = elements[i]); i++ ) {
|
|
cachedruns = matcher.runs++;
|
|
if ( matcher(elem, context) ) {
|
|
results.push( elem );
|
|
}
|
|
}
|
|
}
|
|
|
|
return results;
|
|
};
|
|
|
|
if ( document.querySelectorAll ) {
|
|
(function() {
|
|
var disconnectedMatch,
|
|
oldSelect = select,
|
|
rescape = /'|\\/g,
|
|
rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,
|
|
rbuggyQSA = [],
|
|
// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
|
|
// A support test would require too much code (would include document ready)
|
|
// just skip matchesSelector for :active
|
|
rbuggyMatches = [":active"],
|
|
matches = docElem.matchesSelector ||
|
|
docElem.mozMatchesSelector ||
|
|
docElem.webkitMatchesSelector ||
|
|
docElem.oMatchesSelector ||
|
|
docElem.msMatchesSelector;
|
|
|
|
// Build QSA regex
|
|
// Regex strategy adopted from Diego Perini
|
|
assert(function( div ) {
|
|
div.innerHTML = "<select><option selected></option></select>";
|
|
|
|
// IE8 - Some boolean attributes are not treated correctly
|
|
if ( !div.querySelectorAll("[selected]").length ) {
|
|
rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" );
|
|
}
|
|
|
|
// Webkit/Opera - :checked should return selected option elements
|
|
// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
|
|
// IE8 throws error here (do not put tests after this one)
|
|
if ( !div.querySelectorAll(":checked").length ) {
|
|
rbuggyQSA.push(":checked");
|
|
}
|
|
});
|
|
|
|
assert(function( div ) {
|
|
|
|
// Opera 10-12/IE9 - ^= $= *= and empty values
|
|
// Should not select anything
|
|
div.innerHTML = "<p test=''></p>";
|
|
if ( div.querySelectorAll("[test^='']").length ) {
|
|
rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" );
|
|
}
|
|
|
|
// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
|
|
// IE8 throws error here (do not put tests after this one)
|
|
div.innerHTML = "<input type='hidden'>";
|
|
if ( !div.querySelectorAll(":enabled").length ) {
|
|
rbuggyQSA.push(":enabled", ":disabled");
|
|
}
|
|
});
|
|
|
|
rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
|
|
|
|
select = function( selector, context, results, seed, xml ) {
|
|
// Only use querySelectorAll when not filtering,
|
|
// when this is not xml,
|
|
// and when no QSA bugs apply
|
|
if ( !seed && !xml && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
|
|
if ( context.nodeType === 9 ) {
|
|
try {
|
|
push.apply( results, slice.call(context.querySelectorAll( selector ), 0) );
|
|
return results;
|
|
} catch(qsaError) {}
|
|
// qSA works strangely on Element-rooted queries
|
|
// We can work around this by specifying an extra ID on the root
|
|
// and working up from there (Thanks to Andrew Dupont for the technique)
|
|
// IE 8 doesn't work on object elements
|
|
} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
|
|
var old = context.getAttribute("id"),
|
|
nid = old || expando,
|
|
newContext = rsibling.test( selector ) && context.parentNode || context;
|
|
|
|
if ( old ) {
|
|
nid = nid.replace( rescape, "\\$&" );
|
|
} else {
|
|
context.setAttribute( "id", nid );
|
|
}
|
|
|
|
try {
|
|
push.apply( results, slice.call( newContext.querySelectorAll(
|
|
selector.replace( rgroups, "[id='" + nid + "'] $&" )
|
|
), 0 ) );
|
|
return results;
|
|
} catch(qsaError) {
|
|
} finally {
|
|
if ( !old ) {
|
|
context.removeAttribute("id");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return oldSelect( selector, context, results, seed, xml );
|
|
};
|
|
|
|
if ( matches ) {
|
|
assert(function( div ) {
|
|
// Check to see if it's possible to do matchesSelector
|
|
// on a disconnected node (IE 9)
|
|
disconnectedMatch = matches.call( div, "div" );
|
|
|
|
// This should fail with an exception
|
|
// Gecko does not error, returns false instead
|
|
try {
|
|
matches.call( div, "[test!='']:sizzle" );
|
|
rbuggyMatches.push( Expr.match.PSEUDO );
|
|
} catch ( e ) {}
|
|
});
|
|
|
|
// rbuggyMatches always contains :active, so no need for a length check
|
|
rbuggyMatches = /* rbuggyMatches.length && */ new RegExp( rbuggyMatches.join("|") );
|
|
|
|
Sizzle.matchesSelector = function( elem, expr ) {
|
|
// Make sure that attribute selectors are quoted
|
|
expr = expr.replace( rattributeQuotes, "='$1']" );
|
|
|
|
// rbuggyMatches always contains :active, so no need for an existence check
|
|
if ( !isXML( elem ) && !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 || 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, null, null, [ elem ] ).length > 0;
|
|
};
|
|
}
|
|
})();
|
|
}
|
|
|
|
// Override sizzle attribute retrieval
|
|
Sizzle.attr = jQuery.attr;
|
|
jQuery.find = Sizzle;
|
|
jQuery.expr = Sizzle.selectors;
|
|
jQuery.expr[":"] = jQuery.expr.pseudos;
|
|
jQuery.unique = Sizzle.uniqueSort;
|
|
jQuery.text = Sizzle.getText;
|
|
jQuery.isXMLDoc = Sizzle.isXML;
|
|
jQuery.contains = Sizzle.contains;
|
|
|
|
|
|
})( window );
|
|
var runtil = /Until$/,
|
|
rparentsprev = /^(?:parents|prev(?:Until|All))/,
|
|
isSimple = /^.[^:#\[\.,]*$/,
|
|
rneedsContext = jQuery.expr.match.needsContext,
|
|
// methods guaranteed to produce a unique set when starting from a unique set
|
|
guaranteedUnique = {
|
|
children: true,
|
|
contents: true,
|
|
next: true,
|
|
prev: true
|
|
};
|
|
|
|
jQuery.fn.extend({
|
|
find: function( selector ) {
|
|
var i, l, length, n, r, ret,
|
|
self = this;
|
|
|
|
if ( typeof selector !== "string" ) {
|
|
return jQuery( selector ).filter(function() {
|
|
for ( i = 0, l = self.length; i < l; i++ ) {
|
|
if ( jQuery.contains( self[ i ], this ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
ret = this.pushStack( "", "find", selector );
|
|
|
|
for ( i = 0, l = this.length; i < l; i++ ) {
|
|
length = ret.length;
|
|
jQuery.find( selector, this[i], ret );
|
|
|
|
if ( i > 0 ) {
|
|
// Make sure that the results are unique
|
|
for ( n = length; n < ret.length; n++ ) {
|
|
for ( r = 0; r < length; r++ ) {
|
|
if ( ret[r] === ret[n] ) {
|
|
ret.splice(n--, 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
},
|
|
|
|
has: function( target ) {
|
|
var 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;
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
not: function( selector ) {
|
|
return this.pushStack( winnow(this, selector, false), "not", selector);
|
|
},
|
|
|
|
filter: function( selector ) {
|
|
return this.pushStack( winnow(this, selector, true), "filter", selector );
|
|
},
|
|
|
|
is: function( selector ) {
|
|
return !!selector && (
|
|
typeof selector === "string" ?
|
|
// If this is a positional/relative selector, check membership in the returned set
|
|
// so $("p:first").is("p:last") won't return true for a doc with two "p".
|
|
rneedsContext.test( selector ) ?
|
|
jQuery( selector, this.context ).index( this[0] ) >= 0 :
|
|
jQuery.filter( selector, this ).length > 0 :
|
|
this.filter( selector ).length > 0 );
|
|
},
|
|
|
|
closest: function( selectors, context ) {
|
|
var cur,
|
|
i = 0,
|
|
l = this.length,
|
|
ret = [],
|
|
pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
|
|
jQuery( selectors, context || this.context ) :
|
|
0;
|
|
|
|
for ( ; i < l; i++ ) {
|
|
cur = this[i];
|
|
|
|
while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) {
|
|
if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
|
|
ret.push( cur );
|
|
break;
|
|
}
|
|
cur = cur.parentNode;
|
|
}
|
|
}
|
|
|
|
ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
|
|
|
|
return this.pushStack( ret, "closest", selectors );
|
|
},
|
|
|
|
// Determine the position of an element within
|
|
// the matched set of elements
|
|
index: function( elem ) {
|
|
|
|
// No argument, return index in parent
|
|
if ( !elem ) {
|
|
return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
|
|
}
|
|
|
|
// index in selector
|
|
if ( typeof elem === "string" ) {
|
|
return jQuery.inArray( this[0], jQuery( elem ) );
|
|
}
|
|
|
|
// Locate the position of the desired element
|
|
return jQuery.inArray(
|
|
// If it receives a jQuery object, the first element is used
|
|
elem.jquery ? elem[0] : elem, this );
|
|
},
|
|
|
|
add: function( selector, context ) {
|
|
var set = typeof selector === "string" ?
|
|
jQuery( selector, context ) :
|
|
jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
|
|
all = jQuery.merge( this.get(), set );
|
|
|
|
return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
|
|
all :
|
|
jQuery.unique( all ) );
|
|
},
|
|
|
|
addBack: function( selector ) {
|
|
return this.add( selector == null ?
|
|
this.prevObject : this.prevObject.filter(selector)
|
|
);
|
|
}
|
|
});
|
|
|
|
jQuery.fn.andSelf = jQuery.fn.addBack;
|
|
|
|
// A painfully simple check to see if an element is disconnected
|
|
// from a document (should be improved, where feasible).
|
|
function isDisconnected( node ) {
|
|
return !node || !node.parentNode || node.parentNode.nodeType === 11;
|
|
}
|
|
|
|
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 jQuery.dir( elem, "parentNode" );
|
|
},
|
|
parentsUntil: function( elem, i, until ) {
|
|
return jQuery.dir( elem, "parentNode", until );
|
|
},
|
|
next: function( elem ) {
|
|
return sibling( elem, "nextSibling" );
|
|
},
|
|
prev: function( elem ) {
|
|
return sibling( elem, "previousSibling" );
|
|
},
|
|
nextAll: function( elem ) {
|
|
return jQuery.dir( elem, "nextSibling" );
|
|
},
|
|
prevAll: function( elem ) {
|
|
return jQuery.dir( elem, "previousSibling" );
|
|
},
|
|
nextUntil: function( elem, i, until ) {
|
|
return jQuery.dir( elem, "nextSibling", until );
|
|
},
|
|
prevUntil: function( elem, i, until ) {
|
|
return jQuery.dir( elem, "previousSibling", until );
|
|
},
|
|
siblings: function( elem ) {
|
|
return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
|
|
},
|
|
children: function( elem ) {
|
|
return jQuery.sibling( elem.firstChild );
|
|
},
|
|
contents: function( elem ) {
|
|
return 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 ( !runtil.test( name ) ) {
|
|
selector = until;
|
|
}
|
|
|
|
if ( selector && typeof selector === "string" ) {
|
|
ret = jQuery.filter( selector, ret );
|
|
}
|
|
|
|
ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
|
|
|
|
if ( this.length > 1 && rparentsprev.test( name ) ) {
|
|
ret = ret.reverse();
|
|
}
|
|
|
|
return this.pushStack( ret, name, core_slice.call( arguments ).join(",") );
|
|
};
|
|
});
|
|
|
|
jQuery.extend({
|
|
filter: function( expr, elems, not ) {
|
|
if ( not ) {
|
|
expr = ":not(" + expr + ")";
|
|
}
|
|
|
|
return elems.length === 1 ?
|
|
jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
|
|
jQuery.find.matches(expr, elems);
|
|
},
|
|
|
|
dir: function( elem, dir, until ) {
|
|
var matched = [],
|
|
cur = elem[ dir ];
|
|
|
|
while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
|
|
if ( cur.nodeType === 1 ) {
|
|
matched.push( cur );
|
|
}
|
|
cur = cur[dir];
|
|
}
|
|
return matched;
|
|
},
|
|
|
|
sibling: function( n, elem ) {
|
|
var r = [];
|
|
|
|
for ( ; n; n = n.nextSibling ) {
|
|
if ( n.nodeType === 1 && n !== elem ) {
|
|
r.push( n );
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
});
|
|
|
|
// Implement the identical functionality for filter and not
|
|
function winnow( elements, qualifier, keep ) {
|
|
|
|
// Can't pass null or undefined to indexOf in Firefox 4
|
|
// Set to 0 to skip string check
|
|
qualifier = qualifier || 0;
|
|
|
|
if ( jQuery.isFunction( qualifier ) ) {
|
|
return jQuery.grep(elements, function( elem, i ) {
|
|
var retVal = !!qualifier.call( elem, i, elem );
|
|
return retVal === keep;
|
|
});
|
|
|
|
} else if ( qualifier.nodeType ) {
|
|
return jQuery.grep(elements, function( elem, i ) {
|
|
return ( elem === qualifier ) === keep;
|
|
});
|
|
|
|
} else if ( typeof qualifier === "string" ) {
|
|
var filtered = jQuery.grep(elements, function( elem ) {
|
|
return elem.nodeType === 1;
|
|
});
|
|
|
|
if ( isSimple.test( qualifier ) ) {
|
|
return jQuery.filter(qualifier, filtered, !keep);
|
|
} else {
|
|
qualifier = jQuery.filter( qualifier, filtered );
|
|
}
|
|
}
|
|
|
|
return jQuery.grep(elements, function( elem, i ) {
|
|
return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
|
|
});
|
|
}
|
|
function createSafeFragment( document ) {
|
|
var list = nodeNames.split( "|" ),
|
|
safeFrag = document.createDocumentFragment();
|
|
|
|
if ( safeFrag.createElement ) {
|
|
while ( list.length ) {
|
|
safeFrag.createElement(
|
|
list.pop()
|
|
);
|
|
}
|
|
}
|
|
return safeFrag;
|
|
}
|
|
|
|
var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
|
|
"header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
|
|
rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
|
|
rleadingWhitespace = /^\s+/,
|
|
rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
|
|
rtagName = /<([\w:]+)/,
|
|
rtbody = /<tbody/i,
|
|
rhtml = /<|&#?\w+;/,
|
|
rnoInnerhtml = /<(?:script|style|link)/i,
|
|
rnocache = /<(?:script|object|embed|option|style)/i,
|
|
rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
|
|
rcheckableType = /^(?:checkbox|radio)$/,
|
|
// checked="checked" or checked
|
|
rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
|
|
rscriptType = /\/(java|ecma)script/i,
|
|
rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,
|
|
wrapMap = {
|
|
option: [ 1, "<select multiple='multiple'>", "</select>" ],
|
|
legend: [ 1, "<fieldset>", "</fieldset>" ],
|
|
thead: [ 1, "<table>", "</table>" ],
|
|
tr: [ 2, "<table><tbody>", "</tbody></table>" ],
|
|
td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
|
|
col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
|
|
area: [ 1, "<map>", "</map>" ],
|
|
_default: [ 0, "", "" ]
|
|
},
|
|
safeFragment = createSafeFragment( document ),
|
|
fragmentDiv = safeFragment.appendChild( document.createElement("div") );
|
|
|
|
wrapMap.optgroup = wrapMap.option;
|
|
wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
|
|
wrapMap.th = wrapMap.td;
|
|
|
|
// 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.
|
|
if ( !jQuery.support.htmlSerialize ) {
|
|
wrapMap._default = [ 1, "X<div>", "</div>" ];
|
|
}
|
|
|
|
jQuery.fn.extend({
|
|
text: function( value ) {
|
|
return jQuery.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 );
|
|
},
|
|
|
|
wrapAll: function( html ) {
|
|
if ( jQuery.isFunction( html ) ) {
|
|
return this.each(function(i) {
|
|
jQuery(this).wrapAll( html.call(this, i) );
|
|
});
|
|
}
|
|
|
|
if ( this[0] ) {
|
|
// The elements to wrap the target around
|
|
var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
|
|
|
|
if ( this[0].parentNode ) {
|
|
wrap.insertBefore( this[0] );
|
|
}
|
|
|
|
wrap.map(function() {
|
|
var elem = this;
|
|
|
|
while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
|
|
elem = elem.firstChild;
|
|
}
|
|
|
|
return elem;
|
|
}).append( this );
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
wrapInner: function( html ) {
|
|
if ( jQuery.isFunction( html ) ) {
|
|
return this.each(function(i) {
|
|
jQuery(this).wrapInner( html.call(this, i) );
|
|
});
|
|
}
|
|
|
|
return this.each(function() {
|
|
var self = jQuery( this ),
|
|
contents = self.contents();
|
|
|
|
if ( contents.length ) {
|
|
contents.wrapAll( html );
|
|
|
|
} else {
|
|
self.append( html );
|
|
}
|
|
});
|
|
},
|
|
|
|
wrap: function( html ) {
|
|
var isFunction = jQuery.isFunction( html );
|
|
|
|
return this.each(function(i) {
|
|
jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
|
|
});
|
|
},
|
|
|
|
unwrap: function() {
|
|
return this.parent().each(function() {
|
|
if ( !jQuery.nodeName( this, "body" ) ) {
|
|
jQuery( this ).replaceWith( this.childNodes );
|
|
}
|
|
}).end();
|
|
},
|
|
|
|
append: function() {
|
|
return this.domManip(arguments, true, function( elem ) {
|
|
if ( this.nodeType === 1 || this.nodeType === 11 ) {
|
|
this.appendChild( elem );
|
|
}
|
|
});
|
|
},
|
|
|
|
prepend: function() {
|
|
return this.domManip(arguments, true, function( elem ) {
|
|
if ( this.nodeType === 1 || this.nodeType === 11 ) {
|
|
this.insertBefore( elem, this.firstChild );
|
|
}
|
|
});
|
|
},
|
|
|
|
before: function() {
|
|
if ( !isDisconnected( this[0] ) ) {
|
|
return this.domManip(arguments, false, function( elem ) {
|
|
this.parentNode.insertBefore( elem, this );
|
|
});
|
|
}
|
|
|
|
if ( arguments.length ) {
|
|
var set = jQuery.clean( arguments );
|
|
return this.pushStack( jQuery.merge( set, this ), "before", this.selector );
|
|
}
|
|
},
|
|
|
|
after: function() {
|
|
if ( !isDisconnected( this[0] ) ) {
|
|
return this.domManip(arguments, false, function( elem ) {
|
|
this.parentNode.insertBefore( elem, this.nextSibling );
|
|
});
|
|
}
|
|
|
|
if ( arguments.length ) {
|
|
var set = jQuery.clean( arguments );
|
|
return this.pushStack( jQuery.merge( this, set ), "after", this.selector );
|
|
}
|
|
},
|
|
|
|
// keepData is for internal use only--do not document
|
|
remove: function( selector, keepData ) {
|
|
var elem,
|
|
i = 0;
|
|
|
|
for ( ; (elem = this[i]) != null; i++ ) {
|
|
if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
|
|
if ( !keepData && elem.nodeType === 1 ) {
|
|
jQuery.cleanData( elem.getElementsByTagName("*") );
|
|
jQuery.cleanData( [ elem ] );
|
|
}
|
|
|
|
if ( elem.parentNode ) {
|
|
elem.parentNode.removeChild( elem );
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
empty: function() {
|
|
var elem,
|
|
i = 0;
|
|
|
|
for ( ; (elem = this[i]) != null; i++ ) {
|
|
// Remove element nodes and prevent memory leaks
|
|
if ( elem.nodeType === 1 ) {
|
|
jQuery.cleanData( elem.getElementsByTagName("*") );
|
|
}
|
|
|
|
// Remove any remaining nodes
|
|
while ( elem.firstChild ) {
|
|
elem.removeChild( elem.firstChild );
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
clone: function( dataAndEvents, deepDataAndEvents ) {
|
|
dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
|
|
deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
|
|
|
|
return this.map( function () {
|
|
return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
|
|
});
|
|
},
|
|
|
|
html: function( value ) {
|
|
return jQuery.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 ) &&
|
|
( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) &&
|
|
( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
|
|
!wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
|
|
|
|
value = value.replace( rxhtmlTag, "<$1></$2>" );
|
|
|
|
try {
|
|
for (; i < l; i++ ) {
|
|
// Remove element nodes and prevent memory leaks
|
|
elem = this[i] || {};
|
|
if ( elem.nodeType === 1 ) {
|
|
jQuery.cleanData( elem.getElementsByTagName( "*" ) );
|
|
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( value ) {
|
|
if ( !isDisconnected( this[0] ) ) {
|
|
// Make sure that the elements are removed from the DOM before they are inserted
|
|
// this can help fix replacing a parent with child elements
|
|
if ( jQuery.isFunction( value ) ) {
|
|
return this.each(function(i) {
|
|
var self = jQuery(this), old = self.html();
|
|
self.replaceWith( value.call( this, i, old ) );
|
|
});
|
|
}
|
|
|
|
if ( typeof value !== "string" ) {
|
|
value = jQuery( value ).detach();
|
|
}
|
|
|
|
return this.each(function() {
|
|
var next = this.nextSibling,
|
|
parent = this.parentNode;
|
|
|
|
jQuery( this ).remove();
|
|
|
|
if ( next ) {
|
|
jQuery(next).before( value );
|
|
} else {
|
|
jQuery(parent).append( value );
|
|
}
|
|
});
|
|
}
|
|
|
|
return this.length ?
|
|
this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
|
|
this;
|
|
},
|
|
|
|
detach: function( selector ) {
|
|
return this.remove( selector, true );
|
|
},
|
|
|
|
domManip: function( args, table, callback ) {
|
|
|
|
// Flatten any nested arrays
|
|
args = [].concat.apply( [], args );
|
|
|
|
var results, first, fragment, iNoClone,
|
|
i = 0,
|
|
value = args[0],
|
|
scripts = [],
|
|
l = this.length;
|
|
|
|
// We can't cloneNode fragments that contain checked, in WebKit
|
|
if ( !jQuery.support.checkClone && l > 1 && typeof value === "string" && rchecked.test( value ) ) {
|
|
return this.each(function() {
|
|
jQuery(this).domManip( args, table, callback );
|
|
});
|
|
}
|
|
|
|
if ( jQuery.isFunction(value) ) {
|
|
return this.each(function(i) {
|
|
var self = jQuery(this);
|
|
args[0] = value.call( this, i, table ? self.html() : undefined );
|
|
self.domManip( args, table, callback );
|
|
});
|
|
}
|
|
|
|
if ( this[0] ) {
|
|
results = jQuery.buildFragment( args, this, scripts );
|
|
fragment = results.fragment;
|
|
first = fragment.firstChild;
|
|
|
|
if ( fragment.childNodes.length === 1 ) {
|
|
fragment = first;
|
|
}
|
|
|
|
if ( first ) {
|
|
table = table && jQuery.nodeName( first, "tr" );
|
|
|
|
// Use the original fragment for the last item instead of the first because it can end up
|
|
// being emptied incorrectly in certain situations (#8070).
|
|
// Fragments from the fragment cache must always be cloned and never used in place.
|
|
for ( iNoClone = results.cacheable || l - 1; i < l; i++ ) {
|
|
callback.call(
|
|
table && jQuery.nodeName( this[i], "table" ) ?
|
|
findOrAppend( this[i], "tbody" ) :
|
|
this[i],
|
|
i === iNoClone ?
|
|
fragment :
|
|
jQuery.clone( fragment, true, true )
|
|
);
|
|
}
|
|
}
|
|
|
|
// Fix #11809: Avoid leaking memory
|
|
fragment = first = null;
|
|
|
|
if ( scripts.length ) {
|
|
jQuery.each( scripts, function( i, elem ) {
|
|
if ( elem.src ) {
|
|
if ( jQuery.ajax ) {
|
|
jQuery.ajax({
|
|
url: elem.src,
|
|
type: "GET",
|
|
dataType: "script",
|
|
async: false,
|
|
global: false,
|
|
"throws": true
|
|
});
|
|
} else {
|
|
jQuery.error("no ajax");
|
|
}
|
|
} else {
|
|
jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "" ) );
|
|
}
|
|
|
|
if ( elem.parentNode ) {
|
|
elem.parentNode.removeChild( elem );
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
});
|
|
|
|
function findOrAppend( elem, tag ) {
|
|
return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) );
|
|
}
|
|
|
|
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 cloneFixAttributes( src, dest ) {
|
|
var nodeName;
|
|
|
|
// We do not need to do anything for non-Elements
|
|
if ( dest.nodeType !== 1 ) {
|
|
return;
|
|
}
|
|
|
|
// clearAttributes removes the attributes, which we don't want,
|
|
// but also removes the attachEvent events, which we *do* want
|
|
if ( dest.clearAttributes ) {
|
|
dest.clearAttributes();
|
|
}
|
|
|
|
// mergeAttributes, in contrast, only merges back on the
|
|
// original attributes, not the events
|
|
if ( dest.mergeAttributes ) {
|
|
dest.mergeAttributes( src );
|
|
}
|
|
|
|
nodeName = dest.nodeName.toLowerCase();
|
|
|
|
if ( nodeName === "object" ) {
|
|
// IE6-10 improperly clones children of object elements using classid.
|
|
// IE10 throws NoModificationAllowedError if parent is null, #12132.
|
|
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 ( jQuery.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.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;
|
|
|
|
// IE blanks contents when cloning scripts
|
|
} else if ( nodeName === "script" && dest.text !== src.text ) {
|
|
dest.text = src.text;
|
|
}
|
|
|
|
// Event data gets referenced instead of copied if the expando
|
|
// gets copied too
|
|
dest.removeAttribute( jQuery.expando );
|
|
}
|
|
|
|
jQuery.buildFragment = function( args, context, scripts ) {
|
|
var fragment, cacheable, cachehit,
|
|
first = args[ 0 ];
|
|
|
|
// Set context from what may come in as undefined or a jQuery collection or a node
|
|
context = context || document;
|
|
context = (context[0] || context).ownerDocument || context[0] || context;
|
|
|
|
// Ensure that an attr object doesn't incorrectly stand in as a document object
|
|
// Chrome and Firefox seem to allow this to occur and will throw exception
|
|
// Fixes #8950
|
|
if ( typeof context.createDocumentFragment === "undefined" ) {
|
|
context = document;
|
|
}
|
|
|
|
// Only cache "small" (1/2 KB) HTML strings that are associated with the main document
|
|
// Cloning options loses the selected state, so don't cache them
|
|
// IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
|
|
// Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
|
|
// Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501
|
|
if ( args.length === 1 && typeof first === "string" && first.length < 512 && context === document &&
|
|
first.charAt(0) === "<" && !rnocache.test( first ) &&
|
|
(jQuery.support.checkClone || !rchecked.test( first )) &&
|
|
(jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {
|
|
|
|
// Mark cacheable and look for a hit
|
|
cacheable = true;
|
|
fragment = jQuery.fragments[ first ];
|
|
cachehit = fragment !== undefined;
|
|
}
|
|
|
|
if ( !fragment ) {
|
|
fragment = context.createDocumentFragment();
|
|
jQuery.clean( args, context, fragment, scripts );
|
|
|
|
// Update the cache, but only store false
|
|
// unless this is a second parsing of the same content
|
|
if ( cacheable ) {
|
|
jQuery.fragments[ first ] = cachehit && fragment;
|
|
}
|
|
}
|
|
|
|
return { fragment: fragment, cacheable: cacheable };
|
|
};
|
|
|
|
jQuery.fragments = {};
|
|
|
|
jQuery.each({
|
|
appendTo: "append",
|
|
prependTo: "prepend",
|
|
insertBefore: "before",
|
|
insertAfter: "after",
|
|
replaceAll: "replaceWith"
|
|
}, function( name, original ) {
|
|
jQuery.fn[ name ] = function( selector ) {
|
|
var elems,
|
|
i = 0,
|
|
ret = [],
|
|
insert = jQuery( selector ),
|
|
l = insert.length,
|
|
parent = this.length === 1 && this[0].parentNode;
|
|
|
|
if ( (parent == null || parent && parent.nodeType === 11 && parent.childNodes.length === 1) && l === 1 ) {
|
|
insert[ original ]( this[0] );
|
|
return this;
|
|
} else {
|
|
for ( ; i < l; i++ ) {
|
|
elems = ( i > 0 ? this.clone(true) : this ).get();
|
|
jQuery( insert[i] )[ original ]( elems );
|
|
ret = ret.concat( elems );
|
|
}
|
|
|
|
return this.pushStack( ret, name, insert.selector );
|
|
}
|
|
};
|
|
});
|
|
|
|
function getAll( elem ) {
|
|
if ( typeof elem.getElementsByTagName !== "undefined" ) {
|
|
return elem.getElementsByTagName( "*" );
|
|
|
|
} else if ( typeof elem.querySelectorAll !== "undefined" ) {
|
|
return elem.querySelectorAll( "*" );
|
|
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Used in clean, fixes the defaultChecked property
|
|
function fixDefaultChecked( elem ) {
|
|
if ( rcheckableType.test( elem.type ) ) {
|
|
elem.defaultChecked = elem.checked;
|
|
}
|
|
}
|
|
|
|
jQuery.extend({
|
|
clone: function( elem, dataAndEvents, deepDataAndEvents ) {
|
|
var srcElements,
|
|
destElements,
|
|
i,
|
|
clone;
|
|
|
|
if ( jQuery.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 ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
|
|
(elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
|
|
// IE copies events bound via attachEvent when using cloneNode.
|
|
// Calling detachEvent on the clone will also remove the events
|
|
// from the original. In order to get around this, we use some
|
|
// proprietary methods to clear the events. Thanks to MooTools
|
|
// guys for this hotness.
|
|
|
|
cloneFixAttributes( elem, clone );
|
|
|
|
// Using Sizzle here is crazy slow, so we use getElementsByTagName instead
|
|
srcElements = getAll( elem );
|
|
destElements = getAll( clone );
|
|
|
|
// Weird iteration because IE will replace the length property
|
|
// with an element if you are cloning the body and one of the
|
|
// elements on the page has a name or id of "length"
|
|
for ( i = 0; srcElements[i]; ++i ) {
|
|
// Ensure that the destination node is not null; Fixes #9587
|
|
if ( destElements[i] ) {
|
|
cloneFixAttributes( srcElements[i], destElements[i] );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy the events from the original to the clone
|
|
if ( dataAndEvents ) {
|
|
cloneCopyEvent( elem, clone );
|
|
|
|
if ( deepDataAndEvents ) {
|
|
srcElements = getAll( elem );
|
|
destElements = getAll( clone );
|
|
|
|
for ( i = 0; srcElements[i]; ++i ) {
|
|
cloneCopyEvent( srcElements[i], destElements[i] );
|
|
}
|
|
}
|
|
}
|
|
|
|
srcElements = destElements = null;
|
|
|
|
// Return the cloned set
|
|
return clone;
|
|
},
|
|
|
|
clean: function( elems, context, fragment, scripts ) {
|
|
var j, safe, elem, tag, wrap, depth, div, hasBody, tbody, len, handleScript, jsTags,
|
|
i = 0,
|
|
ret = [];
|
|
|
|
// Ensure that context is a document
|
|
if ( !context || typeof context.createDocumentFragment === "undefined" ) {
|
|
context = document;
|
|
}
|
|
|
|
// Use the already-created safe fragment if context permits
|
|
for ( safe = context === document && safeFragment; (elem = elems[i]) != null; i++ ) {
|
|
if ( typeof elem === "number" ) {
|
|
elem += "";
|
|
}
|
|
|
|
if ( !elem ) {
|
|
continue;
|
|
}
|
|
|
|
// Convert html string into DOM nodes
|
|
if ( typeof elem === "string" ) {
|
|
if ( !rhtml.test( elem ) ) {
|
|
elem = context.createTextNode( elem );
|
|
} else {
|
|
// Ensure a safe container in which to render the html
|
|
safe = safe || createSafeFragment( context );
|
|
div = div || safe.appendChild( context.createElement("div") );
|
|
|
|
// Fix "XHTML"-style tags in all browsers
|
|
elem = elem.replace(rxhtmlTag, "<$1></$2>");
|
|
|
|
// Go to html and back, then peel off extra wrappers
|
|
tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase();
|
|
wrap = wrapMap[ tag ] || wrapMap._default;
|
|
depth = wrap[0];
|
|
div.innerHTML = wrap[1] + elem + wrap[2];
|
|
|
|
// Move to the right depth
|
|
while ( depth-- ) {
|
|
div = div.lastChild;
|
|
}
|
|
|
|
// Remove IE's autoinserted <tbody> from table fragments
|
|
if ( !jQuery.support.tbody ) {
|
|
|
|
// String was a <table>, *may* have spurious <tbody>
|
|
hasBody = rtbody.test(elem);
|
|
tbody = tag === "table" && !hasBody ?
|
|
div.firstChild && div.firstChild.childNodes :
|
|
|
|
// String was a bare <thead> or <tfoot>
|
|
wrap[1] === "<table>" && !hasBody ?
|
|
div.childNodes :
|
|
[];
|
|
|
|
for ( j = tbody.length - 1; j >= 0 ; --j ) {
|
|
if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
|
|
tbody[ j ].parentNode.removeChild( tbody[ j ] );
|
|
}
|
|
}
|
|
}
|
|
|
|
// IE completely kills leading whitespace when innerHTML is used
|
|
if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
|
|
div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
|
|
}
|
|
|
|
elem = div.childNodes;
|
|
|
|
// Remember the top-level container for proper cleanup
|
|
div = safe.lastChild;
|
|
}
|
|
}
|
|
|
|
if ( elem.nodeType ) {
|
|
ret.push( elem );
|
|
} else {
|
|
ret = jQuery.merge( ret, elem );
|
|
}
|
|
}
|
|
|
|
// Fix #11356: Clear elements from safeFragment
|
|
if ( div ) {
|
|
safe.removeChild( div );
|
|
elem = div = safe = null;
|
|
}
|
|
|
|
// Reset defaultChecked for any radios and checkboxes
|
|
// about to be appended to the DOM in IE 6/7 (#8060)
|
|
if ( !jQuery.support.appendChecked ) {
|
|
for ( i = 0; (elem = ret[i]) != null; i++ ) {
|
|
if ( jQuery.nodeName( elem, "input" ) ) {
|
|
fixDefaultChecked( elem );
|
|
} else if ( typeof elem.getElementsByTagName !== "undefined" ) {
|
|
jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Append elements to a provided document fragment
|
|
if ( fragment ) {
|
|
// Special handling of each script element
|
|
handleScript = function( elem ) {
|
|
// Check if we consider it executable
|
|
if ( !elem.type || rscriptType.test( elem.type ) ) {
|
|
// Detach the script and store it in the scripts array (if provided) or the fragment
|
|
// Return truthy to indicate that it has been handled
|
|
return scripts ?
|
|
scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) :
|
|
fragment.appendChild( elem );
|
|
}
|
|
};
|
|
|
|
for ( i = 0; (elem = ret[i]) != null; i++ ) {
|
|
// Check if we're done after handling an executable script
|
|
if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) {
|
|
// Append to fragment and handle embedded scripts
|
|
fragment.appendChild( elem );
|
|
if ( typeof elem.getElementsByTagName !== "undefined" ) {
|
|
// handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration
|
|
jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript );
|
|
|
|
// Splice the scripts into ret after their former ancestor and advance our index beyond them
|
|
ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
|
|
i += jsTags.length;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
},
|
|
|
|
cleanData: function( elems, /* internal */ acceptData ) {
|
|
var data, id, elem, type,
|
|
i = 0,
|
|
internalKey = jQuery.expando,
|
|
cache = jQuery.cache,
|
|
deleteExpando = jQuery.support.deleteExpando,
|
|
special = jQuery.event.special;
|
|
|
|
for ( ; (elem = elems[i]) != null; i++ ) {
|
|
|
|
if ( acceptData || jQuery.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 ];
|
|
|
|
// IE does not allow us to delete expando properties from nodes,
|
|
// nor does it have a removeAttribute function on Document nodes;
|
|
// we must handle all of these cases
|
|
if ( deleteExpando ) {
|
|
delete elem[ internalKey ];
|
|
|
|
} else if ( elem.removeAttribute ) {
|
|
elem.removeAttribute( internalKey );
|
|
|
|
} else {
|
|
elem[ internalKey ] = null;
|
|
}
|
|
|
|
jQuery.deletedIds.push( id );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
// Limit scope pollution from any deprecated API
|
|
(function() {
|
|
|
|
var matched, browser;
|
|
|
|
// Use of jQuery.browser is frowned upon.
|
|
// More details: http://api.jquery.com/jQuery.browser
|
|
// jQuery.uaMatch maintained for back-compat
|
|
jQuery.uaMatch = function( ua ) {
|
|
ua = ua.toLowerCase();
|
|
|
|
var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
|
|
/(webkit)[ \/]([\w.]+)/.exec( ua ) ||
|
|
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
|
|
/(msie) ([\w.]+)/.exec( ua ) ||
|
|
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
|
|
[];
|
|
|
|
return {
|
|
browser: match[ 1 ] || "",
|
|
version: match[ 2 ] || "0"
|
|
};
|
|
};
|
|
|
|
matched = jQuery.uaMatch( navigator.userAgent );
|
|
browser = {};
|
|
|
|
if ( matched.browser ) {
|
|
browser[ matched.browser ] = true;
|
|
browser.version = matched.version;
|
|
}
|
|
|
|
// Deprecated, use jQuery.browser.webkit instead
|
|
// Maintained for back-compat only
|
|
if ( browser.webkit ) {
|
|
browser.safari = true;
|
|
}
|
|
|
|
jQuery.browser = browser;
|
|
|
|
jQuery.sub = function() {
|
|
function jQuerySub( selector, context ) {
|
|
return new jQuerySub.fn.init( selector, context );
|
|
}
|
|
jQuery.extend( true, jQuerySub, this );
|
|
jQuerySub.superclass = this;
|
|
jQuerySub.fn = jQuerySub.prototype = this();
|
|
jQuerySub.fn.constructor = jQuerySub;
|
|
jQuerySub.sub = this.sub;
|
|
jQuerySub.fn.init = function init( selector, context ) {
|
|
if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
|
|
context = jQuerySub( context );
|
|
}
|
|
|
|
return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
|
|
};
|
|
jQuerySub.fn.init.prototype = jQuerySub.fn;
|
|
var rootjQuerySub = jQuerySub(document);
|
|
return jQuerySub;
|
|
};
|
|
|
|
})();
|
|
var curCSS, iframe, iframeDoc,
|
|
ralpha = /alpha\([^)]*\)/i,
|
|
ropacity = /opacity=([^)]*)/,
|
|
rposition = /^(top|right|bottom|left)$/,
|
|
rmargin = /^margin/,
|
|
rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ),
|
|
rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
|
|
rrelNum = new RegExp( "^([-+])=(" + core_pnum + ")", "i" ),
|
|
elemdisplay = {},
|
|
|
|
cssShow = { position: "absolute", visibility: "hidden", display: "block" },
|
|
cssNormalTransform = {
|
|
letterSpacing: 0,
|
|
fontWeight: 400,
|
|
lineHeight: 1
|
|
},
|
|
|
|
cssExpand = [ "Top", "Right", "Bottom", "Left" ],
|
|
cssPrefixes = [ "Webkit", "O", "Moz", "ms" ],
|
|
|
|
eventsToggle = jQuery.fn.toggle;
|
|
|
|
// return a css property mapped to a potentially vendor prefixed property
|
|
function vendorPropName( style, name ) {
|
|
|
|
// shortcut for names that are not vendor prefixed
|
|
if ( name in style ) {
|
|
return name;
|
|
}
|
|
|
|
// check for vendor prefixed names
|
|
var capName = name.charAt(0).toUpperCase() + name.slice(1),
|
|
origName = name,
|
|
i = cssPrefixes.length;
|
|
|
|
while ( i-- ) {
|
|
name = cssPrefixes[ i ] + capName;
|
|
if ( name in style ) {
|
|
return name;
|
|
}
|
|
}
|
|
|
|
return origName;
|
|
}
|
|
|
|
function isHidden( elem, el ) {
|
|
elem = el || elem;
|
|
return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
|
|
}
|
|
|
|
function showHide( elements, show ) {
|
|
var elem, display,
|
|
values = [],
|
|
index = 0,
|
|
length = elements.length;
|
|
|
|
for ( ; index < length; index++ ) {
|
|
elem = elements[ index ];
|
|
if ( !elem.style ) {
|
|
continue;
|
|
}
|
|
values[ index ] = jQuery._data( elem, "olddisplay" );
|
|
if ( show ) {
|
|
// Reset the inline display of this element to learn if it is
|
|
// being hidden by cascaded rules or not
|
|
if ( !values[ index ] && elem.style.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", css_defaultDisplay(elem.nodeName) );
|
|
}
|
|
} else {
|
|
display = curCSS( elem, "display" );
|
|
|
|
if ( !values[ index ] && display !== "none" ) {
|
|
jQuery._data( elem, "olddisplay", 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;
|
|
}
|
|
|
|
jQuery.fn.extend({
|
|
css: function( name, value ) {
|
|
return jQuery.access( this, function( elem, name, value ) {
|
|
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, fn2 ) {
|
|
var bool = typeof state === "boolean";
|
|
|
|
if ( jQuery.isFunction( state ) && jQuery.isFunction( fn2 ) ) {
|
|
return eventsToggle.apply( this, arguments );
|
|
}
|
|
|
|
return this.each(function() {
|
|
if ( bool ? state : isHidden( this ) ) {
|
|
jQuery( this ).show();
|
|
} else {
|
|
jQuery( this ).hide();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
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;
|
|
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
// Exclude the following css properties to add px
|
|
cssNumber: {
|
|
"fillOpacity": true,
|
|
"fontWeight": true,
|
|
"lineHeight": true,
|
|
"opacity": true,
|
|
"orphans": true,
|
|
"widows": true,
|
|
"zIndex": true,
|
|
"zoom": true
|
|
},
|
|
|
|
// Add in properties whose names you wish to fix before
|
|
// setting or getting the value
|
|
cssProps: {
|
|
// normalize float css property
|
|
"float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
|
|
},
|
|
|
|
// Get and set the style property on a DOM Node
|
|
style: function( elem, name, value, extra ) {
|
|
// Don't set styles on text and comment nodes
|
|
if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
|
|
return;
|
|
}
|
|
|
|
// Make sure that we're working with the right name
|
|
var ret, type, hooks,
|
|
origName = jQuery.camelCase( name ),
|
|
style = elem.style;
|
|
|
|
name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, 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 relative number strings (+= or -=) to relative numbers. #7345
|
|
if ( type === "string" && (ret = rrelNum.exec( value )) ) {
|
|
value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
|
|
// Fixes bug #9237
|
|
type = "number";
|
|
}
|
|
|
|
// Make sure that NaN and null values aren't set. See: #7116
|
|
if ( value == null || type === "number" && isNaN( value ) ) {
|
|
return;
|
|
}
|
|
|
|
// If a number was passed in, add 'px' to the (except for certain CSS properties)
|
|
if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
|
|
value += "px";
|
|
}
|
|
|
|
// If a hook was provided, use that value, otherwise just set the specified value
|
|
if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
|
|
// Wrapped to prevent IE from throwing errors when 'invalid' values are provided
|
|
// Fixes bug #5509
|
|
try {
|
|
style[ name ] = value;
|
|
} catch(e) {}
|
|
}
|
|
|
|
} else {
|
|
// If a hook was provided get the non-computed value from there
|
|
if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
|
|
return ret;
|
|
}
|
|
|
|
// Otherwise just get the value from the style object
|
|
return style[ name ];
|
|
}
|
|
},
|
|
|
|
css: function( elem, name, numeric, extra ) {
|
|
var val, num, hooks,
|
|
origName = jQuery.camelCase( name );
|
|
|
|
// Make sure that we're working with the right name
|
|
name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, 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 );
|
|
}
|
|
|
|
//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 ( numeric || extra !== undefined ) {
|
|
num = parseFloat( val );
|
|
return numeric || jQuery.isNumeric( num ) ? num || 0 : val;
|
|
}
|
|
return val;
|
|
},
|
|
|
|
// A method for quickly swapping in/out CSS properties to get correct calculations
|
|
swap: function( elem, options, callback ) {
|
|
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.call( elem );
|
|
|
|
// Revert the old values
|
|
for ( name in options ) {
|
|
elem.style[ name ] = old[ name ];
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
});
|
|
|
|
// NOTE: To any future maintainer, we've used both window.getComputedStyle
|
|
// and getComputedStyle here to produce a better gzip size
|
|
if ( window.getComputedStyle ) {
|
|
curCSS = function( elem, name ) {
|
|
var ret, width, minWidth, maxWidth,
|
|
computed = getComputedStyle( elem, null ),
|
|
style = elem.style;
|
|
|
|
if ( computed ) {
|
|
|
|
ret = computed[ name ];
|
|
if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
|
|
ret = jQuery.style( elem, name );
|
|
}
|
|
|
|
// 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 ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
|
|
width = style.width;
|
|
minWidth = style.minWidth;
|
|
maxWidth = style.maxWidth;
|
|
|
|
style.minWidth = style.maxWidth = style.width = ret;
|
|
ret = computed.width;
|
|
|
|
style.width = width;
|
|
style.minWidth = minWidth;
|
|
style.maxWidth = maxWidth;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
};
|
|
} else if ( document.documentElement.currentStyle ) {
|
|
curCSS = function( elem, name ) {
|
|
var left, rsLeft,
|
|
ret = elem.currentStyle && elem.currentStyle[ name ],
|
|
style = elem.style;
|
|
|
|
// Avoid setting ret to empty string here
|
|
// so we don't default to auto
|
|
if ( ret == null && style && 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;
|
|
rsLeft = elem.runtimeStyle && elem.runtimeStyle.left;
|
|
|
|
// Put in the new values to get a computed value out
|
|
if ( rsLeft ) {
|
|
elem.runtimeStyle.left = elem.currentStyle.left;
|
|
}
|
|
style.left = name === "fontSize" ? "1em" : ret;
|
|
ret = style.pixelLeft + "px";
|
|
|
|
// Revert the changed values
|
|
style.left = left;
|
|
if ( rsLeft ) {
|
|
elem.runtimeStyle.left = rsLeft;
|
|
}
|
|
}
|
|
|
|
return ret === "" ? "auto" : ret;
|
|
};
|
|
}
|
|
|
|
function setPositiveNumber( elem, value, subtract ) {
|
|
var matches = rnumsplit.exec( value );
|
|
return matches ?
|
|
Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
|
|
value;
|
|
}
|
|
|
|
function augmentWidthOrHeight( elem, name, extra, isBorderBox ) {
|
|
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" ) {
|
|
// we use jQuery.css instead of curCSS here
|
|
// because of the reliableMarginRight CSS hook!
|
|
val += jQuery.css( elem, extra + cssExpand[ i ], true );
|
|
}
|
|
|
|
// From this point on we use curCSS for maximum performance (relevant in animations)
|
|
if ( isBorderBox ) {
|
|
// border-box includes padding, so remove it if we want content
|
|
if ( extra === "content" ) {
|
|
val -= parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
|
|
}
|
|
|
|
// at this point, extra isn't border nor margin, so remove border
|
|
if ( extra !== "margin" ) {
|
|
val -= parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
|
|
}
|
|
} else {
|
|
// at this point, extra isn't content, so add padding
|
|
val += parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
|
|
|
|
// at this point, extra isn't content nor padding, so add border
|
|
if ( extra !== "padding" ) {
|
|
val += parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
function getWidthOrHeight( elem, name, extra ) {
|
|
|
|
// Start with offset property, which is equivalent to the border-box value
|
|
var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
|
|
valueIsBorderBox = true,
|
|
isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box";
|
|
|
|
if ( val <= 0 ) {
|
|
// Fall back to computed then uncomputed css if necessary
|
|
val = curCSS( elem, name );
|
|
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 && ( jQuery.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
|
|
)
|
|
) + "px";
|
|
}
|
|
|
|
|
|
// Try to determine the default display value of an element
|
|
function css_defaultDisplay( nodeName ) {
|
|
if ( elemdisplay[ nodeName ] ) {
|
|
return elemdisplay[ nodeName ];
|
|
}
|
|
|
|
var elem = jQuery( "<" + nodeName + ">" ).appendTo( document.body ),
|
|
display = elem.css("display");
|
|
elem.remove();
|
|
|
|
// If the simple way fails,
|
|
// get element's real default display by attaching it to a temp iframe
|
|
if ( display === "none" || display === "" ) {
|
|
// Use the already-created iframe if possible
|
|
iframe = document.body.appendChild(
|
|
iframe || jQuery.extend( document.createElement("iframe"), {
|
|
frameBorder: 0,
|
|
width: 0,
|
|
height: 0
|
|
})
|
|
);
|
|
|
|
// Create a cacheable copy of the iframe document on first call.
|
|
// IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
|
|
// document to it; WebKit & Firefox won't allow reusing the iframe document.
|
|
if ( !iframeDoc || !iframe.createElement ) {
|
|
iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
|
|
iframeDoc.write("<!doctype html><html><body>");
|
|
iframeDoc.close();
|
|
}
|
|
|
|
elem = iframeDoc.body.appendChild( iframeDoc.createElement(nodeName) );
|
|
|
|
display = curCSS( elem, "display" );
|
|
document.body.removeChild( iframe );
|
|
}
|
|
|
|
// Store the correct default display
|
|
elemdisplay[ nodeName ] = display;
|
|
|
|
return display;
|
|
}
|
|
|
|
jQuery.each([ "height", "width" ], function( i, name ) {
|
|
jQuery.cssHooks[ name ] = {
|
|
get: function( elem, computed, extra ) {
|
|
if ( computed ) {
|
|
if ( elem.offsetWidth !== 0 || curCSS( elem, "display" ) !== "none" ) {
|
|
return getWidthOrHeight( elem, name, extra );
|
|
} else {
|
|
return jQuery.swap( elem, cssShow, function() {
|
|
return getWidthOrHeight( elem, name, extra );
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
set: function( elem, value, extra ) {
|
|
return setPositiveNumber( elem, value, extra ?
|
|
augmentWidthOrHeight(
|
|
elem,
|
|
name,
|
|
extra,
|
|
jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box"
|
|
) : 0
|
|
);
|
|
}
|
|
};
|
|
});
|
|
|
|
if ( !jQuery.support.opacity ) {
|
|
jQuery.cssHooks.opacity = {
|
|
get: function( elem, computed ) {
|
|
// IE uses filters for opacity
|
|
return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
|
|
( 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 >= 1 && 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 there is no filter style applied in a css rule, we are done
|
|
if ( currentStyle && !currentStyle.filter ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// otherwise, set new filter values
|
|
style.filter = ralpha.test( filter ) ?
|
|
filter.replace( ralpha, opacity ) :
|
|
filter + " " + opacity;
|
|
}
|
|
};
|
|
}
|
|
|
|
// These hooks cannot be added until DOM ready because the support test
|
|
// for it is not run until after DOM ready
|
|
jQuery(function() {
|
|
if ( !jQuery.support.reliableMarginRight ) {
|
|
jQuery.cssHooks.marginRight = {
|
|
get: function( elem, computed ) {
|
|
// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
|
|
// Work around by temporarily setting element display to inline-block
|
|
return jQuery.swap( elem, { "display": "inline-block" }, function() {
|
|
if ( computed ) {
|
|
return curCSS( elem, "marginRight" );
|
|
}
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
// 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
|
|
if ( !jQuery.support.pixelPosition && jQuery.fn.position ) {
|
|
jQuery.each( [ "top", "left" ], function( i, prop ) {
|
|
jQuery.cssHooks[ prop ] = {
|
|
get: function( elem, computed ) {
|
|
if ( computed ) {
|
|
var ret = curCSS( elem, prop );
|
|
// if curCSS returns percentage, fallback to offset
|
|
return rnumnonpx.test( ret ) ? jQuery( elem ).position()[ prop ] + "px" : ret;
|
|
}
|
|
}
|
|
};
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
if ( jQuery.expr && jQuery.expr.filters ) {
|
|
jQuery.expr.filters.hidden = function( elem ) {
|
|
return ( elem.offsetWidth === 0 && elem.offsetHeight === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || curCSS( elem, "display" )) === "none");
|
|
};
|
|
|
|
jQuery.expr.filters.visible = function( elem ) {
|
|
return !jQuery.expr.filters.hidden( elem );
|
|
};
|
|
}
|
|
|
|
// 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,
|
|
|
|
// assumes a single number if not a string
|
|
parts = typeof value === "string" ? value.split(" ") : [ value ],
|
|
expanded = {};
|
|
|
|
for ( i = 0; 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;
|
|
}
|
|
});
|
|
var r20 = /%20/g,
|
|
rbracket = /\[\]$/,
|
|
rCRLF = /\r?\n/g,
|
|
rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
|
|
rselectTextarea = /^(?:select|textarea)/i;
|
|
|
|
jQuery.fn.extend({
|
|
serialize: function() {
|
|
return jQuery.param( this.serializeArray() );
|
|
},
|
|
serializeArray: function() {
|
|
return this.map(function(){
|
|
return this.elements ? jQuery.makeArray( this.elements ) : this;
|
|
})
|
|
.filter(function(){
|
|
return this.name && !this.disabled &&
|
|
( this.checked || rselectTextarea.test( this.nodeName ) ||
|
|
rinput.test( this.type ) );
|
|
})
|
|
.map(function( i, elem ){
|
|
var val = jQuery( this ).val();
|
|
|
|
return val == null ?
|
|
null :
|
|
jQuery.isArray( val ) ?
|
|
jQuery.map( val, function( val, i ){
|
|
return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
|
|
}) :
|
|
{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
|
|
}).get();
|
|
}
|
|
});
|
|
|
|
//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, "+" );
|
|
};
|
|
|
|
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 {
|
|
// If array item is non-scalar (array or object), encode its
|
|
// numeric index to resolve deserialization ambiguity issues.
|
|
// Note that rack (as of 1.0.0) can't currently deserialize
|
|
// nested arrays properly, and attempting to do so may cause
|
|
// a server error. Possible fixes are to modify rack's
|
|
// deserialization algorithm or to provide an option or flag
|
|
// to force array serialization to be shallow.
|
|
buildParams( prefix + "[" + ( typeof v === "object" ? 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 );
|
|
}
|
|
}
|
|
var // Document location
|
|
ajaxLocation,
|
|
// Document location segments
|
|
ajaxLocParts,
|
|
|
|
rhash = /#.*$/,
|
|
rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
|
|
// #7653, #8125, #8152: local protocol detection
|
|
rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,
|
|
rnoContent = /^(?:GET|HEAD)$/,
|
|
rprotocol = /^\/\//,
|
|
rquery = /\?/,
|
|
rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
|
|
rts = /([?&])_=[^&]*/,
|
|
rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,
|
|
|
|
// Keep a copy of the old load method
|
|
_load = jQuery.fn.load,
|
|
|
|
/* Prefilters
|
|
* 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
|
|
* 2) These are called:
|
|
* - BEFORE asking for a transport
|
|
* - AFTER param serialization (s.data is a string if s.processData is true)
|
|
* 3) key is the dataType
|
|
* 4) the catchall symbol "*" can be used
|
|
* 5) execution will start with transport dataType and THEN continue down to "*" if needed
|
|
*/
|
|
prefilters = {},
|
|
|
|
/* Transports bindings
|
|
* 1) key is the dataType
|
|
* 2) the catchall symbol "*" can be used
|
|
* 3) selection will start with transport dataType and THEN go to "*" if needed
|
|
*/
|
|
transports = {},
|
|
|
|
// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
|
|
allTypes = ["*/"] + ["*"];
|
|
|
|
// #8138, IE may throw an exception when accessing
|
|
// a field from window.location if document.domain has been set
|
|
try {
|
|
ajaxLocation = location.href;
|
|
} catch( e ) {
|
|
// Use the href attribute of an A element
|
|
// since IE will modify it given document.location
|
|
ajaxLocation = document.createElement( "a" );
|
|
ajaxLocation.href = "";
|
|
ajaxLocation = ajaxLocation.href;
|
|
}
|
|
|
|
// Segment location into parts
|
|
ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
|
|
|
|
// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
|
|
function addToPrefiltersOrTransports( structure ) {
|
|
|
|
// dataTypeExpression is optional and defaults to "*"
|
|
return function( dataTypeExpression, func ) {
|
|
|
|
if ( typeof dataTypeExpression !== "string" ) {
|
|
func = dataTypeExpression;
|
|
dataTypeExpression = "*";
|
|
}
|
|
|
|
var dataType, list, placeBefore,
|
|
dataTypes = dataTypeExpression.toLowerCase().split( core_rspace ),
|
|
i = 0,
|
|
length = dataTypes.length;
|
|
|
|
if ( jQuery.isFunction( func ) ) {
|
|
// For each dataType in the dataTypeExpression
|
|
for ( ; i < length; i++ ) {
|
|
dataType = dataTypes[ i ];
|
|
// We control if we're asked to add before
|
|
// any existing element
|
|
placeBefore = /^\+/.test( dataType );
|
|
if ( placeBefore ) {
|
|
dataType = dataType.substr( 1 ) || "*";
|
|
}
|
|
list = structure[ dataType ] = structure[ dataType ] || [];
|
|
// then we add to the structure accordingly
|
|
list[ placeBefore ? "unshift" : "push" ]( func );
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// Base inspection function for prefilters and transports
|
|
function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
|
|
dataType /* internal */, inspected /* internal */ ) {
|
|
|
|
dataType = dataType || options.dataTypes[ 0 ];
|
|
inspected = inspected || {};
|
|
|
|
inspected[ dataType ] = true;
|
|
|
|
var selection,
|
|
list = structure[ dataType ],
|
|
i = 0,
|
|
length = list ? list.length : 0,
|
|
executeOnly = ( structure === prefilters );
|
|
|
|
for ( ; i < length && ( executeOnly || !selection ); i++ ) {
|
|
selection = list[ i ]( options, originalOptions, jqXHR );
|
|
// If we got redirected to another dataType
|
|
// we try there if executing only and not done already
|
|
if ( typeof selection === "string" ) {
|
|
if ( !executeOnly || inspected[ selection ] ) {
|
|
selection = undefined;
|
|
} else {
|
|
options.dataTypes.unshift( selection );
|
|
selection = inspectPrefiltersOrTransports(
|
|
structure, options, originalOptions, jqXHR, selection, inspected );
|
|
}
|
|
}
|
|
}
|
|
// If we're only executing or nothing was selected
|
|
// we try the catchall dataType if not done already
|
|
if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
|
|
selection = inspectPrefiltersOrTransports(
|
|
structure, options, originalOptions, jqXHR, "*", inspected );
|
|
}
|
|
// unnecessary when only executing (prefilters)
|
|
// but it'll be ignored by the caller in that case
|
|
return selection;
|
|
}
|
|
|
|
// A special extend for ajax options
|
|
// that takes "flat" options (not to be deep extended)
|
|
// Fixes #9887
|
|
function ajaxExtend( target, src ) {
|
|
var key, deep,
|
|
flatOptions = jQuery.ajaxSettings.flatOptions || {};
|
|
for ( key in src ) {
|
|
if ( src[ key ] !== undefined ) {
|
|
( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
|
|
}
|
|
}
|
|
if ( deep ) {
|
|
jQuery.extend( true, target, deep );
|
|
}
|
|
}
|
|
|
|
jQuery.fn.load = function( url, params, callback ) {
|
|
if ( typeof url !== "string" && _load ) {
|
|
return _load.apply( this, arguments );
|
|
}
|
|
|
|
// Don't do a request if no elements are being requested
|
|
if ( !this.length ) {
|
|
return this;
|
|
}
|
|
|
|
var selector, type, response,
|
|
self = this,
|
|
off = url.indexOf(" ");
|
|
|
|
if ( off >= 0 ) {
|
|
selector = 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 ( typeof params === "object" ) {
|
|
type = "POST";
|
|
}
|
|
|
|
// Request the remote document
|
|
jQuery.ajax({
|
|
url: url,
|
|
|
|
// if "type" variable is undefined, then "GET" method will be used
|
|
type: type,
|
|
dataType: "html",
|
|
data: params,
|
|
complete: function( jqXHR, status ) {
|
|
if ( callback ) {
|
|
self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
|
|
}
|
|
}
|
|
}).done(function( responseText ) {
|
|
|
|
// Save response for use in complete callback
|
|
response = arguments;
|
|
|
|
// See if a selector was specified
|
|
self.html( selector ?
|
|
|
|
// Create a dummy div to hold the results
|
|
jQuery("<div>")
|
|
|
|
// inject the contents of the document in, removing the scripts
|
|
// to avoid any 'Permission Denied' errors in IE
|
|
.append( responseText.replace( rscript, "" ) )
|
|
|
|
// Locate the specified elements
|
|
.find( selector ) :
|
|
|
|
// If not, just inject the full result
|
|
responseText );
|
|
|
|
});
|
|
|
|
return this;
|
|
};
|
|
|
|
// Attach a bunch of functions for handling common AJAX events
|
|
jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
|
|
jQuery.fn[ o ] = function( f ){
|
|
return this.on( o, f );
|
|
};
|
|
});
|
|
|
|
jQuery.each( [ "get", "post" ], function( i, method ) {
|
|
jQuery[ method ] = function( url, data, callback, type ) {
|
|
// shift arguments if data argument was omitted
|
|
if ( jQuery.isFunction( data ) ) {
|
|
type = type || callback;
|
|
callback = data;
|
|
data = undefined;
|
|
}
|
|
|
|
return jQuery.ajax({
|
|
type: method,
|
|
url: url,
|
|
data: data,
|
|
success: callback,
|
|
dataType: type
|
|
});
|
|
};
|
|
});
|
|
|
|
jQuery.extend({
|
|
|
|
getScript: function( url, callback ) {
|
|
return jQuery.get( url, undefined, callback, "script" );
|
|
},
|
|
|
|
getJSON: function( url, data, callback ) {
|
|
return jQuery.get( url, data, callback, "json" );
|
|
},
|
|
|
|
// Creates a full fledged settings object into target
|
|
// with both ajaxSettings and settings fields.
|
|
// If target is omitted, writes into ajaxSettings.
|
|
ajaxSetup: function( target, settings ) {
|
|
if ( settings ) {
|
|
// Building a settings object
|
|
ajaxExtend( target, jQuery.ajaxSettings );
|
|
} else {
|
|
// Extending ajaxSettings
|
|
settings = target;
|
|
target = jQuery.ajaxSettings;
|
|
}
|
|
ajaxExtend( target, settings );
|
|
return target;
|
|
},
|
|
|
|
ajaxSettings: {
|
|
url: ajaxLocation,
|
|
isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
|
|
global: true,
|
|
type: "GET",
|
|
contentType: "application/x-www-form-urlencoded; charset=UTF-8",
|
|
processData: true,
|
|
async: true,
|
|
/*
|
|
timeout: 0,
|
|
data: null,
|
|
dataType: null,
|
|
username: null,
|
|
password: null,
|
|
cache: null,
|
|
throws: false,
|
|
traditional: false,
|
|
headers: {},
|
|
*/
|
|
|
|
accepts: {
|
|
xml: "application/xml, text/xml",
|
|
html: "text/html",
|
|
text: "text/plain",
|
|
json: "application/json, text/javascript",
|
|
"*": allTypes
|
|
},
|
|
|
|
contents: {
|
|
xml: /xml/,
|
|
html: /html/,
|
|
json: /json/
|
|
},
|
|
|
|
responseFields: {
|
|
xml: "responseXML",
|
|
text: "responseText"
|
|
},
|
|
|
|
// List of data converters
|
|
// 1) key format is "source_type destination_type" (a single space in-between)
|
|
// 2) the catchall symbol "*" can be used for source_type
|
|
converters: {
|
|
|
|
// Convert anything to text
|
|
"* text": window.String,
|
|
|
|
// Text to html (true = no transformation)
|
|
"text html": true,
|
|
|
|
// Evaluate text as a json expression
|
|
"text json": jQuery.parseJSON,
|
|
|
|
// Parse text as xml
|
|
"text xml": jQuery.parseXML
|
|
},
|
|
|
|
// For options that shouldn't be deep extended:
|
|
// you can add your own custom options here if
|
|
// and when you create one that shouldn't be
|
|
// deep extended (see ajaxExtend)
|
|
flatOptions: {
|
|
context: true,
|
|
url: true
|
|
}
|
|
},
|
|
|
|
ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
|
|
ajaxTransport: addToPrefiltersOrTransports( transports ),
|
|
|
|
// Main method
|
|
ajax: function( url, options ) {
|
|
|
|
// If url is an object, simulate pre-1.5 signature
|
|
if ( typeof url === "object" ) {
|
|
options = url;
|
|
url = undefined;
|
|
}
|
|
|
|
// Force options to be an object
|
|
options = options || {};
|
|
|
|
var // ifModified key
|
|
ifModifiedKey,
|
|
// Response headers
|
|
responseHeadersString,
|
|
responseHeaders,
|
|
// transport
|
|
transport,
|
|
// timeout handle
|
|
timeoutTimer,
|
|
// Cross-domain detection vars
|
|
parts,
|
|
// To know if global events are to be dispatched
|
|
fireGlobals,
|
|
// Loop variable
|
|
i,
|
|
// Create the final options object
|
|
s = jQuery.ajaxSetup( {}, options ),
|
|
// Callbacks context
|
|
callbackContext = s.context || s,
|
|
// Context for global events
|
|
// It's the callbackContext if one was provided in the options
|
|
// and if it's a DOM node or a jQuery collection
|
|
globalEventContext = callbackContext !== s &&
|
|
( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
|
|
jQuery( callbackContext ) : jQuery.event,
|
|
// Deferreds
|
|
deferred = jQuery.Deferred(),
|
|
completeDeferred = jQuery.Callbacks( "once memory" ),
|
|
// Status-dependent callbacks
|
|
statusCode = s.statusCode || {},
|
|
// Headers (they are sent all at once)
|
|
requestHeaders = {},
|
|
requestHeadersNames = {},
|
|
// The jqXHR state
|
|
state = 0,
|
|
// Default abort message
|
|
strAbort = "canceled",
|
|
// Fake xhr
|
|
jqXHR = {
|
|
|
|
readyState: 0,
|
|
|
|
// Caches the header
|
|
setRequestHeader: function( name, value ) {
|
|
if ( !state ) {
|
|
var lname = name.toLowerCase();
|
|
name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
|
|
requestHeaders[ name ] = value;
|
|
}
|
|
return this;
|
|
},
|
|
|
|
// Raw string
|
|
getAllResponseHeaders: function() {
|
|
return state === 2 ? responseHeadersString : null;
|
|
},
|
|
|
|
// Builds headers hashtable if needed
|
|
getResponseHeader: function( key ) {
|
|
var match;
|
|
if ( state === 2 ) {
|
|
if ( !responseHeaders ) {
|
|
responseHeaders = {};
|
|
while( ( match = rheaders.exec( responseHeadersString ) ) ) {
|
|
responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
|
|
}
|
|
}
|
|
match = responseHeaders[ key.toLowerCase() ];
|
|
}
|
|
return match === undefined ? null : match;
|
|
},
|
|
|
|
// Overrides response content-type header
|
|
overrideMimeType: function( type ) {
|
|
if ( !state ) {
|
|
s.mimeType = type;
|
|
}
|
|
return this;
|
|
},
|
|
|
|
// Cancel the request
|
|
abort: function( statusText ) {
|
|
statusText = statusText || strAbort;
|
|
if ( transport ) {
|
|
transport.abort( statusText );
|
|
}
|
|
done( 0, statusText );
|
|
return this;
|
|
}
|
|
};
|
|
|
|
// Callback for when everything is done
|
|
// It is defined here because jslint complains if it is declared
|
|
// at the end of the function (which would be more logical and readable)
|
|
function done( status, nativeStatusText, responses, headers ) {
|
|
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 ) {
|
|
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;
|
|
|
|
// Get response data
|
|
if ( responses ) {
|
|
response = ajaxHandleResponses( s, jqXHR, responses );
|
|
}
|
|
|
|
// If successful, handle type chaining
|
|
if ( status >= 200 && status < 300 || status === 304 ) {
|
|
|
|
// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
|
|
if ( s.ifModified ) {
|
|
|
|
modified = jqXHR.getResponseHeader("Last-Modified");
|
|
if ( modified ) {
|
|
jQuery.lastModified[ ifModifiedKey ] = modified;
|
|
}
|
|
modified = jqXHR.getResponseHeader("Etag");
|
|
if ( modified ) {
|
|
jQuery.etag[ ifModifiedKey ] = modified;
|
|
}
|
|
}
|
|
|
|
// If not modified
|
|
if ( status === 304 ) {
|
|
|
|
statusText = "notmodified";
|
|
isSuccess = true;
|
|
|
|
// If we have data
|
|
} else {
|
|
|
|
isSuccess = ajaxConvert( s, response );
|
|
statusText = isSuccess.state;
|
|
success = isSuccess.data;
|
|
error = isSuccess.error;
|
|
isSuccess = !error;
|
|
}
|
|
} else {
|
|
// We extract error from statusText
|
|
// then normalize statusText and status for non-aborts
|
|
error = statusText;
|
|
if ( !statusText || status ) {
|
|
statusText = "error";
|
|
if ( status < 0 ) {
|
|
status = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set data for the fake xhr object
|
|
jqXHR.status = status;
|
|
jqXHR.statusText = "" + ( nativeStatusText || statusText );
|
|
|
|
// Success/Error
|
|
if ( isSuccess ) {
|
|
deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
|
|
} else {
|
|
deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
|
|
}
|
|
|
|
// Status-dependent callbacks
|
|
jqXHR.statusCode( statusCode );
|
|
statusCode = undefined;
|
|
|
|
if ( fireGlobals ) {
|
|
globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
|
|
[ jqXHR, s, isSuccess ? success : error ] );
|
|
}
|
|
|
|
// Complete
|
|
completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
|
|
|
|
if ( fireGlobals ) {
|
|
globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
|
|
// Handle the global AJAX counter
|
|
if ( !( --jQuery.active ) ) {
|
|
jQuery.event.trigger( "ajaxStop" );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Attach deferreds
|
|
deferred.promise( jqXHR );
|
|
jqXHR.success = jqXHR.done;
|
|
jqXHR.error = jqXHR.fail;
|
|
jqXHR.complete = completeDeferred.add;
|
|
|
|
// Status-dependent callbacks
|
|
jqXHR.statusCode = function( map ) {
|
|
if ( map ) {
|
|
var tmp;
|
|
if ( state < 2 ) {
|
|
for ( tmp in map ) {
|
|
statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
|
|
}
|
|
} else {
|
|
tmp = map[ jqXHR.status ];
|
|
jqXHR.always( tmp );
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
|
|
// Remove hash character (#7531: and string promotion)
|
|
// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
|
|
// We also use the url parameter if available
|
|
s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
|
|
|
|
// Extract dataTypes list
|
|
s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( core_rspace );
|
|
|
|
// Determine if a cross-domain request is in order
|
|
if ( s.crossDomain == null ) {
|
|
parts = rurl.exec( s.url.toLowerCase() );
|
|
s.crossDomain = !!( parts &&
|
|
( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] ||
|
|
( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
|
|
( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
|
|
);
|
|
}
|
|
|
|
// Convert data if not already a string
|
|
if ( s.data && s.processData && typeof s.data !== "string" ) {
|
|
s.data = jQuery.param( s.data, s.traditional );
|
|
}
|
|
|
|
// Apply prefilters
|
|
inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
|
|
|
|
// If request was aborted inside a prefilter, stop there
|
|
if ( state === 2 ) {
|
|
return jqXHR;
|
|
}
|
|
|
|
// We can fire global events as of now if asked to
|
|
fireGlobals = s.global;
|
|
|
|
// Uppercase the type
|
|
s.type = s.type.toUpperCase();
|
|
|
|
// Determine if request has content
|
|
s.hasContent = !rnoContent.test( s.type );
|
|
|
|
// Watch for a new set of requests
|
|
if ( fireGlobals && jQuery.active++ === 0 ) {
|
|
jQuery.event.trigger( "ajaxStart" );
|
|
}
|
|
|
|
// More options handling for requests with no content
|
|
if ( !s.hasContent ) {
|
|
|
|
// If data is available, append data to url
|
|
if ( s.data ) {
|
|
s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
|
|
// #9682: remove data so that it's not used in an eventual retry
|
|
delete s.data;
|
|
}
|
|
|
|
// Get ifModifiedKey before adding the anti-cache parameter
|
|
ifModifiedKey = s.url;
|
|
|
|
// Add anti-cache in url if needed
|
|
if ( s.cache === false ) {
|
|
|
|
var ts = jQuery.now(),
|
|
// try replacing _= if it is there
|
|
ret = s.url.replace( rts, "$1_=" + ts );
|
|
|
|
// if nothing was replaced, add timestamp to the end
|
|
s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
|
|
}
|
|
}
|
|
|
|
// Set the correct header, if data is being sent
|
|
if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
|
|
jqXHR.setRequestHeader( "Content-Type", s.contentType );
|
|
}
|
|
|
|
// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
|
|
if ( s.ifModified ) {
|
|
ifModifiedKey = ifModifiedKey || s.url;
|
|
if ( jQuery.lastModified[ ifModifiedKey ] ) {
|
|
jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] );
|
|
}
|
|
if ( jQuery.etag[ ifModifiedKey ] ) {
|
|
jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] );
|
|
}
|
|
}
|
|
|
|
// Set the Accepts header for the server, depending on the dataType
|
|
jqXHR.setRequestHeader(
|
|
"Accept",
|
|
s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
|
|
s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
|
|
s.accepts[ "*" ]
|
|
);
|
|
|
|
// Check for headers option
|
|
for ( i in s.headers ) {
|
|
jqXHR.setRequestHeader( i, s.headers[ i ] );
|
|
}
|
|
|
|
// Allow custom headers/mimetypes and early abort
|
|
if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
|
|
// Abort if not done already 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 ] );
|
|
}
|
|
// Timeout
|
|
if ( s.async && s.timeout > 0 ) {
|
|
timeoutTimer = setTimeout( function(){
|
|
jqXHR.abort( "timeout" );
|
|
}, s.timeout );
|
|
}
|
|
|
|
try {
|
|
state = 1;
|
|
transport.send( requestHeaders, done );
|
|
} catch (e) {
|
|
// Propagate exception as error if not done
|
|
if ( state < 2 ) {
|
|
done( -1, e );
|
|
// Simply rethrow otherwise
|
|
} else {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
return jqXHR;
|
|
},
|
|
|
|
// Counter for holding the number of active queries
|
|
active: 0,
|
|
|
|
// Last-Modified header cache for next request
|
|
lastModified: {},
|
|
etag: {}
|
|
|
|
});
|
|
|
|
/* Handles responses to an ajax request:
|
|
* - sets all responseXXX fields accordingly
|
|
* - finds the right dataType (mediates between content-type and expected dataType)
|
|
* - returns the corresponding response
|
|
*/
|
|
function ajaxHandleResponses( s, jqXHR, responses ) {
|
|
|
|
var ct, type, finalDataType, firstDataType,
|
|
contents = s.contents,
|
|
dataTypes = s.dataTypes,
|
|
responseFields = s.responseFields;
|
|
|
|
// Fill responseXXX fields
|
|
for ( type in responseFields ) {
|
|
if ( type in responses ) {
|
|
jqXHR[ responseFields[type] ] = responses[ type ];
|
|
}
|
|
}
|
|
|
|
// Remove auto dataType and get content-type in the process
|
|
while( dataTypes[ 0 ] === "*" ) {
|
|
dataTypes.shift();
|
|
if ( ct === undefined ) {
|
|
ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
|
|
}
|
|
}
|
|
|
|
// Check if we're dealing with a known content-type
|
|
if ( ct ) {
|
|
for ( type in contents ) {
|
|
if ( contents[ type ] && contents[ type ].test( ct ) ) {
|
|
dataTypes.unshift( type );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check to see if we have a response for the expected dataType
|
|
if ( dataTypes[ 0 ] in responses ) {
|
|
finalDataType = dataTypes[ 0 ];
|
|
} else {
|
|
// Try convertible dataTypes
|
|
for ( type in responses ) {
|
|
if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
|
|
finalDataType = type;
|
|
break;
|
|
}
|
|
if ( !firstDataType ) {
|
|
firstDataType = type;
|
|
}
|
|
}
|
|
// Or just use first one
|
|
finalDataType = finalDataType || firstDataType;
|
|
}
|
|
|
|
// If we found a dataType
|
|
// We add the dataType to the list if needed
|
|
// and return the corresponding response
|
|
if ( finalDataType ) {
|
|
if ( finalDataType !== dataTypes[ 0 ] ) {
|
|
dataTypes.unshift( finalDataType );
|
|
}
|
|
return responses[ finalDataType ];
|
|
}
|
|
}
|
|
|
|
// Chain conversions given the request and the original response
|
|
function ajaxConvert( s, response ) {
|
|
|
|
var conv, conv2, current, tmp,
|
|
// Work with a copy of dataTypes in case we need to modify it for conversion
|
|
dataTypes = s.dataTypes.slice(),
|
|
prev = dataTypes[ 0 ],
|
|
converters = {},
|
|
i = 0;
|
|
|
|
// Apply the dataFilter if provided
|
|
if ( s.dataFilter ) {
|
|
response = s.dataFilter( response, s.dataType );
|
|
}
|
|
|
|
// Create converters map with lowercased keys
|
|
if ( dataTypes[ 1 ] ) {
|
|
for ( conv in s.converters ) {
|
|
converters[ conv.toLowerCase() ] = s.converters[ conv ];
|
|
}
|
|
}
|
|
|
|
// Convert to each sequential dataType, tolerating list modification
|
|
for ( ; (current = dataTypes[++i]); ) {
|
|
|
|
// There's only work to do if current dataType is non-auto
|
|
if ( current !== "*" ) {
|
|
|
|
// Convert response if prev dataType is non-auto and differs from current
|
|
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.splice( i--, 0, current );
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply converter (if not an equivalence)
|
|
if ( conv !== true ) {
|
|
|
|
// Unless errors are allowed to bubble, catch and return them
|
|
if ( conv && s["throws"] ) {
|
|
response = conv( response );
|
|
} else {
|
|
try {
|
|
response = conv( response );
|
|
} catch ( e ) {
|
|
return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update prev for next iteration
|
|
prev = current;
|
|
}
|
|
}
|
|
|
|
return { state: "success", data: response };
|
|
}
|
|
var oldCallbacks = [],
|
|
rquestion = /\?/,
|
|
rjsonp = /(=)\?(?=&|$)|\?\?/,
|
|
nonce = jQuery.now();
|
|
|
|
// 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,
|
|
data = s.data,
|
|
url = s.url,
|
|
hasCallback = s.jsonp !== false,
|
|
replaceInUrl = hasCallback && rjsonp.test( url ),
|
|
replaceInData = hasCallback && !replaceInUrl && typeof data === "string" &&
|
|
!( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") &&
|
|
rjsonp.test( data );
|
|
|
|
// Handle iff the expected data type is "jsonp" or we have a parameter to set
|
|
if ( s.dataTypes[ 0 ] === "jsonp" || replaceInUrl || replaceInData ) {
|
|
|
|
// Get callback name, remembering preexisting value associated with it
|
|
callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
|
|
s.jsonpCallback() :
|
|
s.jsonpCallback;
|
|
overwritten = window[ callbackName ];
|
|
|
|
// Insert callback into url or form data
|
|
if ( replaceInUrl ) {
|
|
s.url = url.replace( rjsonp, "$1" + callbackName );
|
|
} else if ( replaceInData ) {
|
|
s.data = data.replace( rjsonp, "$1" + callbackName );
|
|
} else if ( hasCallback ) {
|
|
s.url += ( rquestion.test( 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
|
|
window[ callbackName ] = function() {
|
|
responseContainer = arguments;
|
|
};
|
|
|
|
// Clean-up function (fires after converters)
|
|
jqXHR.always(function() {
|
|
// Restore preexisting value
|
|
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";
|
|
}
|
|
});
|
|
// Install script dataType
|
|
jQuery.ajaxSetup({
|
|
accepts: {
|
|
script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
|
|
},
|
|
contents: {
|
|
script: /javascript|ecmascript/
|
|
},
|
|
converters: {
|
|
"text script": function( text ) {
|
|
jQuery.globalEval( text );
|
|
return text;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Handle cache's special case and global
|
|
jQuery.ajaxPrefilter( "script", function( s ) {
|
|
if ( s.cache === undefined ) {
|
|
s.cache = false;
|
|
}
|
|
if ( s.crossDomain ) {
|
|
s.type = "GET";
|
|
s.global = false;
|
|
}
|
|
});
|
|
|
|
// Bind script tag hack transport
|
|
jQuery.ajaxTransport( "script", function(s) {
|
|
|
|
// This transport only deals with cross domain requests
|
|
if ( s.crossDomain ) {
|
|
|
|
var script,
|
|
head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
|
|
|
|
return {
|
|
|
|
send: function( _, callback ) {
|
|
|
|
script = document.createElement( "script" );
|
|
|
|
script.async = "async";
|
|
|
|
if ( s.scriptCharset ) {
|
|
script.charset = s.scriptCharset;
|
|
}
|
|
|
|
script.src = s.url;
|
|
|
|
// Attach handlers for all browsers
|
|
script.onload = script.onreadystatechange = function( _, isAbort ) {
|
|
|
|
if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
|
|
|
|
// Handle memory leak in IE
|
|
script.onload = script.onreadystatechange = null;
|
|
|
|
// Remove the script
|
|
if ( head && script.parentNode ) {
|
|
head.removeChild( script );
|
|
}
|
|
|
|
// Dereference the script
|
|
script = undefined;
|
|
|
|
// Callback if not abort
|
|
if ( !isAbort ) {
|
|
callback( 200, "success" );
|
|
}
|
|
}
|
|
};
|
|
// Use insertBefore instead of appendChild to circumvent an IE6 bug.
|
|
// This arises when a base node is used (#2709 and #4378).
|
|
head.insertBefore( script, head.firstChild );
|
|
},
|
|
|
|
abort: function() {
|
|
if ( script ) {
|
|
script.onload( 0, 1 );
|
|
}
|
|
}
|
|
};
|
|
}
|
|
});
|
|
var xhrCallbacks,
|
|
// #5280: Internet Explorer will keep connections alive if we don't abort on unload
|
|
xhrOnUnloadAbort = window.ActiveXObject ? function() {
|
|
// Abort all pending requests
|
|
for ( var key in xhrCallbacks ) {
|
|
xhrCallbacks[ key ]( 0, 1 );
|
|
}
|
|
} : false,
|
|
xhrId = 0;
|
|
|
|
// Functions to create xhrs
|
|
function createStandardXHR() {
|
|
try {
|
|
return new window.XMLHttpRequest();
|
|
} catch( e ) {}
|
|
}
|
|
|
|
function createActiveXHR() {
|
|
try {
|
|
return new window.ActiveXObject( "Microsoft.XMLHTTP" );
|
|
} catch( e ) {}
|
|
}
|
|
|
|
// Create the request object
|
|
// (This is still attached to ajaxSettings for backward compatibility)
|
|
jQuery.ajaxSettings.xhr = window.ActiveXObject ?
|
|
/* Microsoft failed to properly
|
|
* implement the XMLHttpRequest in IE7 (can't request local files),
|
|
* so we use the ActiveXObject when it is available
|
|
* Additionally XMLHttpRequest can be disabled in IE7/IE8 so
|
|
* we need a fallback.
|
|
*/
|
|
function() {
|
|
return !this.isLocal && createStandardXHR() || createActiveXHR();
|
|
} :
|
|
// For all other browsers, use the standard XMLHttpRequest object
|
|
createStandardXHR;
|
|
|
|
// Determine support properties
|
|
(function( xhr ) {
|
|
jQuery.extend( jQuery.support, {
|
|
ajax: !!xhr,
|
|
cors: !!xhr && ( "withCredentials" in xhr )
|
|
});
|
|
})( jQuery.ajaxSettings.xhr() );
|
|
|
|
// Create transport if the browser can provide an xhr
|
|
if ( jQuery.support.ajax ) {
|
|
|
|
jQuery.ajaxTransport(function( s ) {
|
|
// Cross domain only allowed if supported through XMLHttpRequest
|
|
if ( !s.crossDomain || jQuery.support.cors ) {
|
|
|
|
var callback;
|
|
|
|
return {
|
|
send: function( headers, complete ) {
|
|
|
|
// Get a new xhr
|
|
var handle, i,
|
|
xhr = s.xhr();
|
|
|
|
// Open the socket
|
|
// Passing null username, generates a login popup on Opera (#2865)
|
|
if ( s.username ) {
|
|
xhr.open( s.type, s.url, s.async, s.username, s.password );
|
|
} else {
|
|
xhr.open( s.type, s.url, s.async );
|
|
}
|
|
|
|
// Apply custom fields if provided
|
|
if ( s.xhrFields ) {
|
|
for ( i in s.xhrFields ) {
|
|
xhr[ i ] = s.xhrFields[ i ];
|
|
}
|
|
}
|
|
|
|
// Override mime type if needed
|
|
if ( s.mimeType && xhr.overrideMimeType ) {
|
|
xhr.overrideMimeType( s.mimeType );
|
|
}
|
|
|
|
// X-Requested-With header
|
|
// For cross-domain requests, seeing as conditions for a preflight are
|
|
// akin to a jigsaw puzzle, we simply never set it to be sure.
|
|
// (it can always be set on a per-request basis or even using ajaxSetup)
|
|
// For same-domain requests, won't change header if already provided.
|
|
if ( !s.crossDomain && !headers["X-Requested-With"] ) {
|
|
headers[ "X-Requested-With" ] = "XMLHttpRequest";
|
|
}
|
|
|
|
// Need an extra try/catch for cross domain requests in Firefox 3
|
|
try {
|
|
for ( i in headers ) {
|
|
xhr.setRequestHeader( i, headers[ i ] );
|
|
}
|
|
} catch( _ ) {}
|
|
|
|
// Do send the request
|
|
// This may raise an exception which is actually
|
|
// handled in jQuery.ajax (so no try/catch here)
|
|
xhr.send( ( s.hasContent && s.data ) || null );
|
|
|
|
// Listener
|
|
callback = function( _, isAbort ) {
|
|
|
|
var status,
|
|
statusText,
|
|
responseHeaders,
|
|
responses,
|
|
xml;
|
|
|
|
// Firefox throws exceptions when accessing properties
|
|
// of an xhr when a network error occurred
|
|
// http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
|
|
try {
|
|
|
|
// Was never called and is aborted or complete
|
|
if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
|
|
|
|
// Only called once
|
|
callback = undefined;
|
|
|
|
// Do not keep as active anymore
|
|
if ( handle ) {
|
|
xhr.onreadystatechange = jQuery.noop;
|
|
if ( xhrOnUnloadAbort ) {
|
|
delete xhrCallbacks[ handle ];
|
|
}
|
|
}
|
|
|
|
// If it's an abort
|
|
if ( isAbort ) {
|
|
// Abort it manually if needed
|
|
if ( xhr.readyState !== 4 ) {
|
|
xhr.abort();
|
|
}
|
|
} else {
|
|
status = xhr.status;
|
|
responseHeaders = xhr.getAllResponseHeaders();
|
|
responses = {};
|
|
xml = xhr.responseXML;
|
|
|
|
// Construct response list
|
|
if ( xml && xml.documentElement /* #4958 */ ) {
|
|
responses.xml = xml;
|
|
}
|
|
|
|
// When requesting binary data, IE6-9 will throw an exception
|
|
// on any attempt to access responseText (#11426)
|
|
try {
|
|
responses.text = xhr.responseText;
|
|
} catch( _ ) {
|
|
}
|
|
|
|
// Firefox throws an exception when accessing
|
|
// statusText for faulty cross-domain requests
|
|
try {
|
|
statusText = xhr.statusText;
|
|
} catch( e ) {
|
|
// We normalize with Webkit giving an empty statusText
|
|
statusText = "";
|
|
}
|
|
|
|
// Filter status for non standard behaviors
|
|
|
|
// If the request is local and we have data: assume a success
|
|
// (success with no data won't get notified, that's the best we
|
|
// can do given current implementations)
|
|
if ( !status && s.isLocal && !s.crossDomain ) {
|
|
status = responses.text ? 200 : 404;
|
|
// IE - #1450: sometimes returns 1223 when it should be 204
|
|
} else if ( status === 1223 ) {
|
|
status = 204;
|
|
}
|
|
}
|
|
}
|
|
} catch( firefoxAccessException ) {
|
|
if ( !isAbort ) {
|
|
complete( -1, firefoxAccessException );
|
|
}
|
|
}
|
|
|
|
// Call complete if needed
|
|
if ( responses ) {
|
|
complete( status, statusText, responses, responseHeaders );
|
|
}
|
|
};
|
|
|
|
if ( !s.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
|
|
setTimeout( callback, 0 );
|
|
} else {
|
|
handle = ++xhrId;
|
|
if ( xhrOnUnloadAbort ) {
|
|
// Create the active xhrs callbacks list if needed
|
|
// and attach the unload handler
|
|
if ( !xhrCallbacks ) {
|
|
xhrCallbacks = {};
|
|
jQuery( window ).unload( xhrOnUnloadAbort );
|
|
}
|
|
// Add to list of active xhrs callbacks
|
|
xhrCallbacks[ handle ] = callback;
|
|
}
|
|
xhr.onreadystatechange = callback;
|
|
}
|
|
},
|
|
|
|
abort: function() {
|
|
if ( callback ) {
|
|
callback(0,1);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
});
|
|
}
|
|
var fxNow, timerId,
|
|
rfxtypes = /^(?:toggle|show|hide)$/,
|
|
rfxnum = new RegExp( "^(?:([-+])=|)(" + core_pnum + ")([a-z%]*)$", "i" ),
|
|
rrun = /queueHooks$/,
|
|
animationPrefilters = [ defaultPrefilter ],
|
|
tweeners = {
|
|
"*": [function( prop, value ) {
|
|
var end, unit, prevScale,
|
|
tween = this.createTween( prop, value ),
|
|
parts = rfxnum.exec( value ),
|
|
target = tween.cur(),
|
|
start = +target || 0,
|
|
scale = 1;
|
|
|
|
if ( parts ) {
|
|
end = +parts[2];
|
|
unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" );
|
|
|
|
// We need to compute starting value
|
|
if ( unit !== "px" && start ) {
|
|
// Iteratively approximate from a nonzero starting point
|
|
// Prefer the current property, because this process will be trivial if it uses the same units
|
|
// Fallback to end or a simple constant
|
|
start = jQuery.css( tween.elem, prop, true ) || end || 1;
|
|
|
|
do {
|
|
// If previous iteration zeroed out, double until we get *something*
|
|
// Use a string for doubling factor so we don't accidentally see scale as unchanged below
|
|
prevScale = scale = scale || ".5";
|
|
|
|
// Adjust and apply
|
|
start = start / scale;
|
|
jQuery.style( tween.elem, prop, start + unit );
|
|
|
|
// Update scale, tolerating zeroes from tween.cur()
|
|
scale = tween.cur() / target;
|
|
|
|
// Stop looping if we've hit the mark or scale is unchanged
|
|
} while ( scale !== 1 && scale !== prevScale );
|
|
}
|
|
|
|
tween.unit = unit;
|
|
tween.start = start;
|
|
// If a +=/-= token was provided, we're doing a relative animation
|
|
tween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end;
|
|
}
|
|
return tween;
|
|
}]
|
|
};
|
|
|
|
// Animations created synchronously will run synchronously
|
|
function createFxNow() {
|
|
setTimeout(function() {
|
|
fxNow = undefined;
|
|
}, 0 );
|
|
return ( fxNow = jQuery.now() );
|
|
}
|
|
|
|
function createTweens( animation, props ) {
|
|
jQuery.each( props, function( prop, value ) {
|
|
var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
|
|
index = 0,
|
|
length = collection.length;
|
|
for ( ; index < length; index++ ) {
|
|
if ( collection[ index ].call( animation, prop, value ) ) {
|
|
|
|
// we're done with this property
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function Animation( elem, properties, options ) {
|
|
var result,
|
|
index = 0,
|
|
tweenerIndex = 0,
|
|
length = animationPrefilters.length,
|
|
deferred = jQuery.Deferred().always( function() {
|
|
// don't match elem in the :animated selector
|
|
delete tick.elem;
|
|
}),
|
|
tick = function() {
|
|
var currentTime = fxNow || createFxNow(),
|
|
remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
|
|
percent = 1 - ( remaining / animation.duration || 0 ),
|
|
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: {} }, options ),
|
|
originalProperties: properties,
|
|
originalOptions: options,
|
|
startTime: fxNow || createFxNow(),
|
|
duration: options.duration,
|
|
tweens: [],
|
|
createTween: function( prop, end, easing ) {
|
|
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;
|
|
|
|
for ( ; index < length ; index++ ) {
|
|
animation.tweens[ index ].run( 1 );
|
|
}
|
|
|
|
// resolve when we played the last frame
|
|
// otherwise, reject
|
|
if ( gotoEnd ) {
|
|
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 = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
|
|
if ( result ) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
createTweens( animation, props );
|
|
|
|
if ( jQuery.isFunction( animation.opts.start ) ) {
|
|
animation.opts.start.call( elem, animation );
|
|
}
|
|
|
|
jQuery.fx.timer(
|
|
jQuery.extend( tick, {
|
|
anim: animation,
|
|
queue: animation.opts.queue,
|
|
elem: elem
|
|
})
|
|
);
|
|
|
|
// 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 );
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
jQuery.Animation = jQuery.extend( Animation, {
|
|
|
|
tweener: function( props, callback ) {
|
|
if ( jQuery.isFunction( props ) ) {
|
|
callback = props;
|
|
props = [ "*" ];
|
|
} else {
|
|
props = props.split(" ");
|
|
}
|
|
|
|
var prop,
|
|
index = 0,
|
|
length = props.length;
|
|
|
|
for ( ; index < length ; index++ ) {
|
|
prop = props[ index ];
|
|
tweeners[ prop ] = tweeners[ prop ] || [];
|
|
tweeners[ prop ].unshift( callback );
|
|
}
|
|
},
|
|
|
|
prefilter: function( callback, prepend ) {
|
|
if ( prepend ) {
|
|
animationPrefilters.unshift( callback );
|
|
} else {
|
|
animationPrefilters.push( callback );
|
|
}
|
|
}
|
|
});
|
|
|
|
function defaultPrefilter( elem, props, opts ) {
|
|
var index, prop, value, length, dataShow, tween, hooks, oldfire,
|
|
anim = this,
|
|
style = elem.style,
|
|
orig = {},
|
|
handled = [],
|
|
hidden = elem.nodeType && isHidden( elem );
|
|
|
|
// 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
|
|
if ( jQuery.css( elem, "display" ) === "inline" &&
|
|
jQuery.css( elem, "float" ) === "none" ) {
|
|
|
|
// inline-level elements accept inline-block;
|
|
// block-level elements need to be inline with layout
|
|
if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) {
|
|
style.display = "inline-block";
|
|
|
|
} else {
|
|
style.zoom = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( opts.overflow ) {
|
|
style.overflow = "hidden";
|
|
if ( !jQuery.support.shrinkWrapBlocks ) {
|
|
anim.done(function() {
|
|
style.overflow = opts.overflow[ 0 ];
|
|
style.overflowX = opts.overflow[ 1 ];
|
|
style.overflowY = opts.overflow[ 2 ];
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
// show/hide pass
|
|
for ( index in props ) {
|
|
value = props[ index ];
|
|
if ( rfxtypes.exec( value ) ) {
|
|
delete props[ index ];
|
|
if ( value === ( hidden ? "hide" : "show" ) ) {
|
|
continue;
|
|
}
|
|
handled.push( index );
|
|
}
|
|
}
|
|
|
|
length = handled.length;
|
|
if ( length ) {
|
|
dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} );
|
|
if ( hidden ) {
|
|
jQuery( elem ).show();
|
|
} else {
|
|
anim.done(function() {
|
|
jQuery( elem ).hide();
|
|
});
|
|
}
|
|
anim.done(function() {
|
|
var prop;
|
|
jQuery.removeData( elem, "fxshow", true );
|
|
for ( prop in orig ) {
|
|
jQuery.style( elem, prop, orig[ prop ] );
|
|
}
|
|
});
|
|
for ( index = 0 ; index < length ; index++ ) {
|
|
prop = handled[ index ];
|
|
tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 );
|
|
orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop );
|
|
|
|
if ( !( prop in dataShow ) ) {
|
|
dataShow[ prop ] = tween.start;
|
|
if ( hidden ) {
|
|
tween.end = tween.start;
|
|
tween.start = prop === "width" || prop === "height" ? 1 : 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 || "swing";
|
|
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 ];
|
|
|
|
this.pos = eased = jQuery.easing[ this.easing ]( percent, this.options.duration * percent, 0, 1, this.options.duration );
|
|
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;
|
|
|
|
if ( tween.elem[ tween.prop ] != null &&
|
|
(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
|
|
return tween.elem[ tween.prop ];
|
|
}
|
|
|
|
// passing any value as a 4th 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, false, "" );
|
|
// 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.style && ( 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;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Remove in 2.0 - this supports IE8's 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.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" ||
|
|
// special check for .toggle( handler, handler, ... )
|
|
( !i && jQuery.isFunction( speed ) && jQuery.isFunction( easing ) ) ?
|
|
cssFn.apply( this, arguments ) :
|
|
this.animate( genFx( name, true ), speed, easing, callback );
|
|
};
|
|
});
|
|
|
|
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 resolve immediately
|
|
if ( empty ) {
|
|
anim.stop( true );
|
|
}
|
|
};
|
|
|
|
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 );
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// 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
|
|
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;
|
|
}
|
|
|
|
// 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.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.easing = {
|
|
linear: function( p ) {
|
|
return p;
|
|
},
|
|
swing: function( p ) {
|
|
return 0.5 - Math.cos( p*Math.PI ) / 2;
|
|
}
|
|
};
|
|
|
|
jQuery.timers = [];
|
|
jQuery.fx = Tween.prototype.init;
|
|
jQuery.fx.tick = function() {
|
|
var timer,
|
|
timers = jQuery.timers,
|
|
i = 0;
|
|
|
|
for ( ; i < timers.length; i++ ) {
|
|
timer = timers[ i ];
|
|
// Checks the timer has not already been removed
|
|
if ( !timer() && timers[ i ] === timer ) {
|
|
timers.splice( i--, 1 );
|
|
}
|
|
}
|
|
|
|
if ( !timers.length ) {
|
|
jQuery.fx.stop();
|
|
}
|
|
};
|
|
|
|
jQuery.fx.timer = function( timer ) {
|
|
if ( timer() && jQuery.timers.push( timer ) && !timerId ) {
|
|
timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
|
|
}
|
|
};
|
|
|
|
jQuery.fx.interval = 13;
|
|
|
|
jQuery.fx.stop = function() {
|
|
clearInterval( timerId );
|
|
timerId = null;
|
|
};
|
|
|
|
jQuery.fx.speeds = {
|
|
slow: 600,
|
|
fast: 200,
|
|
// Default speed
|
|
_default: 400
|
|
};
|
|
|
|
// Back Compat <1.8 extension point
|
|
jQuery.fx.step = {};
|
|
|
|
if ( jQuery.expr && jQuery.expr.filters ) {
|
|
jQuery.expr.filters.animated = function( elem ) {
|
|
return jQuery.grep(jQuery.timers, function( fn ) {
|
|
return elem === fn.elem;
|
|
}).length;
|
|
};
|
|
}
|
|
var rroot = /^(?:body|html)$/i;
|
|
|
|
jQuery.fn.offset = function( options ) {
|
|
if ( arguments.length ) {
|
|
return options === undefined ?
|
|
this :
|
|
this.each(function( i ) {
|
|
jQuery.offset.setOffset( this, options, i );
|
|
});
|
|
}
|
|
|
|
var box, docElem, body, win, clientTop, clientLeft, scrollTop, scrollLeft, top, left,
|
|
elem = this[ 0 ],
|
|
doc = elem && elem.ownerDocument;
|
|
|
|
if ( !doc ) {
|
|
return;
|
|
}
|
|
|
|
if ( (body = doc.body) === elem ) {
|
|
return jQuery.offset.bodyOffset( elem );
|
|
}
|
|
|
|
docElem = doc.documentElement;
|
|
|
|
// Make sure we're not dealing with a disconnected DOM node
|
|
if ( !jQuery.contains( docElem, elem ) ) {
|
|
return { top: 0, left: 0 };
|
|
}
|
|
|
|
box = elem.getBoundingClientRect();
|
|
win = getWindow( doc );
|
|
clientTop = docElem.clientTop || body.clientTop || 0;
|
|
clientLeft = docElem.clientLeft || body.clientLeft || 0;
|
|
scrollTop = win.pageYOffset || docElem.scrollTop;
|
|
scrollLeft = win.pageXOffset || docElem.scrollLeft;
|
|
top = box.top + scrollTop - clientTop;
|
|
left = box.left + scrollLeft - clientLeft;
|
|
|
|
return { top: top, left: left };
|
|
};
|
|
|
|
jQuery.offset = {
|
|
|
|
bodyOffset: function( body ) {
|
|
var top = body.offsetTop,
|
|
left = body.offsetLeft;
|
|
|
|
if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) {
|
|
top += parseFloat( jQuery.css(body, "marginTop") ) || 0;
|
|
left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
|
|
}
|
|
|
|
return { top: top, left: left };
|
|
},
|
|
|
|
setOffset: function( elem, options, i ) {
|
|
var position = jQuery.css( elem, "position" );
|
|
|
|
// set position first, in-case top/left are set even on static elem
|
|
if ( position === "static" ) {
|
|
elem.style.position = "relative";
|
|
}
|
|
|
|
var curElem = jQuery( elem ),
|
|
curOffset = curElem.offset(),
|
|
curCSSTop = jQuery.css( elem, "top" ),
|
|
curCSSLeft = jQuery.css( elem, "left" ),
|
|
calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
|
|
props = {}, curPosition = {}, curTop, curLeft;
|
|
|
|
// need to be able to calculate position if either top or left is auto and position is either absolute or fixed
|
|
if ( calculatePosition ) {
|
|
curPosition = curElem.position();
|
|
curTop = curPosition.top;
|
|
curLeft = curPosition.left;
|
|
} else {
|
|
curTop = parseFloat( curCSSTop ) || 0;
|
|
curLeft = parseFloat( curCSSLeft ) || 0;
|
|
}
|
|
|
|
if ( jQuery.isFunction( options ) ) {
|
|
options = options.call( elem, i, curOffset );
|
|
}
|
|
|
|
if ( options.top != null ) {
|
|
props.top = ( options.top - curOffset.top ) + curTop;
|
|
}
|
|
if ( options.left != null ) {
|
|
props.left = ( options.left - curOffset.left ) + curLeft;
|
|
}
|
|
|
|
if ( "using" in options ) {
|
|
options.using.call( elem, props );
|
|
} else {
|
|
curElem.css( props );
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
jQuery.fn.extend({
|
|
|
|
position: function() {
|
|
if ( !this[0] ) {
|
|
return;
|
|
}
|
|
|
|
var elem = this[0],
|
|
|
|
// Get *real* offsetParent
|
|
offsetParent = this.offsetParent(),
|
|
|
|
// Get correct offsets
|
|
offset = this.offset(),
|
|
parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
|
|
|
|
// Subtract element margins
|
|
// note: when an element has margin: auto the offsetLeft and marginLeft
|
|
// are the same in Safari causing offset.left to incorrectly be 0
|
|
offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
|
|
offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
|
|
|
|
// Add offsetParent borders
|
|
parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
|
|
parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
|
|
|
|
// Subtract the two offsets
|
|
return {
|
|
top: offset.top - parentOffset.top,
|
|
left: offset.left - parentOffset.left
|
|
};
|
|
},
|
|
|
|
offsetParent: function() {
|
|
return this.map(function() {
|
|
var offsetParent = this.offsetParent || document.body;
|
|
while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
|
|
offsetParent = offsetParent.offsetParent;
|
|
}
|
|
return offsetParent || document.body;
|
|
});
|
|
}
|
|
});
|
|
|
|
|
|
// 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 jQuery.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 );
|
|
};
|
|
});
|
|
|
|
function getWindow( elem ) {
|
|
return jQuery.isWindow( elem ) ?
|
|
elem :
|
|
elem.nodeType === 9 ?
|
|
elem.defaultView || elem.parentWindow :
|
|
false;
|
|
}
|
|
// 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 jQuery.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, value, extra ) :
|
|
|
|
// Set width or height on the element
|
|
jQuery.style( elem, type, value, extra );
|
|
}, type, chainable ? margin : undefined, chainable );
|
|
};
|
|
});
|
|
});
|
|
// Expose jQuery to the global object
|
|
window.jQuery = window.$ = jQuery;
|
|
|
|
// Expose jQuery as an AMD module, but only for AMD loaders that
|
|
// understand the issues with loading multiple versions of jQuery
|
|
// in a page that all might call define(). The loader will indicate
|
|
// they have special allowances for multiple jQuery versions by
|
|
// specifying define.amd.jQuery = true. Register as a named module,
|
|
// since jQuery can be concatenated with other files that may use define,
|
|
// but not use a proper concatenation script that understands anonymous
|
|
// AMD modules. A named AMD is safest and most robust way to register.
|
|
// Lowercase jquery is used because AMD module names are derived from
|
|
// file names, and jQuery is normally delivered in a lowercase file name.
|
|
// Do this after creating the global so that if an AMD module wants to call
|
|
// noConflict to hide this version of jQuery, it will work.
|
|
if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
|
|
define( "jquery", [], function () { return jQuery; } );
|
|
}
|
|
|
|
})( window );;/*! jQuery UI - v1.10.3 - 2013-06-12
|
|
* http://jqueryui.com
|
|
* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.sortable.js
|
|
* Copyright 2013 jQuery Foundation and other contributors Licensed MIT */
|
|
|
|
(function( $, undefined ) {
|
|
|
|
var uuid = 0,
|
|
runiqueId = /^ui-id-\d+$/;
|
|
|
|
// $.ui might exist from components with no dependencies, e.g., $.ui.position
|
|
$.ui = $.ui || {};
|
|
|
|
$.extend( $.ui, {
|
|
version: "1.10.3",
|
|
|
|
keyCode: {
|
|
BACKSPACE: 8,
|
|
COMMA: 188,
|
|
DELETE: 46,
|
|
DOWN: 40,
|
|
END: 35,
|
|
ENTER: 13,
|
|
ESCAPE: 27,
|
|
HOME: 36,
|
|
LEFT: 37,
|
|
NUMPAD_ADD: 107,
|
|
NUMPAD_DECIMAL: 110,
|
|
NUMPAD_DIVIDE: 111,
|
|
NUMPAD_ENTER: 108,
|
|
NUMPAD_MULTIPLY: 106,
|
|
NUMPAD_SUBTRACT: 109,
|
|
PAGE_DOWN: 34,
|
|
PAGE_UP: 33,
|
|
PERIOD: 190,
|
|
RIGHT: 39,
|
|
SPACE: 32,
|
|
TAB: 9,
|
|
UP: 38
|
|
}
|
|
});
|
|
|
|
// plugins
|
|
$.fn.extend({
|
|
focus: (function( orig ) {
|
|
return function( delay, fn ) {
|
|
return typeof delay === "number" ?
|
|
this.each(function() {
|
|
var elem = this;
|
|
setTimeout(function() {
|
|
$( elem ).focus();
|
|
if ( fn ) {
|
|
fn.call( elem );
|
|
}
|
|
}, delay );
|
|
}) :
|
|
orig.apply( this, arguments );
|
|
};
|
|
})( $.fn.focus ),
|
|
|
|
scrollParent: function() {
|
|
var scrollParent;
|
|
if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) {
|
|
scrollParent = this.parents().filter(function() {
|
|
return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
|
|
}).eq(0);
|
|
} else {
|
|
scrollParent = this.parents().filter(function() {
|
|
return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
|
|
}).eq(0);
|
|
}
|
|
|
|
return (/fixed/).test(this.css("position")) || !scrollParent.length ? $(document) : scrollParent;
|
|
},
|
|
|
|
zIndex: function( zIndex ) {
|
|
if ( zIndex !== undefined ) {
|
|
return this.css( "zIndex", zIndex );
|
|
}
|
|
|
|
if ( this.length ) {
|
|
var elem = $( this[ 0 ] ), position, value;
|
|
while ( elem.length && elem[ 0 ] !== document ) {
|
|
// Ignore z-index if position is set to a value where z-index is ignored by the browser
|
|
// This makes behavior of this function consistent across browsers
|
|
// WebKit always returns auto if the element is positioned
|
|
position = elem.css( "position" );
|
|
if ( position === "absolute" || position === "relative" || position === "fixed" ) {
|
|
// IE returns 0 when zIndex is not specified
|
|
// other browsers return a string
|
|
// we ignore the case of nested elements with an explicit value of 0
|
|
// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
|
|
value = parseInt( elem.css( "zIndex" ), 10 );
|
|
if ( !isNaN( value ) && value !== 0 ) {
|
|
return value;
|
|
}
|
|
}
|
|
elem = elem.parent();
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
},
|
|
|
|
uniqueId: function() {
|
|
return this.each(function() {
|
|
if ( !this.id ) {
|
|
this.id = "ui-id-" + (++uuid);
|
|
}
|
|
});
|
|
},
|
|
|
|
removeUniqueId: function() {
|
|
return this.each(function() {
|
|
if ( runiqueId.test( this.id ) ) {
|
|
$( this ).removeAttr( "id" );
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// selectors
|
|
function focusable( element, isTabIndexNotNaN ) {
|
|
var map, mapName, img,
|
|
nodeName = element.nodeName.toLowerCase();
|
|
if ( "area" === nodeName ) {
|
|
map = element.parentNode;
|
|
mapName = map.name;
|
|
if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
|
|
return false;
|
|
}
|
|
img = $( "img[usemap=#" + mapName + "]" )[0];
|
|
return !!img && visible( img );
|
|
}
|
|
return ( /input|select|textarea|button|object/.test( nodeName ) ?
|
|
!element.disabled :
|
|
"a" === nodeName ?
|
|
element.href || isTabIndexNotNaN :
|
|
isTabIndexNotNaN) &&
|
|
// the element and all of its ancestors must be visible
|
|
visible( element );
|
|
}
|
|
|
|
function visible( element ) {
|
|
return $.expr.filters.visible( element ) &&
|
|
!$( element ).parents().addBack().filter(function() {
|
|
return $.css( this, "visibility" ) === "hidden";
|
|
}).length;
|
|
}
|
|
|
|
$.extend( $.expr[ ":" ], {
|
|
data: $.expr.createPseudo ?
|
|
$.expr.createPseudo(function( dataName ) {
|
|
return function( elem ) {
|
|
return !!$.data( elem, dataName );
|
|
};
|
|
}) :
|
|
// support: jQuery <1.8
|
|
function( elem, i, match ) {
|
|
return !!$.data( elem, match[ 3 ] );
|
|
},
|
|
|
|
focusable: function( element ) {
|
|
return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
|
|
},
|
|
|
|
tabbable: function( element ) {
|
|
var tabIndex = $.attr( element, "tabindex" ),
|
|
isTabIndexNaN = isNaN( tabIndex );
|
|
return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
|
|
}
|
|
});
|
|
|
|
// support: jQuery <1.8
|
|
if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
|
|
$.each( [ "Width", "Height" ], function( i, name ) {
|
|
var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
|
|
type = name.toLowerCase(),
|
|
orig = {
|
|
innerWidth: $.fn.innerWidth,
|
|
innerHeight: $.fn.innerHeight,
|
|
outerWidth: $.fn.outerWidth,
|
|
outerHeight: $.fn.outerHeight
|
|
};
|
|
|
|
function reduce( elem, size, border, margin ) {
|
|
$.each( side, function() {
|
|
size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
|
|
if ( border ) {
|
|
size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
|
|
}
|
|
if ( margin ) {
|
|
size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
|
|
}
|
|
});
|
|
return size;
|
|
}
|
|
|
|
$.fn[ "inner" + name ] = function( size ) {
|
|
if ( size === undefined ) {
|
|
return orig[ "inner" + name ].call( this );
|
|
}
|
|
|
|
return this.each(function() {
|
|
$( this ).css( type, reduce( this, size ) + "px" );
|
|
});
|
|
};
|
|
|
|
$.fn[ "outer" + name] = function( size, margin ) {
|
|
if ( typeof size !== "number" ) {
|
|
return orig[ "outer" + name ].call( this, size );
|
|
}
|
|
|
|
return this.each(function() {
|
|
$( this).css( type, reduce( this, size, true, margin ) + "px" );
|
|
});
|
|
};
|
|
});
|
|
}
|
|
|
|
// support: jQuery <1.8
|
|
if ( !$.fn.addBack ) {
|
|
$.fn.addBack = function( selector ) {
|
|
return this.add( selector == null ?
|
|
this.prevObject : this.prevObject.filter( selector )
|
|
);
|
|
};
|
|
}
|
|
|
|
// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
|
|
if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
|
|
$.fn.removeData = (function( removeData ) {
|
|
return function( key ) {
|
|
if ( arguments.length ) {
|
|
return removeData.call( this, $.camelCase( key ) );
|
|
} else {
|
|
return removeData.call( this );
|
|
}
|
|
};
|
|
})( $.fn.removeData );
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// deprecated
|
|
$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() );
|
|
|
|
$.support.selectstart = "onselectstart" in document.createElement( "div" );
|
|
$.fn.extend({
|
|
disableSelection: function() {
|
|
return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
|
|
".ui-disableSelection", function( event ) {
|
|
event.preventDefault();
|
|
});
|
|
},
|
|
|
|
enableSelection: function() {
|
|
return this.unbind( ".ui-disableSelection" );
|
|
}
|
|
});
|
|
|
|
$.extend( $.ui, {
|
|
// $.ui.plugin is deprecated. Use $.widget() extensions instead.
|
|
plugin: {
|
|
add: function( module, option, set ) {
|
|
var i,
|
|
proto = $.ui[ module ].prototype;
|
|
for ( i in set ) {
|
|
proto.plugins[ i ] = proto.plugins[ i ] || [];
|
|
proto.plugins[ i ].push( [ option, set[ i ] ] );
|
|
}
|
|
},
|
|
call: function( instance, name, args ) {
|
|
var i,
|
|
set = instance.plugins[ name ];
|
|
if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) {
|
|
return;
|
|
}
|
|
|
|
for ( i = 0; i < set.length; i++ ) {
|
|
if ( instance.options[ set[ i ][ 0 ] ] ) {
|
|
set[ i ][ 1 ].apply( instance.element, args );
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
// only used by resizable
|
|
hasScroll: function( el, a ) {
|
|
|
|
//If overflow is hidden, the element might have extra content, but the user wants to hide it
|
|
if ( $( el ).css( "overflow" ) === "hidden") {
|
|
return false;
|
|
}
|
|
|
|
var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
|
|
has = false;
|
|
|
|
if ( el[ scroll ] > 0 ) {
|
|
return true;
|
|
}
|
|
|
|
// TODO: determine which cases actually cause this to happen
|
|
// if the element doesn't have the scroll set, see if it's possible to
|
|
// set the scroll
|
|
el[ scroll ] = 1;
|
|
has = ( el[ scroll ] > 0 );
|
|
el[ scroll ] = 0;
|
|
return has;
|
|
}
|
|
});
|
|
|
|
})( jQuery );
|
|
(function( $, undefined ) {
|
|
|
|
var uuid = 0,
|
|
slice = Array.prototype.slice,
|
|
_cleanData = $.cleanData;
|
|
$.cleanData = function( elems ) {
|
|
for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
|
|
try {
|
|
$( elem ).triggerHandler( "remove" );
|
|
// http://bugs.jquery.com/ticket/8235
|
|
} catch( e ) {}
|
|
}
|
|
_cleanData( elems );
|
|
};
|
|
|
|
$.widget = function( name, base, prototype ) {
|
|
var fullName, existingConstructor, constructor, basePrototype,
|
|
// proxiedPrototype allows the provided prototype to remain unmodified
|
|
// so that it can be used as a mixin for multiple widgets (#8876)
|
|
proxiedPrototype = {},
|
|
namespace = name.split( "." )[ 0 ];
|
|
|
|
name = name.split( "." )[ 1 ];
|
|
fullName = namespace + "-" + name;
|
|
|
|
if ( !prototype ) {
|
|
prototype = base;
|
|
base = $.Widget;
|
|
}
|
|
|
|
// create selector for plugin
|
|
$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
|
|
return !!$.data( elem, fullName );
|
|
};
|
|
|
|
$[ namespace ] = $[ namespace ] || {};
|
|
existingConstructor = $[ namespace ][ name ];
|
|
constructor = $[ namespace ][ name ] = function( options, element ) {
|
|
// allow instantiation without "new" keyword
|
|
if ( !this._createWidget ) {
|
|
return new constructor( options, element );
|
|
}
|
|
|
|
// allow instantiation without initializing for simple inheritance
|
|
// must use "new" keyword (the code above always passes args)
|
|
if ( arguments.length ) {
|
|
this._createWidget( options, element );
|
|
}
|
|
};
|
|
// extend with the existing constructor to carry over any static properties
|
|
$.extend( constructor, existingConstructor, {
|
|
version: prototype.version,
|
|
// copy the object used to create the prototype in case we need to
|
|
// redefine the widget later
|
|
_proto: $.extend( {}, prototype ),
|
|
// track widgets that inherit from this widget in case this widget is
|
|
// redefined after a widget inherits from it
|
|
_childConstructors: []
|
|
});
|
|
|
|
basePrototype = new base();
|
|
// we need to make the options hash a property directly on the new instance
|
|
// otherwise we'll modify the options hash on the prototype that we're
|
|
// inheriting from
|
|
basePrototype.options = $.widget.extend( {}, basePrototype.options );
|
|
$.each( prototype, function( prop, value ) {
|
|
if ( !$.isFunction( value ) ) {
|
|
proxiedPrototype[ prop ] = value;
|
|
return;
|
|
}
|
|
proxiedPrototype[ prop ] = (function() {
|
|
var _super = function() {
|
|
return base.prototype[ prop ].apply( this, arguments );
|
|
},
|
|
_superApply = function( args ) {
|
|
return base.prototype[ prop ].apply( this, args );
|
|
};
|
|
return function() {
|
|
var __super = this._super,
|
|
__superApply = this._superApply,
|
|
returnValue;
|
|
|
|
this._super = _super;
|
|
this._superApply = _superApply;
|
|
|
|
returnValue = value.apply( this, arguments );
|
|
|
|
this._super = __super;
|
|
this._superApply = __superApply;
|
|
|
|
return returnValue;
|
|
};
|
|
})();
|
|
});
|
|
constructor.prototype = $.widget.extend( basePrototype, {
|
|
// TODO: remove support for widgetEventPrefix
|
|
// always use the name + a colon as the prefix, e.g., draggable:start
|
|
// don't prefix for widgets that aren't DOM-based
|
|
widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name
|
|
}, proxiedPrototype, {
|
|
constructor: constructor,
|
|
namespace: namespace,
|
|
widgetName: name,
|
|
widgetFullName: fullName
|
|
});
|
|
|
|
// If this widget is being redefined then we need to find all widgets that
|
|
// are inheriting from it and redefine all of them so that they inherit from
|
|
// the new version of this widget. We're essentially trying to replace one
|
|
// level in the prototype chain.
|
|
if ( existingConstructor ) {
|
|
$.each( existingConstructor._childConstructors, function( i, child ) {
|
|
var childPrototype = child.prototype;
|
|
|
|
// redefine the child widget using the same prototype that was
|
|
// originally used, but inherit from the new version of the base
|
|
$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
|
|
});
|
|
// remove the list of existing child constructors from the old constructor
|
|
// so the old child constructors can be garbage collected
|
|
delete existingConstructor._childConstructors;
|
|
} else {
|
|
base._childConstructors.push( constructor );
|
|
}
|
|
|
|
$.widget.bridge( name, constructor );
|
|
};
|
|
|
|
$.widget.extend = function( target ) {
|
|
var input = slice.call( arguments, 1 ),
|
|
inputIndex = 0,
|
|
inputLength = input.length,
|
|
key,
|
|
value;
|
|
for ( ; inputIndex < inputLength; inputIndex++ ) {
|
|
for ( key in input[ inputIndex ] ) {
|
|
value = input[ inputIndex ][ key ];
|
|
if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
|
|
// Clone objects
|
|
if ( $.isPlainObject( value ) ) {
|
|
target[ key ] = $.isPlainObject( target[ key ] ) ?
|
|
$.widget.extend( {}, target[ key ], value ) :
|
|
// Don't extend strings, arrays, etc. with objects
|
|
$.widget.extend( {}, value );
|
|
// Copy everything else by reference
|
|
} else {
|
|
target[ key ] = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return target;
|
|
};
|
|
|
|
$.widget.bridge = function( name, object ) {
|
|
var fullName = object.prototype.widgetFullName || name;
|
|
$.fn[ name ] = function( options ) {
|
|
var isMethodCall = typeof options === "string",
|
|
args = slice.call( arguments, 1 ),
|
|
returnValue = this;
|
|
|
|
// allow multiple hashes to be passed on init
|
|
options = !isMethodCall && args.length ?
|
|
$.widget.extend.apply( null, [ options ].concat(args) ) :
|
|
options;
|
|
|
|
if ( isMethodCall ) {
|
|
this.each(function() {
|
|
var methodValue,
|
|
instance = $.data( this, fullName );
|
|
if ( !instance ) {
|
|
return $.error( "cannot call methods on " + name + " prior to initialization; " +
|
|
"attempted to call method '" + options + "'" );
|
|
}
|
|
if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
|
|
return $.error( "no such method '" + options + "' for " + name + " widget instance" );
|
|
}
|
|
methodValue = instance[ options ].apply( instance, args );
|
|
if ( methodValue !== instance && methodValue !== undefined ) {
|
|
returnValue = methodValue && methodValue.jquery ?
|
|
returnValue.pushStack( methodValue.get() ) :
|
|
methodValue;
|
|
return false;
|
|
}
|
|
});
|
|
} else {
|
|
this.each(function() {
|
|
var instance = $.data( this, fullName );
|
|
if ( instance ) {
|
|
instance.option( options || {} )._init();
|
|
} else {
|
|
$.data( this, fullName, new object( options, this ) );
|
|
}
|
|
});
|
|
}
|
|
|
|
return returnValue;
|
|
};
|
|
};
|
|
|
|
$.Widget = function( /* options, element */ ) {};
|
|
$.Widget._childConstructors = [];
|
|
|
|
$.Widget.prototype = {
|
|
widgetName: "widget",
|
|
widgetEventPrefix: "",
|
|
defaultElement: "<div>",
|
|
options: {
|
|
disabled: false,
|
|
|
|
// callbacks
|
|
create: null
|
|
},
|
|
_createWidget: function( options, element ) {
|
|
element = $( element || this.defaultElement || this )[ 0 ];
|
|
this.element = $( element );
|
|
this.uuid = uuid++;
|
|
this.eventNamespace = "." + this.widgetName + this.uuid;
|
|
this.options = $.widget.extend( {},
|
|
this.options,
|
|
this._getCreateOptions(),
|
|
options );
|
|
|
|
this.bindings = $();
|
|
this.hoverable = $();
|
|
this.focusable = $();
|
|
|
|
if ( element !== this ) {
|
|
$.data( element, this.widgetFullName, this );
|
|
this._on( true, this.element, {
|
|
remove: function( event ) {
|
|
if ( event.target === element ) {
|
|
this.destroy();
|
|
}
|
|
}
|
|
});
|
|
this.document = $( element.style ?
|
|
// element within the document
|
|
element.ownerDocument :
|
|
// element is window or document
|
|
element.document || element );
|
|
this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
|
|
}
|
|
|
|
this._create();
|
|
this._trigger( "create", null, this._getCreateEventData() );
|
|
this._init();
|
|
},
|
|
_getCreateOptions: $.noop,
|
|
_getCreateEventData: $.noop,
|
|
_create: $.noop,
|
|
_init: $.noop,
|
|
|
|
destroy: function() {
|
|
this._destroy();
|
|
// we can probably remove the unbind calls in 2.0
|
|
// all event bindings should go through this._on()
|
|
this.element
|
|
.unbind( this.eventNamespace )
|
|
// 1.9 BC for #7810
|
|
// TODO remove dual storage
|
|
.removeData( this.widgetName )
|
|
.removeData( this.widgetFullName )
|
|
// support: jquery <1.6.3
|
|
// http://bugs.jquery.com/ticket/9413
|
|
.removeData( $.camelCase( this.widgetFullName ) );
|
|
this.widget()
|
|
.unbind( this.eventNamespace )
|
|
.removeAttr( "aria-disabled" )
|
|
.removeClass(
|
|
this.widgetFullName + "-disabled " +
|
|
"ui-state-disabled" );
|
|
|
|
// clean up events and states
|
|
this.bindings.unbind( this.eventNamespace );
|
|
this.hoverable.removeClass( "ui-state-hover" );
|
|
this.focusable.removeClass( "ui-state-focus" );
|
|
},
|
|
_destroy: $.noop,
|
|
|
|
widget: function() {
|
|
return this.element;
|
|
},
|
|
|
|
option: function( key, value ) {
|
|
var options = key,
|
|
parts,
|
|
curOption,
|
|
i;
|
|
|
|
if ( arguments.length === 0 ) {
|
|
// don't return a reference to the internal hash
|
|
return $.widget.extend( {}, this.options );
|
|
}
|
|
|
|
if ( typeof key === "string" ) {
|
|
// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
|
|
options = {};
|
|
parts = key.split( "." );
|
|
key = parts.shift();
|
|
if ( parts.length ) {
|
|
curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
|
|
for ( i = 0; i < parts.length - 1; i++ ) {
|
|
curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
|
|
curOption = curOption[ parts[ i ] ];
|
|
}
|
|
key = parts.pop();
|
|
if ( value === undefined ) {
|
|
return curOption[ key ] === undefined ? null : curOption[ key ];
|
|
}
|
|
curOption[ key ] = value;
|
|
} else {
|
|
if ( value === undefined ) {
|
|
return this.options[ key ] === undefined ? null : this.options[ key ];
|
|
}
|
|
options[ key ] = value;
|
|
}
|
|
}
|
|
|
|
this._setOptions( options );
|
|
|
|
return this;
|
|
},
|
|
_setOptions: function( options ) {
|
|
var key;
|
|
|
|
for ( key in options ) {
|
|
this._setOption( key, options[ key ] );
|
|
}
|
|
|
|
return this;
|
|
},
|
|
_setOption: function( key, value ) {
|
|
this.options[ key ] = value;
|
|
|
|
if ( key === "disabled" ) {
|
|
this.widget()
|
|
.toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
|
|
.attr( "aria-disabled", value );
|
|
this.hoverable.removeClass( "ui-state-hover" );
|
|
this.focusable.removeClass( "ui-state-focus" );
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
enable: function() {
|
|
return this._setOption( "disabled", false );
|
|
},
|
|
disable: function() {
|
|
return this._setOption( "disabled", true );
|
|
},
|
|
|
|
_on: function( suppressDisabledCheck, element, handlers ) {
|
|
var delegateElement,
|
|
instance = this;
|
|
|
|
// no suppressDisabledCheck flag, shuffle arguments
|
|
if ( typeof suppressDisabledCheck !== "boolean" ) {
|
|
handlers = element;
|
|
element = suppressDisabledCheck;
|
|
suppressDisabledCheck = false;
|
|
}
|
|
|
|
// no element argument, shuffle and use this.element
|
|
if ( !handlers ) {
|
|
handlers = element;
|
|
element = this.element;
|
|
delegateElement = this.widget();
|
|
} else {
|
|
// accept selectors, DOM elements
|
|
element = delegateElement = $( element );
|
|
this.bindings = this.bindings.add( element );
|
|
}
|
|
|
|
$.each( handlers, function( event, handler ) {
|
|
function handlerProxy() {
|
|
// allow widgets to customize the disabled handling
|
|
// - disabled as an array instead of boolean
|
|
// - disabled class as method for disabling individual parts
|
|
if ( !suppressDisabledCheck &&
|
|
( instance.options.disabled === true ||
|
|
$( this ).hasClass( "ui-state-disabled" ) ) ) {
|
|
return;
|
|
}
|
|
return ( typeof handler === "string" ? instance[ handler ] : handler )
|
|
.apply( instance, arguments );
|
|
}
|
|
|
|
// copy the guid so direct unbinding works
|
|
if ( typeof handler !== "string" ) {
|
|
handlerProxy.guid = handler.guid =
|
|
handler.guid || handlerProxy.guid || $.guid++;
|
|
}
|
|
|
|
var match = event.match( /^(\w+)\s*(.*)$/ ),
|
|
eventName = match[1] + instance.eventNamespace,
|
|
selector = match[2];
|
|
if ( selector ) {
|
|
delegateElement.delegate( selector, eventName, handlerProxy );
|
|
} else {
|
|
element.bind( eventName, handlerProxy );
|
|
}
|
|
});
|
|
},
|
|
|
|
_off: function( element, eventName ) {
|
|
eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
|
|
element.unbind( eventName ).undelegate( eventName );
|
|
},
|
|
|
|
_delay: function( handler, delay ) {
|
|
function handlerProxy() {
|
|
return ( typeof handler === "string" ? instance[ handler ] : handler )
|
|
.apply( instance, arguments );
|
|
}
|
|
var instance = this;
|
|
return setTimeout( handlerProxy, delay || 0 );
|
|
},
|
|
|
|
_hoverable: function( element ) {
|
|
this.hoverable = this.hoverable.add( element );
|
|
this._on( element, {
|
|
mouseenter: function( event ) {
|
|
$( event.currentTarget ).addClass( "ui-state-hover" );
|
|
},
|
|
mouseleave: function( event ) {
|
|
$( event.currentTarget ).removeClass( "ui-state-hover" );
|
|
}
|
|
});
|
|
},
|
|
|
|
_focusable: function( element ) {
|
|
this.focusable = this.focusable.add( element );
|
|
this._on( element, {
|
|
focusin: function( event ) {
|
|
$( event.currentTarget ).addClass( "ui-state-focus" );
|
|
},
|
|
focusout: function( event ) {
|
|
$( event.currentTarget ).removeClass( "ui-state-focus" );
|
|
}
|
|
});
|
|
},
|
|
|
|
_trigger: function( type, event, data ) {
|
|
var prop, orig,
|
|
callback = this.options[ type ];
|
|
|
|
data = data || {};
|
|
event = $.Event( event );
|
|
event.type = ( type === this.widgetEventPrefix ?
|
|
type :
|
|
this.widgetEventPrefix + type ).toLowerCase();
|
|
// the original event may come from any element
|
|
// so we need to reset the target on the new event
|
|
event.target = this.element[ 0 ];
|
|
|
|
// copy original event properties over to the new event
|
|
orig = event.originalEvent;
|
|
if ( orig ) {
|
|
for ( prop in orig ) {
|
|
if ( !( prop in event ) ) {
|
|
event[ prop ] = orig[ prop ];
|
|
}
|
|
}
|
|
}
|
|
|
|
this.element.trigger( event, data );
|
|
return !( $.isFunction( callback ) &&
|
|
callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
|
|
event.isDefaultPrevented() );
|
|
}
|
|
};
|
|
|
|
$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
|
|
$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
|
|
if ( typeof options === "string" ) {
|
|
options = { effect: options };
|
|
}
|
|
var hasOptions,
|
|
effectName = !options ?
|
|
method :
|
|
options === true || typeof options === "number" ?
|
|
defaultEffect :
|
|
options.effect || defaultEffect;
|
|
options = options || {};
|
|
if ( typeof options === "number" ) {
|
|
options = { duration: options };
|
|
}
|
|
hasOptions = !$.isEmptyObject( options );
|
|
options.complete = callback;
|
|
if ( options.delay ) {
|
|
element.delay( options.delay );
|
|
}
|
|
if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
|
|
element[ method ]( options );
|
|
} else if ( effectName !== method && element[ effectName ] ) {
|
|
element[ effectName ]( options.duration, options.easing, callback );
|
|
} else {
|
|
element.queue(function( next ) {
|
|
$( this )[ method ]();
|
|
if ( callback ) {
|
|
callback.call( element[ 0 ] );
|
|
}
|
|
next();
|
|
});
|
|
}
|
|
};
|
|
});
|
|
|
|
})( jQuery );
|
|
(function( $, undefined ) {
|
|
|
|
var mouseHandled = false;
|
|
$( document ).mouseup( function() {
|
|
mouseHandled = false;
|
|
});
|
|
|
|
$.widget("ui.mouse", {
|
|
version: "1.10.3",
|
|
options: {
|
|
cancel: "input,textarea,button,select,option",
|
|
distance: 1,
|
|
delay: 0
|
|
},
|
|
_mouseInit: function() {
|
|
var that = this;
|
|
|
|
this.element
|
|
.bind("mousedown."+this.widgetName, function(event) {
|
|
return that._mouseDown(event);
|
|
})
|
|
.bind("click."+this.widgetName, function(event) {
|
|
if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) {
|
|
$.removeData(event.target, that.widgetName + ".preventClickEvent");
|
|
event.stopImmediatePropagation();
|
|
return false;
|
|
}
|
|
});
|
|
|
|
this.started = false;
|
|
},
|
|
|
|
// TODO: make sure destroying one instance of mouse doesn't mess with
|
|
// other instances of mouse
|
|
_mouseDestroy: function() {
|
|
this.element.unbind("."+this.widgetName);
|
|
if ( this._mouseMoveDelegate ) {
|
|
$(document)
|
|
.unbind("mousemove."+this.widgetName, this._mouseMoveDelegate)
|
|
.unbind("mouseup."+this.widgetName, this._mouseUpDelegate);
|
|
}
|
|
},
|
|
|
|
_mouseDown: function(event) {
|
|
// don't let more than one widget handle mouseStart
|
|
if( mouseHandled ) { return; }
|
|
|
|
// we may have missed mouseup (out of window)
|
|
(this._mouseStarted && this._mouseUp(event));
|
|
|
|
this._mouseDownEvent = event;
|
|
|
|
var that = this,
|
|
btnIsLeft = (event.which === 1),
|
|
// event.target.nodeName works around a bug in IE 8 with
|
|
// disabled inputs (#7620)
|
|
elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
|
|
if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
|
|
return true;
|
|
}
|
|
|
|
this.mouseDelayMet = !this.options.delay;
|
|
if (!this.mouseDelayMet) {
|
|
this._mouseDelayTimer = setTimeout(function() {
|
|
that.mouseDelayMet = true;
|
|
}, this.options.delay);
|
|
}
|
|
|
|
if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
|
|
this._mouseStarted = (this._mouseStart(event) !== false);
|
|
if (!this._mouseStarted) {
|
|
event.preventDefault();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Click event may never have fired (Gecko & Opera)
|
|
if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) {
|
|
$.removeData(event.target, this.widgetName + ".preventClickEvent");
|
|
}
|
|
|
|
// these delegates are required to keep context
|
|
this._mouseMoveDelegate = function(event) {
|
|
return that._mouseMove(event);
|
|
};
|
|
this._mouseUpDelegate = function(event) {
|
|
return that._mouseUp(event);
|
|
};
|
|
$(document)
|
|
.bind("mousemove."+this.widgetName, this._mouseMoveDelegate)
|
|
.bind("mouseup."+this.widgetName, this._mouseUpDelegate);
|
|
|
|
event.preventDefault();
|
|
|
|
mouseHandled = true;
|
|
return true;
|
|
},
|
|
|
|
_mouseMove: function(event) {
|
|
// IE mouseup check - mouseup happened when mouse was out of window
|
|
if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) {
|
|
return this._mouseUp(event);
|
|
}
|
|
|
|
if (this._mouseStarted) {
|
|
this._mouseDrag(event);
|
|
return event.preventDefault();
|
|
}
|
|
|
|
if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
|
|
this._mouseStarted =
|
|
(this._mouseStart(this._mouseDownEvent, event) !== false);
|
|
(this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
|
|
}
|
|
|
|
return !this._mouseStarted;
|
|
},
|
|
|
|
_mouseUp: function(event) {
|
|
$(document)
|
|
.unbind("mousemove."+this.widgetName, this._mouseMoveDelegate)
|
|
.unbind("mouseup."+this.widgetName, this._mouseUpDelegate);
|
|
|
|
if (this._mouseStarted) {
|
|
this._mouseStarted = false;
|
|
|
|
if (event.target === this._mouseDownEvent.target) {
|
|
$.data(event.target, this.widgetName + ".preventClickEvent", true);
|
|
}
|
|
|
|
this._mouseStop(event);
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
_mouseDistanceMet: function(event) {
|
|
return (Math.max(
|
|
Math.abs(this._mouseDownEvent.pageX - event.pageX),
|
|
Math.abs(this._mouseDownEvent.pageY - event.pageY)
|
|
) >= this.options.distance
|
|
);
|
|
},
|
|
|
|
_mouseDelayMet: function(/* event */) {
|
|
return this.mouseDelayMet;
|
|
},
|
|
|
|
// These are placeholder methods, to be overriden by extending plugin
|
|
_mouseStart: function(/* event */) {},
|
|
_mouseDrag: function(/* event */) {},
|
|
_mouseStop: function(/* event */) {},
|
|
_mouseCapture: function(/* event */) { return true; }
|
|
});
|
|
|
|
})(jQuery);
|
|
(function( $, undefined ) {
|
|
|
|
/*jshint loopfunc: true */
|
|
|
|
function isOverAxis( x, reference, size ) {
|
|
return ( x > reference ) && ( x < ( reference + size ) );
|
|
}
|
|
|
|
function isFloating(item) {
|
|
return (/left|right/).test(item.css("float")) || (/inline|table-cell/).test(item.css("display"));
|
|
}
|
|
|
|
$.widget("ui.sortable", $.ui.mouse, {
|
|
version: "1.10.3",
|
|
widgetEventPrefix: "sort",
|
|
ready: false,
|
|
options: {
|
|
appendTo: "parent",
|
|
axis: false,
|
|
connectWith: false,
|
|
containment: false,
|
|
cursor: "auto",
|
|
cursorAt: false,
|
|
dropOnEmpty: true,
|
|
forcePlaceholderSize: false,
|
|
forceHelperSize: false,
|
|
grid: false,
|
|
handle: false,
|
|
helper: "original",
|
|
items: "> *",
|
|
opacity: false,
|
|
placeholder: false,
|
|
revert: false,
|
|
scroll: true,
|
|
scrollSensitivity: 20,
|
|
scrollSpeed: 20,
|
|
scope: "default",
|
|
tolerance: "intersect",
|
|
zIndex: 1000,
|
|
|
|
// callbacks
|
|
activate: null,
|
|
beforeStop: null,
|
|
change: null,
|
|
deactivate: null,
|
|
out: null,
|
|
over: null,
|
|
receive: null,
|
|
remove: null,
|
|
sort: null,
|
|
start: null,
|
|
stop: null,
|
|
update: null
|
|
},
|
|
_create: function() {
|
|
|
|
var o = this.options;
|
|
this.containerCache = {};
|
|
this.element.addClass("ui-sortable");
|
|
|
|
//Get the items
|
|
this.refresh();
|
|
|
|
//Let's determine if the items are being displayed horizontally
|
|
this.floating = this.items.length ? o.axis === "x" || isFloating(this.items[0].item) : false;
|
|
|
|
//Let's determine the parent's offset
|
|
this.offset = this.element.offset();
|
|
|
|
//Initialize mouse events for interaction
|
|
this._mouseInit();
|
|
|
|
//We're ready to go
|
|
this.ready = true;
|
|
|
|
},
|
|
|
|
_destroy: function() {
|
|
this.element
|
|
.removeClass("ui-sortable ui-sortable-disabled");
|
|
this._mouseDestroy();
|
|
|
|
for ( var i = this.items.length - 1; i >= 0; i-- ) {
|
|
this.items[i].item.removeData(this.widgetName + "-item");
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
_setOption: function(key, value){
|
|
if ( key === "disabled" ) {
|
|
this.options[ key ] = value;
|
|
|
|
this.widget().toggleClass( "ui-sortable-disabled", !!value );
|
|
} else {
|
|
// Don't call widget base _setOption for disable as it adds ui-state-disabled class
|
|
$.Widget.prototype._setOption.apply(this, arguments);
|
|
}
|
|
},
|
|
|
|
_mouseCapture: function(event, overrideHandle) {
|
|
var currentItem = null,
|
|
validHandle = false,
|
|
that = this;
|
|
|
|
if (this.reverting) {
|
|
return false;
|
|
}
|
|
|
|
if(this.options.disabled || this.options.type === "static") {
|
|
return false;
|
|
}
|
|
|
|
//We have to refresh the items data once first
|
|
this._refreshItems(event);
|
|
|
|
//Find out if the clicked node (or one of its parents) is a actual item in this.items
|
|
$(event.target).parents().each(function() {
|
|
if($.data(this, that.widgetName + "-item") === that) {
|
|
currentItem = $(this);
|
|
return false;
|
|
}
|
|
});
|
|
if($.data(event.target, that.widgetName + "-item") === that) {
|
|
currentItem = $(event.target);
|
|
}
|
|
|
|
if(!currentItem) {
|
|
return false;
|
|
}
|
|
if(this.options.handle && !overrideHandle) {
|
|
$(this.options.handle, currentItem).find("*").addBack().each(function() {
|
|
if(this === event.target) {
|
|
validHandle = true;
|
|
}
|
|
});
|
|
if(!validHandle) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
this.currentItem = currentItem;
|
|
this._removeCurrentsFromItems();
|
|
return true;
|
|
|
|
},
|
|
|
|
_mouseStart: function(event, overrideHandle, noActivation) {
|
|
|
|
var i, body,
|
|
o = this.options;
|
|
|
|
this.currentContainer = this;
|
|
|
|
//We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
|
|
this.refreshPositions();
|
|
|
|
//Create and append the visible helper
|
|
this.helper = this._createHelper(event);
|
|
|
|
//Cache the helper size
|
|
this._cacheHelperProportions();
|
|
|
|
/*
|
|
* - Position generation -
|
|
* This block generates everything position related - it's the core of draggables.
|
|
*/
|
|
|
|
//Cache the margins of the original element
|
|
this._cacheMargins();
|
|
|
|
//Get the next scrolling parent
|
|
this.scrollParent = this.helper.scrollParent();
|
|
|
|
//The element's absolute position on the page minus margins
|
|
this.offset = this.currentItem.offset();
|
|
this.offset = {
|
|
top: this.offset.top - this.margins.top,
|
|
left: this.offset.left - this.margins.left
|
|
};
|
|
|
|
$.extend(this.offset, {
|
|
click: { //Where the click happened, relative to the element
|
|
left: event.pageX - this.offset.left,
|
|
top: event.pageY - this.offset.top
|
|
},
|
|
parent: this._getParentOffset(),
|
|
relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
|
|
});
|
|
|
|
// Only after we got the offset, we can change the helper's position to absolute
|
|
// TODO: Still need to figure out a way to make relative sorting possible
|
|
this.helper.css("position", "absolute");
|
|
this.cssPosition = this.helper.css("position");
|
|
|
|
//Generate the original position
|
|
this.originalPosition = this._generatePosition(event);
|
|
this.originalPageX = event.pageX;
|
|
this.originalPageY = event.pageY;
|
|
|
|
//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
|
|
(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
|
|
|
|
//Cache the former DOM position
|
|
this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };
|
|
|
|
//If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way
|
|
if(this.helper[0] !== this.currentItem[0]) {
|
|
this.currentItem.hide();
|
|
}
|
|
|
|
//Create the placeholder
|
|
this._createPlaceholder();
|
|
|
|
//Set a containment if given in the options
|
|
if(o.containment) {
|
|
this._setContainment();
|
|
}
|
|
|
|
if( o.cursor && o.cursor !== "auto" ) { // cursor option
|
|
body = this.document.find( "body" );
|
|
|
|
// support: IE
|
|
this.storedCursor = body.css( "cursor" );
|
|
body.css( "cursor", o.cursor );
|
|
|
|
this.storedStylesheet = $( "<style>*{ cursor: "+o.cursor+" !important; }</style>" ).appendTo( body );
|
|
}
|
|
|
|
if(o.opacity) { // opacity option
|
|
if (this.helper.css("opacity")) {
|
|
this._storedOpacity = this.helper.css("opacity");
|
|
}
|
|
this.helper.css("opacity", o.opacity);
|
|
}
|
|
|
|
if(o.zIndex) { // zIndex option
|
|
if (this.helper.css("zIndex")) {
|
|
this._storedZIndex = this.helper.css("zIndex");
|
|
}
|
|
this.helper.css("zIndex", o.zIndex);
|
|
}
|
|
|
|
//Prepare scrolling
|
|
if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") {
|
|
this.overflowOffset = this.scrollParent.offset();
|
|
}
|
|
|
|
//Call callbacks
|
|
this._trigger("start", event, this._uiHash());
|
|
|
|
//Recache the helper size
|
|
if(!this._preserveHelperProportions) {
|
|
this._cacheHelperProportions();
|
|
}
|
|
|
|
|
|
//Post "activate" events to possible containers
|
|
if( !noActivation ) {
|
|
for ( i = this.containers.length - 1; i >= 0; i-- ) {
|
|
this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) );
|
|
}
|
|
}
|
|
|
|
//Prepare possible droppables
|
|
if($.ui.ddmanager) {
|
|
$.ui.ddmanager.current = this;
|
|
}
|
|
|
|
if ($.ui.ddmanager && !o.dropBehaviour) {
|
|
$.ui.ddmanager.prepareOffsets(this, event);
|
|
}
|
|
|
|
this.dragging = true;
|
|
|
|
this.helper.addClass("ui-sortable-helper");
|
|
this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
|
|
return true;
|
|
|
|
},
|
|
|
|
_mouseDrag: function(event) {
|
|
var i, item, itemElement, intersection,
|
|
o = this.options,
|
|
scrolled = false;
|
|
|
|
//Compute the helpers position
|
|
this.position = this._generatePosition(event);
|
|
this.positionAbs = this._convertPositionTo("absolute");
|
|
|
|
if (!this.lastPositionAbs) {
|
|
this.lastPositionAbs = this.positionAbs;
|
|
}
|
|
|
|
//Do scrolling
|
|
if(this.options.scroll) {
|
|
if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") {
|
|
|
|
if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) {
|
|
this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
|
|
} else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) {
|
|
this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
|
|
}
|
|
|
|
if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) {
|
|
this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
|
|
} else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) {
|
|
this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
|
|
}
|
|
|
|
} else {
|
|
|
|
if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) {
|
|
scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
|
|
} else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) {
|
|
scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
|
|
}
|
|
|
|
if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) {
|
|
scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
|
|
} else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) {
|
|
scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
|
|
}
|
|
|
|
}
|
|
|
|
if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
|
|
$.ui.ddmanager.prepareOffsets(this, event);
|
|
}
|
|
}
|
|
|
|
//Regenerate the absolute position used for position checks
|
|
this.positionAbs = this._convertPositionTo("absolute");
|
|
|
|
//Set the helper position
|
|
if(!this.options.axis || this.options.axis !== "y") {
|
|
this.helper[0].style.left = this.position.left+"px";
|
|
}
|
|
if(!this.options.axis || this.options.axis !== "x") {
|
|
this.helper[0].style.top = this.position.top+"px";
|
|
}
|
|
|
|
//Rearrange
|
|
for (i = this.items.length - 1; i >= 0; i--) {
|
|
|
|
//Cache variables and intersection, continue if no intersection
|
|
item = this.items[i];
|
|
itemElement = item.item[0];
|
|
intersection = this._intersectsWithPointer(item);
|
|
if (!intersection) {
|
|
continue;
|
|
}
|
|
|
|
// Only put the placeholder inside the current Container, skip all
|
|
// items form other containers. This works because when moving
|
|
// an item from one container to another the
|
|
// currentContainer is switched before the placeholder is moved.
|
|
//
|
|
// Without this moving items in "sub-sortables" can cause the placeholder to jitter
|
|
// beetween the outer and inner container.
|
|
if (item.instance !== this.currentContainer) {
|
|
continue;
|
|
}
|
|
|
|
// cannot intersect with itself
|
|
// no useless actions that have been done before
|
|
// no action if the item moved is the parent of the item checked
|
|
if (itemElement !== this.currentItem[0] &&
|
|
this.placeholder[intersection === 1 ? "next" : "prev"]()[0] !== itemElement &&
|
|
!$.contains(this.placeholder[0], itemElement) &&
|
|
(this.options.type === "semi-dynamic" ? !$.contains(this.element[0], itemElement) : true)
|
|
) {
|
|
|
|
this.direction = intersection === 1 ? "down" : "up";
|
|
|
|
if (this.options.tolerance === "pointer" || this._intersectsWithSides(item)) {
|
|
this._rearrange(event, item);
|
|
} else {
|
|
break;
|
|
}
|
|
|
|
this._trigger("change", event, this._uiHash());
|
|
break;
|
|
}
|
|
}
|
|
|
|
//Post events to containers
|
|
this._contactContainers(event);
|
|
|
|
//Interconnect with droppables
|
|
if($.ui.ddmanager) {
|
|
$.ui.ddmanager.drag(this, event);
|
|
}
|
|
|
|
//Call callbacks
|
|
this._trigger("sort", event, this._uiHash());
|
|
|
|
this.lastPositionAbs = this.positionAbs;
|
|
return false;
|
|
|
|
},
|
|
|
|
_mouseStop: function(event, noPropagation) {
|
|
|
|
if(!event) {
|
|
return;
|
|
}
|
|
|
|
//If we are using droppables, inform the manager about the drop
|
|
if ($.ui.ddmanager && !this.options.dropBehaviour) {
|
|
$.ui.ddmanager.drop(this, event);
|
|
}
|
|
|
|
if(this.options.revert) {
|
|
var that = this,
|
|
cur = this.placeholder.offset(),
|
|
axis = this.options.axis,
|
|
animation = {};
|
|
|
|
if ( !axis || axis === "x" ) {
|
|
animation.left = cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollLeft);
|
|
}
|
|
if ( !axis || axis === "y" ) {
|
|
animation.top = cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollTop);
|
|
}
|
|
this.reverting = true;
|
|
$(this.helper).animate( animation, parseInt(this.options.revert, 10) || 500, function() {
|
|
that._clear(event);
|
|
});
|
|
} else {
|
|
this._clear(event, noPropagation);
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
cancel: function() {
|
|
|
|
if(this.dragging) {
|
|
|
|
this._mouseUp({ target: null });
|
|
|
|
if(this.options.helper === "original") {
|
|
this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
|
|
} else {
|
|
this.currentItem.show();
|
|
}
|
|
|
|
//Post deactivating events to containers
|
|
for (var i = this.containers.length - 1; i >= 0; i--){
|
|
this.containers[i]._trigger("deactivate", null, this._uiHash(this));
|
|
if(this.containers[i].containerCache.over) {
|
|
this.containers[i]._trigger("out", null, this._uiHash(this));
|
|
this.containers[i].containerCache.over = 0;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (this.placeholder) {
|
|
//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
|
|
if(this.placeholder[0].parentNode) {
|
|
this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
|
|
}
|
|
if(this.options.helper !== "original" && this.helper && this.helper[0].parentNode) {
|
|
this.helper.remove();
|
|
}
|
|
|
|
$.extend(this, {
|
|
helper: null,
|
|
dragging: false,
|
|
reverting: false,
|
|
_noFinalSort: null
|
|
});
|
|
|
|
if(this.domPosition.prev) {
|
|
$(this.domPosition.prev).after(this.currentItem);
|
|
} else {
|
|
$(this.domPosition.parent).prepend(this.currentItem);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
serialize: function(o) {
|
|
|
|
var items = this._getItemsAsjQuery(o && o.connected),
|
|
str = [];
|
|
o = o || {};
|
|
|
|
$(items).each(function() {
|
|
var res = ($(o.item || this).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[\-=_](.+)/));
|
|
if (res) {
|
|
str.push((o.key || res[1]+"[]")+"="+(o.key && o.expression ? res[1] : res[2]));
|
|
}
|
|
});
|
|
|
|
if(!str.length && o.key) {
|
|
str.push(o.key + "=");
|
|
}
|
|
|
|
return str.join("&");
|
|
|
|
},
|
|
|
|
toArray: function(o) {
|
|
|
|
var items = this._getItemsAsjQuery(o && o.connected),
|
|
ret = [];
|
|
|
|
o = o || {};
|
|
|
|
items.each(function() { ret.push($(o.item || this).attr(o.attribute || "id") || ""); });
|
|
return ret;
|
|
|
|
},
|
|
|
|
/* Be careful with the following core functions */
|
|
_intersectsWith: function(item) {
|
|
|
|
var x1 = this.positionAbs.left,
|
|
x2 = x1 + this.helperProportions.width,
|
|
y1 = this.positionAbs.top,
|
|
y2 = y1 + this.helperProportions.height,
|
|
l = item.left,
|
|
r = l + item.width,
|
|
t = item.top,
|
|
b = t + item.height,
|
|
dyClick = this.offset.click.top,
|
|
dxClick = this.offset.click.left,
|
|
isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t && ( y1 + dyClick ) < b ),
|
|
isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l && ( x1 + dxClick ) < r ),
|
|
isOverElement = isOverElementHeight && isOverElementWidth;
|
|
|
|
if ( this.options.tolerance === "pointer" ||
|
|
this.options.forcePointerForContainers ||
|
|
(this.options.tolerance !== "pointer" && this.helperProportions[this.floating ? "width" : "height"] > item[this.floating ? "width" : "height"])
|
|
) {
|
|
return isOverElement;
|
|
} else {
|
|
|
|
return (l < x1 + (this.helperProportions.width / 2) && // Right Half
|
|
x2 - (this.helperProportions.width / 2) < r && // Left Half
|
|
t < y1 + (this.helperProportions.height / 2) && // Bottom Half
|
|
y2 - (this.helperProportions.height / 2) < b ); // Top Half
|
|
|
|
}
|
|
},
|
|
|
|
_intersectsWithPointer: function(item) {
|
|
|
|
var isOverElementHeight = (this.options.axis === "x") || isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
|
|
isOverElementWidth = (this.options.axis === "y") || isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
|
|
isOverElement = isOverElementHeight && isOverElementWidth,
|
|
verticalDirection = this._getDragVerticalDirection(),
|
|
horizontalDirection = this._getDragHorizontalDirection();
|
|
|
|
if (!isOverElement) {
|
|
return false;
|
|
}
|
|
|
|
return this.floating ?
|
|
( ((horizontalDirection && horizontalDirection === "right") || verticalDirection === "down") ? 2 : 1 )
|
|
: ( verticalDirection && (verticalDirection === "down" ? 2 : 1) );
|
|
|
|
},
|
|
|
|
_intersectsWithSides: function(item) {
|
|
|
|
var isOverBottomHalf = isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
|
|
isOverRightHalf = isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
|
|
verticalDirection = this._getDragVerticalDirection(),
|
|
horizontalDirection = this._getDragHorizontalDirection();
|
|
|
|
if (this.floating && horizontalDirection) {
|
|
return ((horizontalDirection === "right" && isOverRightHalf) || (horizontalDirection === "left" && !isOverRightHalf));
|
|
} else {
|
|
return verticalDirection && ((verticalDirection === "down" && isOverBottomHalf) || (verticalDirection === "up" && !isOverBottomHalf));
|
|
}
|
|
|
|
},
|
|
|
|
_getDragVerticalDirection: function() {
|
|
var delta = this.positionAbs.top - this.lastPositionAbs.top;
|
|
return delta !== 0 && (delta > 0 ? "down" : "up");
|
|
},
|
|
|
|
_getDragHorizontalDirection: function() {
|
|
var delta = this.positionAbs.left - this.lastPositionAbs.left;
|
|
return delta !== 0 && (delta > 0 ? "right" : "left");
|
|
},
|
|
|
|
refresh: function(event) {
|
|
this._refreshItems(event);
|
|
this.refreshPositions();
|
|
return this;
|
|
},
|
|
|
|
_connectWith: function() {
|
|
var options = this.options;
|
|
return options.connectWith.constructor === String ? [options.connectWith] : options.connectWith;
|
|
},
|
|
|
|
_getItemsAsjQuery: function(connected) {
|
|
|
|
var i, j, cur, inst,
|
|
items = [],
|
|
queries = [],
|
|
connectWith = this._connectWith();
|
|
|
|
if(connectWith && connected) {
|
|
for (i = connectWith.length - 1; i >= 0; i--){
|
|
cur = $(connectWith[i]);
|
|
for ( j = cur.length - 1; j >= 0; j--){
|
|
inst = $.data(cur[j], this.widgetFullName);
|
|
if(inst && inst !== this && !inst.options.disabled) {
|
|
queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), inst]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), this]);
|
|
|
|
for (i = queries.length - 1; i >= 0; i--){
|
|
queries[i][0].each(function() {
|
|
items.push(this);
|
|
});
|
|
}
|
|
|
|
return $(items);
|
|
|
|
},
|
|
|
|
_removeCurrentsFromItems: function() {
|
|
|
|
var list = this.currentItem.find(":data(" + this.widgetName + "-item)");
|
|
|
|
this.items = $.grep(this.items, function (item) {
|
|
for (var j=0; j < list.length; j++) {
|
|
if(list[j] === item.item[0]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
|
|
},
|
|
|
|
_refreshItems: function(event) {
|
|
|
|
this.items = [];
|
|
this.containers = [this];
|
|
|
|
var i, j, cur, inst, targetData, _queries, item, queriesLength,
|
|
items = this.items,
|
|
queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]],
|
|
connectWith = this._connectWith();
|
|
|
|
if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down
|
|
for (i = connectWith.length - 1; i >= 0; i--){
|
|
cur = $(connectWith[i]);
|
|
for (j = cur.length - 1; j >= 0; j--){
|
|
inst = $.data(cur[j], this.widgetFullName);
|
|
if(inst && inst !== this && !inst.options.disabled) {
|
|
queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
|
|
this.containers.push(inst);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = queries.length - 1; i >= 0; i--) {
|
|
targetData = queries[i][1];
|
|
_queries = queries[i][0];
|
|
|
|
for (j=0, queriesLength = _queries.length; j < queriesLength; j++) {
|
|
item = $(_queries[j]);
|
|
|
|
item.data(this.widgetName + "-item", targetData); // Data for target checking (mouse manager)
|
|
|
|
items.push({
|
|
item: item,
|
|
instance: targetData,
|
|
width: 0, height: 0,
|
|
left: 0, top: 0
|
|
});
|
|
}
|
|
}
|
|
|
|
},
|
|
|
|
refreshPositions: function(fast) {
|
|
|
|
//This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
|
|
if(this.offsetParent && this.helper) {
|
|
this.offset.parent = this._getParentOffset();
|
|
}
|
|
|
|
var i, item, t, p;
|
|
|
|
for (i = this.items.length - 1; i >= 0; i--){
|
|
item = this.items[i];
|
|
|
|
//We ignore calculating positions of all connected containers when we're not over them
|
|
if(item.instance !== this.currentContainer && this.currentContainer && item.item[0] !== this.currentItem[0]) {
|
|
continue;
|
|
}
|
|
|
|
t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;
|
|
|
|
if (!fast) {
|
|
item.width = t.outerWidth();
|
|
item.height = t.outerHeight();
|
|
}
|
|
|
|
p = t.offset();
|
|
item.left = p.left;
|
|
item.top = p.top;
|
|
}
|
|
|
|
if(this.options.custom && this.options.custom.refreshContainers) {
|
|
this.options.custom.refreshContainers.call(this);
|
|
} else {
|
|
for (i = this.containers.length - 1; i >= 0; i--){
|
|
p = this.containers[i].element.offset();
|
|
this.containers[i].containerCache.left = p.left;
|
|
this.containers[i].containerCache.top = p.top;
|
|
this.containers[i].containerCache.width = this.containers[i].element.outerWidth();
|
|
this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
_createPlaceholder: function(that) {
|
|
that = that || this;
|
|
var className,
|
|
o = that.options;
|
|
|
|
if(!o.placeholder || o.placeholder.constructor === String) {
|
|
className = o.placeholder;
|
|
o.placeholder = {
|
|
element: function() {
|
|
|
|
var nodeName = that.currentItem[0].nodeName.toLowerCase(),
|
|
element = $( "<" + nodeName + ">", that.document[0] )
|
|
.addClass(className || that.currentItem[0].className+" ui-sortable-placeholder")
|
|
.removeClass("ui-sortable-helper");
|
|
|
|
if ( nodeName === "tr" ) {
|
|
that.currentItem.children().each(function() {
|
|
$( "<td> </td>", that.document[0] )
|
|
.attr( "colspan", $( this ).attr( "colspan" ) || 1 )
|
|
.appendTo( element );
|
|
});
|
|
} else if ( nodeName === "img" ) {
|
|
element.attr( "src", that.currentItem.attr( "src" ) );
|
|
}
|
|
|
|
if ( !className ) {
|
|
element.css( "visibility", "hidden" );
|
|
}
|
|
|
|
return element;
|
|
},
|
|
update: function(container, p) {
|
|
|
|
// 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
|
|
// 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
|
|
if(className && !o.forcePlaceholderSize) {
|
|
return;
|
|
}
|
|
|
|
//If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item
|
|
if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css("paddingTop")||0, 10) - parseInt(that.currentItem.css("paddingBottom")||0, 10)); }
|
|
if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css("paddingLeft")||0, 10) - parseInt(that.currentItem.css("paddingRight")||0, 10)); }
|
|
}
|
|
};
|
|
}
|
|
|
|
//Create the placeholder
|
|
that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem));
|
|
|
|
//Append it after the actual current item
|
|
that.currentItem.after(that.placeholder);
|
|
|
|
//Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
|
|
o.placeholder.update(that, that.placeholder);
|
|
|
|
},
|
|
|
|
_contactContainers: function(event) {
|
|
var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, base, cur, nearBottom, floating,
|
|
innermostContainer = null,
|
|
innermostIndex = null;
|
|
|
|
// get innermost container that intersects with item
|
|
for (i = this.containers.length - 1; i >= 0; i--) {
|
|
|
|
// never consider a container that's located within the item itself
|
|
if($.contains(this.currentItem[0], this.containers[i].element[0])) {
|
|
continue;
|
|
}
|
|
|
|
if(this._intersectsWith(this.containers[i].containerCache)) {
|
|
|
|
// if we've already found a container and it's more "inner" than this, then continue
|
|
if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0])) {
|
|
continue;
|
|
}
|
|
|
|
innermostContainer = this.containers[i];
|
|
innermostIndex = i;
|
|
|
|
} else {
|
|
// container doesn't intersect. trigger "out" event if necessary
|
|
if(this.containers[i].containerCache.over) {
|
|
this.containers[i]._trigger("out", event, this._uiHash(this));
|
|
this.containers[i].containerCache.over = 0;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// if no intersecting containers found, return
|
|
if(!innermostContainer) {
|
|
return;
|
|
}
|
|
|
|
// move the item into the container if it's not there already
|
|
if(this.containers.length === 1) {
|
|
if (!this.containers[innermostIndex].containerCache.over) {
|
|
this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
|
|
this.containers[innermostIndex].containerCache.over = 1;
|
|
}
|
|
} else {
|
|
|
|
//When entering a new container, we will find the item with the least distance and append our item near it
|
|
dist = 10000;
|
|
itemWithLeastDistance = null;
|
|
floating = innermostContainer.floating || isFloating(this.currentItem);
|
|
posProperty = floating ? "left" : "top";
|
|
sizeProperty = floating ? "width" : "height";
|
|
base = this.positionAbs[posProperty] + this.offset.click[posProperty];
|
|
for (j = this.items.length - 1; j >= 0; j--) {
|
|
if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) {
|
|
continue;
|
|
}
|
|
if(this.items[j].item[0] === this.currentItem[0]) {
|
|
continue;
|
|
}
|
|
if (floating && !isOverAxis(this.positionAbs.top + this.offset.click.top, this.items[j].top, this.items[j].height)) {
|
|
continue;
|
|
}
|
|
cur = this.items[j].item.offset()[posProperty];
|
|
nearBottom = false;
|
|
if(Math.abs(cur - base) > Math.abs(cur + this.items[j][sizeProperty] - base)){
|
|
nearBottom = true;
|
|
cur += this.items[j][sizeProperty];
|
|
}
|
|
|
|
if(Math.abs(cur - base) < dist) {
|
|
dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j];
|
|
this.direction = nearBottom ? "up": "down";
|
|
}
|
|
}
|
|
|
|
//Check if dropOnEmpty is enabled
|
|
if(!itemWithLeastDistance && !this.options.dropOnEmpty) {
|
|
return;
|
|
}
|
|
|
|
if(this.currentContainer === this.containers[innermostIndex]) {
|
|
return;
|
|
}
|
|
|
|
itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true);
|
|
this._trigger("change", event, this._uiHash());
|
|
this.containers[innermostIndex]._trigger("change", event, this._uiHash(this));
|
|
this.currentContainer = this.containers[innermostIndex];
|
|
|
|
//Update the placeholder
|
|
this.options.placeholder.update(this.currentContainer, this.placeholder);
|
|
|
|
this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
|
|
this.containers[innermostIndex].containerCache.over = 1;
|
|
}
|
|
|
|
|
|
},
|
|
|
|
_createHelper: function(event) {
|
|
|
|
var o = this.options,
|
|
helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper === "clone" ? this.currentItem.clone() : this.currentItem);
|
|
|
|
//Add the helper to the DOM if that didn't happen already
|
|
if(!helper.parents("body").length) {
|
|
$(o.appendTo !== "parent" ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);
|
|
}
|
|
|
|
if(helper[0] === this.currentItem[0]) {
|
|
this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") };
|
|
}
|
|
|
|
if(!helper[0].style.width || o.forceHelperSize) {
|
|
helper.width(this.currentItem.width());
|
|
}
|
|
if(!helper[0].style.height || o.forceHelperSize) {
|
|
helper.height(this.currentItem.height());
|
|
}
|
|
|
|
return helper;
|
|
|
|
},
|
|
|
|
_adjustOffsetFromHelper: function(obj) {
|
|
if (typeof obj === "string") {
|
|
obj = obj.split(" ");
|
|
}
|
|
if ($.isArray(obj)) {
|
|
obj = {left: +obj[0], top: +obj[1] || 0};
|
|
}
|
|
if ("left" in obj) {
|
|
this.offset.click.left = obj.left + this.margins.left;
|
|
}
|
|
if ("right" in obj) {
|
|
this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
|
|
}
|
|
if ("top" in obj) {
|
|
this.offset.click.top = obj.top + this.margins.top;
|
|
}
|
|
if ("bottom" in obj) {
|
|
this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
|
|
}
|
|
},
|
|
|
|
_getParentOffset: function() {
|
|
|
|
|
|
//Get the offsetParent and cache its position
|
|
this.offsetParent = this.helper.offsetParent();
|
|
var po = this.offsetParent.offset();
|
|
|
|
// This is a special case where we need to modify a offset calculated on start, since the following happened:
|
|
// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
|
|
// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
|
|
// the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
|
|
if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
|
|
po.left += this.scrollParent.scrollLeft();
|
|
po.top += this.scrollParent.scrollTop();
|
|
}
|
|
|
|
// This needs to be actually done for all browsers, since pageX/pageY includes this information
|
|
// with an ugly IE fix
|
|
if( this.offsetParent[0] === document.body || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) {
|
|
po = { top: 0, left: 0 };
|
|
}
|
|
|
|
return {
|
|
top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
|
|
left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
|
|
};
|
|
|
|
},
|
|
|
|
_getRelativeOffset: function() {
|
|
|
|
if(this.cssPosition === "relative") {
|
|
var p = this.currentItem.position();
|
|
return {
|
|
top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
|
|
left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
|
|
};
|
|
} else {
|
|
return { top: 0, left: 0 };
|
|
}
|
|
|
|
},
|
|
|
|
_cacheMargins: function() {
|
|
this.margins = {
|
|
left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
|
|
top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
|
|
};
|
|
},
|
|
|
|
_cacheHelperProportions: function() {
|
|
this.helperProportions = {
|
|
width: this.helper.outerWidth(),
|
|
height: this.helper.outerHeight()
|
|
};
|
|
},
|
|
|
|
_setContainment: function() {
|
|
|
|
var ce, co, over,
|
|
o = this.options;
|
|
if(o.containment === "parent") {
|
|
o.containment = this.helper[0].parentNode;
|
|
}
|
|
if(o.containment === "document" || o.containment === "window") {
|
|
this.containment = [
|
|
0 - this.offset.relative.left - this.offset.parent.left,
|
|
0 - this.offset.relative.top - this.offset.parent.top,
|
|
$(o.containment === "document" ? document : window).width() - this.helperProportions.width - this.margins.left,
|
|
($(o.containment === "document" ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
|
|
];
|
|
}
|
|
|
|
if(!(/^(document|window|parent)$/).test(o.containment)) {
|
|
ce = $(o.containment)[0];
|
|
co = $(o.containment).offset();
|
|
over = ($(ce).css("overflow") !== "hidden");
|
|
|
|
this.containment = [
|
|
co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
|
|
co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
|
|
co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left,
|
|
co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top
|
|
];
|
|
}
|
|
|
|
},
|
|
|
|
_convertPositionTo: function(d, pos) {
|
|
|
|
if(!pos) {
|
|
pos = this.position;
|
|
}
|
|
var mod = d === "absolute" ? 1 : -1,
|
|
scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent,
|
|
scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
|
|
|
|
return {
|
|
top: (
|
|
pos.top + // The absolute mouse position
|
|
this.offset.relative.top * mod + // Only for relative positioned nodes: Relative offset from element to offset parent
|
|
this.offset.parent.top * mod - // The offsetParent's offset without borders (offset + border)
|
|
( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
|
|
),
|
|
left: (
|
|
pos.left + // The absolute mouse position
|
|
this.offset.relative.left * mod + // Only for relative positioned nodes: Relative offset from element to offset parent
|
|
this.offset.parent.left * mod - // The offsetParent's offset without borders (offset + border)
|
|
( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
|
|
)
|
|
};
|
|
|
|
},
|
|
|
|
_generatePosition: function(event) {
|
|
|
|
var top, left,
|
|
o = this.options,
|
|
pageX = event.pageX,
|
|
pageY = event.pageY,
|
|
scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
|
|
|
|
// This is another very weird special case that only happens for relative elements:
|
|
// 1. If the css position is relative
|
|
// 2. and the scroll parent is the document or similar to the offset parent
|
|
// we have to refresh the relative offset during the scroll so there are no jumps
|
|
if(this.cssPosition === "relative" && !(this.scrollParent[0] !== document && this.scrollParent[0] !== this.offsetParent[0])) {
|
|
this.offset.relative = this._getRelativeOffset();
|
|
}
|
|
|
|
/*
|
|
* - Position constraining -
|
|
* Constrain the position to a mix of grid, containment.
|
|
*/
|
|
|
|
if(this.originalPosition) { //If we are not dragging yet, we won't check for options
|
|
|
|
if(this.containment) {
|
|
if(event.pageX - this.offset.click.left < this.containment[0]) {
|
|
pageX = this.containment[0] + this.offset.click.left;
|
|
}
|
|
if(event.pageY - this.offset.click.top < this.containment[1]) {
|
|
pageY = this.containment[1] + this.offset.click.top;
|
|
}
|
|
if(event.pageX - this.offset.click.left > this.containment[2]) {
|
|
pageX = this.containment[2] + this.offset.click.left;
|
|
}
|
|
if(event.pageY - this.offset.click.top > this.containment[3]) {
|
|
pageY = this.containment[3] + this.offset.click.top;
|
|
}
|
|
}
|
|
|
|
if(o.grid) {
|
|
top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
|
|
pageY = this.containment ? ( (top - this.offset.click.top >= this.containment[1] && top - this.offset.click.top <= this.containment[3]) ? top : ((top - this.offset.click.top >= this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
|
|
|
|
left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
|
|
pageX = this.containment ? ( (left - this.offset.click.left >= this.containment[0] && left - this.offset.click.left <= this.containment[2]) ? left : ((left - this.offset.click.left >= this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
|
|
}
|
|
|
|
}
|
|
|
|
return {
|
|
top: (
|
|
pageY - // The absolute mouse position
|
|
this.offset.click.top - // Click offset (relative to the element)
|
|
this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent
|
|
this.offset.parent.top + // The offsetParent's offset without borders (offset + border)
|
|
( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
|
|
),
|
|
left: (
|
|
pageX - // The absolute mouse position
|
|
this.offset.click.left - // Click offset (relative to the element)
|
|
this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent
|
|
this.offset.parent.left + // The offsetParent's offset without borders (offset + border)
|
|
( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
|
|
)
|
|
};
|
|
|
|
},
|
|
|
|
_rearrange: function(event, i, a, hardRefresh) {
|
|
|
|
a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction === "down" ? i.item[0] : i.item[0].nextSibling));
|
|
|
|
//Various things done here to improve the performance:
|
|
// 1. we create a setTimeout, that calls refreshPositions
|
|
// 2. on the instance, we have a counter variable, that get's higher after every append
|
|
// 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
|
|
// 4. this lets only the last addition to the timeout stack through
|
|
this.counter = this.counter ? ++this.counter : 1;
|
|
var counter = this.counter;
|
|
|
|
this._delay(function() {
|
|
if(counter === this.counter) {
|
|
this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
|
|
}
|
|
});
|
|
|
|
},
|
|
|
|
_clear: function(event, noPropagation) {
|
|
|
|
this.reverting = false;
|
|
// We delay all events that have to be triggered to after the point where the placeholder has been removed and
|
|
// everything else normalized again
|
|
var i,
|
|
delayedTriggers = [];
|
|
|
|
// We first have to update the dom position of the actual currentItem
|
|
// Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
|
|
if(!this._noFinalSort && this.currentItem.parent().length) {
|
|
this.placeholder.before(this.currentItem);
|
|
}
|
|
this._noFinalSort = null;
|
|
|
|
if(this.helper[0] === this.currentItem[0]) {
|
|
for(i in this._storedCSS) {
|
|
if(this._storedCSS[i] === "auto" || this._storedCSS[i] === "static") {
|
|
this._storedCSS[i] = "";
|
|
}
|
|
}
|
|
this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
|
|
} else {
|
|
this.currentItem.show();
|
|
}
|
|
|
|
if(this.fromOutside && !noPropagation) {
|
|
delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
|
|
}
|
|
if((this.fromOutside || this.domPosition.prev !== this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent !== this.currentItem.parent()[0]) && !noPropagation) {
|
|
delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed
|
|
}
|
|
|
|
// Check if the items Container has Changed and trigger appropriate
|
|
// events.
|
|
if (this !== this.currentContainer) {
|
|
if(!noPropagation) {
|
|
delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
|
|
delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.currentContainer));
|
|
delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.currentContainer));
|
|
}
|
|
}
|
|
|
|
|
|
//Post events to containers
|
|
for (i = this.containers.length - 1; i >= 0; i--){
|
|
if(!noPropagation) {
|
|
delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
|
|
}
|
|
if(this.containers[i].containerCache.over) {
|
|
delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
|
|
this.containers[i].containerCache.over = 0;
|
|
}
|
|
}
|
|
|
|
//Do what was originally in plugins
|
|
if ( this.storedCursor ) {
|
|
this.document.find( "body" ).css( "cursor", this.storedCursor );
|
|
this.storedStylesheet.remove();
|
|
}
|
|
if(this._storedOpacity) {
|
|
this.helper.css("opacity", this._storedOpacity);
|
|
}
|
|
if(this._storedZIndex) {
|
|
this.helper.css("zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex);
|
|
}
|
|
|
|
this.dragging = false;
|
|
if(this.cancelHelperRemoval) {
|
|
if(!noPropagation) {
|
|
this._trigger("beforeStop", event, this._uiHash());
|
|
for (i=0; i < delayedTriggers.length; i++) {
|
|
delayedTriggers[i].call(this, event);
|
|
} //Trigger all delayed events
|
|
this._trigger("stop", event, this._uiHash());
|
|
}
|
|
|
|
this.fromOutside = false;
|
|
return false;
|
|
}
|
|
|
|
if(!noPropagation) {
|
|
this._trigger("beforeStop", event, this._uiHash());
|
|
}
|
|
|
|
//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
|
|
this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
|
|
|
|
if(this.helper[0] !== this.currentItem[0]) {
|
|
this.helper.remove();
|
|
}
|
|
this.helper = null;
|
|
|
|
if(!noPropagation) {
|
|
for (i=0; i < delayedTriggers.length; i++) {
|
|
delayedTriggers[i].call(this, event);
|
|
} //Trigger all delayed events
|
|
this._trigger("stop", event, this._uiHash());
|
|
}
|
|
|
|
this.fromOutside = false;
|
|
return true;
|
|
|
|
},
|
|
|
|
_trigger: function() {
|
|
if ($.Widget.prototype._trigger.apply(this, arguments) === false) {
|
|
this.cancel();
|
|
}
|
|
},
|
|
|
|
_uiHash: function(_inst) {
|
|
var inst = _inst || this;
|
|
return {
|
|
helper: inst.helper,
|
|
placeholder: inst.placeholder || $([]),
|
|
position: inst.position,
|
|
originalPosition: inst.originalPosition,
|
|
offset: inst.positionAbs,
|
|
item: inst.currentItem,
|
|
sender: _inst ? _inst.element : null
|
|
};
|
|
}
|
|
|
|
});
|
|
|
|
})(jQuery);
|
|
;/* jQuery Tiny Pub/Sub - v0.7 - 10/27/2011
|
|
* http://benalman.com/
|
|
* Copyright (c) 2011 "Cowboy" Ben Alman; Licensed MIT, GPL */
|
|
|
|
(function($) {
|
|
|
|
var o = $({});
|
|
|
|
$.subscribe = function() {
|
|
//console.log("SUBSCRIBE: " + arguments[0]);
|
|
o.on.apply(o, arguments);
|
|
};
|
|
|
|
$.unsubscribe = function() {
|
|
o.off.apply(o, arguments);
|
|
};
|
|
|
|
$.publish = function() {
|
|
//console.log("PUBLISH: " + arguments[0]);
|
|
o.trigger.apply(o, arguments);
|
|
};
|
|
|
|
}(jQuery));
|
|
|
|
|
|
;/* ===========================================================
|
|
* bootstrap-tooltip.js v2.1.1
|
|
* http://twitter.github.com/bootstrap/javascript.html#tooltips
|
|
* Inspired by the original jQuery.tipsy by Jason Frame
|
|
* ===========================================================
|
|
* Copyright 2012 Twitter, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
* ========================================================== */
|
|
|
|
|
|
!function ($) {
|
|
|
|
"use strict"; // jshint ;_;
|
|
|
|
|
|
/* TOOLTIP PUBLIC CLASS DEFINITION
|
|
* =============================== */
|
|
|
|
var Tooltip = function (element, options) {
|
|
this.init('tooltip', element, options)
|
|
}
|
|
|
|
Tooltip.prototype = {
|
|
|
|
constructor: Tooltip
|
|
|
|
, init: function (type, element, options) {
|
|
var eventIn
|
|
, eventOut
|
|
|
|
this.type = type
|
|
this.$element = $(element)
|
|
this.options = this.getOptions(options)
|
|
this.enabled = true
|
|
|
|
if (this.options.trigger == 'click') {
|
|
this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
|
|
} else if (this.options.trigger != 'manual') {
|
|
eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
|
|
eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
|
|
this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
|
|
this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
|
|
}
|
|
|
|
this.options.selector ?
|
|
(this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
|
|
this.fixTitle()
|
|
}
|
|
|
|
, getOptions: function (options) {
|
|
options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())
|
|
|
|
if (options.delay && typeof options.delay == 'number') {
|
|
options.delay = {
|
|
show: options.delay
|
|
, hide: options.delay
|
|
}
|
|
}
|
|
|
|
return options
|
|
}
|
|
|
|
, enter: function (e) {
|
|
var self = $(e.currentTarget)[this.type](this._options).data(this.type)
|
|
|
|
if (!self.options.delay || !self.options.delay.show) return self.show()
|
|
|
|
clearTimeout(this.timeout)
|
|
self.hoverState = 'in'
|
|
this.timeout = setTimeout(function() {
|
|
if (self.hoverState == 'in') self.show()
|
|
}, self.options.delay.show)
|
|
}
|
|
|
|
, leave: function (e) {
|
|
var self = $(e.currentTarget)[this.type](this._options).data(this.type)
|
|
|
|
if (this.timeout) clearTimeout(this.timeout)
|
|
if (!self.options.delay || !self.options.delay.hide) return self.hide()
|
|
|
|
self.hoverState = 'out'
|
|
this.timeout = setTimeout(function() {
|
|
if (self.hoverState == 'out') self.hide()
|
|
}, self.options.delay.hide)
|
|
}
|
|
|
|
, show: function () {
|
|
var $tip
|
|
, inside
|
|
, pos
|
|
, actualWidth
|
|
, actualHeight
|
|
, placement
|
|
, tp
|
|
|
|
if (this.hasContent() && this.enabled) {
|
|
$tip = this.tip()
|
|
this.setContent()
|
|
|
|
if (this.options.animation) {
|
|
$tip.addClass('fade')
|
|
}
|
|
|
|
placement = typeof this.options.placement == 'function' ?
|
|
this.options.placement.call(this, $tip[0], this.$element[0]) :
|
|
this.options.placement
|
|
|
|
inside = /in/.test(placement)
|
|
|
|
$tip
|
|
.remove()
|
|
.css({ top: 0, left: 0, display: 'block' })
|
|
.appendTo(inside ? this.$element : document.body)
|
|
|
|
pos = this.getPosition(inside)
|
|
|
|
actualWidth = $tip[0].offsetWidth
|
|
actualHeight = $tip[0].offsetHeight
|
|
|
|
switch (inside ? placement.split(' ')[1] : placement) {
|
|
case 'bottom':
|
|
tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
|
|
break
|
|
case 'top':
|
|
tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
|
|
break
|
|
case 'left':
|
|
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
|
|
break
|
|
case 'right':
|
|
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
|
|
break
|
|
}
|
|
|
|
$tip
|
|
.css(tp)
|
|
.addClass(placement)
|
|
.addClass('in')
|
|
}
|
|
}
|
|
|
|
, setContent: function () {
|
|
var $tip = this.tip()
|
|
, title = this.getTitle()
|
|
|
|
$tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
|
|
$tip.removeClass('fade in top bottom left right')
|
|
}
|
|
|
|
, hide: function () {
|
|
var that = this
|
|
, $tip = this.tip()
|
|
|
|
$tip.removeClass('in')
|
|
|
|
function removeWithAnimation() {
|
|
var timeout = setTimeout(function () {
|
|
$tip.off($.support.transition.end).remove()
|
|
}, 500)
|
|
|
|
$tip.one($.support.transition.end, function () {
|
|
clearTimeout(timeout)
|
|
$tip.remove()
|
|
})
|
|
}
|
|
|
|
$.support.transition && this.$tip.hasClass('fade') ?
|
|
removeWithAnimation() :
|
|
$tip.remove()
|
|
|
|
return this
|
|
}
|
|
|
|
, fixTitle: function () {
|
|
var $e = this.$element
|
|
if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
|
|
$e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
|
|
}
|
|
}
|
|
|
|
, hasContent: function () {
|
|
return this.getTitle()
|
|
}
|
|
|
|
, getPosition: function (inside) {
|
|
return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
|
|
width: this.$element[0].offsetWidth
|
|
, height: this.$element[0].offsetHeight
|
|
})
|
|
}
|
|
|
|
, getTitle: function () {
|
|
var title
|
|
, $e = this.$element
|
|
, o = this.options
|
|
|
|
title = $e.attr('data-original-title')
|
|
|| (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
|
|
|
|
return title
|
|
}
|
|
|
|
, tip: function () {
|
|
return this.$tip = this.$tip || $(this.options.template)
|
|
}
|
|
|
|
, validate: function () {
|
|
if (!this.$element[0].parentNode) {
|
|
this.hide()
|
|
this.$element = null
|
|
this.options = null
|
|
}
|
|
}
|
|
|
|
, enable: function () {
|
|
this.enabled = true
|
|
}
|
|
|
|
, disable: function () {
|
|
this.enabled = false
|
|
}
|
|
|
|
, toggleEnabled: function () {
|
|
this.enabled = !this.enabled
|
|
}
|
|
|
|
, toggle: function () {
|
|
this[this.tip().hasClass('in') ? 'hide' : 'show']()
|
|
}
|
|
|
|
, destroy: function () {
|
|
this.hide().$element.off('.' + this.type).removeData(this.type)
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* TOOLTIP PLUGIN DEFINITION
|
|
* ========================= */
|
|
|
|
$.fn.tooltip = function ( option ) {
|
|
return this.each(function () {
|
|
var $this = $(this)
|
|
, data = $this.data('tooltip')
|
|
, options = typeof option == 'object' && option
|
|
if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
|
|
if (typeof option == 'string') data[option]()
|
|
})
|
|
}
|
|
|
|
$.fn.tooltip.Constructor = Tooltip
|
|
|
|
$.fn.tooltip.defaults = {
|
|
animation: true
|
|
, placement: 'top'
|
|
, selector: false
|
|
, template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
|
|
, trigger: 'hover'
|
|
, title: ''
|
|
, delay: 0
|
|
, html: true
|
|
}
|
|
|
|
}(window.jQuery);
|
|
;(function () {
|
|
var worker = function () {
|
|
(function(b){function a(b,d){if({}.hasOwnProperty.call(a.cache,b))return a.cache[b];var e=a.resolve(b);if(!e)throw new Error('Failed to resolve module '+b);var c={id:b,require:a,filename:b,exports:{},loaded:!1,parent:d,children:[]};d&&d.children.push(c);var f=b.slice(0,b.lastIndexOf('/')+1);return a.cache[b]=c.exports,e.call(c.exports,c,c.exports,f,b),c.loaded=!0,a.cache[b]=c.exports}a.modules={},a.cache={},a.resolve=function(b){return{}.hasOwnProperty.call(a.modules,b)?a.modules[b]:void 0},a.define=function(b,c){a.modules[b]=c},a.define('/gif.worker.coffee',function(d,e,f,g){var b,c;b=a('/GIFEncoder.js',d),c=function(a){var c,d,e;return c=new b(a.width,a.height),a.index===0?c.writeHeader():c.firstFrame=!1,c.setRepeat(a.repeat),c.setDelay(a.delay),c.setQuality(a.quality),c.addFrame(a.data),a.last&&c.finish(),d=c.stream(),a.data=d.pages,a.cursor=d.cursor,a.pageSize=d.constructor.pageSize,a.canTransfer?(e=function(c){var d;for(var b=0,e=a.data.length;b<e;++b)d=a.data[b],c.push(d.buffer);return c}.call(this,[]),self.postMessage(a,e)):self.postMessage(a)},self.onmessage=function(a){return c(a.data)}}),a.define('/GIFEncoder.js',function(e,h,i,j){function c(){this.page=-1,this.pages=[],this.newPage()}function b(a,b){this.width=~~a,this.height=~~b,this.transparent=null,this.transIndex=0,this.repeat=-1,this.delay=0,this.image=null,this.pixels=null,this.indexedPixels=null,this.colorDepth=null,this.colorTab=null,this.usedEntry=new Array,this.palSize=7,this.dispose=-1,this.firstFrame=!0,this.sample=10,this.out=new c}var f=a('/TypedNeuQuant.js',e),g=a('/LZWEncoder.js',e);c.pageSize=4096,c.charMap={};for(var d=0;d<256;d++)c.charMap[d]=String.fromCharCode(d);c.prototype.newPage=function(){this.pages[++this.page]=new Uint8Array(c.pageSize),this.cursor=0},c.prototype.getData=function(){var d='';for(var a=0;a<this.pages.length;a++)for(var b=0;b<c.pageSize;b++)d+=c.charMap[this.pages[a][b]];return d},c.prototype.writeByte=function(a){this.cursor>=c.pageSize&&this.newPage(),this.pages[this.page][this.cursor++]=a},c.prototype.writeUTFBytes=function(b){for(var c=b.length,a=0;a<c;a++)this.writeByte(b.charCodeAt(a))},c.prototype.writeBytes=function(b,d,e){for(var c=e||b.length,a=d||0;a<c;a++)this.writeByte(b[a])},b.prototype.setDelay=function(a){this.delay=Math.round(a/10)},b.prototype.setFrameRate=function(a){this.delay=Math.round(100/a)},b.prototype.setDispose=function(a){a>=0&&(this.dispose=a)},b.prototype.setRepeat=function(a){this.repeat=a},b.prototype.setTransparent=function(a){this.transparent=a},b.prototype.addFrame=function(a){this.image=a,this.getImagePixels(),this.analyzePixels(),this.firstFrame&&(this.writeLSD(),this.writePalette(),this.repeat>=0&&this.writeNetscapeExt()),this.writeGraphicCtrlExt(),this.writeImageDesc(),this.firstFrame||this.writePalette(),this.writePixels(),this.firstFrame=!1},b.prototype.finish=function(){this.out.writeByte(59)},b.prototype.setQuality=function(a){a<1&&(a=1),this.sample=a},b.prototype.writeHeader=function(){this.out.writeUTFBytes('GIF89a')},b.prototype.analyzePixels=function(){var g=this.pixels.length,d=g/3;this.indexedPixels=new Uint8Array(d);var a=new f(this.pixels,this.sample);a.buildColormap(),this.colorTab=a.getColormap();var b=0;for(var c=0;c<d;c++){var e=a.lookupRGB(this.pixels[b++]&255,this.pixels[b++]&255,this.pixels[b++]&255);this.usedEntry[e]=!0,this.indexedPixels[c]=e}this.pixels=null,this.colorDepth=8,this.palSize=7,this.transparent!==null&&(this.transIndex=this.findClosest(this.transparent))},b.prototype.findClosest=function(e){if(this.colorTab===null)return-1;var k=(e&16711680)>>16,l=(e&65280)>>8,m=e&255,c=0,d=16777216,j=this.colorTab.length;for(var a=0;a<j;){var f=k-(this.colorTab[a++]&255),g=l-(this.colorTab[a++]&255),h=m-(this.colorTab[a]&255),i=f*f+g*g+h*h,b=a/3;this.usedEntry[b]&&i<d&&(d=i,c=b),a++}return c},b.prototype.getImagePixels=function(){var a=this.width,g=this.height;this.pixels=new Uint8Array(a*g*3);var b=this.image,c=0;for(var d=0;d<g;d++)for(var e=0;e<a;e++){var f=d*a*4+e*4;this.pixels[c++]=b[f],this.pixels[c++]=b[f+1],this.pixels[c++]=b[f+2]}},b.prototype.writeGraphicCtrlExt=function(){this.out.writeByte(33),this.out.writeByte(249),this.out.writeByte(4);var b,a;this.transparent===null?(b=0,a=0):(b=1,a=2),this.dispose>=0&&(a=dispose&7),a<<=2,this.out.writeByte(0|a|0|b),this.writeShort(this.delay),this.out.writeByte(this.transIndex),this.out.writeByte(0)},b.prototype.writeImageDesc=function(){this.out.writeByte(44),this.writeShort(0),this.writeShort(0),this.writeShort(this.width),this.writeShort(this.height),this.firstFrame?this.out.writeByte(0):this.out.writeByte(128|this.palSize)},b.prototype.writeLSD=function(){this.writeShort(this.width),this.writeShort(this.height),this.out.writeByte(240|this.palSize),this.out.writeByte(0),this.out.writeByte(0)},b.prototype.writeNetscapeExt=function(){this.out.writeByte(33),this.out.writeByte(255),this.out.writeByte(11),this.out.writeUTFBytes('NETSCAPE2.0'),this.out.writeByte(3),this.out.writeByte(1),this.writeShort(this.repeat),this.out.writeByte(0)},b.prototype.writePalette=function(){this.out.writeBytes(this.colorTab);var b=768-this.colorTab.length;for(var a=0;a<b;a++)this.out.writeByte(0)},b.prototype.writeShort=function(a){this.out.writeByte(a&255),this.out.writeByte(a>>8&255)},b.prototype.writePixels=function(){var a=new g(this.width,this.height,this.indexedPixels,this.colorDepth);a.encode(this.out)},b.prototype.stream=function(){return this.out},e.exports=b}),a.define('/LZWEncoder.js',function(e,g,h,i){function f(y,D,C,B){function w(a,b){r[f++]=a,f>=254&&t(b)}function x(b){u(a),k=i+2,j=!0,l(i,b)}function u(b){for(var a=0;a<b;++a)h[a]=-1}function A(z,r){var g,t,d,e,y,w,s;for(q=z,j=!1,n_bits=q,m=p(n_bits),i=1<<z-1,o=i+1,k=i+2,f=0,e=v(),s=0,g=a;g<65536;g*=2)++s;s=8-s,w=a,u(w),l(i,r);a:while((t=v())!=c){if(g=(t<<b)+e,d=t<<s^e,h[d]===g){e=n[d];continue}if(h[d]>=0){y=w-d,d===0&&(y=1);do if((d-=y)<0&&(d+=w),h[d]===g){e=n[d];continue a}while(h[d]>=0)}l(e,r),e=t,k<1<<b?(n[d]=k++,h[d]=g):x(r)}l(e,r),l(o,r)}function z(a){a.writeByte(s),remaining=y*D,curPixel=0,A(s+1,a),a.writeByte(0)}function t(a){f>0&&(a.writeByte(f),a.writeBytes(r,0,f),f=0)}function p(a){return(1<<a)-1}function v(){if(remaining===0)return c;--remaining;var a=C[curPixel++];return a&255}function l(a,c){g&=d[e],e>0?g|=a<<e:g=a,e+=n_bits;while(e>=8)w(g&255,c),g>>=8,e-=8;if((k>m||j)&&(j?(m=p(n_bits=q),j=!1):(++n_bits,n_bits==b?m=1<<b:m=p(n_bits))),a==o){while(e>0)w(g&255,c),g>>=8,e-=8;t(c)}}var s=Math.max(2,B),r=new Uint8Array(256),h=new Int32Array(a),n=new Int32Array(a),g,e=0,f,k=0,m,j=!1,q,i,o;this.encode=z}var c=-1,b=12,a=5003,d=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535];e.exports=f}),a.define('/TypedNeuQuant.js',function(A,F,E,D){function C(A,B){function I(){o=[],q=new Int32Array(256),t=new Int32Array(a),y=new Int32Array(a),z=new Int32Array(a>>3);var c,d;for(c=0;c<a;c++)d=(c<<b+8)/a,o[c]=new Float64Array([d,d,d,0]),y[c]=e/a,t[c]=0}function J(){for(var c=0;c<a;c++)o[c][0]>>=b,o[c][1]>>=b,o[c][2]>>=b,o[c][3]=c}function K(b,a,c,e,f){o[a][0]-=b*(o[a][0]-c)/d,o[a][1]-=b*(o[a][1]-e)/d,o[a][2]-=b*(o[a][2]-f)/d}function L(j,e,n,l,k){var h=Math.abs(e-j),i=Math.min(e+j,a),g=e+1,f=e-1,m=1,b,d;while(g<i||f>h)d=z[m++],g<i&&(b=o[g++],b[0]-=d*(b[0]-n)/c,b[1]-=d*(b[1]-l)/c,b[2]-=d*(b[2]-k)/c),f>h&&(b=o[f--],b[0]-=d*(b[0]-n)/c,b[1]-=d*(b[1]-l)/c,b[2]-=d*(b[2]-k)/c)}function C(p,s,q){var h=2147483647,k=h,d=-1,m=d,c,j,e,n,l;for(c=0;c<a;c++)j=o[c],e=Math.abs(j[0]-p)+Math.abs(j[1]-s)+Math.abs(j[2]-q),e<h&&(h=e,d=c),n=e-(t[c]>>i-b),n<k&&(k=n,m=c),l=y[c]>>g,y[c]-=l,t[c]+=l<<f;return y[d]+=x,t[d]-=r,m}function D(){var d,b,e,c,h,g,f=0,i=0;for(d=0;d<a;d++){for(e=o[d],h=d,g=e[1],b=d+1;b<a;b++)c=o[b],c[1]<g&&(h=b,g=c[1]);if(c=o[h],d!=h&&(b=c[0],c[0]=e[0],e[0]=b,b=c[1],c[1]=e[1],e[1]=b,b=c[2],c[2]=e[2],e[2]=b,b=c[3],c[3]=e[3],e[3]=b),g!=f){for(q[f]=i+d>>1,b=f+1;b<g;b++)q[b]=d;f=g,i=d}}for(q[f]=i+n>>1,b=f+1;b<256;b++)q[b]=n}function E(j,i,k){var b,d,c,e=1e3,h=-1,f=q[i],g=f-1;while(f<a||g>=0)f<a&&(d=o[f],c=d[1]-i,c>=e?f=a:(f++,c<0&&(c=-c),b=d[0]-j,b<0&&(b=-b),c+=b,c<e&&(b=d[2]-k,b<0&&(b=-b),c+=b,c<e&&(e=c,h=d[3])))),g>=0&&(d=o[g],c=i-d[1],c>=e?g=-1:(g--,c<0&&(c=-c),b=d[0]-j,b<0&&(b=-b),c+=b,c<e&&(b=d[2]-k,b<0&&(b=-b),c+=b,c<e&&(e=c,h=d[3]))));return h}function F(){var c,f=A.length,D=30+(B-1)/3,y=f/(3*B),q=~~(y/w),n=d,o=u,a=o>>h;for(a<=1&&(a=0),c=0;c<a;c++)z[c]=n*((a*a-c*c)*m/(a*a));var i;f<s?(B=1,i=3):f%l!==0?i=3*l:f%k!==0?i=3*k:f%p!==0?i=3*p:i=3*j;var r,t,x,e,g=0;c=0;while(c<y)if(r=(A[g]&255)<<b,t=(A[g+1]&255)<<b,x=(A[g+2]&255)<<b,e=C(r,t,x),K(n,e,r,t,x),a!==0&&L(a,e,r,t,x),g+=i,g>=f&&(g-=f),c++,q===0&&(q=1),c%q===0)for(n-=n/D,o-=o/v,a=o>>h,a<=1&&(a=0),e=0;e<a;e++)z[e]=n*((a*a-e*e)*m/(a*a))}function G(){I(),F(),J(),D()}function H(){var b=[],g=[];for(var c=0;c<a;c++)g[o[c][3]]=c;var d=0;for(var e=0;e<a;e++){var f=g[e];b[d++]=o[f][0],b[d++]=o[f][1],b[d++]=o[f][2]}return b}var o,q,t,y,z;this.buildColormap=G,this.getColormap=H,this.lookupRGB=E}var w=100,a=256,n=a-1,b=4,i=16,e=1<<i,f=10,B=1<<f,g=10,x=e>>g,r=e<<f-g,z=a>>3,h=6,t=1<<h,u=z*t,v=30,o=10,d=1<<o,q=8,m=1<<q,y=o+q,c=1<<y,l=499,k=491,p=487,j=503,s=3*j;A.exports=C}),a('/gif.worker.coffee')}.call(this,this))
|
|
};
|
|
try {
|
|
var typedArray = [(worker+"").replace(/function \(\)\s?\{/,"").replace(/\}[^}]*$/, "")];
|
|
var blob = new Blob([typedArray], {type: "text/javascript"}); // pass a useful mime type here
|
|
window.GifWorkerURL = URL.createObjectURL(blob);
|
|
} catch (e) {
|
|
console.error("Could not create worker", e.message);
|
|
}
|
|
})();
|
|
;(function(c){function a(b,d){if({}.hasOwnProperty.call(a.cache,b))return a.cache[b];var e=a.resolve(b);if(!e)throw new Error('Failed to resolve module '+b);var c={id:b,require:a,filename:b,exports:{},loaded:!1,parent:d,children:[]};d&&d.children.push(c);var f=b.slice(0,b.lastIndexOf('/')+1);return a.cache[b]=c.exports,e.call(c.exports,c,c.exports,f,b),c.loaded=!0,a.cache[b]=c.exports}a.modules={},a.cache={},a.resolve=function(b){return{}.hasOwnProperty.call(a.modules,b)?a.modules[b]:void 0},a.define=function(b,c){a.modules[b]=c};var b=function(a){return a='/',{title:'browser',version:'v0.10.5',browser:!0,env:{},argv:[],nextTick:c.setImmediate||function(a){setTimeout(a,0)},cwd:function(){return a},chdir:function(b){a=b}}}();a.define('/gif.coffee',function(b,k,j,i){function c(a,b){return{}.hasOwnProperty.call(a,b)}function g(d,b){for(var a=0,c=b.length;a<c;++a)if(a in b&&b[a]===d)return!0;return!1}function h(a,b){function e(){this.constructor=a}for(var d in b)c(b,d)&&(a[d]=b[d]);return e.prototype=b.prototype,a.prototype=new e,a.__super__=b.prototype,a}var e,d,f;d=a('events',b).EventEmitter,e=a('/browser.coffee',b),f=function(f,b,d){function a(d){var a,c;this.running=!1,this.options={},this.frames=[],this.freeWorkers=[],this.activeWorkers=[],this.setOptions(d);for(a in b)c=b[a],null!=this.options[a]?this.options[a]:this.options[a]=c}return h(a,f),b={workerScript:window.GifWorkerURL,workers:2,repeat:0,background:'#fff',quality:10,width:null,height:null},d={delay:500,copy:!1},a.prototype.setOption=function(a,b){return this.options[a]=b,null!=this._canvas&&(a==='width'||a==='height')?this._canvas[a]=b:void 0},a.prototype.setOptions=function(a){return function(d){var b,e;for(b in a){if(!c(a,b))continue;e=a[b],d.push(this.setOption(b,e))}return d}.call(this,[])},a.prototype.addFrame=function(a,c){var b,e;null==c&&(c={}),b={};for(e in d)b[e]=c[e]||d[e];if(null!=this.options.width||this.setOption('width',a.width),null!=this.options.height||this.setOption('height',a.height),'undefined'!==typeof ImageData&&null!=ImageData&&a instanceof ImageData)b.data=a.data;else if('undefined'!==typeof CanvasRenderingContext2D&&null!=CanvasRenderingContext2D&&a instanceof CanvasRenderingContext2D||'undefined'!==typeof WebGLRenderingContext&&null!=WebGLRenderingContext&&a instanceof WebGLRenderingContext)c.copy?b.data=this.getContextData(a):b.context=a;else if(null!=a.childNodes)c.copy?b.data=this.getImageData(a):b.image=a;else throw new Error('Invalid image');return this.frames.push(b)},a.prototype.render=function(){var d,a;if(this.running)throw new Error('Already running');if(!(null!=this.options.width&&null!=this.options.height))throw new Error('Width and height must be set prior to rendering');this.running=!0,this.nextFrame=0,this.finishedFrames=0,this.imageParts=function(b){var d;for(var a=0,c=function(){var b,b;b=[];for(var a=0;0<=this.frames.length?a<this.frames.length:a>this.frames.length;0<=this.frames.length?++a:--a)b.push(a);return b}.apply(this,arguments).length;a<c;++a)d=function(){var b,b;b=[];for(var a=0;0<=this.frames.length?a<this.frames.length:a>this.frames.length;0<=this.frames.length?++a:--a)b.push(a);return b}.apply(this,arguments)[a],b.push(null);return b}.call(this,[]),a=this.spawnWorkers();for(var b=0,c=function(){var c,c;c=[];for(var b=0;0<=a?b<a:b>a;0<=a?++b:--b)c.push(b);return c}.apply(this,arguments).length;b<c;++b)d=function(){var c,c;c=[];for(var b=0;0<=a?b<a:b>a;0<=a?++b:--b)c.push(b);return c}.apply(this,arguments)[b],this.renderNextFrame();return this.emit('start'),this.emit('progress',0)},a.prototype.abort=function(){var a;while(!0){if(a=this.activeWorkers.shift(),!(null!=a))break;console.log('killing active worker'),a.terminate()}return this.running=!1,this.emit('abort')},a.prototype.spawnWorkers=function(){var a,b;return a=Math.min(this.options.workers,this.frames.length),function(){var c;c=[];for(var b=this.freeWorkers.length;this.freeWorkers.length<=a?b<a:b>a;this.freeWorkers.length<=a?++b:--b)c.push(b);return c}.apply(this,arguments).forEach((b=this,function(d){var a,c;return console.log('spawning worker '+d),c=new Worker(b.options.workerScript),a=b,c.onmessage=function(b){return a.activeWorkers.splice(a.activeWorkers.indexOf(c),1),a.freeWorkers.push(c),a.frameFinished(b.data)},b.freeWorkers.push(c)})),a},a.prototype.frameFinished=function(a){return console.log('frame '+a.index+' finished - '+this.activeWorkers.length+' active'),this.finishedFrames++,this.emit('progress',this.finishedFrames/this.frames.length),this.imageParts[a.index]=a,g(null,this.imageParts)?this.renderNextFrame():this.finishRendering()},a.prototype.finishRendering=function(){var e,a,k,m,b,d,h;b=0;for(var f=0,j=this.imageParts.length;f<j;++f)a=this.imageParts[f],b+=(a.data.length-1)*a.pageSize+a.cursor;b+=a.pageSize-a.cursor,console.log('rendering finished - filesize '+Math.round(b/1e3)+'kb'),e=new Uint8Array(b),d=0;for(var g=0,l=this.imageParts.length;g<l;++g){a=this.imageParts[g];for(var c=0,i=a.data.length;c<i;++c)h=a.data[c],k=c,e.set(h,d),k===a.data.length-1?d+=a.cursor:d+=a.pageSize}return m=new Blob([e],{type:'image/gif'}),this.emit('finished',m,e)},a.prototype.renderNextFrame=function(){var c,a,b;if(this.freeWorkers.length===0)throw new Error('No free workers');return this.nextFrame>=this.frames.length?void 0:(c=this.frames[this.nextFrame++],b=this.freeWorkers.shift(),a=this.getTask(c),console.log('starting frame '+(a.index+1)+' of '+this.frames.length),this.activeWorkers.push(b),b.postMessage(a))},a.prototype.getContextData=function(a){return a.getImageData(0,0,this.options.width,this.options.height).data},a.prototype.getImageData=function(b){var a;return null!=this._canvas||(this._canvas=document.createElement('canvas'),this._canvas.width=this.options.width,this._canvas.height=this.options.height),a=this._canvas.getContext('2d'),a.setFill=this.options.background,a.fillRect(0,0,this.options.width,this.options.height),a.drawImage(b,0,0),this.getContextData(a)},a.prototype.getTask=function(a){var c,b;if(c=this.frames.indexOf(a),b={index:c,last:c===this.frames.length-1,delay:a.delay,width:this.options.width,height:this.options.height,quality:this.options.quality,repeat:this.options.repeat,canTransfer:e.name==='chrome'},null!=a.data)b.data=a.data;else if(null!=a.context)b.data=this.getContextData(a.context);else if(null!=a.image)b.data=this.getImageData(a.image);else throw new Error('Invalid frame');return b},a}(d),b.exports=f}),a.define('/browser.coffee',function(f,g,h,i){var a,d,e,c,b;c=navigator.userAgent.toLowerCase(),e=navigator.platform.toLowerCase(),b=c.match(/(opera|ie|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/)||[null,'unknown',0],d=b[1]==='ie'&&document.documentMode,a={name:b[1]==='version'?b[3]:b[1],version:d||parseFloat(b[1]==='opera'&&b[4]?b[4]:b[2]),platform:{name:c.match(/ip(?:ad|od|hone)/)?'ios':(c.match(/(?:webos|android)/)||e.match(/mac|win|linux/)||['other'])[0]}},a[a.name]=!0,a[a.name+parseInt(a.version,10)]=!0,a.platform[a.platform.name]=!0,f.exports=a}),a.define('events',function(f,e,g,h){b.EventEmitter||(b.EventEmitter=function(){});var a=e.EventEmitter=b.EventEmitter,c=typeof Array.isArray==='function'?Array.isArray:function(a){return Object.prototype.toString.call(a)==='[object Array]'},d=10;a.prototype.setMaxListeners=function(a){this._events||(this._events={}),this._events.maxListeners=a},a.prototype.emit=function(f){if(f==='error'&&(!(this._events&&this._events.error)||c(this._events.error)&&!this._events.error.length))throw arguments[1]instanceof Error?arguments[1]:new Error("Uncaught, unspecified 'error' event.");if(!this._events)return!1;var a=this._events[f];if(!a)return!1;if(!(typeof a=='function'))if(c(a)){var b=Array.prototype.slice.call(arguments,1),e=a.slice();for(var d=0,g=e.length;d<g;d++)e[d].apply(this,b);return!0}else return!1;switch(arguments.length){case 1:a.call(this);break;case 2:a.call(this,arguments[1]);break;case 3:a.call(this,arguments[1],arguments[2]);break;default:var b=Array.prototype.slice.call(arguments,1);a.apply(this,b)}return!0},a.prototype.addListener=function(a,b){if('function'!==typeof b)throw new Error('addListener only takes instances of Function');if(this._events||(this._events={}),this.emit('newListener',a,b),!this._events[a])this._events[a]=b;else if(c(this._events[a])){if(!this._events[a].warned){var e;this._events.maxListeners!==undefined?e=this._events.maxListeners:e=d,e&&e>0&&this._events[a].length>e&&(this._events[a].warned=!0,console.error('(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.',this._events[a].length),console.trace())}this._events[a].push(b)}else this._events[a]=[this._events[a],b];return this},a.prototype.on=a.prototype.addListener,a.prototype.once=function(b,c){var a=this;return a.on(b,function d(){a.removeListener(b,d),c.apply(this,arguments)}),this},a.prototype.removeListener=function(a,d){if('function'!==typeof d)throw new Error('removeListener only takes instances of Function');if(!(this._events&&this._events[a]))return this;var b=this._events[a];if(c(b)){var e=b.indexOf(d);if(e<0)return this;b.splice(e,1),b.length==0&&delete this._events[a]}else this._events[a]===d&&delete this._events[a];return this},a.prototype.removeAllListeners=function(a){return a&&this._events&&this._events[a]&&(this._events[a]=null),this},a.prototype.listeners=function(a){return this._events||(this._events={}),this._events[a]||(this._events[a]=[]),c(this._events[a])||(this._events[a]=[this._events[a]]),this._events[a]}}),c.GIF=a('/gif.coffee')}.call(this,this))
|
|
//# sourceMappingURL=gif.js.map
|
|
// gif.js 0.1.4 - https://github.com/jnordberg/gif.js;// TODO(grosbouddha): put under pskl namespace.
|
|
var Constants = {
|
|
DEFAULT : {
|
|
HEIGHT : 32,
|
|
WIDTH : 32,
|
|
FPS : 12
|
|
},
|
|
|
|
MODEL_VERSION : 1,
|
|
|
|
MAX_HEIGHT : 128,
|
|
MAX_WIDTH : 128,
|
|
|
|
PREVIEW_FILM_SIZE : 120,
|
|
|
|
DEFAULT_PEN_COLOR : '#000000',
|
|
TRANSPARENT_COLOR : 'TRANSPARENT',
|
|
|
|
/*
|
|
* Fake semi-transparent color used to highlight transparent
|
|
* strokes and rectangles:
|
|
*/
|
|
SELECTION_TRANSPARENT_COLOR: 'rgba(255, 255, 255, 0.6)',
|
|
|
|
/*
|
|
* When a tool is hovering the drawing canvas, we highlight the eventual
|
|
* pixel target with this color:
|
|
*/
|
|
TOOL_TARGET_HIGHLIGHT_COLOR: 'rgba(255, 255, 255, 0.2)',
|
|
|
|
/*
|
|
* Default entry point for piskel web service:
|
|
*/
|
|
PISKEL_SERVICE_URL: 'http://3.piskel-app.appspot.com',
|
|
|
|
GRID_STROKE_WIDTH: 1,
|
|
GRID_STROKE_COLOR: "lightgray",
|
|
|
|
LEFT_BUTTON : "left_button_1",
|
|
RIGHT_BUTTON : "right_button_2"
|
|
};;// TODO(grosbouddha): put under pskl namespace.
|
|
Events = {
|
|
|
|
TOOL_SELECTED : "TOOL_SELECTED",
|
|
TOOL_RELEASED : "TOOL_RELEASED",
|
|
PRIMARY_COLOR_SELECTED: "PRIMARY_COLOR_SELECTED",
|
|
PRIMARY_COLOR_UPDATED: "PRIMARY_COLOR_UPDATED",
|
|
SECONDARY_COLOR_SELECTED: "SECONDARY_COLOR_SELECTED",
|
|
SECONDARY_COLOR_UPDATED: "SECONDARY_COLOR_UPDATED",
|
|
|
|
/**
|
|
* When this event is emitted, a request is sent to the localstorage
|
|
* Service to save the current framesheet. The storage service
|
|
* may not immediately store data (internal throttling of requests).
|
|
*/
|
|
LOCALSTORAGE_REQUEST: "LOCALSTORAGE_REQUEST",
|
|
|
|
CANVAS_RIGHT_CLICKED: "CANVAS_RIGHT_CLICKED",
|
|
|
|
/**
|
|
* Event to request a refresh of the display.
|
|
* A bit overkill but, it's just workaround in our current drawing system.
|
|
* TODO: Remove or rework when redraw system is refactored.
|
|
*/
|
|
REFRESH: "REFRESH",
|
|
|
|
/**
|
|
* Temporary event to bind the redraw of right preview film to the canvas.
|
|
* This redraw should be driven by model updates.
|
|
* TODO(vincz): Remove.
|
|
*/
|
|
REDRAW_PREVIEWFILM: "REDRAW_PREVIEWFILM",
|
|
|
|
/**
|
|
* Fired each time a user setting change.
|
|
* The payload will be:
|
|
* 1st argument: Name of the settings
|
|
* 2nd argument: New value
|
|
*/
|
|
USER_SETTINGS_CHANGED: "USER_SETTINGS_CHANGED",
|
|
|
|
/**
|
|
* The framesheet was reseted and is now probably drastically different.
|
|
* Number of frames, content of frames, color used for the palette may have changed.
|
|
*/
|
|
FRAMESHEET_RESET: "FRAMESHEET_RESET",
|
|
|
|
FRAME_SIZE_CHANGED : "FRAME_SIZE_CHANGED",
|
|
|
|
CURRENT_FRAME_SET: "CURRENT_FRAME_SET",
|
|
|
|
SELECTION_CREATED: "SELECTION_CREATED",
|
|
SELECTION_MOVE_REQUEST: "SELECTION_MOVE_REQUEST",
|
|
SELECTION_DISMISSED: "SELECTION_DISMISSED",
|
|
|
|
SHOW_NOTIFICATION: "SHOW_NOTIFICATION",
|
|
HIDE_NOTIFICATION: "HIDE_NOTIFICATION",
|
|
|
|
UNDO: "UNDO",
|
|
REDO: "REDO",
|
|
CUT: "CUT",
|
|
COPY: "COPY",
|
|
PASTE: "PASTE"
|
|
};;jQuery.namespace = function() {
|
|
var a=arguments, o=null, i, j, d;
|
|
for (i=0; i<a.length; i=i+1) {
|
|
d=a[i].split(".");
|
|
o=window;
|
|
for (j=0; j<d.length; j=j+1) {
|
|
o[d[j]]=o[d[j]] || {};
|
|
o=o[d[j]];
|
|
}
|
|
}
|
|
return o;
|
|
};
|
|
|
|
/**
|
|
* Need a polyfill for PhantomJS
|
|
*/
|
|
if (typeof Function.prototype.bind !== "function") {
|
|
Function.prototype.bind = function(scope) {
|
|
"use strict";
|
|
var _function = this;
|
|
return function() {
|
|
return _function.apply(scope, arguments);
|
|
};
|
|
};
|
|
}
|
|
|
|
/*
|
|
* @provide pskl.utils
|
|
*
|
|
* @require Constants
|
|
*/
|
|
(function() { // namespace: pskl.utils
|
|
|
|
var ns = $.namespace("pskl.utils");
|
|
|
|
ns.rgbToHex = function(r, g, b) {
|
|
if (r > 255 || g > 255 || b > 255)
|
|
throw "Invalid color component";
|
|
return ((r << 16) | (g << 8) | b).toString(16);
|
|
};
|
|
|
|
ns.inherit = function(extendedObject, inheritFrom) {
|
|
extendedObject.prototype = Object.create(inheritFrom.prototype);
|
|
extendedObject.prototype.constructor = extendedObject;
|
|
extendedObject.prototype.superclass = inheritFrom.prototype;
|
|
};
|
|
|
|
})();
|
|
|
|
;(function () {
|
|
var ns = $.namespace("pskl");
|
|
|
|
ns.CanvasUtils = {
|
|
createCanvas : function (width, height, classList) {
|
|
var canvas = document.createElement("canvas");
|
|
canvas.setAttribute("width", width);
|
|
canvas.setAttribute("height", height);
|
|
|
|
if (typeof classList == "string") {
|
|
classList = [classList];
|
|
}
|
|
if (Array.isArray(classList)) {
|
|
for (var i = 0 ; i < classList.length ; i++) {
|
|
canvas.classList.add(classList[i]);
|
|
}
|
|
}
|
|
|
|
return canvas;
|
|
}
|
|
};
|
|
})();;(function () {
|
|
var ns = $.namespace('pskl.utils');
|
|
|
|
ns.FrameUtils = {
|
|
merge : function (frames) {
|
|
var merged = null;
|
|
if (frames.length) {
|
|
merged = frames[0].clone();
|
|
var w = merged.getWidth(), h = merged.getHeight();
|
|
for (var i = 1 ; i < frames.length ; i++) {
|
|
pskl.utils.FrameUtils.mergeFrames_(merged, frames[i]);
|
|
}
|
|
}
|
|
return merged;
|
|
},
|
|
|
|
mergeFrames_ : function (frameA, frameB) {
|
|
frameB.forEachPixel(function (p, col, row) {
|
|
if (p != Constants.TRANSPARENT_COLOR) {
|
|
frameA.setPixel(col, row, p);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
})();;(function () {
|
|
var ns = $.namespace("pskl");
|
|
|
|
ns.PixelUtils = {
|
|
|
|
getRectanglePixels : function (x0, y0, x1, y1) {
|
|
var rectangle = this.getOrderedRectangleCoordinates(x0, y0, x1, y1);
|
|
var pixels = [];
|
|
|
|
for(var x = rectangle.x0; x <= rectangle.x1; x++) {
|
|
for(var y = rectangle.y0; y <= rectangle.y1; y++) {
|
|
pixels.push({"col": x, "row": y});
|
|
}
|
|
}
|
|
|
|
return pixels;
|
|
},
|
|
|
|
getBoundRectanglePixels : function (x0, y0, x1, y1) {
|
|
var rectangle = this.getOrderedRectangleCoordinates(x0, y0, x1, y1);
|
|
var pixels = [];
|
|
// Creating horizontal sides of the rectangle:
|
|
for(var x = rectangle.x0; x <= rectangle.x1; x++) {
|
|
pixels.push({"col": x, "row": rectangle.y0});
|
|
pixels.push({"col": x, "row": rectangle.y1});
|
|
}
|
|
|
|
// Creating vertical sides of the rectangle:
|
|
for(var y = rectangle.y0; y <= rectangle.y1; y++) {
|
|
pixels.push({"col": rectangle.x0, "row": y});
|
|
pixels.push({"col": rectangle.x1, "row": y});
|
|
}
|
|
|
|
return pixels;
|
|
},
|
|
|
|
/**
|
|
* Return an object of ordered rectangle coordinate.
|
|
* In returned object {x0, y0} => top left corner - {x1, y1} => bottom right corner
|
|
* @private
|
|
*/
|
|
getOrderedRectangleCoordinates : function (x0, y0, x1, y1) {
|
|
return {
|
|
x0 : Math.min(x0, x1),
|
|
y0 : Math.min(y0, y1),
|
|
x1 : Math.max(x0, x1),
|
|
y1 : Math.max(y0, y1),
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Return the list of pixels that would have been filled by a paintbucket tool applied
|
|
* on pixel at coordinate (x,y).
|
|
* This function is not altering the Frame object argument.
|
|
*
|
|
* @param frame pskl.model.Frame The frame target in which we want to paintbucket
|
|
* @param col number Column coordinate in the frame
|
|
* @param row number Row coordinate in the frame
|
|
*
|
|
* @return an array of the pixel coordinates paint with the replacement color
|
|
*/
|
|
getSimilarConnectedPixelsFromFrame: function(frame, col, row) {
|
|
// To get the list of connected (eg the same color) pixels, we will use the paintbucket algorithm
|
|
// in a fake cloned frame. The returned pixels by the paintbucket algo are the painted pixels
|
|
// and are as well connected.
|
|
var fakeFrame = frame.clone(); // We just want to
|
|
var fakeFillColor = "sdfsdfsdf"; // A fake color that will never match a real color.
|
|
var paintedPixels = this.paintSimilarConnectedPixelsFromFrame(fakeFrame, col, row, fakeFillColor);
|
|
|
|
return paintedPixels;
|
|
},
|
|
|
|
/**
|
|
* Apply the paintbucket tool in a frame at the (col, row) initial position
|
|
* with the replacement color.
|
|
*
|
|
* @param frame pskl.model.Frame The frame target in which we want to paintbucket
|
|
* @param col number Column coordinate in the frame
|
|
* @param row number Row coordinate in the frame
|
|
* @param replacementColor string Hexadecimal color used to fill the area
|
|
*
|
|
* @return an array of the pixel coordinates paint with the replacement color
|
|
*/
|
|
paintSimilarConnectedPixelsFromFrame: function(frame, col, row, replacementColor) {
|
|
/**
|
|
* Queue linear Flood-fill (node, target-color, replacement-color):
|
|
* 1. Set Q to the empty queue.
|
|
* 2. If the color of node is not equal to target-color, return.
|
|
* 3. Add node to Q.
|
|
* 4. For each element n of Q:
|
|
* 5. If the color of n is equal to target-color:
|
|
* 6. Set w and e equal to n.
|
|
* 7. Move w to the west until the color of the node to the west of w no longer matches target-color.
|
|
* 8. Move e to the east until the color of the node to the east of e no longer matches target-color.
|
|
* 9. Set the color of nodes between w and e to replacement-color.
|
|
* 10. For each node n between w and e:
|
|
* 11. If the color of the node to the north of n is target-color, add that node to Q.
|
|
* 12. If the color of the node to the south of n is target-color, add that node to Q.
|
|
* 13. Continue looping until Q is exhausted.
|
|
* 14. Return.
|
|
*/
|
|
var paintedPixels = [];
|
|
var queue = [];
|
|
var dy = [-1, 0, 1, 0];
|
|
var dx = [0, 1, 0, -1];
|
|
var targetColor;
|
|
try {
|
|
targetColor = frame.getPixel(col, row);
|
|
} catch(e) {
|
|
// Frame out of bound exception.
|
|
}
|
|
|
|
if(targetColor == replacementColor) {
|
|
return;
|
|
}
|
|
|
|
|
|
queue.push({"col": col, "row": row});
|
|
var loopCount = 0;
|
|
var cellCount = frame.getWidth() * frame.getHeight();
|
|
while(queue.length > 0) {
|
|
loopCount ++;
|
|
|
|
var currentItem = queue.pop();
|
|
frame.setPixel(currentItem.col, currentItem.row, replacementColor);
|
|
paintedPixels.push({"col": currentItem.col, "row": currentItem.row });
|
|
|
|
for (var i = 0; i < 4; i++) {
|
|
var nextCol = currentItem.col + dx[i];
|
|
var nextRow = currentItem.row + dy[i];
|
|
try {
|
|
if (frame.containsPixel(nextCol, nextRow) && frame.getPixel(nextCol, nextRow) == targetColor) {
|
|
queue.push({"col": nextCol, "row": nextRow });
|
|
}
|
|
} catch(e) {
|
|
// Frame out of bound exception.
|
|
}
|
|
}
|
|
|
|
// Security loop breaker:
|
|
if(loopCount > 10 * cellCount) {
|
|
console.log("loop breaker called");
|
|
break;
|
|
}
|
|
}
|
|
return paintedPixels;
|
|
},
|
|
|
|
/**
|
|
* Calculate and return the maximal DPI to display a picture in a given container.
|
|
*
|
|
* @param container jQueryObject Container where the picture should be displayed
|
|
* @param number pictureHeight height in pixels of the picture to display
|
|
* @param number pictureWidth width in pixels of the picture to display
|
|
* @return number maximal dpi
|
|
*/
|
|
calculateDPIForContainer : function (container, pictureHeight, pictureWidth) {
|
|
return this.calculateDPI(container.height(), container.width(), pictureHeight, pictureWidth);
|
|
},
|
|
|
|
/**
|
|
* Calculate and return the maximal DPI to display a picture for a given height and width.
|
|
*
|
|
* @param height number Height available to display the picture
|
|
* @param width number Width available to display the picture
|
|
* @param number pictureHeight height in pixels of the picture to display
|
|
* @param number pictureWidth width in pixels of the picture to display
|
|
* @return number maximal dpi
|
|
*/
|
|
calculateDPI : function (height, width, pictureHeight, pictureWidth) {
|
|
var heightBoundDpi = Math.floor(height / pictureHeight),
|
|
widthBoundDpi = Math.floor(width / pictureWidth);
|
|
|
|
return Math.min(heightBoundDpi, widthBoundDpi);
|
|
},
|
|
};
|
|
})();;(function () {
|
|
var ns = $.namespace('pskl.utils');
|
|
ns.Serializer = {
|
|
serializePiskel : function (piskel) {
|
|
var serializedLayers = piskel.getLayers().map(function (l) {
|
|
return pskl.utils.Serializer.serializeLayer(l);
|
|
});
|
|
return JSON.stringify({
|
|
modelVersion : Constants.MODEL_VERSION,
|
|
piskel : {
|
|
height : piskel.getHeight(),
|
|
width : piskel.getWidth(),
|
|
fps : piskel.getFps(),
|
|
layers : serializedLayers
|
|
}
|
|
});
|
|
},
|
|
|
|
serializeLayer : function (layer) {
|
|
var serializedFrames = layer.getFrames().map(function (f) {
|
|
return f.serialize();
|
|
});
|
|
return JSON.stringify({
|
|
name : layer.getName(),
|
|
frames : serializedFrames
|
|
});
|
|
},
|
|
|
|
deserializePiskel : function (json) {
|
|
var data = JSON.parse(json);
|
|
if (data.modelVersion == Constants.MODEL_VERSION) {
|
|
var pData = data.piskel;
|
|
var layers = pData.layers.map(function (serializedLayer) {
|
|
return pskl.utils.Serializer.deserializeLayer(serializedLayer);
|
|
});
|
|
var piskel = new pskl.model.Piskel(pData.width, pData.height, pData.fps);
|
|
layers.forEach(function (layer) {
|
|
piskel.addLayer(layer);
|
|
});
|
|
return piskel;
|
|
} else {
|
|
// pre-layer implementation adapter
|
|
}
|
|
},
|
|
|
|
deserializeLayer : function (json) {
|
|
var lData = JSON.parse(json);
|
|
var frames = lData.frames.map(function (serializedFrame) {
|
|
return pskl.utils.Serializer.deserializeFrame(serializedFrame);
|
|
});
|
|
|
|
var layer = new pskl.model.Layer(lData.name);
|
|
frames.forEach(function (frame) {
|
|
layer.addFrame(frame);
|
|
});
|
|
return layer;
|
|
},
|
|
|
|
deserializeFrame : function (json) {
|
|
var framePixelGrid = JSON.parse(json);
|
|
return pskl.model.Frame.fromPixelGrid(framePixelGrid);
|
|
}
|
|
};
|
|
})();;(function () {
|
|
var ns = $.namespace("pskl");
|
|
|
|
ns.utils.Template = {
|
|
get : function (templateId) {
|
|
var template = document.getElementById(templateId);
|
|
if (template) {
|
|
return template.innerHTML;
|
|
} else {
|
|
console.error("Could not find template for id :", templateId);
|
|
}
|
|
},
|
|
|
|
createFromHTML : function (html) {
|
|
var dummyEl = document.createElement("div");
|
|
dummyEl.innerHTML = html;
|
|
return dummyEl.children[0];
|
|
},
|
|
|
|
replace : function (template, dict) {
|
|
for (var key in dict) {
|
|
if (dict.hasOwnProperty(key)) {
|
|
var value = dict[key];
|
|
template = template.replace(new RegExp('\\{\\{'+key+'\\}\\}', 'g'), value);
|
|
}
|
|
}
|
|
return template;
|
|
}
|
|
};
|
|
})();;(function () {
|
|
var ns = $.namespace("pskl");
|
|
|
|
ns.UserSettings = {
|
|
|
|
SHOW_GRID : 'SHOW_GRID',
|
|
CANVAS_BACKGROUND : 'CANVAS_BACKGROUND',
|
|
|
|
KEY_TO_DEFAULT_VALUE_MAP_ : {
|
|
'SHOW_GRID' : false,
|
|
'CANVAS_BACKGROUND' : 'medium-canvas-background'
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
cache_ : {},
|
|
|
|
/**
|
|
* Static method to access a user defined settings value ot its default
|
|
* value if not defined yet.
|
|
*/
|
|
get : function (key) {
|
|
this.checkKeyValidity_(key);
|
|
if (!(key in this.cache_)) {
|
|
this.cache_[key] =
|
|
this.readFromLocalStorage_(key) || this.readFromDefaults_(key);
|
|
}
|
|
return this.cache_[key];
|
|
},
|
|
|
|
set : function (key, value) {
|
|
this.checkKeyValidity_(key);
|
|
this.cache_[key] = value;
|
|
this.writeToLocalStorage_(key, value);
|
|
|
|
$.publish(Events.USER_SETTINGS_CHANGED, [key, value]);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
readFromLocalStorage_ : function(key) {
|
|
var value = window.localStorage[key];
|
|
if (typeof value != "undefined") {
|
|
value = JSON.parse(value);
|
|
}
|
|
return value;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
writeToLocalStorage_ : function(key, value) {
|
|
// TODO(grosbouddha): Catch storage exception here.
|
|
window.localStorage[key] = JSON.stringify(value);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
readFromDefaults_ : function (key) {
|
|
return this.KEY_TO_DEFAULT_VALUE_MAP_[key];
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
checkKeyValidity_ : function(key) {
|
|
if(!(key in this.KEY_TO_DEFAULT_VALUE_MAP_)) {
|
|
// TODO(grosbouddha): Define error catching strategy and throw exception from here.
|
|
console.log("UserSettings key <"+ key +"> not find in supported keys.");
|
|
}
|
|
}
|
|
};
|
|
})();;/**
|
|
* jscolor, JavaScript Color Picker
|
|
*
|
|
* @version 1.4.0
|
|
* @license GNU Lesser General Public License, http://www.gnu.org/copyleft/lesser.html
|
|
* @author Jan Odvarko, http://odvarko.cz
|
|
* @created 2008-06-15
|
|
* @updated 2012-07-06
|
|
* @link http://jscolor.com
|
|
*/
|
|
|
|
|
|
var jscolor = {
|
|
|
|
|
|
dir : '', // location of jscolor directory (leave empty to autodetect)
|
|
bindClass : 'color', // class name
|
|
binding : true, // automatic binding via <input class="...">
|
|
preloading : true, // use image preloading?
|
|
|
|
|
|
install : function() {
|
|
if (document.readyState === "complete") {
|
|
jscolor.init();
|
|
} else {
|
|
jscolor.addEvent(window, 'load', jscolor.init);
|
|
}
|
|
},
|
|
|
|
|
|
init : function() {
|
|
if(jscolor.binding) {
|
|
jscolor.bind();
|
|
}
|
|
if(jscolor.preloading) {
|
|
jscolor.preload();
|
|
}
|
|
},
|
|
|
|
|
|
getDir : function() {
|
|
return "/" + window.location.pathname.split("/")[1] + "/js/lib/jsColor_1_4_0/";
|
|
},
|
|
|
|
|
|
bind : function() {
|
|
var matchClass = new RegExp('(^|\\s)('+jscolor.bindClass+')\\s*(\\{[^}]*\\})?', 'i');
|
|
var e = document.getElementsByTagName('input');
|
|
for(var i=0; i<e.length; i+=1) {
|
|
var m;
|
|
if(!e[i].color && e[i].className && (m = e[i].className.match(matchClass))) {
|
|
var prop = {};
|
|
if(m[3]) {
|
|
try {
|
|
prop = (new Function ('return (' + m[3] + ')'))();
|
|
} catch(eInvalidProp) {}
|
|
}
|
|
e[i].color = new jscolor.color(e[i], prop);
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
preload : function() {
|
|
for(var fn in jscolor.imgRequire) {
|
|
if(jscolor.imgRequire.hasOwnProperty(fn)) {
|
|
jscolor.loadImage(fn);
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
images : {
|
|
pad : [ 181, 101 ],
|
|
sld : [ 16, 101 ],
|
|
cross : [ 15, 15 ],
|
|
arrow : [ 7, 11 ]
|
|
},
|
|
|
|
|
|
imgRequire : {},
|
|
imgLoaded : {},
|
|
|
|
|
|
requireImage : function(filename) {
|
|
jscolor.imgRequire[filename] = true;
|
|
},
|
|
|
|
|
|
loadImage : function(filename) {
|
|
if(!jscolor.imgLoaded[filename]) {
|
|
jscolor.imgLoaded[filename] = new Image();
|
|
jscolor.imgLoaded[filename].src = jscolor.getDir()+filename;
|
|
}
|
|
},
|
|
|
|
|
|
fetchElement : function(mixed) {
|
|
return typeof mixed === 'string' ? document.getElementById(mixed) : mixed;
|
|
},
|
|
|
|
|
|
addEvent : function(el, evnt, func) {
|
|
if(el.addEventListener) {
|
|
el.addEventListener(evnt, func, false);
|
|
} else if(el.attachEvent) {
|
|
el.attachEvent('on'+evnt, func);
|
|
}
|
|
},
|
|
|
|
|
|
fireEvent : function(el, evnt) {
|
|
if(!el) {
|
|
return;
|
|
}
|
|
if(document.createEvent) {
|
|
var ev = document.createEvent('HTMLEvents');
|
|
ev.initEvent(evnt, true, true);
|
|
el.dispatchEvent(ev);
|
|
} else if(document.createEventObject) {
|
|
var ev = document.createEventObject();
|
|
el.fireEvent('on'+evnt, ev);
|
|
} else if(el['on'+evnt]) { // alternatively use the traditional event model (IE5)
|
|
el['on'+evnt]();
|
|
}
|
|
},
|
|
|
|
|
|
getElementPos : function(e) {
|
|
var e1=e, e2=e;
|
|
var x=0, y=0;
|
|
if(e1.offsetParent) {
|
|
do {
|
|
x += e1.offsetLeft;
|
|
y += e1.offsetTop;
|
|
} while(e1 = e1.offsetParent);
|
|
}
|
|
while((e2 = e2.parentNode) && e2.nodeName.toUpperCase() !== 'BODY') {
|
|
x -= e2.scrollLeft;
|
|
y -= e2.scrollTop;
|
|
}
|
|
return [x, y];
|
|
},
|
|
|
|
|
|
getElementSize : function(e) {
|
|
return [e.offsetWidth, e.offsetHeight];
|
|
},
|
|
|
|
|
|
getRelMousePos : function(e) {
|
|
var x = 0, y = 0;
|
|
if (!e) { e = window.event; }
|
|
if (typeof e.offsetX === 'number') {
|
|
x = e.offsetX;
|
|
y = e.offsetY;
|
|
} else if (typeof e.layerX === 'number') {
|
|
x = e.layerX;
|
|
y = e.layerY;
|
|
}
|
|
return { x: x, y: y };
|
|
},
|
|
|
|
|
|
getViewPos : function() {
|
|
if(typeof window.pageYOffset === 'number') {
|
|
return [window.pageXOffset, window.pageYOffset];
|
|
} else if(document.body && (document.body.scrollLeft || document.body.scrollTop)) {
|
|
return [document.body.scrollLeft, document.body.scrollTop];
|
|
} else if(document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop)) {
|
|
return [document.documentElement.scrollLeft, document.documentElement.scrollTop];
|
|
} else {
|
|
return [0, 0];
|
|
}
|
|
},
|
|
|
|
|
|
getViewSize : function() {
|
|
if(typeof window.innerWidth === 'number') {
|
|
return [window.innerWidth, window.innerHeight];
|
|
} else if(document.body && (document.body.clientWidth || document.body.clientHeight)) {
|
|
return [document.body.clientWidth, document.body.clientHeight];
|
|
} else if(document.documentElement && (document.documentElement.clientWidth || document.documentElement.clientHeight)) {
|
|
return [document.documentElement.clientWidth, document.documentElement.clientHeight];
|
|
} else {
|
|
return [0, 0];
|
|
}
|
|
},
|
|
|
|
|
|
URI : function(uri) { // See RFC3986
|
|
|
|
this.scheme = null;
|
|
this.authority = null;
|
|
this.path = '';
|
|
this.query = null;
|
|
this.fragment = null;
|
|
|
|
this.parse = function(uri) {
|
|
var m = uri.match(/^(([A-Za-z][0-9A-Za-z+.-]*)(:))?((\/\/)([^\/?#]*))?([^?#]*)((\?)([^#]*))?((#)(.*))?/);
|
|
this.scheme = m[3] ? m[2] : null;
|
|
this.authority = m[5] ? m[6] : null;
|
|
this.path = m[7];
|
|
this.query = m[9] ? m[10] : null;
|
|
this.fragment = m[12] ? m[13] : null;
|
|
return this;
|
|
};
|
|
|
|
this.toString = function() {
|
|
var result = '';
|
|
if(this.scheme !== null) { result = result + this.scheme + ':'; }
|
|
if(this.authority !== null) { result = result + '//' + this.authority; }
|
|
if(this.path !== null) { result = result + this.path; }
|
|
if(this.query !== null) { result = result + '?' + this.query; }
|
|
if(this.fragment !== null) { result = result + '#' + this.fragment; }
|
|
return result;
|
|
};
|
|
|
|
this.toAbsolute = function(base) {
|
|
var base = new jscolor.URI(base);
|
|
var r = this;
|
|
var t = new jscolor.URI;
|
|
|
|
if(base.scheme === null) { return false; }
|
|
|
|
if(r.scheme !== null && r.scheme.toLowerCase() === base.scheme.toLowerCase()) {
|
|
r.scheme = null;
|
|
}
|
|
|
|
if(r.scheme !== null) {
|
|
t.scheme = r.scheme;
|
|
t.authority = r.authority;
|
|
t.path = removeDotSegments(r.path);
|
|
t.query = r.query;
|
|
} else {
|
|
if(r.authority !== null) {
|
|
t.authority = r.authority;
|
|
t.path = removeDotSegments(r.path);
|
|
t.query = r.query;
|
|
} else {
|
|
if(r.path === '') {
|
|
t.path = base.path;
|
|
if(r.query !== null) {
|
|
t.query = r.query;
|
|
} else {
|
|
t.query = base.query;
|
|
}
|
|
} else {
|
|
if(r.path.substr(0,1) === '/') {
|
|
t.path = removeDotSegments(r.path);
|
|
} else {
|
|
if(base.authority !== null && base.path === '') {
|
|
t.path = '/'+r.path;
|
|
} else {
|
|
t.path = base.path.replace(/[^\/]+$/,'')+r.path;
|
|
}
|
|
t.path = removeDotSegments(t.path);
|
|
}
|
|
t.query = r.query;
|
|
}
|
|
t.authority = base.authority;
|
|
}
|
|
t.scheme = base.scheme;
|
|
}
|
|
t.fragment = r.fragment;
|
|
|
|
return t;
|
|
};
|
|
|
|
function removeDotSegments(path) {
|
|
var out = '';
|
|
while(path) {
|
|
if(path.substr(0,3)==='../' || path.substr(0,2)==='./') {
|
|
path = path.replace(/^\.+/,'').substr(1);
|
|
} else if(path.substr(0,3)==='/./' || path==='/.') {
|
|
path = '/'+path.substr(3);
|
|
} else if(path.substr(0,4)==='/../' || path==='/..') {
|
|
path = '/'+path.substr(4);
|
|
out = out.replace(/\/?[^\/]*$/, '');
|
|
} else if(path==='.' || path==='..') {
|
|
path = '';
|
|
} else {
|
|
var rm = path.match(/^\/?[^\/]*/)[0];
|
|
path = path.substr(rm.length);
|
|
out = out + rm;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
if(uri) {
|
|
this.parse(uri);
|
|
}
|
|
|
|
},
|
|
|
|
|
|
/*
|
|
* Usage example:
|
|
* var myColor = new jscolor.color(myInputElement)
|
|
*/
|
|
|
|
color : function(target, prop) {
|
|
|
|
|
|
this.required = true; // refuse empty values?
|
|
this.adjust = true; // adjust value to uniform notation?
|
|
this.hash = false; // prefix color with # symbol?
|
|
this.caps = true; // uppercase?
|
|
this.slider = true; // show the value/saturation slider?
|
|
this.valueElement = target; // value holder
|
|
this.styleElement = target; // where to reflect current color
|
|
this.onImmediateChange = null; // onchange callback (can be either string or function)
|
|
this.hsv = [0, 0, 1]; // read-only 0-6, 0-1, 0-1
|
|
this.rgb = [1, 1, 1]; // read-only 0-1, 0-1, 0-1
|
|
this.minH = 0; // read-only 0-6
|
|
this.maxH = 6; // read-only 0-6
|
|
this.minS = 0; // read-only 0-1
|
|
this.maxS = 1; // read-only 0-1
|
|
this.minV = 0; // read-only 0-1
|
|
this.maxV = 1; // read-only 0-1
|
|
|
|
this.pickerOnfocus = true; // display picker on focus?
|
|
this.pickerMode = 'HSV'; // HSV | HVS
|
|
this.pickerPosition = 'bottom'; // left | right | top | bottom
|
|
this.pickerSmartPosition = true; // automatically adjust picker position when necessary
|
|
this.pickerButtonHeight = 20; // px
|
|
this.pickerClosable = false;
|
|
this.pickerCloseText = 'Close';
|
|
this.pickerButtonColor = 'ButtonText'; // px
|
|
this.pickerFace = 10; // px
|
|
this.pickerFaceColor = 'ThreeDFace'; // CSS color
|
|
this.pickerBorder = 1; // px
|
|
this.pickerBorderColor = 'ThreeDHighlight ThreeDShadow ThreeDShadow ThreeDHighlight'; // CSS color
|
|
this.pickerInset = 1; // px
|
|
this.pickerInsetColor = 'ThreeDShadow ThreeDHighlight ThreeDHighlight ThreeDShadow'; // CSS color
|
|
this.pickerZIndex = 10000;
|
|
|
|
|
|
for(var p in prop) {
|
|
if(prop.hasOwnProperty(p)) {
|
|
this[p] = prop[p];
|
|
}
|
|
}
|
|
|
|
|
|
this.hidePicker = function() {
|
|
if(isPickerOwner()) {
|
|
removePicker();
|
|
}
|
|
};
|
|
|
|
|
|
this.showPicker = function() {
|
|
if(!isPickerOwner()) {
|
|
var tp = jscolor.getElementPos(target); // target pos
|
|
var ts = jscolor.getElementSize(target); // target size
|
|
var vp = jscolor.getViewPos(); // view pos
|
|
var vs = jscolor.getViewSize(); // view size
|
|
var ps = getPickerDims(this); // picker size
|
|
var a, b, c;
|
|
switch(this.pickerPosition.toLowerCase()) {
|
|
case 'left': a=1; b=0; c=-1; break;
|
|
case 'right':a=1; b=0; c=1; break;
|
|
case 'top': a=0; b=1; c=-1; break;
|
|
default: a=0; b=1; c=1; break;
|
|
}
|
|
var l = (ts[b]+ps[b])/2;
|
|
|
|
// picker pos
|
|
if (!this.pickerSmartPosition) {
|
|
var pp = [
|
|
tp[a],
|
|
tp[b]+ts[b]-l+l*c
|
|
];
|
|
} else {
|
|
var pp = [
|
|
-vp[a]+tp[a]+ps[a] > vs[a] ?
|
|
(-vp[a]+tp[a]+ts[a]/2 > vs[a]/2 && tp[a]+ts[a]-ps[a] >= 0 ? tp[a]+ts[a]-ps[a] : tp[a]) :
|
|
tp[a],
|
|
-vp[b]+tp[b]+ts[b]+ps[b]-l+l*c > vs[b] ?
|
|
(-vp[b]+tp[b]+ts[b]/2 > vs[b]/2 && tp[b]+ts[b]-l-l*c >= 0 ? tp[b]+ts[b]-l-l*c : tp[b]+ts[b]-l+l*c) :
|
|
(tp[b]+ts[b]-l+l*c >= 0 ? tp[b]+ts[b]-l+l*c : tp[b]+ts[b]-l-l*c)
|
|
];
|
|
}
|
|
drawPicker(pp[a], pp[b]);
|
|
}
|
|
};
|
|
|
|
|
|
this.importColor = function() {
|
|
if(!valueElement) {
|
|
this.exportColor();
|
|
} else {
|
|
if(!this.adjust) {
|
|
if(!this.fromString(valueElement.value, leaveValue)) {
|
|
styleElement.style.backgroundImage = styleElement.jscStyle.backgroundImage;
|
|
styleElement.style.backgroundColor = styleElement.jscStyle.backgroundColor;
|
|
styleElement.style.color = styleElement.jscStyle.color;
|
|
this.exportColor(leaveValue | leaveStyle);
|
|
}
|
|
} else if(!this.required && /^\s*$/.test(valueElement.value)) {
|
|
valueElement.value = '';
|
|
styleElement.style.backgroundImage = styleElement.jscStyle.backgroundImage;
|
|
styleElement.style.backgroundColor = styleElement.jscStyle.backgroundColor;
|
|
styleElement.style.color = styleElement.jscStyle.color;
|
|
this.exportColor(leaveValue | leaveStyle);
|
|
|
|
} else if(this.fromString(valueElement.value)) {
|
|
// OK
|
|
} else {
|
|
this.exportColor();
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
this.exportColor = function(flags) {
|
|
if(!(flags & leaveValue) && valueElement) {
|
|
var value = this.toString();
|
|
if(this.caps) { value = value.toUpperCase(); }
|
|
if(this.hash) { value = '#'+value; }
|
|
valueElement.value = value;
|
|
}
|
|
if(!(flags & leaveStyle) && styleElement) {
|
|
styleElement.style.backgroundImage = "none";
|
|
styleElement.style.backgroundColor =
|
|
'#'+this.toString();
|
|
styleElement.style.color =
|
|
0.213 * this.rgb[0] +
|
|
0.715 * this.rgb[1] +
|
|
0.072 * this.rgb[2]
|
|
< 0.5 ? '#FFF' : '#000';
|
|
}
|
|
if(!(flags & leavePad) && isPickerOwner()) {
|
|
redrawPad();
|
|
}
|
|
if(!(flags & leaveSld) && isPickerOwner()) {
|
|
redrawSld();
|
|
}
|
|
};
|
|
|
|
|
|
this.fromHSV = function(h, s, v, flags) { // null = don't change
|
|
if(h !== null) { h = Math.max(0.0, this.minH, Math.min(6.0, this.maxH, h)); }
|
|
if(s !== null) { s = Math.max(0.0, this.minS, Math.min(1.0, this.maxS, s)); }
|
|
if(v !== null) { v = Math.max(0.0, this.minV, Math.min(1.0, this.maxV, v)); }
|
|
|
|
this.rgb = HSV_RGB(
|
|
h===null ? this.hsv[0] : (this.hsv[0]=h),
|
|
s===null ? this.hsv[1] : (this.hsv[1]=s),
|
|
v===null ? this.hsv[2] : (this.hsv[2]=v)
|
|
);
|
|
|
|
this.exportColor(flags);
|
|
};
|
|
|
|
|
|
this.fromRGB = function(r, g, b, flags) { // null = don't change
|
|
if(r !== null) { r = Math.max(0.0, Math.min(1.0, r)); }
|
|
if(g !== null) { g = Math.max(0.0, Math.min(1.0, g)); }
|
|
if(b !== null) { b = Math.max(0.0, Math.min(1.0, b)); }
|
|
|
|
var hsv = RGB_HSV(
|
|
r===null ? this.rgb[0] : r,
|
|
g===null ? this.rgb[1] : g,
|
|
b===null ? this.rgb[2] : b
|
|
);
|
|
if(hsv[0] !== null) {
|
|
this.hsv[0] = Math.max(0.0, this.minH, Math.min(6.0, this.maxH, hsv[0]));
|
|
}
|
|
if(hsv[2] !== 0) {
|
|
this.hsv[1] = hsv[1]===null ? null : Math.max(0.0, this.minS, Math.min(1.0, this.maxS, hsv[1]));
|
|
}
|
|
this.hsv[2] = hsv[2]===null ? null : Math.max(0.0, this.minV, Math.min(1.0, this.maxV, hsv[2]));
|
|
|
|
// update RGB according to final HSV, as some values might be trimmed
|
|
var rgb = HSV_RGB(this.hsv[0], this.hsv[1], this.hsv[2]);
|
|
this.rgb[0] = rgb[0];
|
|
this.rgb[1] = rgb[1];
|
|
this.rgb[2] = rgb[2];
|
|
|
|
this.exportColor(flags);
|
|
};
|
|
|
|
|
|
this.fromString = function(hex, flags) {
|
|
var m = hex.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i);
|
|
if(!m) {
|
|
return false;
|
|
} else {
|
|
if(m[1].length === 6) { // 6-char notation
|
|
this.fromRGB(
|
|
parseInt(m[1].substr(0,2),16) / 255,
|
|
parseInt(m[1].substr(2,2),16) / 255,
|
|
parseInt(m[1].substr(4,2),16) / 255,
|
|
flags
|
|
);
|
|
} else { // 3-char notation
|
|
this.fromRGB(
|
|
parseInt(m[1].charAt(0)+m[1].charAt(0),16) / 255,
|
|
parseInt(m[1].charAt(1)+m[1].charAt(1),16) / 255,
|
|
parseInt(m[1].charAt(2)+m[1].charAt(2),16) / 255,
|
|
flags
|
|
);
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
|
|
this.toString = function() {
|
|
return (
|
|
(0x100 | Math.round(255*this.rgb[0])).toString(16).substr(1) +
|
|
(0x100 | Math.round(255*this.rgb[1])).toString(16).substr(1) +
|
|
(0x100 | Math.round(255*this.rgb[2])).toString(16).substr(1)
|
|
);
|
|
};
|
|
|
|
|
|
function RGB_HSV(r, g, b) {
|
|
var n = Math.min(Math.min(r,g),b);
|
|
var v = Math.max(Math.max(r,g),b);
|
|
var m = v - n;
|
|
if(m === 0) { return [ null, 0, v ]; }
|
|
var h = r===n ? 3+(b-g)/m : (g===n ? 5+(r-b)/m : 1+(g-r)/m);
|
|
return [ h===6?0:h, m/v, v ];
|
|
}
|
|
|
|
|
|
function HSV_RGB(h, s, v) {
|
|
if(h === null) { return [ v, v, v ]; }
|
|
var i = Math.floor(h);
|
|
var f = i%2 ? h-i : 1-(h-i);
|
|
var m = v * (1 - s);
|
|
var n = v * (1 - s*f);
|
|
switch(i) {
|
|
case 6:
|
|
case 0: return [v,n,m];
|
|
case 1: return [n,v,m];
|
|
case 2: return [m,v,n];
|
|
case 3: return [m,n,v];
|
|
case 4: return [n,m,v];
|
|
case 5: return [v,m,n];
|
|
}
|
|
}
|
|
|
|
|
|
function removePicker() {
|
|
delete jscolor.picker.owner;
|
|
document.getElementsByTagName('body')[0].removeChild(jscolor.picker.boxB);
|
|
}
|
|
|
|
|
|
function drawPicker(x, y) {
|
|
if(!jscolor.picker) {
|
|
jscolor.picker = {
|
|
box : document.createElement('div'),
|
|
boxB : document.createElement('div'),
|
|
pad : document.createElement('div'),
|
|
padB : document.createElement('div'),
|
|
padM : document.createElement('div'),
|
|
sld : document.createElement('div'),
|
|
sldB : document.createElement('div'),
|
|
sldM : document.createElement('div'),
|
|
btn : document.createElement('div'),
|
|
btnS : document.createElement('span'),
|
|
btnT : document.createTextNode(THIS.pickerCloseText)
|
|
};
|
|
for(var i=0,segSize=4; i<jscolor.images.sld[1]; i+=segSize) {
|
|
var seg = document.createElement('div');
|
|
seg.style.height = segSize+'px';
|
|
seg.style.fontSize = '1px';
|
|
seg.style.lineHeight = '0';
|
|
jscolor.picker.sld.appendChild(seg);
|
|
}
|
|
jscolor.picker.sldB.appendChild(jscolor.picker.sld);
|
|
jscolor.picker.box.appendChild(jscolor.picker.sldB);
|
|
jscolor.picker.box.appendChild(jscolor.picker.sldM);
|
|
jscolor.picker.padB.appendChild(jscolor.picker.pad);
|
|
jscolor.picker.box.appendChild(jscolor.picker.padB);
|
|
jscolor.picker.box.appendChild(jscolor.picker.padM);
|
|
jscolor.picker.btnS.appendChild(jscolor.picker.btnT);
|
|
jscolor.picker.btn.appendChild(jscolor.picker.btnS);
|
|
jscolor.picker.box.appendChild(jscolor.picker.btn);
|
|
jscolor.picker.boxB.appendChild(jscolor.picker.box);
|
|
}
|
|
|
|
var p = jscolor.picker;
|
|
|
|
// controls interaction
|
|
p.box.onmouseup =
|
|
p.box.onmouseout = function() { target.focus(); };
|
|
p.box.onmousedown = function() { abortBlur=true; };
|
|
p.box.onmousemove = function(e) {
|
|
if (holdPad || holdSld) {
|
|
holdPad && setPad(e);
|
|
holdSld && setSld(e);
|
|
if (document.selection) {
|
|
document.selection.empty();
|
|
} else if (window.getSelection) {
|
|
window.getSelection().removeAllRanges();
|
|
}
|
|
dispatchImmediateChange();
|
|
}
|
|
};
|
|
p.padM.onmouseup =
|
|
p.padM.onmouseout = function() { if(holdPad) { holdPad=false; jscolor.fireEvent(valueElement,'change'); } };
|
|
p.padM.onmousedown = function(e) {
|
|
// if the slider is at the bottom, move it up
|
|
switch(modeID) {
|
|
case 0: if (THIS.hsv[2] === 0) { THIS.fromHSV(null, null, 1.0); }; break;
|
|
case 1: if (THIS.hsv[1] === 0) { THIS.fromHSV(null, 1.0, null); }; break;
|
|
}
|
|
holdPad=true;
|
|
setPad(e);
|
|
dispatchImmediateChange();
|
|
};
|
|
p.sldM.onmouseup =
|
|
p.sldM.onmouseout = function() { if(holdSld) { holdSld=false; jscolor.fireEvent(valueElement,'change'); } };
|
|
p.sldM.onmousedown = function(e) {
|
|
holdSld=true;
|
|
setSld(e);
|
|
dispatchImmediateChange();
|
|
};
|
|
|
|
// picker
|
|
var dims = getPickerDims(THIS);
|
|
p.box.style.width = dims[0] + 'px';
|
|
p.box.style.height = dims[1] + 'px';
|
|
|
|
// picker border
|
|
p.boxB.style.position = 'absolute';
|
|
p.boxB.style.clear = 'both';
|
|
p.boxB.style.left = x+'px';
|
|
p.boxB.style.top = y+'px';
|
|
p.boxB.style.zIndex = THIS.pickerZIndex;
|
|
p.boxB.style.border = THIS.pickerBorder+'px solid';
|
|
p.boxB.style.borderColor = THIS.pickerBorderColor;
|
|
p.boxB.style.background = THIS.pickerFaceColor;
|
|
|
|
// pad image
|
|
p.pad.style.width = jscolor.images.pad[0]+'px';
|
|
p.pad.style.height = jscolor.images.pad[1]+'px';
|
|
|
|
// pad border
|
|
p.padB.style.position = 'absolute';
|
|
p.padB.style.left = THIS.pickerFace+'px';
|
|
p.padB.style.top = THIS.pickerFace+'px';
|
|
p.padB.style.border = THIS.pickerInset+'px solid';
|
|
p.padB.style.borderColor = THIS.pickerInsetColor;
|
|
|
|
// pad mouse area
|
|
p.padM.style.position = 'absolute';
|
|
p.padM.style.left = '0';
|
|
p.padM.style.top = '0';
|
|
p.padM.style.width = THIS.pickerFace + 2*THIS.pickerInset + jscolor.images.pad[0] + jscolor.images.arrow[0] + 'px';
|
|
p.padM.style.height = p.box.style.height;
|
|
p.padM.style.cursor = 'crosshair';
|
|
|
|
// slider image
|
|
p.sld.style.overflow = 'hidden';
|
|
p.sld.style.width = jscolor.images.sld[0]+'px';
|
|
p.sld.style.height = jscolor.images.sld[1]+'px';
|
|
|
|
// slider border
|
|
p.sldB.style.display = THIS.slider ? 'block' : 'none';
|
|
p.sldB.style.position = 'absolute';
|
|
p.sldB.style.right = THIS.pickerFace+'px';
|
|
p.sldB.style.top = THIS.pickerFace+'px';
|
|
p.sldB.style.border = THIS.pickerInset+'px solid';
|
|
p.sldB.style.borderColor = THIS.pickerInsetColor;
|
|
|
|
// slider mouse area
|
|
p.sldM.style.display = THIS.slider ? 'block' : 'none';
|
|
p.sldM.style.position = 'absolute';
|
|
p.sldM.style.right = '0';
|
|
p.sldM.style.top = '0';
|
|
p.sldM.style.width = jscolor.images.sld[0] + jscolor.images.arrow[0] + THIS.pickerFace + 2*THIS.pickerInset + 'px';
|
|
p.sldM.style.height = p.box.style.height;
|
|
try {
|
|
p.sldM.style.cursor = 'pointer';
|
|
} catch(eOldIE) {
|
|
p.sldM.style.cursor = 'hand';
|
|
}
|
|
|
|
// "close" button
|
|
function setBtnBorder() {
|
|
var insetColors = THIS.pickerInsetColor.split(/\s+/);
|
|
var pickerOutsetColor = insetColors.length < 2 ? insetColors[0] : insetColors[1] + ' ' + insetColors[0] + ' ' + insetColors[0] + ' ' + insetColors[1];
|
|
p.btn.style.borderColor = pickerOutsetColor;
|
|
}
|
|
p.btn.style.display = THIS.pickerClosable ? 'block' : 'none';
|
|
p.btn.style.position = 'absolute';
|
|
p.btn.style.left = THIS.pickerFace + 'px';
|
|
p.btn.style.bottom = THIS.pickerFace + 'px';
|
|
p.btn.style.padding = '0 15px';
|
|
p.btn.style.height = '18px';
|
|
p.btn.style.border = THIS.pickerInset + 'px solid';
|
|
setBtnBorder();
|
|
p.btn.style.color = THIS.pickerButtonColor;
|
|
p.btn.style.font = '12px sans-serif';
|
|
p.btn.style.textAlign = 'center';
|
|
try {
|
|
p.btn.style.cursor = 'pointer';
|
|
} catch(eOldIE) {
|
|
p.btn.style.cursor = 'hand';
|
|
}
|
|
p.btn.onmousedown = function () {
|
|
THIS.hidePicker();
|
|
};
|
|
p.btnS.style.lineHeight = p.btn.style.height;
|
|
|
|
// load images in optimal order
|
|
switch(modeID) {
|
|
case 0: var padImg = 'hs.png'; break;
|
|
case 1: var padImg = 'hv.png'; break;
|
|
}
|
|
p.padM.style.backgroundImage = "url('"+jscolor.getDir()+"cross.gif')";
|
|
p.padM.style.backgroundRepeat = "no-repeat";
|
|
p.sldM.style.backgroundImage = "url('"+jscolor.getDir()+"arrow.gif')";
|
|
p.sldM.style.backgroundRepeat = "no-repeat";
|
|
p.pad.style.backgroundImage = "url('"+jscolor.getDir()+padImg+"')";
|
|
p.pad.style.backgroundRepeat = "no-repeat";
|
|
p.pad.style.backgroundPosition = "0 0";
|
|
|
|
// place pointers
|
|
redrawPad();
|
|
redrawSld();
|
|
|
|
jscolor.picker.owner = THIS;
|
|
document.getElementsByTagName('body')[0].appendChild(p.boxB);
|
|
}
|
|
|
|
|
|
function getPickerDims(o) {
|
|
var dims = [
|
|
2*o.pickerInset + 2*o.pickerFace + jscolor.images.pad[0] +
|
|
(o.slider ? 2*o.pickerInset + 2*jscolor.images.arrow[0] + jscolor.images.sld[0] : 0),
|
|
o.pickerClosable ?
|
|
4*o.pickerInset + 3*o.pickerFace + jscolor.images.pad[1] + o.pickerButtonHeight :
|
|
2*o.pickerInset + 2*o.pickerFace + jscolor.images.pad[1]
|
|
];
|
|
return dims;
|
|
}
|
|
|
|
|
|
function redrawPad() {
|
|
// redraw the pad pointer
|
|
switch(modeID) {
|
|
case 0: var yComponent = 1; break;
|
|
case 1: var yComponent = 2; break;
|
|
}
|
|
var x = Math.round((THIS.hsv[0]/6) * (jscolor.images.pad[0]-1));
|
|
var y = Math.round((1-THIS.hsv[yComponent]) * (jscolor.images.pad[1]-1));
|
|
jscolor.picker.padM.style.backgroundPosition =
|
|
(THIS.pickerFace+THIS.pickerInset+x - Math.floor(jscolor.images.cross[0]/2)) + 'px ' +
|
|
(THIS.pickerFace+THIS.pickerInset+y - Math.floor(jscolor.images.cross[1]/2)) + 'px';
|
|
|
|
// redraw the slider image
|
|
var seg = jscolor.picker.sld.childNodes;
|
|
|
|
switch(modeID) {
|
|
case 0:
|
|
var rgb = HSV_RGB(THIS.hsv[0], THIS.hsv[1], 1);
|
|
for(var i=0; i<seg.length; i+=1) {
|
|
seg[i].style.backgroundColor = 'rgb('+
|
|
(rgb[0]*(1-i/seg.length)*100)+'%,'+
|
|
(rgb[1]*(1-i/seg.length)*100)+'%,'+
|
|
(rgb[2]*(1-i/seg.length)*100)+'%)';
|
|
}
|
|
break;
|
|
case 1:
|
|
var rgb, s, c = [ THIS.hsv[2], 0, 0 ];
|
|
var i = Math.floor(THIS.hsv[0]);
|
|
var f = i%2 ? THIS.hsv[0]-i : 1-(THIS.hsv[0]-i);
|
|
switch(i) {
|
|
case 6:
|
|
case 0: rgb=[0,1,2]; break;
|
|
case 1: rgb=[1,0,2]; break;
|
|
case 2: rgb=[2,0,1]; break;
|
|
case 3: rgb=[2,1,0]; break;
|
|
case 4: rgb=[1,2,0]; break;
|
|
case 5: rgb=[0,2,1]; break;
|
|
}
|
|
for(var i=0; i<seg.length; i+=1) {
|
|
s = 1 - 1/(seg.length-1)*i;
|
|
c[1] = c[0] * (1 - s*f);
|
|
c[2] = c[0] * (1 - s);
|
|
seg[i].style.backgroundColor = 'rgb('+
|
|
(c[rgb[0]]*100)+'%,'+
|
|
(c[rgb[1]]*100)+'%,'+
|
|
(c[rgb[2]]*100)+'%)';
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
function redrawSld() {
|
|
// redraw the slider pointer
|
|
switch(modeID) {
|
|
case 0: var yComponent = 2; break;
|
|
case 1: var yComponent = 1; break;
|
|
}
|
|
var y = Math.round((1-THIS.hsv[yComponent]) * (jscolor.images.sld[1]-1));
|
|
jscolor.picker.sldM.style.backgroundPosition =
|
|
'0 ' + (THIS.pickerFace+THIS.pickerInset+y - Math.floor(jscolor.images.arrow[1]/2)) + 'px';
|
|
}
|
|
|
|
|
|
function isPickerOwner() {
|
|
return jscolor.picker && jscolor.picker.owner === THIS;
|
|
}
|
|
|
|
|
|
function blurTarget() {
|
|
if(valueElement === target) {
|
|
THIS.importColor();
|
|
}
|
|
if(THIS.pickerOnfocus) {
|
|
THIS.hidePicker();
|
|
}
|
|
}
|
|
|
|
|
|
function blurValue() {
|
|
if(valueElement !== target) {
|
|
THIS.importColor();
|
|
}
|
|
}
|
|
|
|
|
|
function setPad(e) {
|
|
var mpos = jscolor.getRelMousePos(e);
|
|
var x = mpos.x - THIS.pickerFace - THIS.pickerInset;
|
|
var y = mpos.y - THIS.pickerFace - THIS.pickerInset;
|
|
switch(modeID) {
|
|
case 0: THIS.fromHSV(x*(6/(jscolor.images.pad[0]-1)), 1 - y/(jscolor.images.pad[1]-1), null, leaveSld); break;
|
|
case 1: THIS.fromHSV(x*(6/(jscolor.images.pad[0]-1)), null, 1 - y/(jscolor.images.pad[1]-1), leaveSld); break;
|
|
}
|
|
}
|
|
|
|
|
|
function setSld(e) {
|
|
var mpos = jscolor.getRelMousePos(e);
|
|
var y = mpos.y - THIS.pickerFace - THIS.pickerInset;
|
|
switch(modeID) {
|
|
case 0: THIS.fromHSV(null, null, 1 - y/(jscolor.images.sld[1]-1), leavePad); break;
|
|
case 1: THIS.fromHSV(null, 1 - y/(jscolor.images.sld[1]-1), null, leavePad); break;
|
|
}
|
|
}
|
|
|
|
|
|
function dispatchImmediateChange() {
|
|
if (THIS.onImmediateChange) {
|
|
var callback;
|
|
if (typeof THIS.onImmediateChange === 'string') {
|
|
callback = new Function (THIS.onImmediateChange);
|
|
} else {
|
|
callback = THIS.onImmediateChange;
|
|
}
|
|
callback.call(THIS);
|
|
}
|
|
}
|
|
|
|
|
|
var THIS = this;
|
|
var modeID = this.pickerMode.toLowerCase()==='hvs' ? 1 : 0;
|
|
var abortBlur = false;
|
|
var
|
|
valueElement = jscolor.fetchElement(this.valueElement),
|
|
styleElement = jscolor.fetchElement(this.styleElement);
|
|
var
|
|
holdPad = false,
|
|
holdSld = false;
|
|
var
|
|
leaveValue = 1<<0,
|
|
leaveStyle = 1<<1,
|
|
leavePad = 1<<2,
|
|
leaveSld = 1<<3;
|
|
|
|
// target
|
|
jscolor.addEvent(target, 'focus', function() {
|
|
if(THIS.pickerOnfocus) { THIS.showPicker(); }
|
|
});
|
|
jscolor.addEvent(target, 'blur', function() {
|
|
if(!abortBlur) {
|
|
window.setTimeout(function(){ abortBlur || blurTarget(); abortBlur=false; }, 0);
|
|
} else {
|
|
abortBlur = false;
|
|
}
|
|
});
|
|
|
|
// valueElement
|
|
if(valueElement) {
|
|
var updateField = function() {
|
|
THIS.fromString(valueElement.value, leaveValue);
|
|
dispatchImmediateChange();
|
|
};
|
|
jscolor.addEvent(valueElement, 'keyup', updateField);
|
|
jscolor.addEvent(valueElement, 'input', updateField);
|
|
jscolor.addEvent(valueElement, 'blur', blurValue);
|
|
valueElement.setAttribute('autocomplete', 'off');
|
|
}
|
|
|
|
// styleElement
|
|
if(styleElement) {
|
|
styleElement.jscStyle = {
|
|
backgroundImage : styleElement.style.backgroundImage,
|
|
backgroundColor : styleElement.style.backgroundColor,
|
|
color : styleElement.style.color
|
|
};
|
|
}
|
|
|
|
// require images
|
|
switch(modeID) {
|
|
case 0: jscolor.requireImage('hs.png'); break;
|
|
case 1: jscolor.requireImage('hv.png'); break;
|
|
}
|
|
jscolor.requireImage('cross.gif');
|
|
jscolor.requireImage('arrow.gif');
|
|
|
|
this.importColor();
|
|
}
|
|
|
|
};
|
|
;(function () {
|
|
var ns = $.namespace("pskl.rendering");
|
|
|
|
ns.DrawingLoop = function () {
|
|
this.requestAnimationFrame = this.getRequestAnimationFrameShim_();
|
|
this.isRunning = false;
|
|
this.previousTime = 0;
|
|
this.callbacks = [];
|
|
};
|
|
|
|
ns.DrawingLoop.prototype.addCallback = function (callback, scope, args) {
|
|
var callbackObj = {
|
|
fn : callback,
|
|
scope : scope,
|
|
args : args
|
|
};
|
|
this.callbacks.push(callbackObj);
|
|
return callbackObj;
|
|
};
|
|
|
|
ns.DrawingLoop.prototype.removeCallback = function (callbackObj) {
|
|
var index = this.callbacks.indexOf(callbackObj);
|
|
if (index != -1) {
|
|
this.callbacks.splice(index, 1);
|
|
}
|
|
};
|
|
|
|
ns.DrawingLoop.prototype.start = function () {
|
|
this.isRunning = true;
|
|
this.loop_();
|
|
};
|
|
|
|
ns.DrawingLoop.prototype.loop_ = function () {
|
|
var currentTime = Date.now();
|
|
var delta = currentTime - this.previousTime;
|
|
this.executeCallbacks_(delta);
|
|
this.previousTime = currentTime;
|
|
this.requestAnimationFrame.call(window, this.loop_.bind(this));
|
|
};
|
|
|
|
ns.DrawingLoop.prototype.executeCallbacks_ = function (deltaTime) {
|
|
for (var i = 0 ; i < this.callbacks.length ; i++) {
|
|
var cb = this.callbacks[i];
|
|
cb.fn.call(cb.scope, deltaTime, cb.args);
|
|
}
|
|
};
|
|
|
|
ns.DrawingLoop.prototype.stop = function () {
|
|
this.isRunning = false;
|
|
};
|
|
|
|
ns.DrawingLoop.prototype.getRequestAnimationFrameShim_ = function () {
|
|
var requestAnimationFrame = window.requestAnimationFrame ||
|
|
window.mozRequestAnimationFrame ||
|
|
window.webkitRequestAnimationFrame ||
|
|
window.msRequestAnimationFrame ||
|
|
function (callback) { window.setTimeout(callback, 1000/60); };
|
|
|
|
return requestAnimationFrame;
|
|
};
|
|
})();;(function () {
|
|
var ns = $.namespace("pskl.model");
|
|
|
|
ns.Frame = function (width, height) {
|
|
if (width && height) {
|
|
this.width = width;
|
|
this.height = height;
|
|
|
|
this.pixels = ns.Frame.createEmptyPixelGrid_(width, height);
|
|
this.previousStates = [this.getPixels()];
|
|
this.stateIndex = 0;
|
|
} else {
|
|
throw 'Bad arguments in pskl.model.Frame constructor : ' + width + ', ' + height;
|
|
}
|
|
};
|
|
|
|
ns.Frame.fromPixelGrid = function (pixels) {
|
|
if (pixels.length && pixels[0].length) {
|
|
var w = pixels.length, h = pixels[0].length;
|
|
var frame = new pskl.model.Frame(w, h);
|
|
frame.setPixels(pixels);
|
|
return frame;
|
|
} else {
|
|
throw 'Bad arguments in pskl.model.Frame.fromPixelGrid : ' + pixels;
|
|
}
|
|
};
|
|
|
|
ns.Frame.createEmptyPixelGrid_ = function (width, height) {
|
|
var pixels = []; //new Array(width);
|
|
for (var columnIndex=0; columnIndex < width; columnIndex++) {
|
|
var columnArray = [];
|
|
for(var heightIndex = 0; heightIndex < height; heightIndex++) {
|
|
columnArray.push(Constants.TRANSPARENT_COLOR);
|
|
}
|
|
pixels[columnIndex] = columnArray;
|
|
}
|
|
return pixels;
|
|
};
|
|
|
|
ns.Frame.createEmptyFromFrame = function (frame) {
|
|
return new ns.Frame(frame.getWidth(), frame.getHeight());
|
|
};
|
|
|
|
ns.Frame.prototype.clone = function () {
|
|
var clone = new ns.Frame(this.width, this.height);
|
|
clone.setPixels(this.getPixels());
|
|
return clone;
|
|
};
|
|
|
|
/**
|
|
* Returns a copy of the pixels used by the frame
|
|
*/
|
|
ns.Frame.prototype.getPixels = function () {
|
|
return this.clonePixels_(this.pixels);
|
|
};
|
|
|
|
/**
|
|
* Copies the passed pixels into the frame.
|
|
*/
|
|
ns.Frame.prototype.setPixels = function (pixels) {
|
|
this.pixels = this.clonePixels_(pixels);
|
|
};
|
|
|
|
ns.Frame.prototype.clear = function () {
|
|
var pixels = ns.Frame.createEmptyPixelGrid_(this.getWidth(), this.getHeight());
|
|
this.setPixels(pixels);
|
|
};
|
|
|
|
/**
|
|
* Clone a set of pixels. Should be static utility method
|
|
* @private
|
|
*/
|
|
ns.Frame.prototype.clonePixels_ = function (pixels) {
|
|
var clonedPixels = [];
|
|
for (var col = 0 ; col < pixels.length ; col++) {
|
|
clonedPixels[col] = pixels[col].slice(0 , pixels[col].length);
|
|
}
|
|
return clonedPixels;
|
|
};
|
|
|
|
ns.Frame.prototype.serialize = function () {
|
|
return JSON.stringify(this.pixels);
|
|
};
|
|
|
|
ns.Frame.prototype.setPixel = function (col, row, color) {
|
|
this.pixels[col][row] = color;
|
|
};
|
|
|
|
ns.Frame.prototype.getPixel = function (col, row) {
|
|
return this.pixels[col][row];
|
|
};
|
|
|
|
ns.Frame.prototype.forEachPixel = function (callback) {
|
|
for (var col = 0 ; col < this.getWidth() ; col++) {
|
|
for (var row = 0 ; row < this.getHeight() ; row++) {
|
|
callback(this.getPixel(col, row), col, row);
|
|
}
|
|
}
|
|
};
|
|
|
|
ns.Frame.prototype.getWidth = function () {
|
|
return this.width;
|
|
};
|
|
|
|
ns.Frame.prototype.getHeight = function () {
|
|
return this.height;
|
|
};
|
|
|
|
ns.Frame.prototype.containsPixel = function (col, row) {
|
|
return col >= 0 && row >= 0 && col < this.pixels.length && row < this.pixels[0].length;
|
|
};
|
|
|
|
ns.Frame.prototype.saveState = function () {
|
|
// remove all states past current state
|
|
this.previousStates.length = this.stateIndex + 1;
|
|
// push new state
|
|
this.previousStates.push(this.getPixels());
|
|
// set the stateIndex to latest saved state
|
|
this.stateIndex = this.previousStates.length - 1;
|
|
};
|
|
|
|
ns.Frame.prototype.loadPreviousState = function () {
|
|
if (this.stateIndex > 0) {
|
|
this.stateIndex--;
|
|
this.setPixels(this.previousStates[this.stateIndex]);
|
|
}
|
|
};
|
|
|
|
ns.Frame.prototype.loadNextState = function () {
|
|
if (this.stateIndex < this.previousStates.length - 1) {
|
|
this.stateIndex++;
|
|
this.setPixels(this.previousStates[this.stateIndex]);
|
|
}
|
|
};
|
|
|
|
ns.Frame.prototype.isSameSize = function (otherFrame) {
|
|
return this.getHeight() == otherFrame.getHeight() && this.getWidth() == otherFrame.getWidth();
|
|
};
|
|
})();;(function () {
|
|
var ns = $.namespace('pskl.model');
|
|
|
|
ns.Layer = function (name) {
|
|
if (!name) {
|
|
throw 'Invalid arguments in Layer constructor : \'name\' is mandatory';
|
|
} else {
|
|
this.name = name;
|
|
this.frames = [];
|
|
}
|
|
};
|
|
|
|
ns.Layer.prototype.getName = function () {
|
|
return this.name;
|
|
};
|
|
|
|
ns.Layer.prototype.getFrames = function () {
|
|
return this.frames;
|
|
};
|
|
|
|
ns.Layer.prototype.getFrameAt = function (index) {
|
|
return this.frames[index];
|
|
};
|
|
|
|
ns.Layer.prototype.addFrame = function (frame) {
|
|
this.frames.push(frame);
|
|
};
|
|
|
|
ns.Layer.prototype.addFrameAt = function (frame, index) {
|
|
this.frames.splice(index, 0, frame);
|
|
};
|
|
|
|
ns.Layer.prototype.removeFrame = function (frame) {
|
|
var index = this.frames.indexOf(frame);
|
|
this.removeFrameAt(index);
|
|
};
|
|
|
|
ns.Layer.prototype.removeFrameAt = function (index) {
|
|
if (this.frames[index]) {
|
|
this.frames.splice(index, 1);
|
|
} else {
|
|
throw 'Invalid index in removeFrameAt : ' + index + ' (size : ' + this.length() + ')';
|
|
}
|
|
};
|
|
|
|
ns.Layer.prototype.moveFrame = function (fromIndex, toIndex) {
|
|
var frame = this.frames.splice(fromIndex, 1)[0];
|
|
this.frames.splice(toIndex, 0, frame);
|
|
};
|
|
|
|
ns.Layer.prototype.swapFramesAt = function (fromIndex, toIndex) {
|
|
var fromFrame = this.frames[fromIndex];
|
|
var toFrame = this.frames[toIndex];
|
|
if (fromFrame && toFrame) {
|
|
this.frames[toIndex] = fromFrame;
|
|
this.frames[fromIndex] = toFrame;
|
|
} else {
|
|
console.log('frames', this.frames);
|
|
console.log('fromIndex', fromIndex, 'toIndex', toIndex);
|
|
throw 'Frame not found in moveFrameAt';
|
|
}
|
|
};
|
|
|
|
ns.Layer.prototype.duplicateFrame = function (frame) {
|
|
var index = this.frames.indexOf(frame);
|
|
this.duplicateFrameAt();
|
|
};
|
|
|
|
ns.Layer.prototype.duplicateFrameAt = function (index) {
|
|
var frame = this.frames[index];
|
|
if (frame) {
|
|
var clone = frame.clone();
|
|
this.addFrameAt(clone, index);
|
|
} else {
|
|
throw 'Frame not found in duplicateFrameAt';
|
|
}
|
|
};
|
|
|
|
ns.Layer.prototype.length = function () {
|
|
return this.frames.length;
|
|
};
|
|
})();;(function () {
|
|
var ns = $.namespace('pskl.model');
|
|
|
|
/**
|
|
* @constructor
|
|
* @param {Number} width
|
|
* @param {Number} height
|
|
*/
|
|
ns.Piskel = function (width, height, fps) {
|
|
if (width && height && fps) {
|
|
/** @type {Array} */
|
|
this.layers = [];
|
|
|
|
/** @type {Number} */
|
|
this.fps = fps;
|
|
|
|
/** @type {Number} */
|
|
this.width = width;
|
|
|
|
/** @type {Number} */
|
|
this.height = height;
|
|
} else {
|
|
throw 'Missing arguments in Piskel constructor : ' + Array.prototype.join.call(arguments, ",");
|
|
}
|
|
};
|
|
|
|
ns.Piskel.prototype.getLayers = function () {
|
|
return this.layers;
|
|
};
|
|
|
|
ns.Piskel.prototype.getHeight = function () {
|
|
return this.height;
|
|
};
|
|
|
|
ns.Piskel.prototype.getWidth = function () {
|
|
return this.width;
|
|
};
|
|
|
|
ns.Piskel.prototype.getFps = function () {
|
|
return this.fps;
|
|
};
|
|
|
|
ns.Piskel.prototype.getLayers = function () {
|
|
return this.layers;
|
|
};
|
|
|
|
ns.Piskel.prototype.getLayerAt = function (index) {
|
|
return this.layers[index];
|
|
};
|
|
|
|
ns.Piskel.prototype.getLayersByName = function (name) {
|
|
return this.layers.filter(function (l) {
|
|
return l.getName() == name;
|
|
});
|
|
};
|
|
|
|
ns.Piskel.prototype.addLayer = function (layer) {
|
|
this.layers.push(layer);
|
|
};
|
|
|
|
ns.Piskel.prototype.moveLayerUp = function (layer) {
|
|
var index = this.layers.indexOf(layer);
|
|
if (index > -1 && index < this.layers.length-1) {
|
|
this.layers[index] = this.layers[index+1];
|
|
this.layers[index+1] = layer;
|
|
}
|
|
};
|
|
|
|
ns.Piskel.prototype.moveLayerDown = function (layer) {
|
|
var index = this.layers.indexOf(layer);
|
|
if (index > 0) {
|
|
this.layers[index] = this.layers[index-1];
|
|
this.layers[index-1] = layer;
|
|
}
|
|
};
|
|
|
|
ns.Piskel.prototype.removeLayer = function (layer) {
|
|
var index = this.layers.indexOf(layer);
|
|
if (index != -1) {
|
|
this.layers.splice(index, 1);
|
|
}
|
|
};
|
|
|
|
ns.Piskel.prototype.removeLayerAt = function (index) {
|
|
this.layers.splice(index, 1);
|
|
};
|
|
|
|
})();;(function () {
|
|
var ns = $.namespace("pskl.selection");
|
|
|
|
ns.SelectionManager = function (piskelController) {
|
|
|
|
this.piskelController = piskelController;
|
|
|
|
this.currentSelection = null;
|
|
};
|
|
|
|
ns.SelectionManager.prototype.init = function () {
|
|
$.subscribe(Events.SELECTION_CREATED, $.proxy(this.onSelectionCreated_, this));
|
|
$.subscribe(Events.SELECTION_DISMISSED, $.proxy(this.onSelectionDismissed_, this));
|
|
$.subscribe(Events.SELECTION_MOVE_REQUEST, $.proxy(this.onSelectionMoved_, this));
|
|
|
|
$.subscribe(Events.PASTE, $.proxy(this.onPaste_, this));
|
|
$.subscribe(Events.COPY, $.proxy(this.onCopy_, this));
|
|
$.subscribe(Events.CUT, $.proxy(this.onCut_, this));
|
|
|
|
$.subscribe(Events.TOOL_SELECTED, $.proxy(this.onToolSelected_, this));
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.SelectionManager.prototype.cleanSelection_ = function() {
|
|
if(this.currentSelection) {
|
|
this.currentSelection.reset();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.SelectionManager.prototype.onToolSelected_ = function(evt, tool) {
|
|
var isSelectionTool = tool instanceof pskl.drawingtools.BaseSelect;
|
|
if(!isSelectionTool) {
|
|
this.cleanSelection_();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.SelectionManager.prototype.onSelectionDismissed_ = function(evt) {
|
|
this.cleanSelection_();
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.SelectionManager.prototype.onCut_ = function(evt) {
|
|
if(this.currentSelection) {
|
|
// Put cut target into the selection:
|
|
this.currentSelection.fillSelectionFromFrame(this.piskelController.getCurrentFrame());
|
|
|
|
var pixels = this.currentSelection.pixels;
|
|
var currentFrame = this.piskelController.getCurrentFrame();
|
|
for(var i=0, l=pixels.length; i<l; i++) {
|
|
try {
|
|
currentFrame.setPixel(pixels[i].col, pixels[i].row, Constants.TRANSPARENT_COLOR);
|
|
}
|
|
catch(e) {
|
|
// Catchng out of frame's bound pixels without testing
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
throw "Bad state for CUT callback in SelectionManager";
|
|
}
|
|
};
|
|
|
|
ns.SelectionManager.prototype.onPaste_ = function(evt) {
|
|
if(this.currentSelection && this.currentSelection.hasPastedContent) {
|
|
var pixels = this.currentSelection.pixels;
|
|
var currentFrame = this.piskelController.getCurrentFrame();
|
|
for(var i=0, l=pixels.length; i<l; i++) {
|
|
try {
|
|
currentFrame.setPixel(
|
|
pixels[i].col, pixels[i].row,
|
|
pixels[i].copiedColor);
|
|
}
|
|
catch(e) {
|
|
// Catchng out of frame's bound pixels without testing
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.SelectionManager.prototype.onCopy_ = function(evt) {
|
|
if(this.currentSelection && this.piskelController.getCurrentFrame()) {
|
|
this.currentSelection.fillSelectionFromFrame(this.piskelController.getCurrentFrame());
|
|
}
|
|
else {
|
|
throw "Bad state for CUT callback in SelectionManager";
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.SelectionManager.prototype.onSelectionCreated_ = function(evt, selection) {
|
|
if(selection) {
|
|
this.currentSelection = selection;
|
|
} else {
|
|
throw "No selection set in SelectionManager";
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.SelectionManager.prototype.onSelectionMoved_ = function(evt, colDiff, rowDiff) {
|
|
if(this.currentSelection) {
|
|
this.currentSelection.move(colDiff, rowDiff);
|
|
}
|
|
else {
|
|
throw "Bad state: No currentSelection set when trying to move it in SelectionManager";
|
|
}
|
|
};
|
|
})();
|
|
;(function () {
|
|
var ns = $.namespace("pskl.selection");
|
|
|
|
ns.BaseSelection = function () {
|
|
this.reset();
|
|
};
|
|
|
|
ns.BaseSelection.prototype.reset = function () {
|
|
this.pixels = [];
|
|
this.hasPastedContent = false;
|
|
};
|
|
|
|
ns.BaseSelection.prototype.move = function (colDiff, rowDiff) {
|
|
var movedPixel, movedPixels = [];
|
|
|
|
for(var i=0, l=this.pixels.length; i<l; i++) {
|
|
movedPixel = this.pixels[i];
|
|
movedPixel.col += colDiff;
|
|
movedPixel.row += rowDiff;
|
|
movedPixels.push(movedPixel);
|
|
}
|
|
this.pixels = movedPixels;
|
|
};
|
|
|
|
ns.BaseSelection.prototype.fillSelectionFromFrame = function (targetFrame) {
|
|
var pixelWithCopiedColor;
|
|
for(var i=0, l=this.pixels.length; i<l; i++) {
|
|
pixelWithCopiedColor = this.pixels[i];
|
|
pixelWithCopiedColor.copiedColor =
|
|
targetFrame.getPixel(pixelWithCopiedColor.col, pixelWithCopiedColor.row);
|
|
}
|
|
this.hasPastedContent = true;
|
|
};
|
|
})();;(function () {
|
|
var ns = $.namespace("pskl.selection");
|
|
|
|
ns.RectangularSelection = function (x0, y0, x1, y1) {
|
|
this.pixels = pskl.PixelUtils.getRectanglePixels(x0, y0, x1, y1);
|
|
};
|
|
|
|
pskl.utils.inherit(ns.RectangularSelection, ns.BaseSelection);
|
|
})();;(function () {
|
|
var ns = $.namespace("pskl.selection");
|
|
|
|
ns.ShapeSelection = function (pixels) {
|
|
this.pixels = pixels;
|
|
};
|
|
|
|
pskl.utils.inherit(ns.ShapeSelection, ns.BaseSelection);
|
|
})();;(function () {
|
|
|
|
var ns = $.namespace("pskl.rendering");
|
|
ns.CanvasRenderer = function (frame, dpi) {
|
|
this.frame = frame;
|
|
this.dpi = dpi;
|
|
};
|
|
|
|
ns.CanvasRenderer.prototype.render = function (frame, dpi) {
|
|
var canvas = this.createCanvas_();
|
|
var context = canvas.getContext('2d');
|
|
for(var col = 0, width = this.frame.getWidth(); col < width; col++) {
|
|
for(var row = 0, height = this.frame.getHeight(); row < height; row++) {
|
|
var color = this.frame.getPixel(col, row);
|
|
this.renderPixel_(color, col, row, context);
|
|
}
|
|
}
|
|
|
|
return context;
|
|
};
|
|
|
|
ns.CanvasRenderer.prototype.renderPixel_ = function (color, col, row, context) {
|
|
if(color == Constants.TRANSPARENT_COLOR) {
|
|
color = "#FFF";
|
|
}
|
|
|
|
context.fillStyle = color;
|
|
context.fillRect(col * this.dpi, row * this.dpi, this.dpi, this.dpi);
|
|
};
|
|
|
|
ns.CanvasRenderer.prototype.createCanvas_ = function () {
|
|
var width = this.frame.getWidth() * this.dpi;
|
|
var height = this.frame.getHeight() * this.dpi;
|
|
return pskl.CanvasUtils.createCanvas(width, height);
|
|
};
|
|
})();;(function () {
|
|
var ns = $.namespace("pskl.rendering");
|
|
|
|
ns.FrameRenderer = function (container, renderingOptions, className) {
|
|
this.defaultRenderingOptions = {
|
|
'supportGridRendering' : false
|
|
};
|
|
renderingOptions = $.extend(true, {}, this.defaultRenderingOptions, renderingOptions);
|
|
|
|
if(container === undefined) {
|
|
throw 'Bad FrameRenderer initialization. <container> undefined.';
|
|
}
|
|
|
|
if(isNaN(renderingOptions.dpi)) {
|
|
throw 'Bad FrameRenderer initialization. <dpi> not well defined.';
|
|
}
|
|
|
|
this.container = container;
|
|
this.dpi = renderingOptions.dpi;
|
|
this.className = className;
|
|
this.canvas = null;
|
|
this.supportGridRendering = renderingOptions.supportGridRendering;
|
|
|
|
this.enableGrid(pskl.UserSettings.get(pskl.UserSettings.SHOW_GRID));
|
|
|
|
// Flag to know if the config was altered
|
|
this.canvasConfigDirty = true;
|
|
this.updateBackgroundClass_(pskl.UserSettings.get(pskl.UserSettings.CANVAS_BACKGROUND));
|
|
$.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this));
|
|
};
|
|
|
|
ns.FrameRenderer.prototype.updateDPI = function (newDPI) {
|
|
this.dpi = newDPI;
|
|
this.canvasConfigDirty = true;
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.FrameRenderer.prototype.onUserSettingsChange_ = function (evt, settingName, settingValue) {
|
|
|
|
if(settingName == pskl.UserSettings.SHOW_GRID) {
|
|
this.enableGrid(settingValue);
|
|
}
|
|
else if (settingName == pskl.UserSettings.CANVAS_BACKGROUND) {
|
|
this.updateBackgroundClass_(settingValue);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.FrameRenderer.prototype.updateBackgroundClass_ = function (newClass) {
|
|
var currentClass = this.container.data('current-background-class');
|
|
if (currentClass) {
|
|
this.container.removeClass(currentClass);
|
|
}
|
|
this.container.addClass(newClass);
|
|
this.container.data('current-background-class', newClass);
|
|
};
|
|
|
|
ns.FrameRenderer.prototype.enableGrid = function (flag) {
|
|
this.gridStrokeWidth = (flag && this.supportGridRendering) ? Constants.GRID_STROKE_WIDTH : 0;
|
|
this.canvasConfigDirty = true;
|
|
};
|
|
|
|
ns.FrameRenderer.prototype.render = function (frame) {
|
|
if (frame) {
|
|
this.clear();
|
|
var context = this.getCanvas_(frame).getContext('2d');
|
|
for(var col = 0, width = frame.getWidth(); col < width; col++) {
|
|
for(var row = 0, height = frame.getHeight(); row < height; row++) {
|
|
var color = frame.getPixel(col, row);
|
|
this.renderPixel_(color, col, row, context);
|
|
}
|
|
}
|
|
this.lastRenderedFrame = frame;
|
|
}
|
|
};
|
|
|
|
ns.FrameRenderer.prototype.renderPixel_ = function (color, col, row, context) {
|
|
if(color != Constants.TRANSPARENT_COLOR) {
|
|
context.fillStyle = color;
|
|
context.fillRect(this.getFramePos_(col), this.getFramePos_(row), this.dpi, this.dpi);
|
|
}
|
|
};
|
|
|
|
ns.FrameRenderer.prototype.clear = function () {
|
|
if (this.canvas) {
|
|
this.canvas.getContext("2d").clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Transform a screen pixel-based coordinate (relative to the top-left corner of the rendered
|
|
* frame) into a sprite coordinate in column and row.
|
|
* @public
|
|
*/
|
|
ns.FrameRenderer.prototype.convertPixelCoordinatesIntoSpriteCoordinate = function(coords) {
|
|
var cellSize = this.dpi + this.gridStrokeWidth;
|
|
return {
|
|
"col" : (coords.x - coords.x % cellSize) / cellSize,
|
|
"row" : (coords.y - coords.y % cellSize) / cellSize
|
|
};
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.FrameRenderer.prototype.getFramePos_ = function(index) {
|
|
return index * this.dpi + ((index - 1) * this.gridStrokeWidth);
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.FrameRenderer.prototype.drawGrid_ = function(canvas, width, height, col, row) {
|
|
var ctx = canvas.getContext("2d");
|
|
ctx.lineWidth = Constants.GRID_STROKE_WIDTH;
|
|
ctx.strokeStyle = Constants.GRID_STROKE_COLOR;
|
|
for(var c=1; c < col; c++) {
|
|
ctx.moveTo(this.getFramePos_(c), 0);
|
|
ctx.lineTo(this.getFramePos_(c), height);
|
|
ctx.stroke();
|
|
}
|
|
|
|
for(var r=1; r < row; r++) {
|
|
ctx.moveTo(0, this.getFramePos_(r));
|
|
ctx.lineTo(width, this.getFramePos_(r));
|
|
ctx.stroke();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.FrameRenderer.prototype.getCanvas_ = function (frame) {
|
|
if(this.canvasConfigDirty) {
|
|
$(this.canvas).remove();
|
|
|
|
var col = frame.getWidth(),
|
|
row = frame.getHeight();
|
|
|
|
var pixelWidth = col * this.dpi + this.gridStrokeWidth * (col - 1);
|
|
var pixelHeight = row * this.dpi + this.gridStrokeWidth * (row - 1);
|
|
var classes = ['canvas'];
|
|
if (this.className) {
|
|
classes = classes.concat(this.className.split(' '));
|
|
}
|
|
var canvas = pskl.CanvasUtils.createCanvas(pixelWidth, pixelHeight, classes);
|
|
|
|
this.container.append(canvas);
|
|
|
|
if(this.gridStrokeWidth > 0) {
|
|
this.drawGrid_(canvas, pixelWidth, pixelHeight, col, row);
|
|
}
|
|
|
|
this.canvas = canvas;
|
|
this.canvasConfigDirty = false;
|
|
}
|
|
return this.canvas;
|
|
};
|
|
})();;(function () {
|
|
|
|
var ns = $.namespace("pskl.rendering");
|
|
|
|
ns.SpritesheetRenderer = function (piskelController) {
|
|
this.piskelController = piskelController;
|
|
};
|
|
|
|
ns.SpritesheetRenderer.prototype.renderAsImageDataSpritesheetPNG = function () {
|
|
var canvas = this.createCanvas_();
|
|
for (var i = 0 ; i < this.piskelController.getFrameCount() ; i++) {
|
|
var frame = this.piskelController.getFrameAt(i);
|
|
this.drawFrameInCanvas_(frame, canvas, i * this.piskelController.getWidth(), 0);
|
|
}
|
|
return canvas.toDataURL("image/png");
|
|
};
|
|
|
|
/**
|
|
* TODO(juliandescottes): Mutualize with code already present in FrameRenderer
|
|
*/
|
|
ns.SpritesheetRenderer.prototype.drawFrameInCanvas_ = function (frame, canvas, offsetWidth, offsetHeight) {
|
|
var context = canvas.getContext('2d');
|
|
for(var col = 0, width = frame.getWidth(); col < width; col++) {
|
|
for(var row = 0, height = frame.getHeight(); row < height; row++) {
|
|
var color = frame.getPixel(col, row);
|
|
if(color != Constants.TRANSPARENT_COLOR) {
|
|
context.fillStyle = color;
|
|
context.fillRect(col + offsetWidth, row + offsetHeight, 1, 1);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
ns.SpritesheetRenderer.prototype.createCanvas_ = function () {
|
|
var frameCount = this.piskelController.getFrameCount();
|
|
if (frameCount > 0){
|
|
var width = frameCount * this.piskelController.getWidth();
|
|
var height = this.piskelController.getHeight();
|
|
return pskl.CanvasUtils.createCanvas(width, height);
|
|
} else {
|
|
throw "Cannot render empty Spritesheet";
|
|
}
|
|
};
|
|
})();;(function () {
|
|
var ns = $.namespace('pskl.controller');
|
|
|
|
ns.PiskelController = function (piskel) {
|
|
this.setPiskel(piskel);
|
|
};
|
|
|
|
ns.PiskelController.prototype.setPiskel = function (piskel) {
|
|
this.piskel = piskel;
|
|
this.currentLayerIndex = 0;
|
|
this.currentFrameIndex = 0;
|
|
|
|
this.layerIdCounter = 1;
|
|
|
|
$.publish(Events.FRAMESHEET_RESET);
|
|
$.publish(Events.FRAME_SIZE_CHANGED);
|
|
};
|
|
|
|
ns.PiskelController.prototype.getHeight = function () {
|
|
return this.piskel.getHeight();
|
|
};
|
|
|
|
ns.PiskelController.prototype.getWidth = function () {
|
|
return this.piskel.getWidth();
|
|
};
|
|
|
|
ns.PiskelController.prototype.getLayers = function () {
|
|
return this.piskel.getLayers();
|
|
};
|
|
|
|
ns.PiskelController.prototype.getCurrentLayer = function () {
|
|
return this.piskel.getLayerAt(this.currentLayerIndex);
|
|
};
|
|
|
|
ns.PiskelController.prototype.getCurrentFrame = function () {
|
|
var layer = this.getCurrentLayer();
|
|
return layer.getFrameAt(this.currentFrameIndex);
|
|
};
|
|
|
|
ns.PiskelController.prototype.getFrameAt = function (index) {
|
|
var frames = this.getLayers().map(function (l) {
|
|
return l.getFrameAt(index);
|
|
});
|
|
return pskl.utils.FrameUtils.merge(frames);
|
|
};
|
|
|
|
ns.PiskelController.prototype.hasFrameAt = function (index) {
|
|
return !!this.getCurrentLayer().getFrameAt(index);
|
|
};
|
|
|
|
// backward from framesheet
|
|
ns.PiskelController.prototype.getFrameByIndex =
|
|
ns.PiskelController.prototype.getMergedFrameAt;
|
|
|
|
ns.PiskelController.prototype.addEmptyFrame = function () {
|
|
var layers = this.getLayers();
|
|
layers.forEach(function (l) {
|
|
l.addFrame(this.createEmptyFrame_());
|
|
}.bind(this));
|
|
};
|
|
|
|
ns.PiskelController.prototype.createEmptyFrame_ = function () {
|
|
var w = this.piskel.getWidth(), h = this.piskel.getHeight();
|
|
return new pskl.model.Frame(w, h);
|
|
};
|
|
|
|
ns.PiskelController.prototype.removeFrameAt = function (index) {
|
|
var layers = this.getLayers();
|
|
layers.forEach(function (l) {
|
|
l.removeFrameAt(index);
|
|
});
|
|
// Current frame index is impacted if the removed frame was before the current frame
|
|
if (this.currentFrameIndex >= index) {
|
|
this.setCurrentFrameIndex(this.currentFrameIndex - 1);
|
|
}
|
|
|
|
$.publish(Events.FRAMESHEET_RESET);
|
|
};
|
|
|
|
ns.PiskelController.prototype.duplicateFrameAt = function (index) {
|
|
var layers = this.getLayers();
|
|
layers.forEach(function (l) {
|
|
l.duplicateFrameAt(index);
|
|
});
|
|
};
|
|
|
|
ns.PiskelController.prototype.moveFrame = function (fromIndex, toIndex) {
|
|
var layers = this.getLayers();
|
|
layers.forEach(function (l) {
|
|
l.moveFrame(fromIndex, toIndex);
|
|
});
|
|
};
|
|
|
|
ns.PiskelController.prototype.getFrameCount = function () {
|
|
var layer = this.piskel.getLayerAt(0);
|
|
return layer.length();
|
|
};
|
|
|
|
ns.PiskelController.prototype.setCurrentFrameIndex = function (index) {
|
|
this.currentFrameIndex = index;
|
|
$.publish(Events.FRAMESHEET_RESET);
|
|
};
|
|
|
|
ns.PiskelController.prototype.setCurrentLayerIndex = function (index) {
|
|
this.currentLayerIndex = index;
|
|
$.publish(Events.FRAMESHEET_RESET);
|
|
};
|
|
|
|
ns.PiskelController.prototype.selectLayer = function (layer) {
|
|
var index = this.getLayers().indexOf(layer);
|
|
if (index != -1) {
|
|
this.setCurrentLayerIndex(index);
|
|
}
|
|
};
|
|
|
|
ns.PiskelController.prototype.selectLayerByName = function (name) {
|
|
if (this.hasLayerForName_(name)) {
|
|
var layer = this.piskel.getLayersByName(name)[0];
|
|
this.selectLayer(layer);
|
|
}
|
|
};
|
|
|
|
ns.PiskelController.prototype.generateLayerName_ = function () {
|
|
var name = "Layer " + this.layerIdCounter;
|
|
while (this.hasLayerForName_(name)) {
|
|
this.layerIdCounter++;
|
|
name = "Layer " + this.layerIdCounter;
|
|
}
|
|
return name;
|
|
};
|
|
|
|
ns.PiskelController.prototype.createLayer = function (name) {
|
|
if (!name) {
|
|
name = this.generateLayerName_();
|
|
}
|
|
if (!this.hasLayerForName_(name)) {
|
|
var layer = new pskl.model.Layer(name);
|
|
for (var i = 0 ; i < this.getFrameCount() ; i++) {
|
|
layer.addFrame(this.createEmptyFrame_());
|
|
}
|
|
this.piskel.addLayer(layer);
|
|
this.setCurrentLayerIndex(this.piskel.getLayers().length - 1);
|
|
} else {
|
|
throw 'Layer name should be unique';
|
|
}
|
|
};
|
|
|
|
ns.PiskelController.prototype.hasLayerForName_ = function (name) {
|
|
return this.piskel.getLayersByName(name).length > 0;
|
|
};
|
|
|
|
ns.PiskelController.prototype.moveLayerUp = function () {
|
|
var layer = this.getCurrentLayer();
|
|
this.piskel.moveLayerUp(layer);
|
|
this.selectLayer(layer);
|
|
};
|
|
|
|
ns.PiskelController.prototype.moveLayerDown = function () {
|
|
var layer = this.getCurrentLayer();
|
|
this.piskel.moveLayerDown(layer);
|
|
this.selectLayer(layer);
|
|
};
|
|
|
|
ns.PiskelController.prototype.removeCurrentLayer = function () {
|
|
if (this.getLayers().length > 1) {
|
|
var layer = this.getCurrentLayer();
|
|
this.piskel.removeLayer(layer);
|
|
this.setCurrentLayerIndex(0);
|
|
}
|
|
};
|
|
|
|
ns.PiskelController.prototype.serialize = function () {
|
|
return pskl.utils.Serializer.serializePiskel(this.piskel);
|
|
};
|
|
|
|
ns.PiskelController.prototype.deserialize = function (json) {
|
|
try {
|
|
var piskel = pskl.utils.Serializer.deserializePiskel(json);
|
|
this.setPiskel(piskel);
|
|
} catch (e) {
|
|
console.error('Failed to deserialize');
|
|
console.error(e.stack);
|
|
}
|
|
};
|
|
})();;(function () {
|
|
var ns = $.namespace("pskl.controller");
|
|
ns.DrawingController = function (piskelController, container) {
|
|
/**
|
|
* @public
|
|
*/
|
|
this.piskelController = piskelController;
|
|
|
|
/**
|
|
* @public
|
|
*/
|
|
this.overlayFrame = pskl.model.Frame.createEmptyFromFrame(piskelController.getCurrentFrame());
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
this.container = container;
|
|
|
|
// TODO(vincz): Store user prefs in a localstorage string ?
|
|
var renderingOptions = {
|
|
"dpi": this.calculateDPI_(),
|
|
"supportGridRendering" : true
|
|
};
|
|
|
|
this.overlayRenderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, "canvas-overlay");
|
|
this.renderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, "drawing-canvas");
|
|
this.layersDownRenderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, "layers-canvas layers-down-canvas");
|
|
this.layersUpRenderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, "layers-canvas layers-up-canvas");
|
|
|
|
|
|
// State of drawing controller:
|
|
this.isClicked = false;
|
|
this.isRightClicked = false;
|
|
this.previousMousemoveTime = 0;
|
|
this.currentToolBehavior = null;
|
|
this.primaryColor = Constants.DEFAULT_PEN_COLOR;
|
|
this.secondaryColor = Constants.TRANSPARENT_COLOR;
|
|
};
|
|
|
|
ns.DrawingController.prototype.init = function () {
|
|
// this.render();
|
|
|
|
this.initMouseBehavior();
|
|
|
|
$.subscribe(Events.TOOL_SELECTED, $.proxy(function(evt, toolBehavior) {
|
|
console.log("Tool selected: ", toolBehavior);
|
|
this.currentToolBehavior = toolBehavior;
|
|
this.overlayFrame.clear();
|
|
}, this));
|
|
|
|
/**
|
|
* TODO(grosbouddha): Primary/secondary color state are kept in this general controller.
|
|
* Find a better place to store that. Perhaps PaletteController?
|
|
*/
|
|
$.subscribe(Events.PRIMARY_COLOR_SELECTED, $.proxy(function(evt, color) {
|
|
console.log("Primary color selected: ", color);
|
|
this.primaryColor = color;
|
|
$.publish(Events.PRIMARY_COLOR_UPDATED, [color]);
|
|
}, this));
|
|
$.subscribe(Events.SECONDARY_COLOR_SELECTED, $.proxy(function(evt, color) {
|
|
console.log("Secondary color selected: ", color);
|
|
this.secondaryColor = color;
|
|
$.publish(Events.SECONDARY_COLOR_UPDATED, [color]);
|
|
}, this));
|
|
|
|
$(window).resize($.proxy(this.startDPIUpdateTimer_, this));
|
|
|
|
$.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this));
|
|
$.subscribe(Events.FRAME_SIZE_CHANGED, $.proxy(this.updateDPI_, this));
|
|
|
|
this.updateDPI_();
|
|
};
|
|
|
|
ns.DrawingController.prototype.initMouseBehavior = function() {
|
|
var body = $('body');
|
|
this.container.mousedown($.proxy(this.onMousedown_, this));
|
|
this.container.mousemove($.proxy(this.onMousemove_, this));
|
|
body.mouseup($.proxy(this.onMouseup_, this));
|
|
|
|
// Deactivate right click:
|
|
body.contextmenu(this.onCanvasContextMenu_);
|
|
};
|
|
|
|
|
|
|
|
ns.DrawingController.prototype.startDPIUpdateTimer_ = function () {
|
|
if (this.dpiUpdateTimer) {
|
|
window.clearInterval(this.dpiUpdateTimer);
|
|
}
|
|
this.dpiUpdateTimer = window.setTimeout($.proxy(this.updateDPI_, this), 200);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.DrawingController.prototype.onUserSettingsChange_ = function (evt, settingsName, settingsValue) {
|
|
if(settingsName == pskl.UserSettings.SHOW_GRID) {
|
|
this.updateDPI_();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.DrawingController.prototype.onMousedown_ = function (event) {
|
|
this.isClicked = true;
|
|
|
|
if(event.button == 2) { // right click
|
|
this.isRightClicked = true;
|
|
$.publish(Events.CANVAS_RIGHT_CLICKED);
|
|
}
|
|
|
|
var coords = this.getSpriteCoordinates(event);
|
|
|
|
this.currentToolBehavior.applyToolAt(
|
|
coords.col, coords.row,
|
|
this.getCurrentColor_(),
|
|
this.piskelController.getCurrentFrame(),
|
|
this.overlayFrame,
|
|
this.wrapEvtInfo_(event)
|
|
);
|
|
|
|
$.publish(Events.LOCALSTORAGE_REQUEST);
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.DrawingController.prototype.onMousemove_ = function (event) {
|
|
var currentTime = new Date().getTime();
|
|
// Throttling of the mousemove event:
|
|
if ((currentTime - this.previousMousemoveTime) > 40 ) {
|
|
var coords = this.getSpriteCoordinates(event);
|
|
if (this.isClicked) {
|
|
|
|
this.currentToolBehavior.moveToolAt(
|
|
coords.col, coords.row,
|
|
this.getCurrentColor_(),
|
|
this.piskelController.getCurrentFrame(),
|
|
this.overlayFrame,
|
|
this.wrapEvtInfo_(event)
|
|
);
|
|
|
|
// TODO(vincz): Find a way to move that to the model instead of being at the interaction level.
|
|
// Eg when drawing, it may make sense to have it here. However for a non drawing tool,
|
|
// you don't need to draw anything when mousemoving and you request useless localStorage.
|
|
$.publish(Events.LOCALSTORAGE_REQUEST);
|
|
} else {
|
|
|
|
this.currentToolBehavior.moveUnactiveToolAt(
|
|
coords.col, coords.row,
|
|
this.getCurrentColor_(),
|
|
this.piskelController.getCurrentFrame(),
|
|
this.overlayFrame,
|
|
this.wrapEvtInfo_(event)
|
|
);
|
|
}
|
|
this.previousMousemoveTime = currentTime;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.DrawingController.prototype.onMouseup_ = function (event) {
|
|
if(this.isClicked || this.isRightClicked) {
|
|
// A mouse button was clicked on the drawing canvas before this mouseup event,
|
|
// the user was probably drawing on the canvas.
|
|
// Note: The mousemove movement (and the mouseup) may end up outside
|
|
// of the drawing canvas.
|
|
|
|
this.isClicked = false;
|
|
this.isRightClicked = false;
|
|
|
|
var coords = this.getSpriteCoordinates(event);
|
|
//console.log("mousemove: col: " + spriteCoordinate.col + " - row: " + spriteCoordinate.row);
|
|
this.currentToolBehavior.releaseToolAt(
|
|
coords.col, coords.row,
|
|
this.getCurrentColor_(),
|
|
this.piskelController.getCurrentFrame(),
|
|
this.overlayFrame,
|
|
this.wrapEvtInfo_(event)
|
|
);
|
|
|
|
$.publish(Events.TOOL_RELEASED);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.DrawingController.prototype.wrapEvtInfo_ = function (event) {
|
|
var evtInfo = {};
|
|
if (event.button === 0) {
|
|
evtInfo.button = Constants.LEFT_BUTTON;
|
|
} else if (event.button == 2) {
|
|
evtInfo.button = Constants.RIGHT_BUTTON;
|
|
}
|
|
return evtInfo;
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.DrawingController.prototype.getRelativeCoordinates = function (clientX, clientY) {
|
|
var canvasPageOffset = this.container.offset();
|
|
return {
|
|
x : clientX - canvasPageOffset.left,
|
|
y : clientY - canvasPageOffset.top
|
|
};
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.DrawingController.prototype.getSpriteCoordinates = function(event) {
|
|
var coords = this.getRelativeCoordinates(event.clientX, event.clientY);
|
|
return this.renderer.convertPixelCoordinatesIntoSpriteCoordinate(coords);
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.DrawingController.prototype.getCurrentColor_ = function () {
|
|
if(this.isRightClicked) {
|
|
return this.secondaryColor;
|
|
} else {
|
|
return this.primaryColor;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.DrawingController.prototype.onCanvasContextMenu_ = function (event) {
|
|
if ($(event.target).closest('#drawing-canvas-container').length) {
|
|
// Deactivate right click on drawing canvas only.
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
event.cancelBubble = true;
|
|
return false;
|
|
}
|
|
};
|
|
|
|
ns.DrawingController.prototype.render = function () {
|
|
this.renderLayers();
|
|
this.renderFrame();
|
|
this.renderOverlay();
|
|
};
|
|
|
|
ns.DrawingController.prototype.renderFrame = function () {
|
|
var frame = this.piskelController.getCurrentFrame();
|
|
var serializedFrame = frame.serialize();
|
|
if (this.serializedFrame != serializedFrame) {
|
|
if (!frame.isSameSize(this.overlayFrame)) {
|
|
this.overlayFrame = pskl.model.Frame.createEmptyFromFrame(frame);
|
|
}
|
|
this.serializedFrame = serializedFrame;
|
|
this.renderer.render(frame);
|
|
}
|
|
};
|
|
|
|
ns.DrawingController.prototype.renderOverlay = function () {
|
|
var serializedOverlay = this.overlayFrame.serialize();
|
|
if (this.serializedOverlay != serializedOverlay) {
|
|
this.serializedOverlay = serializedOverlay;
|
|
this.overlayRenderer.render(this.overlayFrame);
|
|
}
|
|
};
|
|
|
|
ns.DrawingController.prototype.renderLayers = function () {
|
|
var layers = this.piskelController.getLayers();
|
|
var currentFrameIndex = this.piskelController.currentFrameIndex;
|
|
var currentLayerIndex = this.piskelController.currentLayerIndex;
|
|
|
|
var serialized = [currentFrameIndex, this.piskelController.currentLayerIndex, layers.length].join("-");
|
|
if (this.serializedLayerFrame != serialized) {
|
|
this.layersUpRenderer.clear();
|
|
this.layersDownRenderer.clear();
|
|
|
|
var downLayers = layers.slice(0, currentLayerIndex);
|
|
var downFrame = this.getFrameForLayersAt_(currentFrameIndex, downLayers);
|
|
this.layersDownRenderer.render(downFrame);
|
|
|
|
if (currentLayerIndex + 1 < layers.length) {
|
|
var upLayers = layers.slice(currentLayerIndex + 1, layers.length);
|
|
var upFrame = this.getFrameForLayersAt_(currentFrameIndex, upLayers);
|
|
this.layersUpRenderer.render(upFrame);
|
|
}
|
|
|
|
this.serializedLayerFrame = serialized;
|
|
}
|
|
};
|
|
|
|
ns.DrawingController.prototype.getFrameForLayersAt_ = function (frameIndex, layers) {
|
|
var frames = layers.map(function (l) {
|
|
return l.getFrameAt(frameIndex);
|
|
});
|
|
return pskl.utils.FrameUtils.merge(frames);
|
|
};
|
|
|
|
ns.DrawingController.prototype.forceRendering_ = function () {
|
|
this.serializedFrame = this.serializedOverlay = null;
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.DrawingController.prototype.calculateDPI_ = function() {
|
|
var availableViewportHeight = $('#main-wrapper').height(),
|
|
leftSectionWidth = $('.left-column').outerWidth(true),
|
|
rightSectionWidth = $('.right-column').outerWidth(true),
|
|
availableViewportWidth = $('#main-wrapper').width() - leftSectionWidth - rightSectionWidth,
|
|
framePixelHeight = this.piskelController.getCurrentFrame().getHeight(),
|
|
framePixelWidth = this.piskelController.getCurrentFrame().getWidth();
|
|
|
|
if (pskl.UserSettings.get(pskl.UserSettings.SHOW_GRID)) {
|
|
availableViewportWidth = availableViewportWidth - (framePixelWidth * Constants.GRID_STROKE_WIDTH);
|
|
availableViewportHeight = availableViewportHeight - (framePixelHeight * Constants.GRID_STROKE_WIDTH);
|
|
}
|
|
|
|
var dpi = pskl.PixelUtils.calculateDPI(
|
|
availableViewportHeight, availableViewportWidth, framePixelHeight, framePixelWidth);
|
|
|
|
return dpi;
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.DrawingController.prototype.updateDPI_ = function() {
|
|
var dpi = this.calculateDPI_();
|
|
this.overlayRenderer.updateDPI(dpi);
|
|
this.renderer.updateDPI(dpi);
|
|
|
|
this.layersUpRenderer.updateDPI(dpi);
|
|
this.layersDownRenderer.updateDPI(dpi);
|
|
this.serializedLayerFrame ="";
|
|
|
|
var currentFrameHeight = this.piskelController.getCurrentFrame().getHeight();
|
|
var canvasHeight = currentFrameHeight * dpi;
|
|
if (pskl.UserSettings.get(pskl.UserSettings.SHOW_GRID)) {
|
|
canvasHeight += Constants.GRID_STROKE_WIDTH * currentFrameHeight;
|
|
}
|
|
|
|
var verticalGapInPixel = Math.floor(($('#main-wrapper').height() - canvasHeight) / 2);
|
|
$('#column-wrapper').css({
|
|
'top': verticalGapInPixel + 'px',
|
|
'height': canvasHeight + 'px'
|
|
});
|
|
|
|
this.forceRendering_();
|
|
};
|
|
})();;(function () {
|
|
var ns = $.namespace("pskl.controller");
|
|
ns.PreviewFilmController = function (piskelController, container, dpi) {
|
|
|
|
this.piskelController = piskelController;
|
|
this.container = container;
|
|
this.dpi = this.calculateDPI_();
|
|
|
|
this.redrawFlag = true;
|
|
};
|
|
|
|
ns.PreviewFilmController.prototype.init = function() {
|
|
$.subscribe(Events.TOOL_RELEASED, this.flagForRedraw_.bind(this));
|
|
$.subscribe(Events.FRAMESHEET_RESET, this.flagForRedraw_.bind(this));
|
|
$.subscribe(Events.FRAMESHEET_RESET, this.refreshDPI_.bind(this));
|
|
|
|
$('#preview-list-scroller').scroll(this.updateScrollerOverflows.bind(this));
|
|
this.updateScrollerOverflows();
|
|
};
|
|
|
|
ns.PreviewFilmController.prototype.addFrame = function () {
|
|
this.piskelController.addEmptyFrame();
|
|
this.piskelController.setCurrentFrameIndex(this.piskelController.getFrameCount() - 1);
|
|
this.updateScrollerOverflows();
|
|
};
|
|
|
|
ns.PreviewFilmController.prototype.flagForRedraw_ = function () {
|
|
this.redrawFlag = true;
|
|
};
|
|
|
|
ns.PreviewFilmController.prototype.refreshDPI_ = function () {
|
|
this.dpi = this.calculateDPI_();
|
|
};
|
|
|
|
ns.PreviewFilmController.prototype.render = function () {
|
|
if (this.redrawFlag) {
|
|
// TODO(vincz): Full redraw on any drawing modification, optimize.
|
|
this.createPreviews_();
|
|
this.redrawFlag = false;
|
|
}
|
|
};
|
|
|
|
ns.PreviewFilmController.prototype.updateScrollerOverflows = function () {
|
|
var scroller = $('#preview-list-scroller');
|
|
var scrollerHeight = scroller.height();
|
|
var scrollTop = scroller.scrollTop();
|
|
var scrollerContentHeight = $('#preview-list').height();
|
|
var treshold = $('.top-overflow').height();
|
|
var overflowTop = false,
|
|
overflowBottom = false;
|
|
if (scrollerHeight < scrollerContentHeight) {
|
|
if (scrollTop > treshold) {
|
|
overflowTop = true;
|
|
}
|
|
var scrollBottom = (scrollerContentHeight - scrollTop) - scrollerHeight;
|
|
if (scrollBottom > treshold) {
|
|
overflowBottom = true;
|
|
}
|
|
}
|
|
var wrapper = $('#preview-list-wrapper');
|
|
wrapper.toggleClass('top-overflow-visible', overflowTop);
|
|
wrapper.toggleClass('bottom-overflow-visible', overflowBottom);
|
|
};
|
|
|
|
ns.PreviewFilmController.prototype.createPreviews_ = function () {
|
|
|
|
this.container.html("");
|
|
// Manually remove tooltips since mouseout events were shortcut by the DOM refresh:
|
|
$(".tooltip").remove();
|
|
|
|
var frameCount = this.piskelController.getFrameCount();
|
|
|
|
for (var i = 0, l = frameCount; i < l ; i++) {
|
|
this.container.append(this.createPreviewTile_(i));
|
|
}
|
|
// Append 'new empty frame' button
|
|
var newFrameButton = document.createElement("div");
|
|
newFrameButton.id = "add-frame-action";
|
|
newFrameButton.className = "add-frame-action";
|
|
newFrameButton.innerHTML = "<p class='label'>Add new frame</p>";
|
|
this.container.append(newFrameButton);
|
|
|
|
$(newFrameButton).click(this.addFrame.bind(this));
|
|
|
|
var needDragndropBehavior = (frameCount > 1);
|
|
if(needDragndropBehavior) {
|
|
this.initDragndropBehavior_();
|
|
}
|
|
this.updateScrollerOverflows();
|
|
};
|
|
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.PreviewFilmController.prototype.initDragndropBehavior_ = function () {
|
|
|
|
$("#preview-list").sortable({
|
|
placeholder: "preview-tile-drop-proxy",
|
|
update: $.proxy(this.onUpdate_, this),
|
|
items: ".preview-tile"
|
|
});
|
|
$("#preview-list").disableSelection();
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.PreviewFilmController.prototype.onUpdate_ = function( event, ui ) {
|
|
var originFrameId = parseInt(ui.item.data("tile-number"), 10);
|
|
var targetInsertionId = $('.preview-tile').index(ui.item);
|
|
|
|
this.piskelController.moveFrame(originFrameId, targetInsertionId);
|
|
this.piskelController.setCurrentFrameIndex(targetInsertionId);
|
|
|
|
// TODO(grosbouddha): move localstorage request to the model layer?
|
|
$.publish(Events.LOCALSTORAGE_REQUEST);
|
|
};
|
|
|
|
|
|
/**
|
|
* @private
|
|
* TODO(vincz): clean this giant rendering function & remove listeners.
|
|
*/
|
|
ns.PreviewFilmController.prototype.createPreviewTile_ = function(tileNumber) {
|
|
var currentFrame = this.piskelController.getCurrentLayer().getFrameAt(tileNumber);
|
|
|
|
var previewTileRoot = document.createElement("li");
|
|
var classname = "preview-tile";
|
|
previewTileRoot.setAttribute("data-tile-number", tileNumber);
|
|
|
|
if (this.piskelController.getCurrentFrame() == currentFrame) {
|
|
classname += " selected";
|
|
}
|
|
previewTileRoot.className = classname;
|
|
|
|
var canvasContainer = document.createElement("div");
|
|
canvasContainer.className = "canvas-container";
|
|
|
|
var canvasBackground = document.createElement("div");
|
|
canvasBackground.className = "canvas-background";
|
|
canvasContainer.appendChild(canvasBackground);
|
|
|
|
previewTileRoot.addEventListener('click', this.onPreviewClick_.bind(this, tileNumber));
|
|
|
|
var cloneFrameButton = document.createElement("button");
|
|
cloneFrameButton.setAttribute('rel', 'tooltip');
|
|
cloneFrameButton.setAttribute('data-placement', 'right');
|
|
cloneFrameButton.setAttribute('title', 'Duplicate this frame');
|
|
cloneFrameButton.className = "tile-overlay duplicate-frame-action";
|
|
previewTileRoot.appendChild(cloneFrameButton);
|
|
cloneFrameButton.addEventListener('click', this.onAddButtonClick_.bind(this, tileNumber));
|
|
|
|
// TODO(vincz): Eventually optimize this part by not recreating a FrameRenderer. Note that the real optim
|
|
// is to make this update function (#createPreviewTile) less aggressive.
|
|
var renderingOptions = {"dpi": this.dpi };
|
|
var currentFrameRenderer = new pskl.rendering.FrameRenderer($(canvasContainer), renderingOptions, "tile-view");
|
|
currentFrameRenderer.render(currentFrame);
|
|
|
|
previewTileRoot.appendChild(canvasContainer);
|
|
|
|
if(tileNumber > 0 || this.piskelController.getFrameCount() > 1) {
|
|
// Add 'remove frame' button.
|
|
var deleteButton = document.createElement("button");
|
|
deleteButton.setAttribute('rel', 'tooltip');
|
|
deleteButton.setAttribute('data-placement', 'right');
|
|
deleteButton.setAttribute('title', 'Delete this frame');
|
|
deleteButton.className = "tile-overlay delete-frame-action";
|
|
deleteButton.addEventListener('click', this.onDeleteButtonClick_.bind(this, tileNumber));
|
|
previewTileRoot.appendChild(deleteButton);
|
|
|
|
// Add 'dragndrop handle'.
|
|
var dndHandle = document.createElement("div");
|
|
dndHandle.className = "tile-overlay dnd-action";
|
|
previewTileRoot.appendChild(dndHandle);
|
|
}
|
|
var tileCount = document.createElement("div");
|
|
tileCount.className = "tile-overlay tile-count";
|
|
tileCount.innerHTML = tileNumber;
|
|
previewTileRoot.appendChild(tileCount);
|
|
|
|
|
|
return previewTileRoot;
|
|
};
|
|
|
|
ns.PreviewFilmController.prototype.onPreviewClick_ = function (index, evt) {
|
|
// has not class tile-action:
|
|
if(!evt.target.classList.contains('tile-overlay')) {
|
|
this.piskelController.setCurrentFrameIndex(index);
|
|
}
|
|
};
|
|
|
|
ns.PreviewFilmController.prototype.onDeleteButtonClick_ = function (index, evt) {
|
|
this.piskelController.removeFrameAt(index);
|
|
$.publish(Events.LOCALSTORAGE_REQUEST); // Should come from model
|
|
this.updateScrollerOverflows();
|
|
};
|
|
|
|
ns.PreviewFilmController.prototype.onAddButtonClick_ = function (index, evt) {
|
|
this.piskelController.duplicateFrameAt(index);
|
|
$.publish(Events.LOCALSTORAGE_REQUEST); // Should come from model
|
|
this.piskelController.setCurrentFrameIndex(index + 1);
|
|
this.updateScrollerOverflows();
|
|
};
|
|
|
|
/**
|
|
* Calculate the preview DPI depending on the piskel size
|
|
*/
|
|
ns.PreviewFilmController.prototype.calculateDPI_ = function () {
|
|
var curFrame = this.piskelController.getCurrentFrame(),
|
|
frameHeight = curFrame.getHeight(),
|
|
frameWidth = curFrame.getWidth(),
|
|
maxFrameDim = Math.max(frameWidth, frameHeight);
|
|
|
|
var previewHeight = Constants.PREVIEW_FILM_SIZE * frameHeight / maxFrameDim;
|
|
var previewWidth = Constants.PREVIEW_FILM_SIZE * frameWidth / maxFrameDim;
|
|
|
|
return pskl.PixelUtils.calculateDPI(previewHeight, previewWidth, frameHeight, frameWidth) || 1;
|
|
};
|
|
})();;(function () {
|
|
var ns = $.namespace('pskl.controller');
|
|
|
|
ns.LayersController = function (piskelController) {
|
|
this.piskelController = piskelController;
|
|
};
|
|
|
|
ns.LayersController.prototype.init = function () {
|
|
this.layerItemTemplate_ = pskl.utils.Template.get('layer-item-template');
|
|
this.rootEl = document.querySelectorAll('.layers-container')[0];
|
|
this.layersListEl = document.querySelectorAll('.layers-list')[0];
|
|
|
|
this.rootEl.addEventListener('click', this.onClick_.bind(this));
|
|
|
|
$.subscribe(Events.FRAMESHEET_RESET, this.renderLayerList_.bind(this));
|
|
|
|
this.renderLayerList_();
|
|
};
|
|
|
|
ns.LayersController.prototype.renderLayerList_ = function () {
|
|
this.layersListEl.innerHTML = '';
|
|
var layers = this.piskelController.getLayers();
|
|
layers.forEach(this.addLayerItem.bind(this));
|
|
};
|
|
|
|
ns.LayersController.prototype.addLayerItem = function (layer) {
|
|
var layerItemHtml = pskl.utils.Template.replace(this.layerItemTemplate_, {
|
|
layername : layer.getName()
|
|
});
|
|
var layerItem = pskl.utils.Template.createFromHTML(layerItemHtml);
|
|
if (this.piskelController.getCurrentLayer() === layer) {
|
|
layerItem.classList.add('current-layer-item');
|
|
}
|
|
this.layersListEl.insertBefore(layerItem, this.layersListEl.firstChild);
|
|
};
|
|
|
|
ns.LayersController.prototype.onClick_ = function (evt) {
|
|
var el = evt.target || evt.srcElement;
|
|
if (el.nodeName == 'BUTTON') {
|
|
this.onButtonClick_(el);
|
|
} else if (el.nodeName == 'LI') {
|
|
var layerName = el.getAttribute('data-layer-name');
|
|
this.piskelController.selectLayerByName(layerName);
|
|
}
|
|
};
|
|
|
|
ns.LayersController.prototype.onButtonClick_ = function (button) {
|
|
var action = button.getAttribute('data-action');
|
|
if (action == 'up') {
|
|
this.piskelController.moveLayerUp();
|
|
} else if (action == 'down') {
|
|
this.piskelController.moveLayerDown();
|
|
} else if (action == 'add') {
|
|
this.piskelController.createLayer();
|
|
} else if (action == 'delete') {
|
|
this.piskelController.removeCurrentLayer();
|
|
}
|
|
};
|
|
})();;(function () {
|
|
var ns = $.namespace("pskl.controller");
|
|
ns.AnimatedPreviewController = function (piskelController, container, dpi) {
|
|
this.piskelController = piskelController;
|
|
this.container = container;
|
|
|
|
this.elapsedTime = 0;
|
|
this.currentIndex = 0;
|
|
|
|
this.fps = parseInt($("#preview-fps")[0].value, 10);
|
|
|
|
var renderingOptions = {
|
|
"dpi": this.calculateDPI_()
|
|
};
|
|
this.renderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions);
|
|
|
|
$.subscribe(Events.FRAME_SIZE_CHANGED, this.updateDPI_.bind(this));
|
|
};
|
|
|
|
ns.AnimatedPreviewController.prototype.init = function () {
|
|
// the oninput event won't work on IE10 unfortunately, but at least will provide a
|
|
// consistent behavior across all other browsers that support the input type range
|
|
// see https://bugzilla.mozilla.org/show_bug.cgi?id=853670
|
|
$("#preview-fps")[0].addEventListener('change', this.onFPSSliderChange.bind(this));
|
|
};
|
|
|
|
ns.AnimatedPreviewController.prototype.onFPSSliderChange = function (evt) {
|
|
this.setFPS(parseInt($("#preview-fps")[0].value, 10));
|
|
};
|
|
|
|
ns.AnimatedPreviewController.prototype.setFPS = function (fps) {
|
|
this.fps = fps;
|
|
$("#preview-fps").val(this.fps);
|
|
$("#display-fps").html(this.fps + " FPS");
|
|
};
|
|
|
|
ns.AnimatedPreviewController.prototype.render = function (delta) {
|
|
this.elapsedTime += delta;
|
|
var index = Math.floor(this.elapsedTime / (1000/this.fps));
|
|
if (index != this.currentIndex) {
|
|
this.currentIndex = index;
|
|
if (!this.piskelController.hasFrameAt(this.currentIndex)) {
|
|
this.currentIndex = 0;
|
|
this.elapsedTime = 0;
|
|
}
|
|
this.renderer.render(this.piskelController.getFrameAt(this.currentIndex));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Calculate the preview DPI depending on the framesheet size
|
|
*/
|
|
ns.AnimatedPreviewController.prototype.calculateDPI_ = function () {
|
|
var previewSize = 200,
|
|
framePixelHeight = this.piskelController.getCurrentFrame().getHeight(),
|
|
framePixelWidth = this.piskelController.getCurrentFrame().getWidth();
|
|
// TODO (julz) : should have a utility to get a Size from framesheet easily (what about empty framesheets though ?)
|
|
|
|
//return pskl.PixelUtils.calculateDPIForContainer($(".preview-container"), framePixelHeight, framePixelWidth);
|
|
return pskl.PixelUtils.calculateDPI(previewSize, previewSize, framePixelHeight, framePixelWidth);
|
|
};
|
|
|
|
ns.AnimatedPreviewController.prototype.updateDPI_ = function () {
|
|
this.dpi = this.calculateDPI_();
|
|
this.renderer.updateDPI(this.dpi);
|
|
};
|
|
})();;(function () {
|
|
var ns = $.namespace("pskl.controller");
|
|
|
|
|
|
ns.ToolController = function () {
|
|
|
|
this.toolInstances = {
|
|
"simplePen" : new pskl.drawingtools.SimplePen(),
|
|
"verticalMirrorPen" : new pskl.drawingtools.VerticalMirrorPen(),
|
|
"eraser" : new pskl.drawingtools.Eraser(),
|
|
"paintBucket" : new pskl.drawingtools.PaintBucket(),
|
|
"stroke" : new pskl.drawingtools.Stroke(),
|
|
"rectangle" : new pskl.drawingtools.Rectangle(),
|
|
"circle" : new pskl.drawingtools.Circle(),
|
|
"move" : new pskl.drawingtools.Move(),
|
|
"rectangleSelect" : new pskl.drawingtools.RectangleSelect(),
|
|
"shapeSelect" : new pskl.drawingtools.ShapeSelect(),
|
|
"colorPicker" : new pskl.drawingtools.ColorPicker()
|
|
};
|
|
|
|
this.currentSelectedTool = this.toolInstances.simplePen;
|
|
this.previousSelectedTool = this.toolInstances.simplePen;
|
|
};
|
|
|
|
/**
|
|
* @public
|
|
*/
|
|
ns.ToolController.prototype.init = function() {
|
|
this.createToolMarkup_();
|
|
|
|
// Initialize tool:
|
|
// Set SimplePen as default selected tool:
|
|
this.selectTool_(this.toolInstances.simplePen);
|
|
// Activate listener on tool panel:
|
|
$("#tool-section").click($.proxy(this.onToolIconClicked_, this));
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.ToolController.prototype.activateToolOnStage_ = function(tool) {
|
|
var stage = $("body");
|
|
var previousSelectedToolClass = stage.data("selected-tool-class");
|
|
if(previousSelectedToolClass) {
|
|
stage.removeClass(previousSelectedToolClass);
|
|
}
|
|
stage.addClass(tool.toolId);
|
|
stage.data("selected-tool-class", tool.toolId);
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.ToolController.prototype.selectTool_ = function(tool) {
|
|
console.log("Selecting Tool:" , this.currentSelectedTool);
|
|
this.currentSelectedTool = tool;
|
|
this.activateToolOnStage_(this.currentSelectedTool);
|
|
$.publish(Events.TOOL_SELECTED, [tool]);
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.ToolController.prototype.onToolIconClicked_ = function(evt) {
|
|
var target = $(evt.target);
|
|
var clickedTool = target.closest(".tool-icon");
|
|
|
|
if(clickedTool.length) {
|
|
var toolId = clickedTool.data().toolId;
|
|
var tool = this.getToolById_(toolId);
|
|
if (tool) {
|
|
this.selectTool_(tool);
|
|
|
|
// Show tool as selected:
|
|
$('#tool-section .tool-icon.selected').removeClass('selected');
|
|
clickedTool.addClass('selected');
|
|
}
|
|
}
|
|
};
|
|
|
|
ns.ToolController.prototype.getToolById_ = function (toolId) {
|
|
for(var key in this.toolInstances) {
|
|
if (this.toolInstances[key].toolId == toolId) {
|
|
return this.toolInstances[key];
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.ToolController.prototype.createToolMarkup_ = function() {
|
|
var currentTool, toolMarkup = '', extraClass;
|
|
// TODO(vincz): Tools rendering order is not enforced by the data stucture (this.toolInstances), fix that.
|
|
for (var toolKey in this.toolInstances) {
|
|
currentTool = this.toolInstances[toolKey];
|
|
extraClass = currentTool.toolId;
|
|
if (this.currentSelectedTool == currentTool) {
|
|
extraClass = extraClass + " selected";
|
|
}
|
|
toolMarkup += '<li rel="tooltip" data-placement="right" class="tool-icon ' + extraClass + '" data-tool-id="' + currentTool.toolId +
|
|
'" title="' + currentTool.helpText + '"></li>';
|
|
}
|
|
$('#tools-container').html(toolMarkup);
|
|
};
|
|
})();;(function () {
|
|
var ns = $.namespace("pskl.controller");
|
|
|
|
ns.PaletteController = function () {};
|
|
|
|
/**
|
|
* @public
|
|
*/
|
|
ns.PaletteController.prototype.init = function() {
|
|
var transparentColorPalette = $(".palette-color[data-color=TRANSPARENT]");
|
|
transparentColorPalette.mouseup($.proxy(this.onPaletteColorClick_, this));
|
|
|
|
$.subscribe(Events.PRIMARY_COLOR_UPDATED, $.proxy(function(evt, color) {
|
|
this.updateColorPicker_(color, $('#color-picker'));
|
|
}, this));
|
|
|
|
$.subscribe(Events.SECONDARY_COLOR_UPDATED, $.proxy(function(evt, color) {
|
|
this.updateColorPicker_(color, $('#secondary-color-picker'));
|
|
}, this));
|
|
|
|
// Initialize colorpickers:
|
|
var colorPicker = $('#color-picker');
|
|
colorPicker.val(Constants.DEFAULT_PEN_COLOR);
|
|
colorPicker.change({isPrimary : true}, $.proxy(this.onPickerChange_, this));
|
|
|
|
|
|
var secondaryColorPicker = $('#secondary-color-picker');
|
|
secondaryColorPicker.val(Constants.TRANSPARENT_COLOR);
|
|
secondaryColorPicker.change({isPrimary : false}, $.proxy(this.onPickerChange_, this));
|
|
|
|
window.jscolor.install();
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.PaletteController.prototype.onPickerChange_ = function(evt, isPrimary) {
|
|
var inputPicker = $(evt.target);
|
|
if(evt.data.isPrimary) {
|
|
$.publish(Events.PRIMARY_COLOR_SELECTED, [inputPicker.val()]);
|
|
} else {
|
|
$.publish(Events.SECONDARY_COLOR_SELECTED, [inputPicker.val()]);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.PaletteController.prototype.onPaletteColorClick_ = function (event) {
|
|
var selectedColor = $(event.target).data("color");
|
|
var isLeftClick = (event.which == 1);
|
|
var isRightClick = (event.which == 3);
|
|
if (isLeftClick) {
|
|
$.publish(Events.PRIMARY_COLOR_SELECTED, [selectedColor]);
|
|
} else if (isRightClick) {
|
|
$.publish(Events.SECONDARY_COLOR_SELECTED, [selectedColor]);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.PaletteController.prototype.updateColorPicker_ = function (color, colorPicker) {
|
|
if (color == Constants.TRANSPARENT_COLOR) {
|
|
// We can set the current palette color to transparent.
|
|
// You can then combine this transparent color with an advanced
|
|
// tool for customized deletions.
|
|
// Eg: bucket + transparent: Delete a colored area
|
|
// Stroke + transparent: hollow out the equivalent of a stroke
|
|
|
|
// The colorpicker can't be set to a transparent state.
|
|
// We set its background to white and insert the
|
|
// string "TRANSPARENT" to mimic this state:
|
|
colorPicker[0].color.fromString("#fff");
|
|
colorPicker.val(Constants.TRANSPARENT_COLOR);
|
|
} else {
|
|
colorPicker[0].color.fromString(color);
|
|
}
|
|
};
|
|
})();
|
|
|
|
|
|
|
|
;(function () {
|
|
var ns = $.namespace("pskl.controller");
|
|
|
|
ns.NotificationController = function () {};
|
|
|
|
/**
|
|
* @public
|
|
*/
|
|
ns.NotificationController.prototype.init = function() {
|
|
$.subscribe(Events.SHOW_NOTIFICATION, $.proxy(this.displayMessage_, this));
|
|
$.subscribe(Events.HIDE_NOTIFICATION, $.proxy(this.removeMessage_, this));
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.NotificationController.prototype.displayMessage_ = function (evt, messageInfo) {
|
|
var message = document.createElement('div');
|
|
message.id = "user-message";
|
|
message.className = "user-message";
|
|
message.innerHTML = messageInfo.content;
|
|
message.innerHTML = message.innerHTML + "<div title='Close message' class='close'>x</div>";
|
|
document.body.appendChild(message);
|
|
$(message).find(".close").click($.proxy(this.removeMessage_, this));
|
|
if(messageInfo.behavior) {
|
|
messageInfo.behavior(message);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.NotificationController.prototype.removeMessage_ = function (evt) {
|
|
var message = $("#user-message");
|
|
if (message.length) {
|
|
message.remove();
|
|
}
|
|
};
|
|
})();
|
|
;(function () {
|
|
var ns = $.namespace("pskl.controller.settings");
|
|
|
|
ns.ApplicationSettingsController = function () {};
|
|
|
|
/**
|
|
* @public
|
|
*/
|
|
ns.ApplicationSettingsController.prototype.init = function() {
|
|
// Highlight selected background picker:
|
|
var backgroundClass = pskl.UserSettings.get(pskl.UserSettings.CANVAS_BACKGROUND);
|
|
$('#background-picker-wrapper')
|
|
.find('.background-picker[data-background-class=' + backgroundClass + ']')
|
|
.addClass('selected');
|
|
|
|
// Initial state for grid display:
|
|
var show_grid = pskl.UserSettings.get(pskl.UserSettings.SHOW_GRID);
|
|
$('#show-grid').prop('checked', show_grid);
|
|
|
|
// Handle grid display changes:
|
|
$('#show-grid').change($.proxy(function(evt) {
|
|
var checked = $('#show-grid').prop('checked');
|
|
pskl.UserSettings.set(pskl.UserSettings.SHOW_GRID, checked);
|
|
}, this));
|
|
|
|
// Handle canvas background changes:
|
|
$('#background-picker-wrapper').click(function(evt) {
|
|
var target = $(evt.target).closest('.background-picker');
|
|
if (target.length) {
|
|
var backgroundClass = target.data('background-class');
|
|
pskl.UserSettings.set(pskl.UserSettings.CANVAS_BACKGROUND, backgroundClass);
|
|
|
|
$('.background-picker').removeClass('selected');
|
|
target.addClass('selected');
|
|
}
|
|
});
|
|
};
|
|
})();;(function () {
|
|
var ns = $.namespace("pskl.controller.settings");
|
|
ns.GifExportController = function (piskelController) {
|
|
this.piskelController = piskelController;
|
|
};
|
|
|
|
ns.GifExportController.prototype.init = function () {
|
|
this.radioTemplate_ = pskl.utils.Template.get("export-gif-radio-template");
|
|
|
|
this.previewContainerEl = document.querySelectorAll(".export-gif-preview div")[0];
|
|
this.radioGroupEl = document.querySelectorAll(".gif-export-radio-group")[0];
|
|
|
|
this.uploadFormJQ = $("[name=gif-export-upload-form]");
|
|
this.uploadFormJQ.submit(this.upload.bind(this));
|
|
|
|
this.initRadioElements_();
|
|
};
|
|
|
|
ns.GifExportController.prototype.upload = function (evt) {
|
|
evt.originalEvent.preventDefault();
|
|
var selectedDpi = this.getSelectedDpi_(),
|
|
fps = pskl.app.animationController.fps,
|
|
dpi = selectedDpi;
|
|
|
|
this.renderAsImageDataAnimatedGIF(dpi, fps, function (imageData) {
|
|
this.updatePreview_(imageData);
|
|
this.previewContainerEl.classList.add("preview-upload-ongoing");
|
|
pskl.app.imageUploadService.upload(imageData, function (imageUrl) {
|
|
this.updatePreview_(imageUrl);
|
|
this.previewContainerEl.classList.remove("preview-upload-ongoing");
|
|
}.bind(this));
|
|
}.bind(this));
|
|
};
|
|
|
|
ns.GifExportController.prototype.updatePreview_ = function (src) {
|
|
this.previewContainerEl.innerHTML = "<img style='max-width:240px;' src='"+src+"'/>";
|
|
};
|
|
|
|
ns.GifExportController.prototype.getSelectedDpi_ = function () {
|
|
var radiosColl = this.uploadFormJQ.get(0).querySelectorAll("[name=gif-dpi]"),
|
|
radios = Array.prototype.slice.call(radiosColl,0);
|
|
var selectedRadios = radios.filter(function(radio) {return !!radio.checked;});
|
|
|
|
if (selectedRadios.length == 1) {
|
|
return selectedRadios[0].value;
|
|
} else {
|
|
throw "Unexpected error when retrieving selected dpi";
|
|
}
|
|
};
|
|
|
|
ns.GifExportController.prototype.initRadioElements_ = function () {
|
|
var dpis = [
|
|
[1],
|
|
[5],
|
|
[10,true], //default
|
|
[20],
|
|
];
|
|
|
|
for (var i = 0 ; i < dpis.length ; i++) {
|
|
var dpi = dpis[i];
|
|
var radio = this.createRadioForDpi_(dpi);
|
|
this.radioGroupEl.appendChild(radio);
|
|
}
|
|
};
|
|
|
|
ns.GifExportController.prototype.createRadioForDpi_ = function (dpi) {
|
|
var label = dpi[0]*this.piskelController.getWidth() + "x" + dpi[0]*this.piskelController.getHeight();
|
|
var value = dpi[0];
|
|
var radioHTML = pskl.utils.Template.replace(this.radioTemplate_, {value : value, label : label});
|
|
var radio = pskl.utils.Template.createFromHTML(radioHTML);
|
|
|
|
if (dpi[1]) {
|
|
radio.getElementsByTagName("input")[0].setAttribute("checked", "checked");
|
|
}
|
|
|
|
return radio;
|
|
};
|
|
|
|
ns.GifExportController.prototype.blobToBase64_ = function(blob, cb) {
|
|
var reader = new FileReader();
|
|
reader.onload = function() {
|
|
var dataUrl = reader.result;
|
|
cb(dataUrl);
|
|
};
|
|
reader.readAsDataURL(blob);
|
|
};
|
|
|
|
ns.GifExportController.prototype.renderAsImageDataAnimatedGIF = function(dpi, fps, cb) {
|
|
var gif = new window.GIF({
|
|
workers: 2,
|
|
quality: 10,
|
|
width: this.piskelController.getWidth()*dpi,
|
|
height: this.piskelController.getHeight()*dpi
|
|
});
|
|
|
|
for (var i = 0; i < this.piskelController.getFrameCount(); i++) {
|
|
var frame = this.piskelController.getFrameAt(i);
|
|
var renderer = new pskl.rendering.CanvasRenderer(frame, dpi);
|
|
gif.addFrame(renderer.render(), {
|
|
delay: 1000 / fps
|
|
});
|
|
}
|
|
|
|
gif.on('finished', function(blob) {
|
|
this.blobToBase64_(blob, cb);
|
|
}.bind(this));
|
|
|
|
gif.render();
|
|
};
|
|
})();;(function () {
|
|
var ns = $.namespace("pskl.controller");
|
|
|
|
var settings = {
|
|
user : {
|
|
template : 'templates/settings-application.html',
|
|
controller : ns.settings.ApplicationSettingsController
|
|
},
|
|
gif : {
|
|
template : 'templates/settings-export-gif.html',
|
|
controller : ns.settings.GifExportController
|
|
}
|
|
};
|
|
|
|
var SEL_SETTING_CLS = 'has-expanded-drawer';
|
|
var EXP_DRAWER_CLS = 'expanded';
|
|
|
|
ns.SettingsController = function (piskelController) {
|
|
this.piskelController = piskelController;
|
|
this.drawerContainer = document.getElementById("drawer-container");
|
|
this.settingsContainer = $('[data-pskl-controller=settings]');
|
|
this.expanded = false;
|
|
this.currentSetting = null;
|
|
};
|
|
|
|
/**
|
|
* @public
|
|
*/
|
|
ns.SettingsController.prototype.init = function() {
|
|
// Expand drawer when clicking 'Settings' tab.
|
|
$('[data-setting]').click(function(evt) {
|
|
var el = evt.originalEvent.currentTarget;
|
|
var setting = el.getAttribute("data-setting");
|
|
if (this.currentSetting != setting) {
|
|
this.loadSetting(setting);
|
|
} else {
|
|
this.closeDrawer();
|
|
}
|
|
}.bind(this));
|
|
|
|
$('body').click(function (evt) {
|
|
var isInSettingsContainer = $.contains(this.settingsContainer.get(0), evt.target);
|
|
if (this.expanded && !isInSettingsContainer) {
|
|
this.closeDrawer();
|
|
}
|
|
}.bind(this));
|
|
};
|
|
|
|
ns.SettingsController.prototype.loadSetting = function (setting) {
|
|
this.drawerContainer.innerHTML = pskl.utils.Template.get(settings[setting].template);
|
|
(new settings[setting].controller(this.piskelController)).init();
|
|
|
|
this.settingsContainer.addClass(EXP_DRAWER_CLS);
|
|
|
|
$('.' + SEL_SETTING_CLS).removeClass(SEL_SETTING_CLS);
|
|
$('[data-setting='+setting+']').addClass(SEL_SETTING_CLS);
|
|
|
|
this.expanded = true;
|
|
this.currentSetting = setting;
|
|
};
|
|
|
|
ns.SettingsController.prototype.closeDrawer = function () {
|
|
this.settingsContainer.removeClass(EXP_DRAWER_CLS);
|
|
$('.' + SEL_SETTING_CLS).removeClass(SEL_SETTING_CLS);
|
|
|
|
this.expanded = false;
|
|
this.currentSetting = null;
|
|
};
|
|
|
|
})();;(function () {
|
|
var ns = $.namespace("pskl.service");
|
|
|
|
ns.LocalStorageService = function (piskelController) {
|
|
|
|
if(piskelController === undefined) {
|
|
throw "Bad LocalStorageService initialization: <undefined piskelController>";
|
|
}
|
|
this.piskelController = piskelController;
|
|
this.localStorageThrottler_ = null;
|
|
};
|
|
|
|
/**
|
|
* @public
|
|
*/
|
|
ns.LocalStorageService.prototype.init = function(piskelController) {
|
|
$.subscribe(Events.LOCALSTORAGE_REQUEST, $.proxy(this.persistToLocalStorageRequest_, this));
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.LocalStorageService.prototype.persistToLocalStorageRequest_ = function () {
|
|
// Persist to localStorage when drawing. We throttle localStorage accesses
|
|
// for high frequency drawing (eg mousemove).
|
|
if(this.localStorageThrottler_ !== null) {
|
|
window.clearTimeout(this.localStorageThrottler_);
|
|
}
|
|
this.localStorageThrottler_ = window.setTimeout($.proxy(function() {
|
|
this.persistToLocalStorage_();
|
|
this.localStorageThrottler_ = null;
|
|
}, this), 1000);
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.LocalStorageService.prototype.persistToLocalStorage_ = function() {
|
|
|
|
console.log('[LocalStorage service]: Snapshot stored');
|
|
window.localStorage.snapShot = this.piskelController.serialize();
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.LocalStorageService.prototype.restoreFromLocalStorage_ = function() {
|
|
|
|
this.piskelController.deserialize(window.localStorage.snapShot);
|
|
this.piskelController.setCurrentFrameIndex(0);
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.LocalStorageService.prototype.cleanLocalStorage_ = function() {
|
|
console.log('[LocalStorage service]: Snapshot removed');
|
|
delete window.localStorage.snapShot;
|
|
};
|
|
|
|
/**
|
|
* @public
|
|
*/
|
|
ns.LocalStorageService.prototype.displayRestoreNotification = function() {
|
|
if(window.localStorage && window.localStorage.snapShot) {
|
|
var reloadLink = "<a href='#' class='localstorage-restore onclick='pskl.app.restoreFromLocalStorage()'>reload</a>";
|
|
var discardLink = "<a href='#' class='localstorage-discard' onclick='pskl.app.cleanLocalStorage()'>discard</a>";
|
|
var content = "Non saved version found. " + reloadLink + " or " + discardLink;
|
|
|
|
$.publish(Events.SHOW_NOTIFICATION, [{
|
|
"content": content,
|
|
"behavior": $.proxy(function(rootNode) {
|
|
rootNode = $(rootNode);
|
|
rootNode.click($.proxy(function(evt) {
|
|
var target = $(evt.target);
|
|
if(target.hasClass("localstorage-restore")) {
|
|
this.restoreFromLocalStorage_();
|
|
}
|
|
else if (target.hasClass("localstorage-discard")) {
|
|
this.cleanLocalStorage_();
|
|
}
|
|
$.publish(Events.HIDE_NOTIFICATION);
|
|
}, this));
|
|
}, this)
|
|
}]);
|
|
}
|
|
};
|
|
})();;(function () {
|
|
var ns = $.namespace("pskl.service");
|
|
ns.HistoryService = function (piskelController) {
|
|
this.piskelController = piskelController;
|
|
};
|
|
|
|
ns.HistoryService.prototype.init = function () {
|
|
|
|
$.subscribe(Events.TOOL_RELEASED, this.saveState.bind(this));
|
|
$.subscribe(Events.UNDO, this.undo.bind(this));
|
|
$.subscribe(Events.REDO, this.redo.bind(this));
|
|
};
|
|
|
|
ns.HistoryService.prototype.saveState = function () {
|
|
this.piskelController.getCurrentFrame().saveState();
|
|
};
|
|
|
|
ns.HistoryService.prototype.undo = function () {
|
|
this.piskelController.getCurrentFrame().loadPreviousState();
|
|
$.publish(Events.FRAMESHEET_RESET);
|
|
};
|
|
|
|
ns.HistoryService.prototype.redo = function () {
|
|
this.piskelController.getCurrentFrame().loadNextState();
|
|
$.publish(Events.FRAMESHEET_RESET);
|
|
};
|
|
|
|
})();;(function () {
|
|
var ns = $.namespace("pskl.service");
|
|
|
|
ns.KeyboardEventService = function () {};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.KeyboardEventService.prototype.KeyboardActions_ = {
|
|
|
|
"ctrl" : {
|
|
"z" : Events.UNDO,
|
|
"y" : Events.REDO,
|
|
"x" : Events.CUT,
|
|
"c" : Events.COPY,
|
|
"v" : Events.PASTE
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.KeyboardEventService.prototype.CharCodeToKeyCodeMap_ = {
|
|
|
|
90 : "z",
|
|
89 : "y",
|
|
88 : "x",
|
|
67 : "c",
|
|
86 : "v"
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.KeyboardEventService.prototype.onKeyUp_ = function(evt) {
|
|
var isMac = false;
|
|
if (navigator.appVersion.indexOf("Mac")!=-1) {
|
|
// Welcome in mac world where vowels are consons and meta used instead of ctrl:
|
|
isMac = true;
|
|
}
|
|
|
|
if (isMac ? evt.metaKey : evt.ctrlKey) {
|
|
// Get key pressed:
|
|
var letter = this.CharCodeToKeyCodeMap_[evt.which];
|
|
if(letter) {
|
|
var eventToTrigger = this.KeyboardActions_.ctrl[letter];
|
|
if(eventToTrigger) {
|
|
$.publish(eventToTrigger);
|
|
|
|
evt.preventDefault();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @public
|
|
*/
|
|
ns.KeyboardEventService.prototype.init = function() {
|
|
$(document.body).keydown($.proxy(this.onKeyUp_, this));
|
|
};
|
|
|
|
})();;(function () {
|
|
var ns = $.namespace("pskl.service");
|
|
ns.ImageUploadService = function () {
|
|
this.serviceUrl_ = "http://screenletstore.appspot.com/__/upload";
|
|
};
|
|
|
|
ns.ImageUploadService.prototype.init = function () {
|
|
// service interface
|
|
};
|
|
|
|
/**
|
|
* Upload a base64 image data to distant service. If successful, will call provided callback with the image URL as first argument;
|
|
* @param {String} imageData base64 image data (such as the return value of canvas.toDataUrl())
|
|
* @param {Function} cbSuccess success callback. 1st argument will be the uploaded image URL
|
|
* @param {Function} cbError error callback
|
|
*/
|
|
ns.ImageUploadService.prototype.upload = function (imageData, cbSuccess, cbError) {
|
|
var xhr = new XMLHttpRequest();
|
|
var formData = new FormData();
|
|
formData.append('data', imageData);
|
|
xhr.open('POST', this.serviceUrl_, true);
|
|
xhr.onload = function (e) {
|
|
if (this.status == 200) {
|
|
var imageUrl = "http://screenletstore.appspot.com/img/" + this.responseText;
|
|
cbSuccess(imageUrl);
|
|
} else {
|
|
cbError();
|
|
}
|
|
};
|
|
|
|
xhr.send(formData);
|
|
};
|
|
|
|
})();;/*
|
|
* @provide pskl.drawingtools.BaseTool
|
|
*
|
|
* @require pskl.utils
|
|
*/
|
|
(function() {
|
|
var ns = $.namespace("pskl.drawingtools");
|
|
|
|
ns.BaseTool = function() {};
|
|
|
|
ns.BaseTool.prototype.applyToolAt = function(col, row, color, frame, overlay) {};
|
|
|
|
ns.BaseTool.prototype.moveToolAt = function(col, row, color, frame, overlay) {};
|
|
|
|
ns.BaseTool.prototype.moveUnactiveToolAt = function(col, row, color, frame, overlay) {
|
|
if (overlay.containsPixel(col, row)) {
|
|
if (!isNaN(this.highlightedPixelCol) &&
|
|
!isNaN(this.highlightedPixelRow) &&
|
|
(this.highlightedPixelRow != row ||
|
|
this.highlightedPixelCol != col)) {
|
|
|
|
// Clean the previously highlighted pixel:
|
|
overlay.clear();
|
|
}
|
|
|
|
// Show the current pixel targeted by the tool:
|
|
overlay.setPixel(col, row, Constants.TOOL_TARGET_HIGHLIGHT_COLOR);
|
|
|
|
this.highlightedPixelCol = col;
|
|
this.highlightedPixelRow = row;
|
|
}
|
|
};
|
|
|
|
ns.BaseTool.prototype.releaseToolAt = function(col, row, color, frame, overlay) {};
|
|
|
|
/**
|
|
* Bresenham line algorihtm: Get an array of pixels from
|
|
* start and end coordinates.
|
|
*
|
|
* http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
|
|
* http://stackoverflow.com/questions/4672279/bresenham-algorithm-in-javascript
|
|
*
|
|
* @private
|
|
*/
|
|
ns.BaseTool.prototype.getLinePixels_ = function(x0, x1, y0, y1) {
|
|
|
|
var pixels = [];
|
|
var dx = Math.abs(x1-x0);
|
|
var dy = Math.abs(y1-y0);
|
|
var sx = (x0 < x1) ? 1 : -1;
|
|
var sy = (y0 < y1) ? 1 : -1;
|
|
var err = dx-dy;
|
|
|
|
while(true){
|
|
|
|
// Do what you need to for this
|
|
pixels.push({"col": x0, "row": y0});
|
|
|
|
if ((x0==x1) && (y0==y1)) break;
|
|
var e2 = 2*err;
|
|
if (e2>-dy){
|
|
err -= dy;
|
|
x0 += sx;
|
|
}
|
|
if (e2 < dx) {
|
|
err += dx;
|
|
y0 += sy;
|
|
}
|
|
}
|
|
return pixels;
|
|
};
|
|
})();
|
|
;/*
|
|
* @provide pskl.drawingtools.SimplePen
|
|
*
|
|
* @require pskl.utils
|
|
*/
|
|
(function() {
|
|
var ns = $.namespace("pskl.drawingtools");
|
|
|
|
ns.SimplePen = function() {
|
|
this.toolId = "tool-pen";
|
|
this.helpText = "Pen tool";
|
|
|
|
this.previousCol = null;
|
|
this.previousRow = null;
|
|
|
|
};
|
|
|
|
pskl.utils.inherit(ns.SimplePen, ns.BaseTool);
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
ns.SimplePen.prototype.applyToolAt = function(col, row, color, frame, overlay) {
|
|
if (frame.containsPixel(col, row)) {
|
|
frame.setPixel(col, row, color);
|
|
}
|
|
this.previousCol = col;
|
|
this.previousRow = row;
|
|
};
|
|
|
|
ns.SimplePen.prototype.moveToolAt = function(col, row, color, frame, overlay) {
|
|
if((Math.abs(col - this.previousCol) > 1) || (Math.abs(row - this.previousRow) > 1)) {
|
|
// The pen movement is too fast for the mousemove frequency, there is a gap between the
|
|
// current point and the previously drawn one.
|
|
// We fill the gap by calculating missing dots (simple linear interpolation) and draw them.
|
|
var interpolatedPixels = this.getLinePixels_(col, this.previousCol, row, this.previousRow);
|
|
for(var i=0, l=interpolatedPixels.length; i<l; i++) {
|
|
var coords = interpolatedPixels[i];
|
|
this.applyToolAt(coords.col, coords.row, color, frame, overlay);
|
|
}
|
|
}
|
|
else {
|
|
this.applyToolAt(col, row, color, frame, overlay);
|
|
}
|
|
|
|
this.previousCol = col;
|
|
this.previousRow = row;
|
|
};
|
|
})();
|
|
;(function() {
|
|
var ns = $.namespace("pskl.drawingtools");
|
|
|
|
ns.VerticalMirrorPen = function() {
|
|
this.toolId = "tool-vertical-mirror-pen";
|
|
this.helpText = "vertical mirror pen tool";
|
|
|
|
this.swap = null;
|
|
this.mirroredPreviousCol = null;
|
|
this.mirroredPreviousRow = null;
|
|
};
|
|
|
|
pskl.utils.inherit(ns.VerticalMirrorPen, ns.SimplePen);
|
|
|
|
|
|
ns.VerticalMirrorPen.prototype.setMirrorContext = function() {
|
|
this.swap = this.previousCol;
|
|
this.previousCol = this.mirroredPreviousCol;
|
|
};
|
|
|
|
ns.VerticalMirrorPen.prototype.unsetMirrorContext = function() {
|
|
this.mirroredPreviousCol = this.previousCol;
|
|
this.previousCol = this.swap;
|
|
};
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
ns.VerticalMirrorPen.prototype.applyToolAt = function(col, row, color, frame, overlay) {
|
|
this.superclass.applyToolAt.call(this, col, row, color, frame, overlay);
|
|
|
|
var mirroredCol = this.getSymmetricCol_(col, frame);
|
|
this.mirroredPreviousCol = mirroredCol;
|
|
|
|
this.setMirrorContext();
|
|
this.superclass.applyToolAt.call(this, mirroredCol, row, color, frame, overlay);
|
|
this.unsetMirrorContext();
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ns.VerticalMirrorPen.prototype.getSymmetricCol_ = function(col, frame) {
|
|
return frame.getWidth() - col - 1;
|
|
};
|
|
})();
|
|
;/*
|
|
* @provide pskl.drawingtools.Eraser
|
|
*
|
|
* @require Constants
|
|
* @require pskl.utils
|
|
*/
|
|
(function() {
|
|
var ns = $.namespace("pskl.drawingtools");
|
|
|
|
ns.Eraser = function() {
|
|
this.toolId = "tool-eraser";
|
|
this.helpText = "Eraser tool";
|
|
};
|
|
|
|
pskl.utils.inherit(ns.Eraser, ns.SimplePen);
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
ns.Eraser.prototype.applyToolAt = function(col, row, color, frame, overlay) {
|
|
this.superclass.applyToolAt.call(this, col, row, Constants.TRANSPARENT_COLOR, frame, overlay);
|
|
};
|
|
})();;/*
|
|
* @provide pskl.drawingtools.Stroke
|
|
*
|
|
* @require pskl.utils
|
|
*/
|
|
(function() {
|
|
var ns = $.namespace("pskl.drawingtools");
|
|
|
|
ns.Stroke = function() {
|
|
this.toolId = "tool-stroke";
|
|
this.helpText = "Stroke tool";
|
|
|
|
// Stroke's first point coordinates (set in applyToolAt)
|
|
this.startCol = null;
|
|
this.startRow = null;
|
|
};
|
|
|
|
pskl.utils.inherit(ns.Stroke, ns.BaseTool);
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
ns.Stroke.prototype.applyToolAt = function(col, row, color, frame, overlay) {
|
|
this.startCol = col;
|
|
this.startRow = row;
|
|
|
|
// When drawing a stroke we don't change the model instantly, since the
|
|
// user can move his cursor to change the stroke direction and length
|
|
// dynamically. Instead we draw the (preview) stroke in a fake canvas that
|
|
// overlay the drawing canvas.
|
|
// We wait for the releaseToolAt callback to impact both the
|
|
// frame model and canvas rendering.
|
|
|
|
// The fake canvas where we will draw the preview of the stroke:
|
|
// Drawing the first point of the stroke in the fake overlay canvas:
|
|
overlay.setPixel(col, row, color);
|
|
};
|
|
|
|
ns.Stroke.prototype.moveToolAt = function(col, row, color, frame, overlay) {
|
|
overlay.clear();
|
|
|
|
// When the user moussemove (before releasing), we dynamically compute the
|
|
// pixel to draw the line and draw this line in the overlay canvas:
|
|
var strokePoints = this.getLinePixels_(this.startCol, col, this.startRow, row);
|
|
|
|
// Drawing current stroke:
|
|
for(var i = 0; i< strokePoints.length; i++) {
|
|
|
|
if(color == Constants.TRANSPARENT_COLOR) {
|
|
// When mousemoving the stroke tool, we draw in the canvas overlay above the drawing canvas.
|
|
// If the stroke color is transparent, we won't be
|
|
// able to see it during the movement.
|
|
// We set it to a semi-opaque white during the tool mousemove allowing to see colors below the stroke.
|
|
// When the stroke tool will be released, It will draw a transparent stroke,
|
|
// eg deleting the equivalent of a stroke.
|
|
color = Constants.SELECTION_TRANSPARENT_COLOR;
|
|
}
|
|
overlay.setPixel(strokePoints[i].col, strokePoints[i].row, color);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
ns.Stroke.prototype.releaseToolAt = function(col, row, color, frame, overlay) {
|
|
// If the stroke tool is released outside of the canvas, we cancel the stroke:
|
|
// TODO: Mutualize this check in common method
|
|
if(frame.containsPixel(col, row)) {
|
|
// The user released the tool to draw a line. We will compute the pixel coordinate, impact
|
|
// the model and draw them in the drawing canvas (not the fake overlay anymore)
|
|
var strokePoints = this.getLinePixels_(this.startCol, col, this.startRow, row);
|
|
for(var i = 0; i< strokePoints.length; i++) {
|
|
// Change model:
|
|
frame.setPixel(strokePoints[i].col, strokePoints[i].row, color);
|
|
}
|
|
}
|
|
// For now, we are done with the stroke tool and don't need an overlay anymore:
|
|
overlay.clear();
|
|
};
|
|
})();
|
|
;/*
|
|
* @provide pskl.drawingtools.PaintBucket
|
|
*
|
|
* @require pskl.utils
|
|
*/
|
|
(function() {
|
|
var ns = $.namespace("pskl.drawingtools");
|
|
|
|
ns.PaintBucket = function() {
|
|
this.toolId = "tool-paint-bucket";
|
|
this.helpText = "Paint bucket tool";
|
|
};
|
|
|
|
pskl.utils.inherit(ns.PaintBucket, ns.BaseTool);
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
ns.PaintBucket.prototype.applyToolAt = function(col, row, color, frame, overlay) {
|
|
|
|
pskl.PixelUtils.paintSimilarConnectedPixelsFromFrame(frame, col, row, color);
|
|
};
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;/*
|
|
* @provide pskl.drawingtools.Rectangle
|
|
*
|
|
* @require pskl.utils
|
|
*/
|
|
(function() {
|
|
var ns = $.namespace("pskl.drawingtools");
|
|
|
|
ns.Rectangle = function() {
|
|
this.toolId = "tool-rectangle";
|
|
this.helpText = "Rectangle tool";
|
|
|
|
// Rectangle's first point coordinates (set in applyToolAt)
|
|
this.startCol = null;
|
|
this.startRow = null;
|
|
};
|
|
|
|
pskl.utils.inherit(ns.Rectangle, ns.BaseTool);
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
ns.Rectangle.prototype.applyToolAt = function(col, row, color, frame, overlay) {
|
|
this.startCol = col;
|
|
this.startRow = row;
|
|
|
|
// Drawing the first point of the rectangle in the fake overlay canvas:
|
|
overlay.setPixel(col, row, color);
|
|
};
|
|
|
|
ns.Rectangle.prototype.moveToolAt = function(col, row, color, frame, overlay) {
|
|
overlay.clear();
|
|
if(color == Constants.TRANSPARENT_COLOR) {
|
|
color = Constants.SELECTION_TRANSPARENT_COLOR;
|
|
}
|
|
|
|
// draw in overlay
|
|
this.drawRectangle_(col, row, color, overlay);
|
|
};
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
ns.Rectangle.prototype.releaseToolAt = function(col, row, color, frame, overlay) {
|
|
overlay.clear();
|
|
if(frame.containsPixel(col, row)) { // cancel if outside of canvas
|
|
// draw in frame to finalize
|
|
this.drawRectangle_(col, row, color, frame);
|
|
}
|
|
};
|
|
|
|
ns.Rectangle.prototype.drawRectangle_ = function (col, row, color, targetFrame) {
|
|
var strokePoints = pskl.PixelUtils.getBoundRectanglePixels(this.startCol, this.startRow, col, row);
|
|
for(var i = 0; i< strokePoints.length; i++) {
|
|
// Change model:
|
|
targetFrame.setPixel(strokePoints[i].col, strokePoints[i].row, color);
|
|
}
|
|
};
|
|
})();
|
|
;/*
|
|
* @provide pskl.drawingtools.Circle
|
|
*
|
|
* @require pskl.utils
|
|
*/
|
|
(function() {
|
|
var ns = $.namespace("pskl.drawingtools");
|
|
|
|
ns.Circle = function() {
|
|
this.toolId = "tool-circle";
|
|
this.helpText = "Circle tool";
|
|
|
|
// Circle's first point coordinates (set in applyToolAt)
|
|
this.startCol = null;
|
|
this.startRow = null;
|
|
};
|
|
|
|
pskl.utils.inherit(ns.Circle, ns.BaseTool);
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
ns.Circle.prototype.applyToolAt = function(col, row, color, frame, overlay) {
|
|
this.startCol = col;
|
|
this.startRow = row;
|
|
|
|
// Drawing the first point of the rectangle in the fake overlay canvas:
|
|
overlay.setPixel(col, row, color);
|
|
};
|
|
|
|
ns.Circle.prototype.moveToolAt = function(col, row, color, frame, overlay) {
|
|
overlay.clear();
|
|
if(color == Constants.TRANSPARENT_COLOR) {
|
|
color = Constants.SELECTION_TRANSPARENT_COLOR;
|
|
}
|
|
|
|
// draw in overlay
|
|
this.drawCircle_(col, row, color, overlay);
|
|
};
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
ns.Circle.prototype.releaseToolAt = function(col, row, color, frame, overlay) {
|
|
overlay.clear();
|
|
if(frame.containsPixel(col, row)) { // cancel if outside of canvas
|
|
// draw in frame to finalize
|
|
this.drawCircle_(col, row, color, frame);
|
|
}
|
|
};
|
|
|
|
ns.Circle.prototype.drawCircle_ = function (col, row, color, targetFrame) {
|
|
var circlePoints = this.getCirclePixels_(this.startCol, this.startRow, col, row);
|
|
for(var i = 0; i< circlePoints.length; i++) {
|
|
// Change model:
|
|
targetFrame.setPixel(circlePoints[i].col, circlePoints[i].row, color);
|
|
}
|
|
};
|
|
|
|
ns.Circle.prototype.getCirclePixels_ = function (x0, y0, x1, y1) {
|
|
var coords = pskl.PixelUtils.getOrderedRectangleCoordinates(x0, y0, x1, y1);
|
|
var xC = (coords.x0 + coords.x1)/2;
|
|
var yC = (coords.y0 + coords.y1)/2;
|
|
|
|
var rX = coords.x1 - xC;
|
|
var rY = coords.y1 - yC;
|
|
|
|
var pixels = [];
|
|
var x, y, angle;
|
|
for (x = coords.x0 ; x < coords.x1 ; x++) {
|
|
angle = Math.acos((x - xC)/rX);
|
|
y = Math.round(rY * Math.sin(angle) + yC);
|
|
pixels.push({"col": x, "row": y});
|
|
pixels.push({"col": 2*xC - x, "row": 2*yC - y});
|
|
}
|
|
|
|
for (y = coords.y0 ; y < coords.y1 ; y++) {
|
|
angle = Math.asin((y - yC)/rY);
|
|
x = Math.round(rX * Math.cos(angle) + xC);
|
|
pixels.push({"col": x, "row": y});
|
|
pixels.push({"col": 2*xC - x, "row": 2*yC - y});
|
|
}
|
|
return pixels;
|
|
};
|
|
})();
|
|
;/*
|
|
* @provide pskl.drawingtools.Move
|
|
*
|
|
* @require pskl.utils
|
|
*/
|
|
(function() {
|
|
var ns = $.namespace("pskl.drawingtools");
|
|
|
|
ns.Move = function() {
|
|
this.toolId = "tool-move";
|
|
this.helpText = "Move tool";
|
|
|
|
// Stroke's first point coordinates (set in applyToolAt)
|
|
this.startCol = null;
|
|
this.startRow = null;
|
|
};
|
|
|
|
pskl.utils.inherit(ns.Move, ns.BaseTool);
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
ns.Move.prototype.applyToolAt = function(col, row, color, frame, overlay) {
|
|
this.startCol = col;
|
|
this.startRow = row;
|
|
this.frameClone = frame.clone();
|
|
};
|
|
|
|
ns.Move.prototype.moveToolAt = function(col, row, color, frame, overlay) {
|
|
var colDiff = col - this.startCol, rowDiff = row - this.startRow;
|
|
this.shiftFrame(colDiff, rowDiff, frame, this.frameClone);
|
|
};
|
|
|
|
ns.Move.prototype.shiftFrame = function (colDiff, rowDiff, frame, reference) {
|
|
var color;
|
|
for (var col = 0 ; col < frame.getWidth() ; col++) {
|
|
for (var row = 0 ; row < frame.getHeight() ; row++) {
|
|
if (reference.containsPixel(col - colDiff, row - rowDiff)) {
|
|
color = reference.getPixel(col - colDiff, row - rowDiff);
|
|
} else {
|
|
color = Constants.TRANSPARENT_COLOR;
|
|
}
|
|
frame.setPixel(col, row, color);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
ns.Move.prototype.releaseToolAt = function(col, row, color, frame, overlay) {
|
|
this.moveToolAt(col, row, color, frame, overlay);
|
|
};
|
|
})();
|
|
;/*
|
|
* @provide pskl.drawingtools.BaseSelect
|
|
*
|
|
* @require pskl.utils
|
|
*/
|
|
(function() {
|
|
var ns = $.namespace("pskl.drawingtools");
|
|
|
|
ns.BaseSelect = function() {
|
|
this.secondaryToolId = "tool-move";
|
|
this.BodyRoot = $('body');
|
|
|
|
// Select's first point coordinates (set in applyToolAt)
|
|
this.startCol = null;
|
|
this.startRow = null;
|
|
};
|
|
|
|
pskl.utils.inherit(ns.BaseSelect, ns.BaseTool);
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
ns.BaseSelect.prototype.applyToolAt = function(col, row, color, frame, overlay) {
|
|
this.startCol = col;
|
|
this.startRow = row;
|
|
|
|
this.lastCol = col;
|
|
this.lastRow = row;
|
|
|
|
// The select tool can be in two different state.
|
|
// If the inital click of the tool is not on a selection, we go in "select"
|
|
// mode to create a selection.
|
|
// If the initial click is on a previous selection, we go in "moveSelection"
|
|
// mode to allow to move the selection by drag'n dropping it.
|
|
if(overlay.getPixel(col, row) != Constants.SELECTION_TRANSPARENT_COLOR) {
|
|
|
|
this.mode = "select";
|
|
this.onSelectStart_(col, row, color, frame, overlay);
|
|
}
|
|
else {
|
|
|
|
this.mode = "moveSelection";
|
|
this.onSelectionDragStart_(col, row, color, frame, overlay);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
ns.BaseSelect.prototype.moveToolAt = function(col, row, color, frame, overlay) {
|
|
if(this.mode == "select") {
|
|
|
|
this.onSelect_(col, row, color, frame, overlay);
|
|
}
|
|
else if(this.mode == "moveSelection") {
|
|
|
|
this.onSelectionDrag_(col, row, color, frame, overlay);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
ns.BaseSelect.prototype.releaseToolAt = function(col, row, color, frame, overlay) {
|
|
if(this.mode == "select") {
|
|
this.onSelectEnd_(col, row, color, frame, overlay);
|
|
} else if(this.mode == "moveSelection") {
|
|
|
|
this.onSelectionDragEnd_(col, row, color, frame, overlay);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* If we mouseover the selection draw inside the overlay frame, show the 'move' cursor
|
|
* instead of the 'select' one. It indicates that we can move the selection by dragndroping it.
|
|
* @override
|
|
*/
|
|
ns.BaseSelect.prototype.moveUnactiveToolAt = function(col, row, color, frame, overlay) {
|
|
|
|
if(overlay.getPixel(col, row) != Constants.SELECTION_TRANSPARENT_COLOR) {
|
|
// We're hovering the selection, show the move tool:
|
|
this.BodyRoot.addClass(this.toolId);
|
|
this.BodyRoot.removeClass(this.secondaryToolId);
|
|
} else {
|
|
// We're not hovering the selection, show create selection tool:
|
|
this.BodyRoot.addClass(this.secondaryToolId);
|
|
this.BodyRoot.removeClass(this.toolId);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* For each pixel in the selection draw it in white transparent on the tool overlay
|
|
* @protected
|
|
*/
|
|
ns.BaseSelect.prototype.drawSelectionOnOverlay_ = function (selection, overlay) {
|
|
var pixels = selection.pixels;
|
|
for(var i=0, l=pixels.length; i<l; i++) {
|
|
overlay.setPixel(pixels[i].col, pixels[i].row, Constants.SELECTION_TRANSPARENT_COLOR);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Move the overlay frame filled with semi-transparent pixels that represent the selection.
|
|
* @private
|
|
*/
|
|
ns.BaseSelect.prototype.shiftOverlayFrame_ = function (colDiff, rowDiff, overlayFrame, reference) {
|
|
var color;
|
|
for (var col = 0 ; col < overlayFrame.getWidth() ; col++) {
|
|
for (var row = 0 ; row < overlayFrame.getHeight() ; row++) {
|
|
if (reference.containsPixel(col - colDiff, row - rowDiff)) {
|
|
color = reference.getPixel(col - colDiff, row - rowDiff);
|
|
} else {
|
|
color = Constants.TRANSPARENT_COLOR;
|
|
}
|
|
overlayFrame.setPixel(col, row, color);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
// The list of callbacks to implement by specialized tools to implement the selection creation behavior.
|
|
/** @protected */
|
|
ns.BaseSelect.prototype.onSelectStart_ = function (col, row, color, frame, overlay) {};
|
|
/** @protected */
|
|
ns.BaseSelect.prototype.onSelect_ = function (col, row, color, frame, overlay) {};
|
|
/** @protected */
|
|
ns.BaseSelect.prototype.onSelectEnd_ = function (col, row, color, frame, overlay) {};
|
|
|
|
|
|
// The list of callbacks that define the drag'n drop behavior of the selection.
|
|
/** @private */
|
|
ns.BaseSelect.prototype.onSelectionDragStart_ = function (col, row, color, frame, overlay) {
|
|
// Since we will move the overlayFrame in which the current selection is rendered,
|
|
// we clone it to have a reference for the later shifting process.
|
|
this.overlayFrameReference = overlay.clone();
|
|
};
|
|
|
|
/** @private */
|
|
ns.BaseSelect.prototype.onSelectionDrag_ = function (col, row, color, frame, overlay) {
|
|
var deltaCol = col - this.lastCol;
|
|
var deltaRow = row - this.lastRow;
|
|
|
|
var colDiff = col - this.startCol, rowDiff = row - this.startRow;
|
|
|
|
// Shifting selection on overlay frame:
|
|
this.shiftOverlayFrame_(colDiff, rowDiff, overlay, this.overlayFrameReference);
|
|
|
|
// Update selection model:
|
|
$.publish(Events.SELECTION_MOVE_REQUEST, [deltaCol, deltaRow]);
|
|
|
|
this.lastCol = col;
|
|
this.lastRow = row;
|
|
};
|
|
|
|
/** @private */
|
|
ns.BaseSelect.prototype.onSelectionDragEnd_ = function (col, row, color, frame, overlay) {
|
|
this.onSelectionDrag_(col, row, color, frame, overlay);
|
|
};
|
|
})();
|
|
;/*
|
|
* @provide pskl.drawingtools.RectangleSelect
|
|
*
|
|
* @require pskl.utils
|
|
*/
|
|
(function() {
|
|
var ns = $.namespace("pskl.drawingtools");
|
|
|
|
ns.RectangleSelect = function() {
|
|
this.toolId = "tool-rectangle-select";
|
|
this.helpText = "Rectangle selection tool";
|
|
|
|
ns.BaseSelect.call(this);
|
|
};
|
|
|
|
pskl.utils.inherit(ns.RectangleSelect, ns.BaseSelect);
|
|
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
ns.RectangleSelect.prototype.onSelectStart_ = function (col, row, color, frame, overlay) {
|
|
// Drawing the first point of the rectangle in the fake overlay canvas:
|
|
overlay.setPixel(col, row, color);
|
|
};
|
|
|
|
/**
|
|
* When creating the rectangle selection, we clear the current overlayFrame and
|
|
* redraw the current rectangle based on the orgin coordinate and
|
|
* the current mouse coordiinate in sprite.
|
|
* @override
|
|
*/
|
|
ns.RectangleSelect.prototype.onSelect_ = function (col, row, color, frame, overlay) {
|
|
overlay.clear();
|
|
if(this.startCol == col &&this.startRow == row) {
|
|
$.publish(Events.SELECTION_DISMISSED);
|
|
} else {
|
|
var selection = new pskl.selection.RectangularSelection(
|
|
this.startCol, this.startRow, col, row);
|
|
$.publish(Events.SELECTION_CREATED, [selection]);
|
|
this.drawSelectionOnOverlay_(selection, overlay);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
ns.RectangleSelect.prototype.onSelectEnd_ = function (col, row, color, frame, overlay) {
|
|
this.onSelect_(col, row, color, frame, overlay);
|
|
};
|
|
|
|
})();
|
|
;/*
|
|
* @provide pskl.drawingtools.ShapeSelect
|
|
*
|
|
* @require pskl.utils
|
|
*/
|
|
(function() {
|
|
var ns = $.namespace("pskl.drawingtools");
|
|
|
|
ns.ShapeSelect = function() {
|
|
this.toolId = "tool-shape-select";
|
|
this.helpText = "Shape selection tool";
|
|
|
|
ns.BaseSelect.call(this);
|
|
};
|
|
|
|
pskl.utils.inherit(ns.ShapeSelect, ns.BaseSelect);
|
|
|
|
/**
|
|
* For the shape select tool, you just need to click one time to create a selection.
|
|
* So we jsut need to implement onSelectStart_ (no need for onSelect_ & onSelectEnd_)
|
|
* @override
|
|
*/
|
|
ns.ShapeSelect.prototype.onSelectStart_ = function (col, row, color, frame, overlay) {
|
|
// Clean previous selection:
|
|
$.publish(Events.SELECTION_DISMISSED);
|
|
overlay.clear();
|
|
|
|
// From the pixel cliked, get shape using an algorithm similar to the paintbucket one:
|
|
var pixels = pskl.PixelUtils.getSimilarConnectedPixelsFromFrame(frame, col, row);
|
|
var selection = new pskl.selection.ShapeSelection(pixels);
|
|
|
|
$.publish(Events.SELECTION_CREATED, [selection]);
|
|
this.drawSelectionOnOverlay_(selection, overlay);
|
|
};
|
|
|
|
})();
|
|
;/*
|
|
* @provide pskl.drawingtools.ColorPicker
|
|
*
|
|
* @require pskl.utils
|
|
*/
|
|
(function() {
|
|
var ns = $.namespace("pskl.drawingtools");
|
|
|
|
ns.ColorPicker = function() {
|
|
this.toolId = "tool-colorpicker";
|
|
this.helpText = "Color picker";
|
|
};
|
|
|
|
pskl.utils.inherit(ns.ColorPicker, ns.BaseTool);
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
ns.ColorPicker.prototype.applyToolAt = function(col, row, color, frame, overlay, context) {
|
|
if (frame.containsPixel(col, row)) {
|
|
var sampledColor = frame.getPixel(col, row);
|
|
if (context.button == Constants.LEFT_BUTTON) {
|
|
$.publish(Events.PRIMARY_COLOR_SELECTED, [sampledColor]);
|
|
} else if (context.button == Constants.RIGHT_BUTTON) {
|
|
$.publish(Events.SECONDARY_COLOR_SELECTED, [sampledColor]);
|
|
}
|
|
}
|
|
};
|
|
})();
|
|
;/**
|
|
* @require Constants
|
|
* @require Events
|
|
*/
|
|
(function () {
|
|
var ns = $.namespace("pskl");
|
|
/**
|
|
* Main application controller
|
|
*/
|
|
ns.app = {
|
|
|
|
init : function () {
|
|
var size = this.readSizeFromURL_();
|
|
var piskel = new pskl.model.Piskel(size.width, size.height, Constants.DEFAULT.FPS);
|
|
|
|
var layer = new pskl.model.Layer("Layer 1");
|
|
var frame = new pskl.model.Frame(size.width, size.height);
|
|
layer.addFrame(frame);
|
|
|
|
piskel.addLayer(layer);
|
|
|
|
this.piskelController = new pskl.controller.PiskelController(piskel);
|
|
|
|
this.drawingController = new pskl.controller.DrawingController(this.piskelController, $('#drawing-canvas-container'));
|
|
this.drawingController.init();
|
|
|
|
this.animationController = new pskl.controller.AnimatedPreviewController(this.piskelController, $('#preview-canvas-container'));
|
|
this.animationController.init();
|
|
|
|
this.previewsController = new pskl.controller.PreviewFilmController(this.piskelController, $('#preview-list'));
|
|
this.previewsController.init();
|
|
|
|
this.layersController = new pskl.controller.LayersController(this.piskelController);
|
|
this.layersController.init();
|
|
|
|
this.settingsController = new pskl.controller.SettingsController(this.piskelController);
|
|
this.settingsController.init();
|
|
|
|
this.selectionManager = new pskl.selection.SelectionManager(this.piskelController);
|
|
this.selectionManager.init();
|
|
|
|
this.historyService = new pskl.service.HistoryService(this.piskelController);
|
|
this.historyService.init();
|
|
|
|
this.keyboardEventService = new pskl.service.KeyboardEventService();
|
|
this.keyboardEventService.init();
|
|
|
|
this.notificationController = new pskl.controller.NotificationController();
|
|
this.notificationController.init();
|
|
|
|
this.localStorageService = new pskl.service.LocalStorageService(this.piskelController);
|
|
this.localStorageService.init();
|
|
|
|
this.imageUploadService = new pskl.service.ImageUploadService();
|
|
this.imageUploadService.init();
|
|
|
|
this.toolController = new pskl.controller.ToolController();
|
|
this.toolController.init();
|
|
|
|
this.paletteController = new pskl.controller.PaletteController();
|
|
this.paletteController.init();
|
|
|
|
var drawingLoop = new pskl.rendering.DrawingLoop();
|
|
drawingLoop.addCallback(this.render, this);
|
|
drawingLoop.start();
|
|
|
|
this.initBootstrapTooltips_();
|
|
|
|
|
|
/**
|
|
* True when piskel is running in static mode (no back end needed).
|
|
* When started from APP Engine, appEngineToken_ (Boolean) should be set on window.pskl
|
|
*/
|
|
this.isStaticVersion = !pskl.appEngineToken_;
|
|
|
|
if (this.isStaticVersion) {
|
|
var framesheetId = this.readFramesheetIdFromURL_();
|
|
if (framesheetId) {
|
|
$.publish(Events.SHOW_NOTIFICATION, [{
|
|
"content" : "Loading animation with id : [" + framesheetId + "]"
|
|
}]);
|
|
this.loadFramesheetFromService(framesheetId);
|
|
} else {
|
|
this.localStorageService.displayRestoreNotification();
|
|
}
|
|
} else {
|
|
if (pskl.framesheetData_ && pskl.framesheetData_.content) {
|
|
this.piskelController.load(pskl.framesheetData_.content);
|
|
pskl.app.animationController.setFPS(pskl.framesheetData_.fps);
|
|
}
|
|
}
|
|
},
|
|
|
|
initBootstrapTooltips_ : function () {
|
|
$('body').tooltip({
|
|
selector: '[rel=tooltip]'
|
|
});
|
|
},
|
|
|
|
render : function (delta) {
|
|
this.drawingController.render(delta);
|
|
this.animationController.render(delta);
|
|
this.previewsController.render(delta);
|
|
},
|
|
|
|
readSizeFromURL_ : function () {
|
|
var sizeParam = this.readUrlParameter_("size"),
|
|
size;
|
|
// parameter expected as size=64x48 => size=widthxheight
|
|
var parts = sizeParam.split("x");
|
|
if (parts && parts.length == 2 && !isNaN(parts[0]) && !isNaN(parts[1])) {
|
|
var width = parseInt(parts[0], 10),
|
|
height = parseInt(parts[1], 10);
|
|
|
|
size = {
|
|
height : Math.min(height, Constants.MAX_HEIGHT),
|
|
width : Math.min(width, Constants.MAX_WIDTH)
|
|
};
|
|
} else {
|
|
size = {
|
|
height : Constants.DEFAULT.HEIGHT,
|
|
width : Constants.DEFAULT.WIDTH
|
|
};
|
|
}
|
|
return size;
|
|
},
|
|
|
|
readFramesheetIdFromURL_ : function () {
|
|
return this.readUrlParameter_("frameId");
|
|
},
|
|
|
|
readUrlParameter_ : function (paramName) {
|
|
var searchString = window.location.search.substring(1),
|
|
i, val, params = searchString.split("&");
|
|
|
|
for (i = 0; i < params.length; i++) {
|
|
val = params[i].split("=");
|
|
if (val[0] == paramName) {
|
|
return window.unescape(val[1]);
|
|
}
|
|
}
|
|
return "";
|
|
},
|
|
|
|
loadFramesheetFromService : function (frameId) {
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('GET', Constants.PISKEL_SERVICE_URL + '/get?l=' + frameId, true);
|
|
xhr.responseType = 'text';
|
|
var piskelController = this.piskelController;
|
|
xhr.onload = function (e) {
|
|
var res = JSON.parse(this.responseText);
|
|
piskelController.deserialize(JSON.stringify(res.framesheet));
|
|
pskl.app.animationController.setFPS(res.fps);
|
|
$.publish(Events.HIDE_NOTIFICATION);
|
|
};
|
|
|
|
xhr.onerror = function () {
|
|
$.publish(Events.HIDE_NOTIFICATION);
|
|
};
|
|
|
|
xhr.send();
|
|
},
|
|
|
|
getFirstFrameAsPNGData_ : function () {
|
|
throw 'getFirstFrameAsPNGData_ not implemented';
|
|
},
|
|
|
|
// TODO(julz): Create package ?
|
|
storeSheet : function (event) {
|
|
var xhr = new XMLHttpRequest();
|
|
var formData = new FormData();
|
|
formData.append('framesheet_content', this.piskelController.serialize());
|
|
formData.append('fps_speed', $('#preview-fps').val());
|
|
|
|
if (this.isStaticVersion) {
|
|
// anonymous save on old app-engine backend
|
|
xhr.open('POST', Constants.PISKEL_SERVICE_URL + "/store", true);
|
|
} else {
|
|
// additional values only used with latest app-engine backend
|
|
formData.append('name', $('#piskel-name').val());
|
|
formData.append('frames', this.piskelController.getFrameCount());
|
|
|
|
// Get image/png data for first frame
|
|
formData.append('preview', this.getFirstFrameAsPNGData_());
|
|
|
|
var imageData = (new pskl.rendering.SpritesheetRenderer(this.piskelController)).renderAsImageDataSpritesheetPNG();
|
|
formData.append('framesheet', imageData);
|
|
|
|
xhr.open('POST', "save", true);
|
|
}
|
|
|
|
xhr.onload = function(e) {
|
|
if (this.status == 200) {
|
|
if (pskl.app.isStaticVersion) {
|
|
var baseUrl = window.location.href.replace(window.location.search, "");
|
|
window.location.href = baseUrl + "?frameId=" + this.responseText;
|
|
} else {
|
|
$.publish(Events.SHOW_NOTIFICATION, [{"content": "Successfully saved !"}]);
|
|
}
|
|
} else {
|
|
this.onerror(e);
|
|
}
|
|
};
|
|
xhr.onerror = function(e) {
|
|
$.publish(Events.SHOW_NOTIFICATION, [{"content": "Saving failed ("+this.status+")"}]);
|
|
};
|
|
xhr.send(formData);
|
|
|
|
if(event) {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
}
|
|
return false;
|
|
},
|
|
|
|
uploadAsSpritesheetPNG : function () {
|
|
var imageData = (new pskl.rendering.SpritesheetRenderer(this.piskelController)).renderAsImageDataSpritesheetPNG();
|
|
this.imageUploadService.upload(imageData, this.openWindow.bind(this));
|
|
},
|
|
|
|
openWindow : function (url) {
|
|
var options = [
|
|
"dialog=yes", "scrollbars=no", "status=no",
|
|
"width=" + this.piskelController.getWidth() * this.piskelController.getFrameCount(),
|
|
"height=" + this.piskelController.getHeight()
|
|
].join(",");
|
|
|
|
window.open(url, "piskel-export", options);
|
|
}
|
|
};
|
|
})();
|