15793 lines
494 KiB
JavaScript
15793 lines
494 KiB
JavaScript
/*
|
|
This file is part of Sencha Touch 2.1
|
|
|
|
Copyright (c) 2011-2012 Sencha Inc
|
|
|
|
Contact: http://www.sencha.com/contact
|
|
|
|
GNU General Public License Usage
|
|
This file may be used under the terms of the GNU General Public License version 3.0 as
|
|
published by the Free Software Foundation and appearing in the file LICENSE included in the
|
|
packaging of this file.
|
|
|
|
Please review the following information to ensure the GNU General Public License version 3.0
|
|
requirements will be met: http://www.gnu.org/copyleft/gpl.html.
|
|
|
|
If you are unsure which license is appropriate for your use, please contact the sales department
|
|
at http://www.sencha.com/contact.
|
|
|
|
Build date: 2012-11-05 22:31:29 (08c91901ae8449841ff23e5d3fb404d6128d3b0b)
|
|
*/
|
|
//@tag foundation,core
|
|
//@define Ext
|
|
|
|
/**
|
|
* @class Ext
|
|
* @singleton
|
|
*/
|
|
(function() {
|
|
var global = this,
|
|
objectPrototype = Object.prototype,
|
|
toString = objectPrototype.toString,
|
|
enumerables = true,
|
|
enumerablesTest = { toString: 1 },
|
|
emptyFn = function(){},
|
|
i;
|
|
|
|
if (typeof Ext === 'undefined') {
|
|
global.Ext = {};
|
|
}
|
|
|
|
Ext.global = global;
|
|
|
|
for (i in enumerablesTest) {
|
|
enumerables = null;
|
|
}
|
|
|
|
if (enumerables) {
|
|
enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable',
|
|
'toLocaleString', 'toString', 'constructor'];
|
|
}
|
|
|
|
/**
|
|
* An array containing extra enumerables for old browsers.
|
|
* @property {String[]}
|
|
*/
|
|
Ext.enumerables = enumerables;
|
|
|
|
/**
|
|
* Copies all the properties of config to the specified object.
|
|
* Note that if recursive merging and cloning without referencing the original objects / arrays is needed, use
|
|
* {@link Ext.Object#merge} instead.
|
|
* @param {Object} object The receiver of the properties.
|
|
* @param {Object} config The source of the properties.
|
|
* @param {Object} [defaults] A different object that will also be applied for default values.
|
|
* @return {Object} returns obj
|
|
*/
|
|
Ext.apply = function(object, config, defaults) {
|
|
if (defaults) {
|
|
Ext.apply(object, defaults);
|
|
}
|
|
|
|
if (object && config && typeof config === 'object') {
|
|
var i, j, k;
|
|
|
|
for (i in config) {
|
|
object[i] = config[i];
|
|
}
|
|
|
|
if (enumerables) {
|
|
for (j = enumerables.length; j--;) {
|
|
k = enumerables[j];
|
|
if (config.hasOwnProperty(k)) {
|
|
object[k] = config[k];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return object;
|
|
};
|
|
|
|
Ext.buildSettings = Ext.apply({
|
|
baseCSSPrefix: 'x-',
|
|
scopeResetCSS: false
|
|
}, Ext.buildSettings || {});
|
|
|
|
Ext.apply(Ext, {
|
|
/**
|
|
* @property {Function}
|
|
* A reusable empty function
|
|
*/
|
|
emptyFn: emptyFn,
|
|
|
|
baseCSSPrefix: Ext.buildSettings.baseCSSPrefix,
|
|
|
|
/**
|
|
* Copies all the properties of config to object if they don't already exist.
|
|
* @param {Object} object The receiver of the properties.
|
|
* @param {Object} config The source of the properties.
|
|
* @return {Object} returns obj
|
|
*/
|
|
applyIf: function(object, config) {
|
|
var property;
|
|
|
|
if (object) {
|
|
for (property in config) {
|
|
if (object[property] === undefined) {
|
|
object[property] = config[property];
|
|
}
|
|
}
|
|
}
|
|
|
|
return object;
|
|
},
|
|
|
|
/**
|
|
* Iterates either an array or an object. This method delegates to
|
|
* {@link Ext.Array#each Ext.Array.each} if the given value is iterable, and {@link Ext.Object#each Ext.Object.each} otherwise.
|
|
*
|
|
* @param {Object/Array} object The object or array to be iterated.
|
|
* @param {Function} fn The function to be called for each iteration. See and {@link Ext.Array#each Ext.Array.each} and
|
|
* {@link Ext.Object#each Ext.Object.each} for detailed lists of arguments passed to this function depending on the given object
|
|
* type that is being iterated.
|
|
* @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
|
|
* Defaults to the object being iterated itself.
|
|
*/
|
|
iterate: function(object, fn, scope) {
|
|
if (Ext.isEmpty(object)) {
|
|
return;
|
|
}
|
|
|
|
if (scope === undefined) {
|
|
scope = object;
|
|
}
|
|
|
|
if (Ext.isIterable(object)) {
|
|
Ext.Array.each.call(Ext.Array, object, fn, scope);
|
|
}
|
|
else {
|
|
Ext.Object.each.call(Ext.Object, object, fn, scope);
|
|
}
|
|
}
|
|
});
|
|
|
|
Ext.apply(Ext, {
|
|
|
|
/**
|
|
* This method deprecated. Use {@link Ext#define Ext.define} instead.
|
|
* @method
|
|
* @param {Function} superclass
|
|
* @param {Object} overrides
|
|
* @return {Function} The subclass constructor from the `overrides` parameter, or a generated one if not provided.
|
|
* @deprecated 4.0.0 Please use {@link Ext#define Ext.define} instead
|
|
*/
|
|
extend: function() {
|
|
// inline overrides
|
|
var objectConstructor = objectPrototype.constructor,
|
|
inlineOverrides = function(o) {
|
|
for (var m in o) {
|
|
if (!o.hasOwnProperty(m)) {
|
|
continue;
|
|
}
|
|
this[m] = o[m];
|
|
}
|
|
};
|
|
|
|
return function(subclass, superclass, overrides) {
|
|
// First we check if the user passed in just the superClass with overrides
|
|
if (Ext.isObject(superclass)) {
|
|
overrides = superclass;
|
|
superclass = subclass;
|
|
subclass = overrides.constructor !== objectConstructor ? overrides.constructor : function() {
|
|
superclass.apply(this, arguments);
|
|
};
|
|
}
|
|
|
|
//<debug>
|
|
if (!superclass) {
|
|
Ext.Error.raise({
|
|
sourceClass: 'Ext',
|
|
sourceMethod: 'extend',
|
|
msg: 'Attempting to extend from a class which has not been loaded on the page.'
|
|
});
|
|
}
|
|
//</debug>
|
|
|
|
// We create a new temporary class
|
|
var F = function() {},
|
|
subclassProto, superclassProto = superclass.prototype;
|
|
|
|
F.prototype = superclassProto;
|
|
subclassProto = subclass.prototype = new F();
|
|
subclassProto.constructor = subclass;
|
|
subclass.superclass = superclassProto;
|
|
|
|
if (superclassProto.constructor === objectConstructor) {
|
|
superclassProto.constructor = superclass;
|
|
}
|
|
|
|
subclass.override = function(overrides) {
|
|
Ext.override(subclass, overrides);
|
|
};
|
|
|
|
subclassProto.override = inlineOverrides;
|
|
subclassProto.proto = subclassProto;
|
|
|
|
subclass.override(overrides);
|
|
subclass.extend = function(o) {
|
|
return Ext.extend(subclass, o);
|
|
};
|
|
|
|
return subclass;
|
|
};
|
|
}(),
|
|
|
|
/**
|
|
* Proxy to {@link Ext.Base#override}. Please refer {@link Ext.Base#override} for further details.
|
|
*
|
|
* @param {Object} cls The class to override
|
|
* @param {Object} overrides The properties to add to `origClass`. This should be specified as an object literal
|
|
* containing one or more properties.
|
|
* @method override
|
|
* @deprecated 4.1.0 Please use {@link Ext#define Ext.define} instead.
|
|
*/
|
|
override: function(cls, overrides) {
|
|
if (cls.$isClass) {
|
|
return cls.override(overrides);
|
|
}
|
|
else {
|
|
Ext.apply(cls.prototype, overrides);
|
|
}
|
|
}
|
|
});
|
|
|
|
// A full set of static methods to do type checking
|
|
Ext.apply(Ext, {
|
|
|
|
/**
|
|
* Returns the given value itself if it's not empty, as described in {@link Ext#isEmpty}; returns the default
|
|
* value (second argument) otherwise.
|
|
*
|
|
* @param {Object} value The value to test.
|
|
* @param {Object} defaultValue The value to return if the original value is empty.
|
|
* @param {Boolean} [allowBlank=false] (optional) `true` to allow zero length strings to qualify as non-empty.
|
|
* @return {Object} `value`, if non-empty, else `defaultValue`.
|
|
*/
|
|
valueFrom: function(value, defaultValue, allowBlank){
|
|
return Ext.isEmpty(value, allowBlank) ? defaultValue : value;
|
|
},
|
|
|
|
/**
|
|
* Returns the type of the given variable in string format. List of possible values are:
|
|
*
|
|
* - `undefined`: If the given value is `undefined`
|
|
* - `null`: If the given value is `null`
|
|
* - `string`: If the given value is a string
|
|
* - `number`: If the given value is a number
|
|
* - `boolean`: If the given value is a boolean value
|
|
* - `date`: If the given value is a `Date` object
|
|
* - `function`: If the given value is a function reference
|
|
* - `object`: If the given value is an object
|
|
* - `array`: If the given value is an array
|
|
* - `regexp`: If the given value is a regular expression
|
|
* - `element`: If the given value is a DOM Element
|
|
* - `textnode`: If the given value is a DOM text node and contains something other than whitespace
|
|
* - `whitespace`: If the given value is a DOM text node and contains only whitespace
|
|
*
|
|
* @param {Object} value
|
|
* @return {String}
|
|
*/
|
|
typeOf: function(value) {
|
|
if (value === null) {
|
|
return 'null';
|
|
}
|
|
|
|
var type = typeof value;
|
|
|
|
if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
|
|
return type;
|
|
}
|
|
|
|
var typeToString = toString.call(value);
|
|
|
|
switch(typeToString) {
|
|
case '[object Array]':
|
|
return 'array';
|
|
case '[object Date]':
|
|
return 'date';
|
|
case '[object Boolean]':
|
|
return 'boolean';
|
|
case '[object Number]':
|
|
return 'number';
|
|
case '[object RegExp]':
|
|
return 'regexp';
|
|
}
|
|
|
|
if (type === 'function') {
|
|
return 'function';
|
|
}
|
|
|
|
if (type === 'object') {
|
|
if (value.nodeType !== undefined) {
|
|
if (value.nodeType === 3) {
|
|
return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace';
|
|
}
|
|
else {
|
|
return 'element';
|
|
}
|
|
}
|
|
|
|
return 'object';
|
|
}
|
|
|
|
//<debug error>
|
|
Ext.Error.raise({
|
|
sourceClass: 'Ext',
|
|
sourceMethod: 'typeOf',
|
|
msg: 'Failed to determine the type of the specified value "' + value + '". This is most likely a bug.'
|
|
});
|
|
//</debug>
|
|
},
|
|
|
|
/**
|
|
* Returns `true` if the passed value is empty, `false` otherwise. The value is deemed to be empty if it is either:
|
|
*
|
|
* - `null`
|
|
* - `undefined`
|
|
* - a zero-length array.
|
|
* - a zero-length string (Unless the `allowEmptyString` parameter is set to `true`).
|
|
*
|
|
* @param {Object} value The value to test.
|
|
* @param {Boolean} [allowEmptyString=false] (optional) `true` to allow empty strings.
|
|
* @return {Boolean}
|
|
*/
|
|
isEmpty: function(value, allowEmptyString) {
|
|
return (value === null) || (value === undefined) || (!allowEmptyString ? value === '' : false) || (Ext.isArray(value) && value.length === 0);
|
|
},
|
|
|
|
/**
|
|
* Returns `true` if the passed value is a JavaScript Array, `false` otherwise.
|
|
*
|
|
* @param {Object} target The target to test.
|
|
* @return {Boolean}
|
|
* @method
|
|
*/
|
|
isArray: ('isArray' in Array) ? Array.isArray : function(value) {
|
|
return toString.call(value) === '[object Array]';
|
|
},
|
|
|
|
/**
|
|
* Returns `true` if the passed value is a JavaScript Date object, `false` otherwise.
|
|
* @param {Object} object The object to test.
|
|
* @return {Boolean}
|
|
*/
|
|
isDate: function(value) {
|
|
return toString.call(value) === '[object Date]';
|
|
},
|
|
|
|
/**
|
|
* Returns `true` if the passed value is a JavaScript Object, `false` otherwise.
|
|
* @param {Object} value The value to test.
|
|
* @return {Boolean}
|
|
* @method
|
|
*/
|
|
isObject: (toString.call(null) === '[object Object]') ?
|
|
function(value) {
|
|
// check ownerDocument here as well to exclude DOM nodes
|
|
return value !== null && value !== undefined && toString.call(value) === '[object Object]' && value.ownerDocument === undefined;
|
|
} :
|
|
function(value) {
|
|
return toString.call(value) === '[object Object]';
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
isSimpleObject: function(value) {
|
|
return value instanceof Object && value.constructor === Object;
|
|
},
|
|
/**
|
|
* Returns `true` if the passed value is a JavaScript 'primitive', a string, number or Boolean.
|
|
* @param {Object} value The value to test.
|
|
* @return {Boolean}
|
|
*/
|
|
isPrimitive: function(value) {
|
|
var type = typeof value;
|
|
|
|
return type === 'string' || type === 'number' || type === 'boolean';
|
|
},
|
|
|
|
/**
|
|
* Returns `true` if the passed value is a JavaScript Function, `false` otherwise.
|
|
* @param {Object} value The value to test.
|
|
* @return {Boolean}
|
|
* @method
|
|
*/
|
|
isFunction:
|
|
// Safari 3.x and 4.x returns 'function' for typeof <NodeList>, hence we need to fall back to using
|
|
// Object.prorotype.toString (slower)
|
|
(typeof document !== 'undefined' && typeof document.getElementsByTagName('body') === 'function') ? function(value) {
|
|
return toString.call(value) === '[object Function]';
|
|
} : function(value) {
|
|
return typeof value === 'function';
|
|
},
|
|
|
|
/**
|
|
* Returns `true` if the passed value is a number. Returns `false` for non-finite numbers.
|
|
* @param {Object} value The value to test.
|
|
* @return {Boolean}
|
|
*/
|
|
isNumber: function(value) {
|
|
return typeof value === 'number' && isFinite(value);
|
|
},
|
|
|
|
/**
|
|
* Validates that a value is numeric.
|
|
* @param {Object} value Examples: 1, '1', '2.34'
|
|
* @return {Boolean} `true` if numeric, `false` otherwise.
|
|
*/
|
|
isNumeric: function(value) {
|
|
return !isNaN(parseFloat(value)) && isFinite(value);
|
|
},
|
|
|
|
/**
|
|
* Returns `true` if the passed value is a string.
|
|
* @param {Object} value The value to test.
|
|
* @return {Boolean}
|
|
*/
|
|
isString: function(value) {
|
|
return typeof value === 'string';
|
|
},
|
|
|
|
/**
|
|
* Returns `true` if the passed value is a Boolean.
|
|
*
|
|
* @param {Object} value The value to test.
|
|
* @return {Boolean}
|
|
*/
|
|
isBoolean: function(value) {
|
|
return typeof value === 'boolean';
|
|
},
|
|
|
|
/**
|
|
* Returns `true` if the passed value is an HTMLElement.
|
|
* @param {Object} value The value to test.
|
|
* @return {Boolean}
|
|
*/
|
|
isElement: function(value) {
|
|
return value ? value.nodeType === 1 : false;
|
|
},
|
|
|
|
/**
|
|
* Returns `true` if the passed value is a TextNode.
|
|
* @param {Object} value The value to test.
|
|
* @return {Boolean}
|
|
*/
|
|
isTextNode: function(value) {
|
|
return value ? value.nodeName === "#text" : false;
|
|
},
|
|
|
|
/**
|
|
* Returns `true` if the passed value is defined.
|
|
* @param {Object} value The value to test.
|
|
* @return {Boolean}
|
|
*/
|
|
isDefined: function(value) {
|
|
return typeof value !== 'undefined';
|
|
},
|
|
|
|
/**
|
|
* Returns `true` if the passed value is iterable, `false` otherwise.
|
|
* @param {Object} value The value to test.
|
|
* @return {Boolean}
|
|
*/
|
|
isIterable: function(value) {
|
|
return (value && typeof value !== 'string') ? value.length !== undefined : false;
|
|
}
|
|
});
|
|
|
|
Ext.apply(Ext, {
|
|
|
|
/**
|
|
* Clone almost any type of variable including array, object, DOM nodes and Date without keeping the old reference.
|
|
* @param {Object} item The variable to clone.
|
|
* @return {Object} clone
|
|
*/
|
|
clone: function(item) {
|
|
if (item === null || item === undefined) {
|
|
return item;
|
|
}
|
|
|
|
// DOM nodes
|
|
if (item.nodeType && item.cloneNode) {
|
|
return item.cloneNode(true);
|
|
}
|
|
|
|
// Strings
|
|
var type = toString.call(item);
|
|
|
|
// Dates
|
|
if (type === '[object Date]') {
|
|
return new Date(item.getTime());
|
|
}
|
|
|
|
var i, j, k, clone, key;
|
|
|
|
// Arrays
|
|
if (type === '[object Array]') {
|
|
i = item.length;
|
|
|
|
clone = [];
|
|
|
|
while (i--) {
|
|
clone[i] = Ext.clone(item[i]);
|
|
}
|
|
}
|
|
// Objects
|
|
else if (type === '[object Object]' && item.constructor === Object) {
|
|
clone = {};
|
|
|
|
for (key in item) {
|
|
clone[key] = Ext.clone(item[key]);
|
|
}
|
|
|
|
if (enumerables) {
|
|
for (j = enumerables.length; j--;) {
|
|
k = enumerables[j];
|
|
clone[k] = item[k];
|
|
}
|
|
}
|
|
}
|
|
|
|
return clone || item;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* Generate a unique reference of Ext in the global scope, useful for sandboxing.
|
|
*/
|
|
getUniqueGlobalNamespace: function() {
|
|
var uniqueGlobalNamespace = this.uniqueGlobalNamespace;
|
|
|
|
if (uniqueGlobalNamespace === undefined) {
|
|
var i = 0;
|
|
|
|
do {
|
|
uniqueGlobalNamespace = 'ExtBox' + (++i);
|
|
} while (Ext.global[uniqueGlobalNamespace] !== undefined);
|
|
|
|
Ext.global[uniqueGlobalNamespace] = Ext;
|
|
this.uniqueGlobalNamespace = uniqueGlobalNamespace;
|
|
}
|
|
|
|
return uniqueGlobalNamespace;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
functionFactory: function() {
|
|
var args = Array.prototype.slice.call(arguments),
|
|
ln = args.length;
|
|
|
|
if (ln > 0) {
|
|
args[ln - 1] = 'var Ext=window.' + this.getUniqueGlobalNamespace() + ';' + args[ln - 1];
|
|
}
|
|
|
|
return Function.prototype.constructor.apply(Function.prototype, args);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
globalEval: ('execScript' in global) ? function(code) {
|
|
global.execScript(code)
|
|
} : function(code) {
|
|
(function(){
|
|
eval(code);
|
|
})();
|
|
}
|
|
|
|
//<feature logger>
|
|
/**
|
|
* @private
|
|
* @property
|
|
*/
|
|
,Logger: {
|
|
log: function(message, priority) {
|
|
if ('console' in global) {
|
|
if (!priority || !(priority in global.console)) {
|
|
priority = 'log';
|
|
}
|
|
message = '[' + priority.toUpperCase() + '] ' + message;
|
|
global.console[priority](message);
|
|
}
|
|
},
|
|
verbose: function(message) {
|
|
this.log(message, 'verbose');
|
|
},
|
|
info: function(message) {
|
|
this.log(message, 'info');
|
|
},
|
|
warn: function(message) {
|
|
this.log(message, 'warn');
|
|
},
|
|
error: function(message) {
|
|
throw new Error(message);
|
|
},
|
|
deprecate: function(message) {
|
|
this.log(message, 'warn');
|
|
}
|
|
}
|
|
//</feature>
|
|
});
|
|
|
|
/**
|
|
* Old alias to {@link Ext#typeOf}.
|
|
* @deprecated 4.0.0 Please use {@link Ext#typeOf} instead.
|
|
* @method
|
|
* @alias Ext#typeOf
|
|
*/
|
|
Ext.type = Ext.typeOf;
|
|
|
|
})();
|
|
|
|
//@tag foundation,core
|
|
//@define Ext.Version
|
|
//@require Ext
|
|
|
|
/**
|
|
* @author Jacky Nguyen <jacky@sencha.com>
|
|
* @docauthor Jacky Nguyen <jacky@sencha.com>
|
|
* @class Ext.Version
|
|
*
|
|
* A utility class that wrap around a string version number and provide convenient
|
|
* method to perform comparison. See also: {@link Ext.Version#compare compare}. Example:
|
|
*
|
|
* var version = new Ext.Version('1.0.2beta');
|
|
* console.log("Version is " + version); // Version is 1.0.2beta
|
|
*
|
|
* console.log(version.getMajor()); // 1
|
|
* console.log(version.getMinor()); // 0
|
|
* console.log(version.getPatch()); // 2
|
|
* console.log(version.getBuild()); // 0
|
|
* console.log(version.getRelease()); // beta
|
|
*
|
|
* console.log(version.isGreaterThan('1.0.1')); // true
|
|
* console.log(version.isGreaterThan('1.0.2alpha')); // true
|
|
* console.log(version.isGreaterThan('1.0.2RC')); // false
|
|
* console.log(version.isGreaterThan('1.0.2')); // false
|
|
* console.log(version.isLessThan('1.0.2')); // true
|
|
*
|
|
* console.log(version.match(1.0)); // true
|
|
* console.log(version.match('1.0.2')); // true
|
|
*/
|
|
(function() {
|
|
|
|
// Current core version
|
|
var version = '4.1.0', Version;
|
|
Ext.Version = Version = Ext.extend(Object, {
|
|
|
|
/**
|
|
* Creates new Version object.
|
|
* @param {String/Number} version The version number in the follow standard format: major[.minor[.patch[.build[release]]]]
|
|
* Examples: 1.0 or 1.2.3beta or 1.2.3.4RC
|
|
* @return {Ext.Version} this
|
|
*/
|
|
constructor: function(version) {
|
|
var toNumber = this.toNumber,
|
|
parts, releaseStartIndex;
|
|
|
|
if (version instanceof Version) {
|
|
return version;
|
|
}
|
|
|
|
this.version = this.shortVersion = String(version).toLowerCase().replace(/_/g, '.').replace(/[\-+]/g, '');
|
|
|
|
releaseStartIndex = this.version.search(/([^\d\.])/);
|
|
|
|
if (releaseStartIndex !== -1) {
|
|
this.release = this.version.substr(releaseStartIndex, version.length);
|
|
this.shortVersion = this.version.substr(0, releaseStartIndex);
|
|
}
|
|
|
|
this.shortVersion = this.shortVersion.replace(/[^\d]/g, '');
|
|
|
|
parts = this.version.split('.');
|
|
|
|
this.major = toNumber(parts.shift());
|
|
this.minor = toNumber(parts.shift());
|
|
this.patch = toNumber(parts.shift());
|
|
this.build = toNumber(parts.shift());
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* @param value
|
|
* @return {Number}
|
|
*/
|
|
toNumber: function(value) {
|
|
value = parseInt(value || 0, 10);
|
|
|
|
if (isNaN(value)) {
|
|
value = 0;
|
|
}
|
|
|
|
return value;
|
|
},
|
|
|
|
/**
|
|
* Override the native `toString()` method.
|
|
* @private
|
|
* @return {String} version
|
|
*/
|
|
toString: function() {
|
|
return this.version;
|
|
},
|
|
|
|
/**
|
|
* Override the native `valueOf()` method.
|
|
* @private
|
|
* @return {String} version
|
|
*/
|
|
valueOf: function() {
|
|
return this.version;
|
|
},
|
|
|
|
/**
|
|
* Returns the major component value.
|
|
* @return {Number} major
|
|
*/
|
|
getMajor: function() {
|
|
return this.major || 0;
|
|
},
|
|
|
|
/**
|
|
* Returns the minor component value.
|
|
* @return {Number} minor
|
|
*/
|
|
getMinor: function() {
|
|
return this.minor || 0;
|
|
},
|
|
|
|
/**
|
|
* Returns the patch component value.
|
|
* @return {Number} patch
|
|
*/
|
|
getPatch: function() {
|
|
return this.patch || 0;
|
|
},
|
|
|
|
/**
|
|
* Returns the build component value.
|
|
* @return {Number} build
|
|
*/
|
|
getBuild: function() {
|
|
return this.build || 0;
|
|
},
|
|
|
|
/**
|
|
* Returns the release component value.
|
|
* @return {Number} release
|
|
*/
|
|
getRelease: function() {
|
|
return this.release || '';
|
|
},
|
|
|
|
/**
|
|
* Returns whether this version if greater than the supplied argument.
|
|
* @param {String/Number} target The version to compare with.
|
|
* @return {Boolean} `true` if this version if greater than the target, `false` otherwise.
|
|
*/
|
|
isGreaterThan: function(target) {
|
|
return Version.compare(this.version, target) === 1;
|
|
},
|
|
|
|
/**
|
|
* Returns whether this version if greater than or equal to the supplied argument.
|
|
* @param {String/Number} target The version to compare with.
|
|
* @return {Boolean} `true` if this version if greater than or equal to the target, `false` otherwise.
|
|
*/
|
|
isGreaterThanOrEqual: function(target) {
|
|
return Version.compare(this.version, target) >= 0;
|
|
},
|
|
|
|
/**
|
|
* Returns whether this version if smaller than the supplied argument.
|
|
* @param {String/Number} target The version to compare with.
|
|
* @return {Boolean} `true` if this version if smaller than the target, `false` otherwise.
|
|
*/
|
|
isLessThan: function(target) {
|
|
return Version.compare(this.version, target) === -1;
|
|
},
|
|
|
|
/**
|
|
* Returns whether this version if less than or equal to the supplied argument.
|
|
* @param {String/Number} target The version to compare with.
|
|
* @return {Boolean} `true` if this version if less than or equal to the target, `false` otherwise.
|
|
*/
|
|
isLessThanOrEqual: function(target) {
|
|
return Version.compare(this.version, target) <= 0;
|
|
},
|
|
|
|
/**
|
|
* Returns whether this version equals to the supplied argument.
|
|
* @param {String/Number} target The version to compare with.
|
|
* @return {Boolean} `true` if this version equals to the target, `false` otherwise.
|
|
*/
|
|
equals: function(target) {
|
|
return Version.compare(this.version, target) === 0;
|
|
},
|
|
|
|
/**
|
|
* Returns whether this version matches the supplied argument. Example:
|
|
*
|
|
* var version = new Ext.Version('1.0.2beta');
|
|
* console.log(version.match(1)); // true
|
|
* console.log(version.match(1.0)); // true
|
|
* console.log(version.match('1.0.2')); // true
|
|
* console.log(version.match('1.0.2RC')); // false
|
|
*
|
|
* @param {String/Number} target The version to compare with.
|
|
* @return {Boolean} `true` if this version matches the target, `false` otherwise.
|
|
*/
|
|
match: function(target) {
|
|
target = String(target);
|
|
return this.version.substr(0, target.length) === target;
|
|
},
|
|
|
|
/**
|
|
* Returns this format: [major, minor, patch, build, release]. Useful for comparison.
|
|
* @return {Number[]}
|
|
*/
|
|
toArray: function() {
|
|
return [this.getMajor(), this.getMinor(), this.getPatch(), this.getBuild(), this.getRelease()];
|
|
},
|
|
|
|
/**
|
|
* Returns shortVersion version without dots and release.
|
|
* @return {String}
|
|
*/
|
|
getShortVersion: function() {
|
|
return this.shortVersion;
|
|
},
|
|
|
|
/**
|
|
* Convenient alias to {@link Ext.Version#isGreaterThan isGreaterThan}
|
|
* @param {String/Number} target
|
|
* @return {Boolean}
|
|
*/
|
|
gt: function() {
|
|
return this.isGreaterThan.apply(this, arguments);
|
|
},
|
|
|
|
/**
|
|
* Convenient alias to {@link Ext.Version#isLessThan isLessThan}
|
|
* @param {String/Number} target
|
|
* @return {Boolean}
|
|
*/
|
|
lt: function() {
|
|
return this.isLessThan.apply(this, arguments);
|
|
},
|
|
|
|
/**
|
|
* Convenient alias to {@link Ext.Version#isGreaterThanOrEqual isGreaterThanOrEqual}
|
|
* @param {String/Number} target
|
|
* @return {Boolean}
|
|
*/
|
|
gtEq: function() {
|
|
return this.isGreaterThanOrEqual.apply(this, arguments);
|
|
},
|
|
|
|
/**
|
|
* Convenient alias to {@link Ext.Version#isLessThanOrEqual isLessThanOrEqual}
|
|
* @param {String/Number} target
|
|
* @return {Boolean}
|
|
*/
|
|
ltEq: function() {
|
|
return this.isLessThanOrEqual.apply(this, arguments);
|
|
}
|
|
});
|
|
|
|
Ext.apply(Version, {
|
|
// @private
|
|
releaseValueMap: {
|
|
'dev': -6,
|
|
'alpha': -5,
|
|
'a': -5,
|
|
'beta': -4,
|
|
'b': -4,
|
|
'rc': -3,
|
|
'#': -2,
|
|
'p': -1,
|
|
'pl': -1
|
|
},
|
|
|
|
/**
|
|
* Converts a version component to a comparable value.
|
|
*
|
|
* @static
|
|
* @param {Object} value The value to convert
|
|
* @return {Object}
|
|
*/
|
|
getComponentValue: function(value) {
|
|
return !value ? 0 : (isNaN(value) ? this.releaseValueMap[value] || value : parseInt(value, 10));
|
|
},
|
|
|
|
/**
|
|
* Compare 2 specified versions, starting from left to right. If a part contains special version strings,
|
|
* they are handled in the following order:
|
|
* 'dev' < 'alpha' = 'a' < 'beta' = 'b' < 'RC' = 'rc' < '#' < 'pl' = 'p' < 'anything else'
|
|
*
|
|
* @static
|
|
* @param {String} current The current version to compare to.
|
|
* @param {String} target The target version to compare to.
|
|
* @return {Number} Returns -1 if the current version is smaller than the target version, 1 if greater, and 0 if they're equivalent.
|
|
*/
|
|
compare: function(current, target) {
|
|
var currentValue, targetValue, i;
|
|
|
|
current = new Version(current).toArray();
|
|
target = new Version(target).toArray();
|
|
|
|
for (i = 0; i < Math.max(current.length, target.length); i++) {
|
|
currentValue = this.getComponentValue(current[i]);
|
|
targetValue = this.getComponentValue(target[i]);
|
|
|
|
if (currentValue < targetValue) {
|
|
return -1;
|
|
} else if (currentValue > targetValue) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
});
|
|
|
|
Ext.apply(Ext, {
|
|
/**
|
|
* @private
|
|
*/
|
|
versions: {},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
lastRegisteredVersion: null,
|
|
|
|
/**
|
|
* Set version number for the given package name.
|
|
*
|
|
* @param {String} packageName The package name, for example: 'core', 'touch', 'extjs'.
|
|
* @param {String/Ext.Version} version The version, for example: '1.2.3alpha', '2.4.0-dev'.
|
|
* @return {Ext}
|
|
*/
|
|
setVersion: function(packageName, version) {
|
|
Ext.versions[packageName] = new Version(version);
|
|
Ext.lastRegisteredVersion = Ext.versions[packageName];
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Get the version number of the supplied package name; will return the last registered version
|
|
* (last `Ext.setVersion()` call) if there's no package name given.
|
|
*
|
|
* @param {String} packageName (Optional) The package name, for example: 'core', 'touch', 'extjs'.
|
|
* @return {Ext.Version} The version.
|
|
*/
|
|
getVersion: function(packageName) {
|
|
if (packageName === undefined) {
|
|
return Ext.lastRegisteredVersion;
|
|
}
|
|
|
|
return Ext.versions[packageName];
|
|
},
|
|
|
|
/**
|
|
* Create a closure for deprecated code.
|
|
*
|
|
* // This means Ext.oldMethod is only supported in 4.0.0beta and older.
|
|
* // If Ext.getVersion('extjs') returns a version that is later than '4.0.0beta', for example '4.0.0RC',
|
|
* // the closure will not be invoked
|
|
* Ext.deprecate('extjs', '4.0.0beta', function() {
|
|
* Ext.oldMethod = Ext.newMethod;
|
|
* // ...
|
|
* });
|
|
*
|
|
* @param {String} packageName The package name.
|
|
* @param {String} since The last version before it's deprecated.
|
|
* @param {Function} closure The callback function to be executed with the specified version is less than the current version.
|
|
* @param {Object} scope The execution scope (`this`) if the closure
|
|
*/
|
|
deprecate: function(packageName, since, closure, scope) {
|
|
if (Version.compare(Ext.getVersion(packageName), since) < 1) {
|
|
closure.call(scope);
|
|
}
|
|
}
|
|
}); // End Versioning
|
|
|
|
Ext.setVersion('core', version);
|
|
|
|
})();
|
|
|
|
//@tag foundation,core
|
|
//@define Ext.String
|
|
//@require Ext.Version
|
|
|
|
/**
|
|
* @class Ext.String
|
|
*
|
|
* A collection of useful static methods to deal with strings.
|
|
* @singleton
|
|
*/
|
|
|
|
Ext.String = {
|
|
trimRegex: /^[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+|[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+$/g,
|
|
escapeRe: /('|\\)/g,
|
|
formatRe: /\{(\d+)\}/g,
|
|
escapeRegexRe: /([-.*+?^${}()|[\]\/\\])/g,
|
|
|
|
/**
|
|
* Convert certain characters (&, <, >, and ") to their HTML character equivalents for literal display in web pages.
|
|
* @param {String} value The string to encode.
|
|
* @return {String} The encoded text.
|
|
* @method
|
|
*/
|
|
htmlEncode: (function() {
|
|
var entities = {
|
|
'&': '&',
|
|
'>': '>',
|
|
'<': '<',
|
|
'"': '"'
|
|
}, keys = [], p, regex;
|
|
|
|
for (p in entities) {
|
|
keys.push(p);
|
|
}
|
|
|
|
regex = new RegExp('(' + keys.join('|') + ')', 'g');
|
|
|
|
return function(value) {
|
|
return (!value) ? value : String(value).replace(regex, function(match, capture) {
|
|
return entities[capture];
|
|
});
|
|
};
|
|
})(),
|
|
|
|
/**
|
|
* Convert certain characters (&, <, >, and ") from their HTML character equivalents.
|
|
* @param {String} value The string to decode.
|
|
* @return {String} The decoded text.
|
|
* @method
|
|
*/
|
|
htmlDecode: (function() {
|
|
var entities = {
|
|
'&': '&',
|
|
'>': '>',
|
|
'<': '<',
|
|
'"': '"'
|
|
}, keys = [], p, regex;
|
|
|
|
for (p in entities) {
|
|
keys.push(p);
|
|
}
|
|
|
|
regex = new RegExp('(' + keys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
|
|
|
|
return function(value) {
|
|
return (!value) ? value : String(value).replace(regex, function(match, capture) {
|
|
if (capture in entities) {
|
|
return entities[capture];
|
|
} else {
|
|
return String.fromCharCode(parseInt(capture.substr(2), 10));
|
|
}
|
|
});
|
|
};
|
|
})(),
|
|
|
|
/**
|
|
* Appends content to the query string of a URL, handling logic for whether to place
|
|
* a question mark or ampersand.
|
|
* @param {String} url The URL to append to.
|
|
* @param {String} string The content to append to the URL.
|
|
* @return {String} The resulting URL.
|
|
*/
|
|
urlAppend : function(url, string) {
|
|
if (!Ext.isEmpty(string)) {
|
|
return url + (url.indexOf('?') === -1 ? '?' : '&') + string;
|
|
}
|
|
|
|
return url;
|
|
},
|
|
|
|
/**
|
|
* Trims whitespace from either end of a string, leaving spaces within the string intact. Example:
|
|
*
|
|
* @example
|
|
* var s = ' foo bar ';
|
|
* alert('-' + s + '-'); // alerts "- foo bar -"
|
|
* alert('-' + Ext.String.trim(s) + '-'); // alerts "-foo bar-"
|
|
*
|
|
* @param {String} string The string to escape
|
|
* @return {String} The trimmed string
|
|
*/
|
|
trim: function(string) {
|
|
return string.replace(Ext.String.trimRegex, "");
|
|
},
|
|
|
|
/**
|
|
* Capitalize the given string.
|
|
* @param {String} string
|
|
* @return {String}
|
|
*/
|
|
capitalize: function(string) {
|
|
return string.charAt(0).toUpperCase() + string.substr(1);
|
|
},
|
|
|
|
/**
|
|
* Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length.
|
|
* @param {String} value The string to truncate.
|
|
* @param {Number} length The maximum length to allow before truncating.
|
|
* @param {Boolean} word `true` to try to find a common word break.
|
|
* @return {String} The converted text.
|
|
*/
|
|
ellipsis: function(value, len, word) {
|
|
if (value && value.length > len) {
|
|
if (word) {
|
|
var vs = value.substr(0, len - 2),
|
|
index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
|
|
if (index !== -1 && index >= (len - 15)) {
|
|
return vs.substr(0, index) + "...";
|
|
}
|
|
}
|
|
return value.substr(0, len - 3) + "...";
|
|
}
|
|
return value;
|
|
},
|
|
|
|
/**
|
|
* Escapes the passed string for use in a regular expression.
|
|
* @param {String} string
|
|
* @return {String}
|
|
*/
|
|
escapeRegex: function(string) {
|
|
return string.replace(Ext.String.escapeRegexRe, "\\$1");
|
|
},
|
|
|
|
/**
|
|
* Escapes the passed string for ' and \.
|
|
* @param {String} string The string to escape.
|
|
* @return {String} The escaped string.
|
|
*/
|
|
escape: function(string) {
|
|
return string.replace(Ext.String.escapeRe, "\\$1");
|
|
},
|
|
|
|
/**
|
|
* Utility function that allows you to easily switch a string between two alternating values. The passed value
|
|
* is compared to the current string, and if they are equal, the other value that was passed in is returned. If
|
|
* they are already different, the first value passed in is returned. Note that this method returns the new value
|
|
* but does not change the current string.
|
|
*
|
|
* // alternate sort directions
|
|
* sort = Ext.String.toggle(sort, 'ASC', 'DESC');
|
|
*
|
|
* // instead of conditional logic:
|
|
* sort = (sort == 'ASC' ? 'DESC' : 'ASC');
|
|
*
|
|
* @param {String} string The current string.
|
|
* @param {String} value The value to compare to the current string.
|
|
* @param {String} other The new value to use if the string already equals the first value passed in.
|
|
* @return {String} The new value.
|
|
*/
|
|
toggle: function(string, value, other) {
|
|
return string === value ? other : value;
|
|
},
|
|
|
|
/**
|
|
* Pads the left side of a string with a specified character. This is especially useful
|
|
* for normalizing number and date strings. Example usage:
|
|
*
|
|
* var s = Ext.String.leftPad('123', 5, '0');
|
|
* alert(s); // '00123'
|
|
*
|
|
* @param {String} string The original string.
|
|
* @param {Number} size The total length of the output string.
|
|
* @param {String} [character= ] (optional) The character with which to pad the original string (defaults to empty string " ").
|
|
* @return {String} The padded string.
|
|
*/
|
|
leftPad: function(string, size, character) {
|
|
var result = String(string);
|
|
character = character || " ";
|
|
while (result.length < size) {
|
|
result = character + result;
|
|
}
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens. Each
|
|
* token must be unique, and must increment in the format {0}, {1}, etc. Example usage:
|
|
*
|
|
* var cls = 'my-class',
|
|
* text = 'Some text';
|
|
* var s = Ext.String.format('<div class="{0}">{1}</div>', cls, text);
|
|
* alert(s); // '<div class="my-class">Some text</div>'
|
|
*
|
|
* @param {String} string The tokenized string to be formatted.
|
|
* @param {String} value1 The value to replace token {0}.
|
|
* @param {String} value2 Etc...
|
|
* @return {String} The formatted string.
|
|
*/
|
|
format: function(format) {
|
|
var args = Ext.Array.toArray(arguments, 1);
|
|
return format.replace(Ext.String.formatRe, function(m, i) {
|
|
return args[i];
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Returns a string with a specified number of repetitions a given string pattern.
|
|
* The pattern be separated by a different string.
|
|
*
|
|
* var s = Ext.String.repeat('---', 4); // '------------'
|
|
* var t = Ext.String.repeat('--', 3, '/'); // '--/--/--'
|
|
*
|
|
* @param {String} pattern The pattern to repeat.
|
|
* @param {Number} count The number of times to repeat the pattern (may be 0).
|
|
* @param {String} sep An option string to separate each pattern.
|
|
*/
|
|
repeat: function(pattern, count, sep) {
|
|
for (var buf = [], i = count; i--; ) {
|
|
buf.push(pattern);
|
|
}
|
|
return buf.join(sep || '');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Old alias to {@link Ext.String#htmlEncode}.
|
|
* @deprecated Use {@link Ext.String#htmlEncode} instead.
|
|
* @method
|
|
* @member Ext
|
|
* @alias Ext.String#htmlEncode
|
|
*/
|
|
Ext.htmlEncode = Ext.String.htmlEncode;
|
|
|
|
|
|
/**
|
|
* Old alias to {@link Ext.String#htmlDecode}.
|
|
* @deprecated Use {@link Ext.String#htmlDecode} instead.
|
|
* @method
|
|
* @member Ext
|
|
* @alias Ext.String#htmlDecode
|
|
*/
|
|
Ext.htmlDecode = Ext.String.htmlDecode;
|
|
|
|
/**
|
|
* Old alias to {@link Ext.String#urlAppend}.
|
|
* @deprecated Use {@link Ext.String#urlAppend} instead.
|
|
* @method
|
|
* @member Ext
|
|
* @alias Ext.String#urlAppend
|
|
*/
|
|
Ext.urlAppend = Ext.String.urlAppend;
|
|
|
|
//@tag foundation,core
|
|
//@define Ext.Array
|
|
//@require Ext.String
|
|
|
|
/**
|
|
* @class Ext.Array
|
|
* @singleton
|
|
* @author Jacky Nguyen <jacky@sencha.com>
|
|
* @docauthor Jacky Nguyen <jacky@sencha.com>
|
|
*
|
|
* A set of useful static methods to deal with arrays; provide missing methods for older browsers.
|
|
*/
|
|
(function() {
|
|
|
|
var arrayPrototype = Array.prototype,
|
|
slice = arrayPrototype.slice,
|
|
supportsSplice = function () {
|
|
var array = [],
|
|
lengthBefore,
|
|
j = 20;
|
|
|
|
if (!array.splice) {
|
|
return false;
|
|
}
|
|
|
|
// This detects a bug in IE8 splice method:
|
|
// see http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/6e946d03-e09f-4b22-a4dd-cd5e276bf05a/
|
|
|
|
while (j--) {
|
|
array.push("A");
|
|
}
|
|
|
|
array.splice(15, 0, "F", "F", "F", "F", "F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F");
|
|
|
|
lengthBefore = array.length; //41
|
|
array.splice(13, 0, "XXX"); // add one element
|
|
|
|
if (lengthBefore+1 != array.length) {
|
|
return false;
|
|
}
|
|
// end IE8 bug
|
|
|
|
return true;
|
|
}(),
|
|
supportsForEach = 'forEach' in arrayPrototype,
|
|
supportsMap = 'map' in arrayPrototype,
|
|
supportsIndexOf = 'indexOf' in arrayPrototype,
|
|
supportsEvery = 'every' in arrayPrototype,
|
|
supportsSome = 'some' in arrayPrototype,
|
|
supportsFilter = 'filter' in arrayPrototype,
|
|
supportsSort = function() {
|
|
var a = [1,2,3,4,5].sort(function(){ return 0; });
|
|
return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
|
|
}(),
|
|
supportsSliceOnNodeList = true,
|
|
ExtArray;
|
|
|
|
try {
|
|
// IE 6 - 8 will throw an error when using Array.prototype.slice on NodeList
|
|
if (typeof document !== 'undefined') {
|
|
slice.call(document.getElementsByTagName('body'));
|
|
}
|
|
} catch (e) {
|
|
supportsSliceOnNodeList = false;
|
|
}
|
|
|
|
function fixArrayIndex (array, index) {
|
|
return (index < 0) ? Math.max(0, array.length + index)
|
|
: Math.min(array.length, index);
|
|
}
|
|
|
|
/*
|
|
Does the same work as splice, but with a slightly more convenient signature. The splice
|
|
method has bugs in IE8, so this is the implementation we use on that platform.
|
|
|
|
The rippling of items in the array can be tricky. Consider two use cases:
|
|
|
|
index=2
|
|
removeCount=2
|
|
/=====\
|
|
+---+---+---+---+---+---+---+---+
|
|
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|
|
+---+---+---+---+---+---+---+---+
|
|
/ \/ \/ \/ \
|
|
/ /\ /\ /\ \
|
|
/ / \/ \/ \ +--------------------------+
|
|
/ / /\ /\ +--------------------------+ \
|
|
/ / / \/ +--------------------------+ \ \
|
|
/ / / /+--------------------------+ \ \ \
|
|
/ / / / \ \ \ \
|
|
v v v v v v v v
|
|
+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+
|
|
| 0 | 1 | 4 | 5 | 6 | 7 | | 0 | 1 | a | b | c | 4 | 5 | 6 | 7 |
|
|
+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+
|
|
A B \=========/
|
|
insert=[a,b,c]
|
|
|
|
In case A, it is obvious that copying of [4,5,6,7] must be left-to-right so
|
|
that we don't end up with [0,1,6,7,6,7]. In case B, we have the opposite; we
|
|
must go right-to-left or else we would end up with [0,1,a,b,c,4,4,4,4].
|
|
*/
|
|
function replaceSim (array, index, removeCount, insert) {
|
|
var add = insert ? insert.length : 0,
|
|
length = array.length,
|
|
pos = fixArrayIndex(array, index);
|
|
|
|
// we try to use Array.push when we can for efficiency...
|
|
if (pos === length) {
|
|
if (add) {
|
|
array.push.apply(array, insert);
|
|
}
|
|
} else {
|
|
var remove = Math.min(removeCount, length - pos),
|
|
tailOldPos = pos + remove,
|
|
tailNewPos = tailOldPos + add - remove,
|
|
tailCount = length - tailOldPos,
|
|
lengthAfterRemove = length - remove,
|
|
i;
|
|
|
|
if (tailNewPos < tailOldPos) { // case A
|
|
for (i = 0; i < tailCount; ++i) {
|
|
array[tailNewPos+i] = array[tailOldPos+i];
|
|
}
|
|
} else if (tailNewPos > tailOldPos) { // case B
|
|
for (i = tailCount; i--; ) {
|
|
array[tailNewPos+i] = array[tailOldPos+i];
|
|
}
|
|
} // else, add == remove (nothing to do)
|
|
|
|
if (add && pos === lengthAfterRemove) {
|
|
array.length = lengthAfterRemove; // truncate array
|
|
array.push.apply(array, insert);
|
|
} else {
|
|
array.length = lengthAfterRemove + add; // reserves space
|
|
for (i = 0; i < add; ++i) {
|
|
array[pos+i] = insert[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
function replaceNative (array, index, removeCount, insert) {
|
|
if (insert && insert.length) {
|
|
if (index < array.length) {
|
|
array.splice.apply(array, [index, removeCount].concat(insert));
|
|
} else {
|
|
array.push.apply(array, insert);
|
|
}
|
|
} else {
|
|
array.splice(index, removeCount);
|
|
}
|
|
return array;
|
|
}
|
|
|
|
function eraseSim (array, index, removeCount) {
|
|
return replaceSim(array, index, removeCount);
|
|
}
|
|
|
|
function eraseNative (array, index, removeCount) {
|
|
array.splice(index, removeCount);
|
|
return array;
|
|
}
|
|
|
|
function spliceSim (array, index, removeCount) {
|
|
var pos = fixArrayIndex(array, index),
|
|
removed = array.slice(index, fixArrayIndex(array, pos+removeCount));
|
|
|
|
if (arguments.length < 4) {
|
|
replaceSim(array, pos, removeCount);
|
|
} else {
|
|
replaceSim(array, pos, removeCount, slice.call(arguments, 3));
|
|
}
|
|
|
|
return removed;
|
|
}
|
|
|
|
function spliceNative (array) {
|
|
return array.splice.apply(array, slice.call(arguments, 1));
|
|
}
|
|
|
|
var erase = supportsSplice ? eraseNative : eraseSim,
|
|
replace = supportsSplice ? replaceNative : replaceSim,
|
|
splice = supportsSplice ? spliceNative : spliceSim;
|
|
|
|
// NOTE: from here on, use erase, replace or splice (not native methods)...
|
|
ExtArray = Ext.Array = {
|
|
/**
|
|
* Iterates an array or an iterable value and invoke the given callback function for each item.
|
|
*
|
|
* var countries = ['Vietnam', 'Singapore', 'United States', 'Russia'];
|
|
*
|
|
* Ext.Array.each(countries, function(name, index, countriesItSelf) {
|
|
* console.log(name);
|
|
* });
|
|
*
|
|
* var sum = function() {
|
|
* var sum = 0;
|
|
*
|
|
* Ext.Array.each(arguments, function(value) {
|
|
* sum += value;
|
|
* });
|
|
*
|
|
* return sum;
|
|
* };
|
|
*
|
|
* sum(1, 2, 3); // returns 6
|
|
*
|
|
* The iteration can be stopped by returning false in the function callback.
|
|
*
|
|
* Ext.Array.each(countries, function(name, index, countriesItSelf) {
|
|
* if (name === 'Singapore') {
|
|
* return false; // break here
|
|
* }
|
|
* });
|
|
*
|
|
* {@link Ext#each Ext.each} is alias for {@link Ext.Array#each Ext.Array.each}
|
|
*
|
|
* @param {Array/NodeList/Object} iterable The value to be iterated. If this
|
|
* argument is not iterable, the callback function is called once.
|
|
* @param {Function} fn The callback function. If it returns `false`, the iteration stops and this method returns
|
|
* the current `index`.
|
|
* @param {Object} fn.item The item at the current `index` in the passed `array`
|
|
* @param {Number} fn.index The current `index` within the `array`
|
|
* @param {Array} fn.allItems The `array` itself which was passed as the first argument
|
|
* @param {Boolean} fn.return Return false to stop iteration.
|
|
* @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
|
|
* @param {Boolean} [reverse=false] (Optional) Reverse the iteration order (loop from the end to the beginning).
|
|
* @return {Boolean} See description for the `fn` parameter.
|
|
*/
|
|
each: function(array, fn, scope, reverse) {
|
|
array = ExtArray.from(array);
|
|
|
|
var i,
|
|
ln = array.length;
|
|
|
|
if (reverse !== true) {
|
|
for (i = 0; i < ln; i++) {
|
|
if (fn.call(scope || array[i], array[i], i, array) === false) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (i = ln - 1; i > -1; i--) {
|
|
if (fn.call(scope || array[i], array[i], i, array) === false) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* Iterates an array and invoke the given callback function for each item. Note that this will simply
|
|
* delegate to the native `Array.prototype.forEach` method if supported. It doesn't support stopping the
|
|
* iteration by returning `false` in the callback function like {@link Ext.Array#each}. However, performance
|
|
* could be much better in modern browsers comparing with {@link Ext.Array#each}
|
|
*
|
|
* @param {Array} array The array to iterate.
|
|
* @param {Function} fn The callback function.
|
|
* @param {Object} fn.item The item at the current `index` in the passed `array`.
|
|
* @param {Number} fn.index The current `index` within the `array`.
|
|
* @param {Array} fn.allItems The `array` itself which was passed as the first argument.
|
|
* @param {Object} scope (Optional) The execution scope (`this`) in which the specified function is executed.
|
|
*/
|
|
forEach: supportsForEach ? function(array, fn, scope) {
|
|
return array.forEach(fn, scope);
|
|
} : function(array, fn, scope) {
|
|
var i = 0,
|
|
ln = array.length;
|
|
|
|
for (; i < ln; i++) {
|
|
fn.call(scope, array[i], i, array);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Get the index of the provided `item` in the given `array`, a supplement for the
|
|
* missing arrayPrototype.indexOf in Internet Explorer.
|
|
*
|
|
* @param {Array} array The array to check.
|
|
* @param {Object} item The item to look for.
|
|
* @param {Number} from (Optional) The index at which to begin the search.
|
|
* @return {Number} The index of item in the array (or -1 if it is not found).
|
|
*/
|
|
indexOf: (supportsIndexOf) ? function(array, item, from) {
|
|
return array.indexOf(item, from);
|
|
} : function(array, item, from) {
|
|
var i, length = array.length;
|
|
|
|
for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) {
|
|
if (array[i] === item) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
},
|
|
|
|
/**
|
|
* Checks whether or not the given `array` contains the specified `item`.
|
|
*
|
|
* @param {Array} array The array to check.
|
|
* @param {Object} item The item to look for.
|
|
* @return {Boolean} `true` if the array contains the item, `false` otherwise.
|
|
*/
|
|
contains: supportsIndexOf ? function(array, item) {
|
|
return array.indexOf(item) !== -1;
|
|
} : function(array, item) {
|
|
var i, ln;
|
|
|
|
for (i = 0, ln = array.length; i < ln; i++) {
|
|
if (array[i] === item) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Converts any iterable (numeric indices and a length property) into a true array.
|
|
*
|
|
* function test() {
|
|
* var args = Ext.Array.toArray(arguments),
|
|
* fromSecondToLastArgs = Ext.Array.toArray(arguments, 1);
|
|
*
|
|
* alert(args.join(' '));
|
|
* alert(fromSecondToLastArgs.join(' '));
|
|
* }
|
|
*
|
|
* test('just', 'testing', 'here'); // alerts 'just testing here';
|
|
* // alerts 'testing here';
|
|
*
|
|
* Ext.Array.toArray(document.getElementsByTagName('div')); // will convert the NodeList into an array
|
|
* Ext.Array.toArray('splitted'); // returns ['s', 'p', 'l', 'i', 't', 't', 'e', 'd']
|
|
* Ext.Array.toArray('splitted', 0, 3); // returns ['s', 'p', 'l', 'i']
|
|
*
|
|
* {@link Ext#toArray Ext.toArray} is alias for {@link Ext.Array#toArray Ext.Array.toArray}
|
|
*
|
|
* @param {Object} iterable the iterable object to be turned into a true Array.
|
|
* @param {Number} [start=0] (Optional) a zero-based index that specifies the start of extraction.
|
|
* @param {Number} [end=-1] (Optional) a zero-based index that specifies the end of extraction.
|
|
* @return {Array}
|
|
*/
|
|
toArray: function(iterable, start, end){
|
|
if (!iterable || !iterable.length) {
|
|
return [];
|
|
}
|
|
|
|
if (typeof iterable === 'string') {
|
|
iterable = iterable.split('');
|
|
}
|
|
|
|
if (supportsSliceOnNodeList) {
|
|
return slice.call(iterable, start || 0, end || iterable.length);
|
|
}
|
|
|
|
var array = [],
|
|
i;
|
|
|
|
start = start || 0;
|
|
end = end ? ((end < 0) ? iterable.length + end : end) : iterable.length;
|
|
|
|
for (i = start; i < end; i++) {
|
|
array.push(iterable[i]);
|
|
}
|
|
|
|
return array;
|
|
},
|
|
|
|
/**
|
|
* Plucks the value of a property from each item in the Array. Example:
|
|
*
|
|
* Ext.Array.pluck(Ext.query("p"), "className"); // [el1.className, el2.className, ..., elN.className]
|
|
*
|
|
* @param {Array/NodeList} array The Array of items to pluck the value from.
|
|
* @param {String} propertyName The property name to pluck from each element.
|
|
* @return {Array} The value from each item in the Array.
|
|
*/
|
|
pluck: function(array, propertyName) {
|
|
var ret = [],
|
|
i, ln, item;
|
|
|
|
for (i = 0, ln = array.length; i < ln; i++) {
|
|
item = array[i];
|
|
|
|
ret.push(item[propertyName]);
|
|
}
|
|
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
* Creates a new array with the results of calling a provided function on every element in this array.
|
|
*
|
|
* @param {Array} array
|
|
* @param {Function} fn Callback function for each item.
|
|
* @param {Object} scope Callback function scope.
|
|
* @return {Array} results
|
|
*/
|
|
map: supportsMap ? function(array, fn, scope) {
|
|
return array.map(fn, scope);
|
|
} : function(array, fn, scope) {
|
|
var results = [],
|
|
i = 0,
|
|
len = array.length;
|
|
|
|
for (; i < len; i++) {
|
|
results[i] = fn.call(scope, array[i], i, array);
|
|
}
|
|
|
|
return results;
|
|
},
|
|
|
|
/**
|
|
* Executes the specified function for each array element until the function returns a falsy value.
|
|
* If such an item is found, the function will return `false` immediately.
|
|
* Otherwise, it will return `true`.
|
|
*
|
|
* @param {Array} array
|
|
* @param {Function} fn Callback function for each item.
|
|
* @param {Object} scope Callback function scope.
|
|
* @return {Boolean} `true` if no `false` value is returned by the callback function.
|
|
*/
|
|
every: function(array, fn, scope) {
|
|
//<debug>
|
|
if (!fn) {
|
|
Ext.Error.raise('Ext.Array.every must have a callback function passed as second argument.');
|
|
}
|
|
//</debug>
|
|
if (supportsEvery) {
|
|
return array.every(fn, scope);
|
|
}
|
|
|
|
var i = 0,
|
|
ln = array.length;
|
|
|
|
for (; i < ln; ++i) {
|
|
if (!fn.call(scope, array[i], i, array)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* Executes the specified function for each array element until the function returns a truthy value.
|
|
* If such an item is found, the function will return `true` immediately. Otherwise, it will return `false`.
|
|
*
|
|
* @param {Array} array
|
|
* @param {Function} fn Callback function for each item.
|
|
* @param {Object} scope Callback function scope.
|
|
* @return {Boolean} `true` if the callback function returns a truthy value.
|
|
*/
|
|
some: function(array, fn, scope) {
|
|
//<debug>
|
|
if (!fn) {
|
|
Ext.Error.raise('Ext.Array.some must have a callback function passed as second argument.');
|
|
}
|
|
//</debug>
|
|
if (supportsSome) {
|
|
return array.some(fn, scope);
|
|
}
|
|
|
|
var i = 0,
|
|
ln = array.length;
|
|
|
|
for (; i < ln; ++i) {
|
|
if (fn.call(scope, array[i], i, array)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Filter through an array and remove empty item as defined in {@link Ext#isEmpty Ext.isEmpty}.
|
|
*
|
|
* See {@link Ext.Array#filter}
|
|
*
|
|
* @param {Array} array
|
|
* @return {Array} results
|
|
*/
|
|
clean: function(array) {
|
|
var results = [],
|
|
i = 0,
|
|
ln = array.length,
|
|
item;
|
|
|
|
for (; i < ln; i++) {
|
|
item = array[i];
|
|
|
|
if (!Ext.isEmpty(item)) {
|
|
results.push(item);
|
|
}
|
|
}
|
|
|
|
return results;
|
|
},
|
|
|
|
/**
|
|
* Returns a new array with unique items.
|
|
*
|
|
* @param {Array} array
|
|
* @return {Array} results
|
|
*/
|
|
unique: function(array) {
|
|
var clone = [],
|
|
i = 0,
|
|
ln = array.length,
|
|
item;
|
|
|
|
for (; i < ln; i++) {
|
|
item = array[i];
|
|
|
|
if (ExtArray.indexOf(clone, item) === -1) {
|
|
clone.push(item);
|
|
}
|
|
}
|
|
|
|
return clone;
|
|
},
|
|
|
|
/**
|
|
* Creates a new array with all of the elements of this array for which
|
|
* the provided filtering function returns `true`.
|
|
*
|
|
* @param {Array} array
|
|
* @param {Function} fn Callback function for each item.
|
|
* @param {Object} scope Callback function scope.
|
|
* @return {Array} results
|
|
*/
|
|
filter: function(array, fn, scope) {
|
|
if (supportsFilter) {
|
|
return array.filter(fn, scope);
|
|
}
|
|
|
|
var results = [],
|
|
i = 0,
|
|
ln = array.length;
|
|
|
|
for (; i < ln; i++) {
|
|
if (fn.call(scope, array[i], i, array)) {
|
|
results.push(array[i]);
|
|
}
|
|
}
|
|
|
|
return results;
|
|
},
|
|
|
|
/**
|
|
* Converts a value to an array if it's not already an array; returns:
|
|
*
|
|
* - An empty array if given value is `undefined` or `null`
|
|
* - Itself if given value is already an array
|
|
* - An array copy if given value is {@link Ext#isIterable iterable} (arguments, NodeList and alike)
|
|
* - An array with one item which is the given value, otherwise
|
|
*
|
|
* @param {Object} value The value to convert to an array if it's not already is an array.
|
|
* @param {Boolean} [newReference=false] (Optional) `true` to clone the given array and return a new reference if necessary.
|
|
* @return {Array} array
|
|
*/
|
|
from: function(value, newReference) {
|
|
if (value === undefined || value === null) {
|
|
return [];
|
|
}
|
|
|
|
if (Ext.isArray(value)) {
|
|
return (newReference) ? slice.call(value) : value;
|
|
}
|
|
|
|
if (value && value.length !== undefined && typeof value !== 'string') {
|
|
return ExtArray.toArray(value);
|
|
}
|
|
|
|
return [value];
|
|
},
|
|
|
|
/**
|
|
* Removes the specified item from the array if it exists.
|
|
*
|
|
* @param {Array} array The array.
|
|
* @param {Object} item The item to remove.
|
|
* @return {Array} The passed array itself.
|
|
*/
|
|
remove: function(array, item) {
|
|
var index = ExtArray.indexOf(array, item);
|
|
|
|
if (index !== -1) {
|
|
erase(array, index, 1);
|
|
}
|
|
|
|
return array;
|
|
},
|
|
|
|
/**
|
|
* Push an item into the array only if the array doesn't contain it yet.
|
|
*
|
|
* @param {Array} array The array.
|
|
* @param {Object} item The item to include.
|
|
*/
|
|
include: function(array, item) {
|
|
if (!ExtArray.contains(array, item)) {
|
|
array.push(item);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Clone a flat array without referencing the previous one. Note that this is different
|
|
* from `Ext.clone` since it doesn't handle recursive cloning. It's simply a convenient, easy-to-remember method
|
|
* for `Array.prototype.slice.call(array)`.
|
|
*
|
|
* @param {Array} array The array
|
|
* @return {Array} The clone array
|
|
*/
|
|
clone: function(array) {
|
|
return slice.call(array);
|
|
},
|
|
|
|
/**
|
|
* Merge multiple arrays into one with unique items.
|
|
*
|
|
* {@link Ext.Array#union} is alias for {@link Ext.Array#merge}
|
|
*
|
|
* @param {Array} array1
|
|
* @param {Array} array2
|
|
* @param {Array} etc
|
|
* @return {Array} merged
|
|
*/
|
|
merge: function() {
|
|
var args = slice.call(arguments),
|
|
array = [],
|
|
i, ln;
|
|
|
|
for (i = 0, ln = args.length; i < ln; i++) {
|
|
array = array.concat(args[i]);
|
|
}
|
|
|
|
return ExtArray.unique(array);
|
|
},
|
|
|
|
/**
|
|
* Merge multiple arrays into one with unique items that exist in all of the arrays.
|
|
*
|
|
* @param {Array} array1
|
|
* @param {Array} array2
|
|
* @param {Array} etc
|
|
* @return {Array} intersect
|
|
*/
|
|
intersect: function() {
|
|
var intersect = [],
|
|
arrays = slice.call(arguments),
|
|
i, j, k, minArray, array, x, y, ln, arraysLn, arrayLn;
|
|
|
|
if (!arrays.length) {
|
|
return intersect;
|
|
}
|
|
|
|
// Find the smallest array
|
|
for (i = x = 0,ln = arrays.length; i < ln,array = arrays[i]; i++) {
|
|
if (!minArray || array.length < minArray.length) {
|
|
minArray = array;
|
|
x = i;
|
|
}
|
|
}
|
|
|
|
minArray = ExtArray.unique(minArray);
|
|
erase(arrays, x, 1);
|
|
|
|
// Use the smallest unique'd array as the anchor loop. If the other array(s) do contain
|
|
// an item in the small array, we're likely to find it before reaching the end
|
|
// of the inner loop and can terminate the search early.
|
|
for (i = 0,ln = minArray.length; i < ln,x = minArray[i]; i++) {
|
|
var count = 0;
|
|
|
|
for (j = 0,arraysLn = arrays.length; j < arraysLn,array = arrays[j]; j++) {
|
|
for (k = 0,arrayLn = array.length; k < arrayLn,y = array[k]; k++) {
|
|
if (x === y) {
|
|
count++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count === arraysLn) {
|
|
intersect.push(x);
|
|
}
|
|
}
|
|
|
|
return intersect;
|
|
},
|
|
|
|
/**
|
|
* Perform a set difference A-B by subtracting all items in array B from array A.
|
|
*
|
|
* @param {Array} arrayA
|
|
* @param {Array} arrayB
|
|
* @return {Array} difference
|
|
*/
|
|
difference: function(arrayA, arrayB) {
|
|
var clone = slice.call(arrayA),
|
|
ln = clone.length,
|
|
i, j, lnB;
|
|
|
|
for (i = 0,lnB = arrayB.length; i < lnB; i++) {
|
|
for (j = 0; j < ln; j++) {
|
|
if (clone[j] === arrayB[i]) {
|
|
erase(clone, j, 1);
|
|
j--;
|
|
ln--;
|
|
}
|
|
}
|
|
}
|
|
|
|
return clone;
|
|
},
|
|
|
|
/**
|
|
* Returns a shallow copy of a part of an array. This is equivalent to the native
|
|
* call `Array.prototype.slice.call(array, begin, end)`. This is often used when "array"
|
|
* is "arguments" since the arguments object does not supply a slice method but can
|
|
* be the context object to `Array.prototype.slice()`.
|
|
*
|
|
* @param {Array} array The array (or arguments object).
|
|
* @param {Number} begin The index at which to begin. Negative values are offsets from
|
|
* the end of the array.
|
|
* @param {Number} end The index at which to end. The copied items do not include
|
|
* end. Negative values are offsets from the end of the array. If end is omitted,
|
|
* all items up to the end of the array are copied.
|
|
* @return {Array} The copied piece of the array.
|
|
*/
|
|
slice: function(array, begin, end) {
|
|
return slice.call(array, begin, end);
|
|
},
|
|
|
|
/**
|
|
* Sorts the elements of an Array.
|
|
* By default, this method sorts the elements alphabetically and ascending.
|
|
*
|
|
* @param {Array} array The array to sort.
|
|
* @param {Function} sortFn (optional) The comparison function.
|
|
* @return {Array} The sorted array.
|
|
*/
|
|
sort: function(array, sortFn) {
|
|
if (supportsSort) {
|
|
if (sortFn) {
|
|
return array.sort(sortFn);
|
|
} else {
|
|
return array.sort();
|
|
}
|
|
}
|
|
|
|
var length = array.length,
|
|
i = 0,
|
|
comparison,
|
|
j, min, tmp;
|
|
|
|
for (; i < length; i++) {
|
|
min = i;
|
|
for (j = i + 1; j < length; j++) {
|
|
if (sortFn) {
|
|
comparison = sortFn(array[j], array[min]);
|
|
if (comparison < 0) {
|
|
min = j;
|
|
}
|
|
} else if (array[j] < array[min]) {
|
|
min = j;
|
|
}
|
|
}
|
|
if (min !== i) {
|
|
tmp = array[i];
|
|
array[i] = array[min];
|
|
array[min] = tmp;
|
|
}
|
|
}
|
|
|
|
return array;
|
|
},
|
|
|
|
/**
|
|
* Recursively flattens into 1-d Array. Injects Arrays inline.
|
|
*
|
|
* @param {Array} array The array to flatten
|
|
* @return {Array} The 1-d array.
|
|
*/
|
|
flatten: function(array) {
|
|
var worker = [];
|
|
|
|
function rFlatten(a) {
|
|
var i, ln, v;
|
|
|
|
for (i = 0, ln = a.length; i < ln; i++) {
|
|
v = a[i];
|
|
|
|
if (Ext.isArray(v)) {
|
|
rFlatten(v);
|
|
} else {
|
|
worker.push(v);
|
|
}
|
|
}
|
|
|
|
return worker;
|
|
}
|
|
|
|
return rFlatten(array);
|
|
},
|
|
|
|
/**
|
|
* Returns the minimum value in the Array.
|
|
*
|
|
* @param {Array/NodeList} array The Array from which to select the minimum value.
|
|
* @param {Function} comparisonFn (optional) a function to perform the comparison which determines minimization.
|
|
* If omitted the "<" operator will be used.
|
|
* __Note:__ gt = 1; eq = 0; lt = -1
|
|
* @return {Object} minValue The minimum value.
|
|
*/
|
|
min: function(array, comparisonFn) {
|
|
var min = array[0],
|
|
i, ln, item;
|
|
|
|
for (i = 0, ln = array.length; i < ln; i++) {
|
|
item = array[i];
|
|
|
|
if (comparisonFn) {
|
|
if (comparisonFn(min, item) === 1) {
|
|
min = item;
|
|
}
|
|
}
|
|
else {
|
|
if (item < min) {
|
|
min = item;
|
|
}
|
|
}
|
|
}
|
|
|
|
return min;
|
|
},
|
|
|
|
/**
|
|
* Returns the maximum value in the Array.
|
|
*
|
|
* @param {Array/NodeList} array The Array from which to select the maximum value.
|
|
* @param {Function} comparisonFn (optional) a function to perform the comparison which determines maximization.
|
|
* If omitted the ">" operator will be used.
|
|
* __Note:__ gt = 1; eq = 0; lt = -1
|
|
* @return {Object} maxValue The maximum value
|
|
*/
|
|
max: function(array, comparisonFn) {
|
|
var max = array[0],
|
|
i, ln, item;
|
|
|
|
for (i = 0, ln = array.length; i < ln; i++) {
|
|
item = array[i];
|
|
|
|
if (comparisonFn) {
|
|
if (comparisonFn(max, item) === -1) {
|
|
max = item;
|
|
}
|
|
}
|
|
else {
|
|
if (item > max) {
|
|
max = item;
|
|
}
|
|
}
|
|
}
|
|
|
|
return max;
|
|
},
|
|
|
|
/**
|
|
* Calculates the mean of all items in the array.
|
|
*
|
|
* @param {Array} array The Array to calculate the mean value of.
|
|
* @return {Number} The mean.
|
|
*/
|
|
mean: function(array) {
|
|
return array.length > 0 ? ExtArray.sum(array) / array.length : undefined;
|
|
},
|
|
|
|
/**
|
|
* Calculates the sum of all items in the given array.
|
|
*
|
|
* @param {Array} array The Array to calculate the sum value of.
|
|
* @return {Number} The sum.
|
|
*/
|
|
sum: function(array) {
|
|
var sum = 0,
|
|
i, ln, item;
|
|
|
|
for (i = 0,ln = array.length; i < ln; i++) {
|
|
item = array[i];
|
|
|
|
sum += item;
|
|
}
|
|
|
|
return sum;
|
|
},
|
|
|
|
//<debug>
|
|
_replaceSim: replaceSim, // for unit testing
|
|
_spliceSim: spliceSim,
|
|
//</debug>
|
|
|
|
/**
|
|
* Removes items from an array. This is functionally equivalent to the splice method
|
|
* of Array, but works around bugs in IE8's splice method and does not copy the
|
|
* removed elements in order to return them (because very often they are ignored).
|
|
*
|
|
* @param {Array} array The Array on which to replace.
|
|
* @param {Number} index The index in the array at which to operate.
|
|
* @param {Number} removeCount The number of items to remove at index.
|
|
* @return {Array} The array passed.
|
|
* @method
|
|
*/
|
|
erase: erase,
|
|
|
|
/**
|
|
* Inserts items in to an array.
|
|
*
|
|
* @param {Array} array The Array on which to replace.
|
|
* @param {Number} index The index in the array at which to operate.
|
|
* @param {Array} items The array of items to insert at index.
|
|
* @return {Array} The array passed.
|
|
*/
|
|
insert: function (array, index, items) {
|
|
return replace(array, index, 0, items);
|
|
},
|
|
|
|
/**
|
|
* Replaces items in an array. This is functionally equivalent to the splice method
|
|
* of Array, but works around bugs in IE8's splice method and is often more convenient
|
|
* to call because it accepts an array of items to insert rather than use a variadic
|
|
* argument list.
|
|
*
|
|
* @param {Array} array The Array on which to replace.
|
|
* @param {Number} index The index in the array at which to operate.
|
|
* @param {Number} removeCount The number of items to remove at index (can be 0).
|
|
* @param {Array} insert (optional) An array of items to insert at index.
|
|
* @return {Array} The array passed.
|
|
* @method
|
|
*/
|
|
replace: replace,
|
|
|
|
/**
|
|
* Replaces items in an array. This is equivalent to the splice method of Array, but
|
|
* works around bugs in IE8's splice method. The signature is exactly the same as the
|
|
* splice method except that the array is the first argument. All arguments following
|
|
* removeCount are inserted in the array at index.
|
|
*
|
|
* @param {Array} array The Array on which to replace.
|
|
* @param {Number} index The index in the array at which to operate.
|
|
* @param {Number} removeCount The number of items to remove at index (can be 0).
|
|
* @return {Array} An array containing the removed items.
|
|
* @method
|
|
*/
|
|
splice: splice
|
|
};
|
|
|
|
/**
|
|
* @method
|
|
* @member Ext
|
|
* @alias Ext.Array#each
|
|
*/
|
|
Ext.each = ExtArray.each;
|
|
|
|
/**
|
|
* @method
|
|
* @member Ext.Array
|
|
* @alias Ext.Array#merge
|
|
*/
|
|
ExtArray.union = ExtArray.merge;
|
|
|
|
/**
|
|
* Old alias to {@link Ext.Array#min}
|
|
* @deprecated 4.0.0 Please use {@link Ext.Array#min} instead
|
|
* @method
|
|
* @member Ext
|
|
* @alias Ext.Array#min
|
|
*/
|
|
Ext.min = ExtArray.min;
|
|
|
|
/**
|
|
* Old alias to {@link Ext.Array#max}
|
|
* @deprecated 4.0.0 Please use {@link Ext.Array#max} instead
|
|
* @method
|
|
* @member Ext
|
|
* @alias Ext.Array#max
|
|
*/
|
|
Ext.max = ExtArray.max;
|
|
|
|
/**
|
|
* Old alias to {@link Ext.Array#sum}
|
|
* @deprecated 4.0.0 Please use {@link Ext.Array#sum} instead
|
|
* @method
|
|
* @member Ext
|
|
* @alias Ext.Array#sum
|
|
*/
|
|
Ext.sum = ExtArray.sum;
|
|
|
|
/**
|
|
* Old alias to {@link Ext.Array#mean}
|
|
* @deprecated 4.0.0 Please use {@link Ext.Array#mean} instead
|
|
* @method
|
|
* @member Ext
|
|
* @alias Ext.Array#mean
|
|
*/
|
|
Ext.mean = ExtArray.mean;
|
|
|
|
/**
|
|
* Old alias to {@link Ext.Array#flatten}
|
|
* @deprecated 4.0.0 Please use {@link Ext.Array#flatten} instead
|
|
* @method
|
|
* @member Ext
|
|
* @alias Ext.Array#flatten
|
|
*/
|
|
Ext.flatten = ExtArray.flatten;
|
|
|
|
/**
|
|
* Old alias to {@link Ext.Array#clean}
|
|
* @deprecated 4.0.0 Please use {@link Ext.Array#clean} instead
|
|
* @method
|
|
* @member Ext
|
|
* @alias Ext.Array#clean
|
|
*/
|
|
Ext.clean = ExtArray.clean;
|
|
|
|
/**
|
|
* Old alias to {@link Ext.Array#unique}
|
|
* @deprecated 4.0.0 Please use {@link Ext.Array#unique} instead
|
|
* @method
|
|
* @member Ext
|
|
* @alias Ext.Array#unique
|
|
*/
|
|
Ext.unique = ExtArray.unique;
|
|
|
|
/**
|
|
* Old alias to {@link Ext.Array#pluck Ext.Array.pluck}
|
|
* @deprecated 4.0.0 Please use {@link Ext.Array#pluck Ext.Array.pluck} instead
|
|
* @method
|
|
* @member Ext
|
|
* @alias Ext.Array#pluck
|
|
*/
|
|
Ext.pluck = ExtArray.pluck;
|
|
|
|
/**
|
|
* @method
|
|
* @member Ext
|
|
* @alias Ext.Array#toArray
|
|
*/
|
|
Ext.toArray = function() {
|
|
return ExtArray.toArray.apply(ExtArray, arguments);
|
|
};
|
|
})();
|
|
|
|
//@tag foundation,core
|
|
//@define Ext.Number
|
|
//@require Ext.Array
|
|
|
|
/**
|
|
* @class Ext.Number
|
|
*
|
|
* A collection of useful static methods to deal with numbers
|
|
* @singleton
|
|
*/
|
|
|
|
(function() {
|
|
|
|
var isToFixedBroken = (0.9).toFixed() !== '1';
|
|
|
|
Ext.Number = {
|
|
/**
|
|
* Checks whether or not the passed number is within a desired range. If the number is already within the
|
|
* range it is returned, otherwise the min or max value is returned depending on which side of the range is
|
|
* exceeded. Note that this method returns the constrained value but does not change the current number.
|
|
* @param {Number} number The number to check
|
|
* @param {Number} min The minimum number in the range
|
|
* @param {Number} max The maximum number in the range
|
|
* @return {Number} The constrained value if outside the range, otherwise the current value
|
|
*/
|
|
constrain: function(number, min, max) {
|
|
number = parseFloat(number);
|
|
|
|
if (!isNaN(min)) {
|
|
number = Math.max(number, min);
|
|
}
|
|
if (!isNaN(max)) {
|
|
number = Math.min(number, max);
|
|
}
|
|
return number;
|
|
},
|
|
|
|
/**
|
|
* Snaps the passed number between stopping points based upon a passed increment value.
|
|
* @param {Number} value The unsnapped value.
|
|
* @param {Number} increment The increment by which the value must move.
|
|
* @param {Number} minValue The minimum value to which the returned value must be constrained. Overrides the increment..
|
|
* @param {Number} maxValue The maximum value to which the returned value must be constrained. Overrides the increment..
|
|
* @return {Number} The value of the nearest snap target.
|
|
*/
|
|
snap : function(value, increment, minValue, maxValue) {
|
|
var newValue = value,
|
|
m;
|
|
|
|
if (!(increment && value)) {
|
|
return value;
|
|
}
|
|
m = value % increment;
|
|
if (m !== 0) {
|
|
newValue -= m;
|
|
if (m * 2 >= increment) {
|
|
newValue += increment;
|
|
} else if (m * 2 < -increment) {
|
|
newValue -= increment;
|
|
}
|
|
}
|
|
return Ext.Number.constrain(newValue, minValue, maxValue);
|
|
},
|
|
|
|
/**
|
|
* Formats a number using fixed-point notation
|
|
* @param {Number} value The number to format
|
|
* @param {Number} precision The number of digits to show after the decimal point
|
|
*/
|
|
toFixed: function(value, precision) {
|
|
if (isToFixedBroken) {
|
|
precision = precision || 0;
|
|
var pow = Math.pow(10, precision);
|
|
return (Math.round(value * pow) / pow).toFixed(precision);
|
|
}
|
|
|
|
return value.toFixed(precision);
|
|
},
|
|
|
|
/**
|
|
* Validate that a value is numeric and convert it to a number if necessary. Returns the specified default value if
|
|
* it is not.
|
|
|
|
Ext.Number.from('1.23', 1); // returns 1.23
|
|
Ext.Number.from('abc', 1); // returns 1
|
|
|
|
* @param {Object} value
|
|
* @param {Number} defaultValue The value to return if the original value is non-numeric
|
|
* @return {Number} value, if numeric, defaultValue otherwise
|
|
*/
|
|
from: function(value, defaultValue) {
|
|
if (isFinite(value)) {
|
|
value = parseFloat(value);
|
|
}
|
|
|
|
return !isNaN(value) ? value : defaultValue;
|
|
}
|
|
};
|
|
|
|
})();
|
|
|
|
/**
|
|
* This method is deprecated, please use {@link Ext.Number#from Ext.Number.from} instead
|
|
*
|
|
* @deprecated 4.0.0 Replaced by Ext.Number.from
|
|
* @member Ext
|
|
* @method num
|
|
*/
|
|
Ext.num = function() {
|
|
return Ext.Number.from.apply(this, arguments);
|
|
};
|
|
|
|
//@tag foundation,core
|
|
//@define Ext.Object
|
|
//@require Ext.Number
|
|
|
|
/**
|
|
* @author Jacky Nguyen <jacky@sencha.com>
|
|
* @docauthor Jacky Nguyen <jacky@sencha.com>
|
|
* @class Ext.Object
|
|
*
|
|
* A collection of useful static methods to deal with objects.
|
|
*
|
|
* @singleton
|
|
*/
|
|
|
|
(function() {
|
|
|
|
// The "constructor" for chain:
|
|
var TemplateClass = function(){};
|
|
|
|
var ExtObject = Ext.Object = {
|
|
|
|
/**
|
|
* Returns a new object with the given object as the prototype chain.
|
|
* @param {Object} object The prototype chain for the new object.
|
|
*/
|
|
chain: ('create' in Object) ? function(object){
|
|
return Object.create(object);
|
|
} : function (object) {
|
|
TemplateClass.prototype = object;
|
|
var result = new TemplateClass();
|
|
TemplateClass.prototype = null;
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* Convert a `name` - `value` pair to an array of objects with support for nested structures; useful to construct
|
|
* query strings. For example:
|
|
*
|
|
* Non-recursive:
|
|
*
|
|
* var objects = Ext.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']);
|
|
*
|
|
* // objects then equals:
|
|
* [
|
|
* { name: 'hobbies', value: 'reading' },
|
|
* { name: 'hobbies', value: 'cooking' },
|
|
* { name: 'hobbies', value: 'swimming' }
|
|
* ]
|
|
*
|
|
* Recursive:
|
|
*
|
|
* var objects = Ext.Object.toQueryObjects('dateOfBirth', {
|
|
* day: 3,
|
|
* month: 8,
|
|
* year: 1987,
|
|
* extra: {
|
|
* hour: 4,
|
|
* minute: 30
|
|
* }
|
|
* }, true);
|
|
*
|
|
* // objects then equals:
|
|
* [
|
|
* { name: 'dateOfBirth[day]', value: 3 },
|
|
* { name: 'dateOfBirth[month]', value: 8 },
|
|
* { name: 'dateOfBirth[year]', value: 1987 },
|
|
* { name: 'dateOfBirth[extra][hour]', value: 4 },
|
|
* { name: 'dateOfBirth[extra][minute]', value: 30 }
|
|
* ]
|
|
*
|
|
* @param {String} name
|
|
* @param {Object} value
|
|
* @param {Boolean} [recursive=false] `true` to recursively encode any sub-objects.
|
|
* @return {Object[]} Array of objects with `name` and `value` fields.
|
|
*/
|
|
toQueryObjects: function(name, value, recursive) {
|
|
var self = ExtObject.toQueryObjects,
|
|
objects = [],
|
|
i, ln;
|
|
|
|
if (Ext.isArray(value)) {
|
|
for (i = 0, ln = value.length; i < ln; i++) {
|
|
if (recursive) {
|
|
objects = objects.concat(self(name + '[' + i + ']', value[i], true));
|
|
}
|
|
else {
|
|
objects.push({
|
|
name: name,
|
|
value: value[i]
|
|
});
|
|
}
|
|
}
|
|
}
|
|
else if (Ext.isObject(value)) {
|
|
for (i in value) {
|
|
if (value.hasOwnProperty(i)) {
|
|
if (recursive) {
|
|
objects = objects.concat(self(name + '[' + i + ']', value[i], true));
|
|
}
|
|
else {
|
|
objects.push({
|
|
name: name,
|
|
value: value[i]
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
objects.push({
|
|
name: name,
|
|
value: value
|
|
});
|
|
}
|
|
|
|
return objects;
|
|
},
|
|
|
|
/**
|
|
* Takes an object and converts it to an encoded query string.
|
|
*
|
|
* Non-recursive:
|
|
*
|
|
* Ext.Object.toQueryString({foo: 1, bar: 2}); // returns "foo=1&bar=2"
|
|
* Ext.Object.toQueryString({foo: null, bar: 2}); // returns "foo=&bar=2"
|
|
* Ext.Object.toQueryString({'some price': '$300'}); // returns "some%20price=%24300"
|
|
* Ext.Object.toQueryString({date: new Date(2011, 0, 1)}); // returns "date=%222011-01-01T00%3A00%3A00%22"
|
|
* Ext.Object.toQueryString({colors: ['red', 'green', 'blue']}); // returns "colors=red&colors=green&colors=blue"
|
|
*
|
|
* Recursive:
|
|
*
|
|
* Ext.Object.toQueryString({
|
|
* username: 'Jacky',
|
|
* dateOfBirth: {
|
|
* day: 1,
|
|
* month: 2,
|
|
* year: 1911
|
|
* },
|
|
* hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
|
|
* }, true);
|
|
*
|
|
* // returns the following string (broken down and url-decoded for ease of reading purpose):
|
|
* // username=Jacky
|
|
* // &dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911
|
|
* // &hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff
|
|
*
|
|
* @param {Object} object The object to encode.
|
|
* @param {Boolean} [recursive=false] Whether or not to interpret the object in recursive format.
|
|
* (PHP / Ruby on Rails servers and similar).
|
|
* @return {String} queryString
|
|
*/
|
|
toQueryString: function(object, recursive) {
|
|
var paramObjects = [],
|
|
params = [],
|
|
i, j, ln, paramObject, value;
|
|
|
|
for (i in object) {
|
|
if (object.hasOwnProperty(i)) {
|
|
paramObjects = paramObjects.concat(ExtObject.toQueryObjects(i, object[i], recursive));
|
|
}
|
|
}
|
|
|
|
for (j = 0, ln = paramObjects.length; j < ln; j++) {
|
|
paramObject = paramObjects[j];
|
|
value = paramObject.value;
|
|
|
|
if (Ext.isEmpty(value)) {
|
|
value = '';
|
|
}
|
|
else if (Ext.isDate(value)) {
|
|
value = Ext.Date.toString(value);
|
|
}
|
|
|
|
params.push(encodeURIComponent(paramObject.name) + '=' + encodeURIComponent(String(value)));
|
|
}
|
|
|
|
return params.join('&');
|
|
},
|
|
|
|
/**
|
|
* Converts a query string back into an object.
|
|
*
|
|
* Non-recursive:
|
|
*
|
|
* Ext.Object.fromQueryString("foo=1&bar=2"); // returns {foo: 1, bar: 2}
|
|
* Ext.Object.fromQueryString("foo=&bar=2"); // returns {foo: null, bar: 2}
|
|
* Ext.Object.fromQueryString("some%20price=%24300"); // returns {'some price': '$300'}
|
|
* Ext.Object.fromQueryString("colors=red&colors=green&colors=blue"); // returns {colors: ['red', 'green', 'blue']}
|
|
*
|
|
* Recursive:
|
|
*
|
|
* Ext.Object.fromQueryString("username=Jacky&dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911&hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff", true);
|
|
*
|
|
* // returns
|
|
* {
|
|
* username: 'Jacky',
|
|
* dateOfBirth: {
|
|
* day: '1',
|
|
* month: '2',
|
|
* year: '1911'
|
|
* },
|
|
* hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
|
|
* }
|
|
*
|
|
* @param {String} queryString The query string to decode.
|
|
* @param {Boolean} [recursive=false] Whether or not to recursively decode the string. This format is supported by
|
|
* PHP / Ruby on Rails servers and similar.
|
|
* @return {Object}
|
|
*/
|
|
fromQueryString: function(queryString, recursive) {
|
|
var parts = queryString.replace(/^\?/, '').split('&'),
|
|
object = {},
|
|
temp, components, name, value, i, ln,
|
|
part, j, subLn, matchedKeys, matchedName,
|
|
keys, key, nextKey;
|
|
|
|
for (i = 0, ln = parts.length; i < ln; i++) {
|
|
part = parts[i];
|
|
|
|
if (part.length > 0) {
|
|
components = part.split('=');
|
|
name = decodeURIComponent(components[0]);
|
|
value = (components[1] !== undefined) ? decodeURIComponent(components[1]) : '';
|
|
|
|
if (!recursive) {
|
|
if (object.hasOwnProperty(name)) {
|
|
if (!Ext.isArray(object[name])) {
|
|
object[name] = [object[name]];
|
|
}
|
|
|
|
object[name].push(value);
|
|
}
|
|
else {
|
|
object[name] = value;
|
|
}
|
|
}
|
|
else {
|
|
matchedKeys = name.match(/(\[):?([^\]]*)\]/g);
|
|
matchedName = name.match(/^([^\[]+)/);
|
|
|
|
//<debug error>
|
|
if (!matchedName) {
|
|
throw new Error('[Ext.Object.fromQueryString] Malformed query string given, failed parsing name from "' + part + '"');
|
|
}
|
|
//</debug>
|
|
|
|
name = matchedName[0];
|
|
keys = [];
|
|
|
|
if (matchedKeys === null) {
|
|
object[name] = value;
|
|
continue;
|
|
}
|
|
|
|
for (j = 0, subLn = matchedKeys.length; j < subLn; j++) {
|
|
key = matchedKeys[j];
|
|
key = (key.length === 2) ? '' : key.substring(1, key.length - 1);
|
|
keys.push(key);
|
|
}
|
|
|
|
keys.unshift(name);
|
|
|
|
temp = object;
|
|
|
|
for (j = 0, subLn = keys.length; j < subLn; j++) {
|
|
key = keys[j];
|
|
|
|
if (j === subLn - 1) {
|
|
if (Ext.isArray(temp) && key === '') {
|
|
temp.push(value);
|
|
}
|
|
else {
|
|
temp[key] = value;
|
|
}
|
|
}
|
|
else {
|
|
if (temp[key] === undefined || typeof temp[key] === 'string') {
|
|
nextKey = keys[j+1];
|
|
|
|
temp[key] = (Ext.isNumeric(nextKey) || nextKey === '') ? [] : {};
|
|
}
|
|
|
|
temp = temp[key];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return object;
|
|
},
|
|
|
|
/**
|
|
* Iterate through an object and invoke the given callback function for each iteration. The iteration can be stop
|
|
* by returning `false` in the callback function. For example:
|
|
*
|
|
* var person = {
|
|
* name: 'Jacky',
|
|
* hairColor: 'black',
|
|
* loves: ['food', 'sleeping', 'wife']
|
|
* };
|
|
*
|
|
* Ext.Object.each(person, function(key, value, myself) {
|
|
* console.log(key + ":" + value);
|
|
*
|
|
* if (key === 'hairColor') {
|
|
* return false; // stop the iteration
|
|
* }
|
|
* });
|
|
*
|
|
* @param {Object} object The object to iterate
|
|
* @param {Function} fn The callback function.
|
|
* @param {String} fn.key
|
|
* @param {Mixed} fn.value
|
|
* @param {Object} fn.object The object itself
|
|
* @param {Object} [scope] The execution scope (`this`) of the callback function
|
|
*/
|
|
each: function(object, fn, scope) {
|
|
for (var property in object) {
|
|
if (object.hasOwnProperty(property)) {
|
|
if (fn.call(scope || object, property, object[property], object) === false) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Merges any number of objects recursively without referencing them or their children.
|
|
*
|
|
* var extjs = {
|
|
* companyName: 'Ext JS',
|
|
* products: ['Ext JS', 'Ext GWT', 'Ext Designer'],
|
|
* isSuperCool: true,
|
|
* office: {
|
|
* size: 2000,
|
|
* location: 'Palo Alto',
|
|
* isFun: true
|
|
* }
|
|
* };
|
|
*
|
|
* var newStuff = {
|
|
* companyName: 'Sencha Inc.',
|
|
* products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
|
|
* office: {
|
|
* size: 40000,
|
|
* location: 'Redwood City'
|
|
* }
|
|
* };
|
|
*
|
|
* var sencha = Ext.Object.merge({}, extjs, newStuff);
|
|
*
|
|
* // sencha then equals to
|
|
* {
|
|
* companyName: 'Sencha Inc.',
|
|
* products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
|
|
* isSuperCool: true
|
|
* office: {
|
|
* size: 40000,
|
|
* location: 'Redwood City'
|
|
* isFun: true
|
|
* }
|
|
* }
|
|
*
|
|
* @param {Object} source The first object into which to merge the others.
|
|
* @param {Object...} objs One or more objects to be merged into the first.
|
|
* @return {Object} The object that is created as a result of merging all the objects passed in.
|
|
*/
|
|
merge: function(source) {
|
|
var i = 1,
|
|
ln = arguments.length,
|
|
mergeFn = ExtObject.merge,
|
|
cloneFn = Ext.clone,
|
|
object, key, value, sourceKey;
|
|
|
|
for (; i < ln; i++) {
|
|
object = arguments[i];
|
|
|
|
for (key in object) {
|
|
value = object[key];
|
|
if (value && value.constructor === Object) {
|
|
sourceKey = source[key];
|
|
if (sourceKey && sourceKey.constructor === Object) {
|
|
mergeFn(sourceKey, value);
|
|
}
|
|
else {
|
|
source[key] = cloneFn(value);
|
|
}
|
|
}
|
|
else {
|
|
source[key] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
return source;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @param source
|
|
*/
|
|
mergeIf: function(source) {
|
|
var i = 1,
|
|
ln = arguments.length,
|
|
cloneFn = Ext.clone,
|
|
object, key, value;
|
|
|
|
for (; i < ln; i++) {
|
|
object = arguments[i];
|
|
|
|
for (key in object) {
|
|
if (!(key in source)) {
|
|
value = object[key];
|
|
|
|
if (value && value.constructor === Object) {
|
|
source[key] = cloneFn(value);
|
|
}
|
|
else {
|
|
source[key] = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return source;
|
|
},
|
|
|
|
/**
|
|
* Returns the first matching key corresponding to the given value.
|
|
* If no matching value is found, `null` is returned.
|
|
*
|
|
* var person = {
|
|
* name: 'Jacky',
|
|
* loves: 'food'
|
|
* };
|
|
*
|
|
* alert(Ext.Object.getKey(sencha, 'food')); // alerts 'loves'
|
|
*
|
|
* @param {Object} object
|
|
* @param {Object} value The value to find
|
|
*/
|
|
getKey: function(object, value) {
|
|
for (var property in object) {
|
|
if (object.hasOwnProperty(property) && object[property] === value) {
|
|
return property;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* Gets all values of the given object as an array.
|
|
*
|
|
* var values = Ext.Object.getValues({
|
|
* name: 'Jacky',
|
|
* loves: 'food'
|
|
* }); // ['Jacky', 'food']
|
|
*
|
|
* @param {Object} object
|
|
* @return {Array} An array of values from the object.
|
|
*/
|
|
getValues: function(object) {
|
|
var values = [],
|
|
property;
|
|
|
|
for (property in object) {
|
|
if (object.hasOwnProperty(property)) {
|
|
values.push(object[property]);
|
|
}
|
|
}
|
|
|
|
return values;
|
|
},
|
|
|
|
/**
|
|
* Gets all keys of the given object as an array.
|
|
*
|
|
* var values = Ext.Object.getKeys({
|
|
* name: 'Jacky',
|
|
* loves: 'food'
|
|
* }); // ['name', 'loves']
|
|
*
|
|
* @param {Object} object
|
|
* @return {String[]} An array of keys from the object.
|
|
* @method
|
|
*/
|
|
getKeys: ('keys' in Object) ? Object.keys : function(object) {
|
|
var keys = [],
|
|
property;
|
|
|
|
for (property in object) {
|
|
if (object.hasOwnProperty(property)) {
|
|
keys.push(property);
|
|
}
|
|
}
|
|
|
|
return keys;
|
|
},
|
|
|
|
/**
|
|
* Gets the total number of this object's own properties.
|
|
*
|
|
* var size = Ext.Object.getSize({
|
|
* name: 'Jacky',
|
|
* loves: 'food'
|
|
* }); // size equals 2
|
|
*
|
|
* @param {Object} object
|
|
* @return {Number} size
|
|
*/
|
|
getSize: function(object) {
|
|
var size = 0,
|
|
property;
|
|
|
|
for (property in object) {
|
|
if (object.hasOwnProperty(property)) {
|
|
size++;
|
|
}
|
|
}
|
|
|
|
return size;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
classify: function(object) {
|
|
var objectProperties = [],
|
|
arrayProperties = [],
|
|
propertyClassesMap = {},
|
|
objectClass = function() {
|
|
var i = 0,
|
|
ln = objectProperties.length,
|
|
property;
|
|
|
|
for (; i < ln; i++) {
|
|
property = objectProperties[i];
|
|
this[property] = new propertyClassesMap[property];
|
|
}
|
|
|
|
ln = arrayProperties.length;
|
|
|
|
for (i = 0; i < ln; i++) {
|
|
property = arrayProperties[i];
|
|
this[property] = object[property].slice();
|
|
}
|
|
},
|
|
key, value, constructor;
|
|
|
|
for (key in object) {
|
|
if (object.hasOwnProperty(key)) {
|
|
value = object[key];
|
|
|
|
if (value) {
|
|
constructor = value.constructor;
|
|
|
|
if (constructor === Object) {
|
|
objectProperties.push(key);
|
|
propertyClassesMap[key] = ExtObject.classify(value);
|
|
}
|
|
else if (constructor === Array) {
|
|
arrayProperties.push(key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
objectClass.prototype = object;
|
|
|
|
return objectClass;
|
|
},
|
|
|
|
defineProperty: ('defineProperty' in Object) ? Object.defineProperty : function(object, name, descriptor) {
|
|
if (descriptor.get) {
|
|
object.__defineGetter__(name, descriptor.get);
|
|
}
|
|
|
|
if (descriptor.set) {
|
|
object.__defineSetter__(name, descriptor.set);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* A convenient alias method for {@link Ext.Object#merge}.
|
|
*
|
|
* @member Ext
|
|
* @method merge
|
|
*/
|
|
Ext.merge = Ext.Object.merge;
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
Ext.mergeIf = Ext.Object.mergeIf;
|
|
|
|
/**
|
|
* A convenient alias method for {@link Ext.Object#toQueryString}.
|
|
*
|
|
* @member Ext
|
|
* @method urlEncode
|
|
* @deprecated 4.0.0 Please use `{@link Ext.Object#toQueryString Ext.Object.toQueryString}` instead
|
|
*/
|
|
Ext.urlEncode = function() {
|
|
var args = Ext.Array.from(arguments),
|
|
prefix = '';
|
|
|
|
// Support for the old `pre` argument
|
|
if ((typeof args[1] === 'string')) {
|
|
prefix = args[1] + '&';
|
|
args[1] = false;
|
|
}
|
|
|
|
return prefix + ExtObject.toQueryString.apply(ExtObject, args);
|
|
};
|
|
|
|
/**
|
|
* A convenient alias method for {@link Ext.Object#fromQueryString}.
|
|
*
|
|
* @member Ext
|
|
* @method urlDecode
|
|
* @deprecated 4.0.0 Please use {@link Ext.Object#fromQueryString Ext.Object.fromQueryString} instead
|
|
*/
|
|
Ext.urlDecode = function() {
|
|
return ExtObject.fromQueryString.apply(ExtObject, arguments);
|
|
};
|
|
|
|
})();
|
|
|
|
//@tag foundation,core
|
|
//@define Ext.Function
|
|
//@require Ext.Object
|
|
|
|
/**
|
|
* @class Ext.Function
|
|
*
|
|
* A collection of useful static methods to deal with function callbacks.
|
|
* @singleton
|
|
* @alternateClassName Ext.util.Functions
|
|
*/
|
|
Ext.Function = {
|
|
|
|
/**
|
|
* A very commonly used method throughout the framework. It acts as a wrapper around another method
|
|
* which originally accepts 2 arguments for `name` and `value`.
|
|
* The wrapped function then allows "flexible" value setting of either:
|
|
*
|
|
* - `name` and `value` as 2 arguments
|
|
* - one single object argument with multiple key - value pairs
|
|
*
|
|
* For example:
|
|
*
|
|
* var setValue = Ext.Function.flexSetter(function(name, value) {
|
|
* this[name] = value;
|
|
* });
|
|
*
|
|
* // Afterwards
|
|
* // Setting a single name - value
|
|
* setValue('name1', 'value1');
|
|
*
|
|
* // Settings multiple name - value pairs
|
|
* setValue({
|
|
* name1: 'value1',
|
|
* name2: 'value2',
|
|
* name3: 'value3'
|
|
* });
|
|
*
|
|
* @param {Function} setter
|
|
* @return {Function} flexSetter
|
|
*/
|
|
flexSetter: function(fn) {
|
|
return function(a, b) {
|
|
var k, i;
|
|
|
|
if (a === null) {
|
|
return this;
|
|
}
|
|
|
|
if (typeof a !== 'string') {
|
|
for (k in a) {
|
|
if (a.hasOwnProperty(k)) {
|
|
fn.call(this, k, a[k]);
|
|
}
|
|
}
|
|
|
|
if (Ext.enumerables) {
|
|
for (i = Ext.enumerables.length; i--;) {
|
|
k = Ext.enumerables[i];
|
|
if (a.hasOwnProperty(k)) {
|
|
fn.call(this, k, a[k]);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
fn.call(this, a, b);
|
|
}
|
|
|
|
return this;
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Create a new function from the provided `fn`, change `this` to the provided scope, optionally
|
|
* overrides arguments for the call. Defaults to the arguments passed by the caller.
|
|
*
|
|
* {@link Ext#bind Ext.bind} is alias for {@link Ext.Function#bind Ext.Function.bind}
|
|
*
|
|
* @param {Function} fn The function to delegate.
|
|
* @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
|
|
* **If omitted, defaults to the browser window.**
|
|
* @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
|
|
* @param {Boolean/Number} appendArgs (optional) if `true` args are appended to call args instead of overriding,
|
|
* if a number the args are inserted at the specified position.
|
|
* @return {Function} The new function.
|
|
*/
|
|
bind: function(fn, scope, args, appendArgs) {
|
|
if (arguments.length === 2) {
|
|
return function() {
|
|
return fn.apply(scope, arguments);
|
|
}
|
|
}
|
|
|
|
var method = fn,
|
|
slice = Array.prototype.slice;
|
|
|
|
return function() {
|
|
var callArgs = args || arguments;
|
|
|
|
if (appendArgs === true) {
|
|
callArgs = slice.call(arguments, 0);
|
|
callArgs = callArgs.concat(args);
|
|
}
|
|
else if (typeof appendArgs == 'number') {
|
|
callArgs = slice.call(arguments, 0); // copy arguments first
|
|
Ext.Array.insert(callArgs, appendArgs, args);
|
|
}
|
|
|
|
return method.apply(scope || window, callArgs);
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Create a new function from the provided `fn`, the arguments of which are pre-set to `args`.
|
|
* New arguments passed to the newly created callback when it's invoked are appended after the pre-set ones.
|
|
* This is especially useful when creating callbacks.
|
|
*
|
|
* For example:
|
|
*
|
|
* var originalFunction = function(){
|
|
* alert(Ext.Array.from(arguments).join(' '));
|
|
* };
|
|
*
|
|
* var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']);
|
|
*
|
|
* callback(); // alerts 'Hello World'
|
|
* callback('by Me'); // alerts 'Hello World by Me'
|
|
*
|
|
* {@link Ext#pass Ext.pass} is alias for {@link Ext.Function#pass Ext.Function.pass}
|
|
*
|
|
* @param {Function} fn The original function.
|
|
* @param {Array} args The arguments to pass to new callback.
|
|
* @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
|
|
* @return {Function} The new callback function.
|
|
*/
|
|
pass: function(fn, args, scope) {
|
|
if (!Ext.isArray(args)) {
|
|
args = Ext.Array.clone(args);
|
|
}
|
|
|
|
return function() {
|
|
args.push.apply(args, arguments);
|
|
return fn.apply(scope || this, args);
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Create an alias to the provided method property with name `methodName` of `object`.
|
|
* Note that the execution scope will still be bound to the provided `object` itself.
|
|
*
|
|
* @param {Object/Function} object
|
|
* @param {String} methodName
|
|
* @return {Function} aliasFn
|
|
*/
|
|
alias: function(object, methodName) {
|
|
return function() {
|
|
return object[methodName].apply(object, arguments);
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Create a "clone" of the provided method. The returned method will call the given
|
|
* method passing along all arguments and the "this" pointer and return its result.
|
|
*
|
|
* @param {Function} method
|
|
* @return {Function} cloneFn
|
|
*/
|
|
clone: function(method) {
|
|
return function() {
|
|
return method.apply(this, arguments);
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Creates an interceptor function. The passed function is called before the original one. If it returns false,
|
|
* the original one is not called. The resulting function returns the results of the original function.
|
|
* The passed function is called with the parameters of the original function. Example usage:
|
|
*
|
|
* var sayHi = function(name){
|
|
* alert('Hi, ' + name);
|
|
* };
|
|
*
|
|
* sayHi('Fred'); // alerts "Hi, Fred"
|
|
*
|
|
* // create a new function that validates input without
|
|
* // directly modifying the original function:
|
|
* var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){
|
|
* return name === 'Brian';
|
|
* });
|
|
*
|
|
* sayHiToFriend('Fred'); // no alert
|
|
* sayHiToFriend('Brian'); // alerts "Hi, Brian"
|
|
*
|
|
* @param {Function} origFn The original function.
|
|
* @param {Function} newFn The function to call before the original.
|
|
* @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
|
|
* **If omitted, defaults to the scope in which the original function is called or the browser window.**
|
|
* @param {Object} [returnValue=null] (optional) The value to return if the passed function return `false`.
|
|
* @return {Function} The new function.
|
|
*/
|
|
createInterceptor: function(origFn, newFn, scope, returnValue) {
|
|
var method = origFn;
|
|
if (!Ext.isFunction(newFn)) {
|
|
return origFn;
|
|
}
|
|
else {
|
|
return function() {
|
|
var me = this,
|
|
args = arguments;
|
|
newFn.target = me;
|
|
newFn.method = origFn;
|
|
return (newFn.apply(scope || me || window, args) !== false) ? origFn.apply(me || window, args) : returnValue || null;
|
|
};
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Creates a delegate (callback) which, when called, executes after a specific delay.
|
|
*
|
|
* @param {Function} fn The function which will be called on a delay when the returned function is called.
|
|
* Optionally, a replacement (or additional) argument list may be specified.
|
|
* @param {Number} delay The number of milliseconds to defer execution by whenever called.
|
|
* @param {Object} scope (optional) The scope (`this` reference) used by the function at execution time.
|
|
* @param {Array} args (optional) Override arguments for the call. (Defaults to the arguments passed by the caller)
|
|
* @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
|
|
* if a number the args are inserted at the specified position.
|
|
* @return {Function} A function which, when called, executes the original function after the specified delay.
|
|
*/
|
|
createDelayed: function(fn, delay, scope, args, appendArgs) {
|
|
if (scope || args) {
|
|
fn = Ext.Function.bind(fn, scope, args, appendArgs);
|
|
}
|
|
|
|
return function() {
|
|
var me = this,
|
|
args = Array.prototype.slice.call(arguments);
|
|
|
|
setTimeout(function() {
|
|
fn.apply(me, args);
|
|
}, delay);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Calls this function after the number of milliseconds specified, optionally in a specific scope. Example usage:
|
|
*
|
|
* var sayHi = function(name){
|
|
* alert('Hi, ' + name);
|
|
* };
|
|
*
|
|
* // executes immediately:
|
|
* sayHi('Fred');
|
|
*
|
|
* // executes after 2 seconds:
|
|
* Ext.Function.defer(sayHi, 2000, this, ['Fred']);
|
|
*
|
|
* // this syntax is sometimes useful for deferring
|
|
* // execution of an anonymous function:
|
|
* Ext.Function.defer(function(){
|
|
* alert('Anonymous');
|
|
* }, 100);
|
|
*
|
|
* {@link Ext#defer Ext.defer} is alias for {@link Ext.Function#defer Ext.Function.defer}
|
|
*
|
|
* @param {Function} fn The function to defer.
|
|
* @param {Number} millis The number of milliseconds for the `setTimeout()` call.
|
|
* If less than or equal to 0 the function is executed immediately.
|
|
* @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
|
|
* If omitted, defaults to the browser window.
|
|
* @param {Array} args (optional) Overrides arguments for the call. Defaults to the arguments passed by the caller.
|
|
* @param {Boolean/Number} appendArgs (optional) if `true`, args are appended to call args instead of overriding,
|
|
* if a number the args are inserted at the specified position.
|
|
* @return {Number} The timeout id that can be used with `clearTimeout()`.
|
|
*/
|
|
defer: function(fn, millis, scope, args, appendArgs) {
|
|
fn = Ext.Function.bind(fn, scope, args, appendArgs);
|
|
if (millis > 0) {
|
|
return setTimeout(fn, millis);
|
|
}
|
|
fn();
|
|
return 0;
|
|
},
|
|
|
|
/**
|
|
* Create a combined function call sequence of the original function + the passed function.
|
|
* The resulting function returns the results of the original function.
|
|
* The passed function is called with the parameters of the original function. Example usage:
|
|
*
|
|
* var sayHi = function(name){
|
|
* alert('Hi, ' + name);
|
|
* };
|
|
*
|
|
* sayHi('Fred'); // alerts "Hi, Fred"
|
|
*
|
|
* var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){
|
|
* alert('Bye, ' + name);
|
|
* });
|
|
*
|
|
* sayGoodbye('Fred'); // both alerts show
|
|
*
|
|
* @param {Function} originalFn The original function.
|
|
* @param {Function} newFn The function to sequence.
|
|
* @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
|
|
* If omitted, defaults to the scope in which the original function is called or the browser window.
|
|
* @return {Function} The new function.
|
|
*/
|
|
createSequence: function(originalFn, newFn, scope) {
|
|
if (!newFn) {
|
|
return originalFn;
|
|
}
|
|
else {
|
|
return function() {
|
|
var result = originalFn.apply(this, arguments);
|
|
newFn.apply(scope || this, arguments);
|
|
return result;
|
|
};
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Creates a delegate function, optionally with a bound scope which, when called, buffers
|
|
* the execution of the passed function for the configured number of milliseconds.
|
|
* If called again within that period, the impending invocation will be canceled, and the
|
|
* timeout period will begin again.
|
|
*
|
|
* @param {Function} fn The function to invoke on a buffered timer.
|
|
* @param {Number} buffer The number of milliseconds by which to buffer the invocation of the
|
|
* function.
|
|
* @param {Object} scope (optional) The scope (`this` reference) in which
|
|
* the passed function is executed. If omitted, defaults to the scope specified by the caller.
|
|
* @param {Array} args (optional) Override arguments for the call. Defaults to the arguments
|
|
* passed by the caller.
|
|
* @return {Function} A function which invokes the passed function after buffering for the specified time.
|
|
*/
|
|
|
|
createBuffered: function(fn, buffer, scope, args) {
|
|
var timerId;
|
|
|
|
return function() {
|
|
var callArgs = args || Array.prototype.slice.call(arguments, 0),
|
|
me = scope || this;
|
|
|
|
if (timerId) {
|
|
clearTimeout(timerId);
|
|
}
|
|
|
|
timerId = setTimeout(function(){
|
|
fn.apply(me, callArgs);
|
|
}, buffer);
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Creates a throttled version of the passed function which, when called repeatedly and
|
|
* rapidly, invokes the passed function only after a certain interval has elapsed since the
|
|
* previous invocation.
|
|
*
|
|
* This is useful for wrapping functions which may be called repeatedly, such as
|
|
* a handler of a mouse move event when the processing is expensive.
|
|
*
|
|
* @param {Function} fn The function to execute at a regular time interval.
|
|
* @param {Number} interval The interval, in milliseconds, on which the passed function is executed.
|
|
* @param {Object} scope (optional) The scope (`this` reference) in which
|
|
* the passed function is executed. If omitted, defaults to the scope specified by the caller.
|
|
* @return {Function} A function which invokes the passed function at the specified interval.
|
|
*/
|
|
createThrottled: function(fn, interval, scope) {
|
|
var lastCallTime, elapsed, lastArgs, timer, execute = function() {
|
|
fn.apply(scope || this, lastArgs);
|
|
lastCallTime = new Date().getTime();
|
|
};
|
|
|
|
return function() {
|
|
elapsed = new Date().getTime() - lastCallTime;
|
|
lastArgs = arguments;
|
|
|
|
clearTimeout(timer);
|
|
if (!lastCallTime || (elapsed >= interval)) {
|
|
execute();
|
|
} else {
|
|
timer = setTimeout(execute, interval - elapsed);
|
|
}
|
|
};
|
|
},
|
|
|
|
interceptBefore: function(object, methodName, fn) {
|
|
var method = object[methodName] || Ext.emptyFn;
|
|
|
|
return object[methodName] = function() {
|
|
var ret = fn.apply(this, arguments);
|
|
method.apply(this, arguments);
|
|
|
|
return ret;
|
|
};
|
|
},
|
|
|
|
interceptAfter: function(object, methodName, fn) {
|
|
var method = object[methodName] || Ext.emptyFn;
|
|
|
|
return object[methodName] = function() {
|
|
method.apply(this, arguments);
|
|
return fn.apply(this, arguments);
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @method
|
|
* @member Ext
|
|
* @alias Ext.Function#defer
|
|
*/
|
|
Ext.defer = Ext.Function.alias(Ext.Function, 'defer');
|
|
|
|
/**
|
|
* @method
|
|
* @member Ext
|
|
* @alias Ext.Function#pass
|
|
*/
|
|
Ext.pass = Ext.Function.alias(Ext.Function, 'pass');
|
|
|
|
/**
|
|
* @method
|
|
* @member Ext
|
|
* @alias Ext.Function#bind
|
|
*/
|
|
Ext.bind = Ext.Function.alias(Ext.Function, 'bind');
|
|
|
|
//@tag foundation,core
|
|
//@define Ext.JSON
|
|
//@require Ext.Function
|
|
|
|
/**
|
|
* @class Ext.JSON
|
|
* Modified version of Douglas Crockford's json.js that doesn't
|
|
* mess with the Object prototype.
|
|
* [http://www.json.org/js.html](http://www.json.org/js.html)
|
|
* @singleton
|
|
*/
|
|
Ext.JSON = new(function() {
|
|
var useHasOwn = !! {}.hasOwnProperty,
|
|
isNative = function() {
|
|
var useNative = null;
|
|
|
|
return function() {
|
|
if (useNative === null) {
|
|
useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]';
|
|
}
|
|
|
|
return useNative;
|
|
};
|
|
}(),
|
|
pad = function(n) {
|
|
return n < 10 ? "0" + n : n;
|
|
},
|
|
doDecode = function(json) {
|
|
return eval("(" + json + ')');
|
|
},
|
|
doEncode = function(o) {
|
|
if (!Ext.isDefined(o) || o === null) {
|
|
return "null";
|
|
} else if (Ext.isArray(o)) {
|
|
return encodeArray(o);
|
|
} else if (Ext.isDate(o)) {
|
|
return Ext.JSON.encodeDate(o);
|
|
} else if (Ext.isString(o)) {
|
|
return encodeString(o);
|
|
} else if (typeof o == "number") {
|
|
//don't use isNumber here, since finite checks happen inside isNumber
|
|
return isFinite(o) ? String(o) : "null";
|
|
} else if (Ext.isBoolean(o)) {
|
|
return String(o);
|
|
} else if (Ext.isObject(o)) {
|
|
return encodeObject(o);
|
|
} else if (typeof o === "function") {
|
|
return "null";
|
|
}
|
|
return 'undefined';
|
|
},
|
|
m = {
|
|
"\b": '\\b',
|
|
"\t": '\\t',
|
|
"\n": '\\n',
|
|
"\f": '\\f',
|
|
"\r": '\\r',
|
|
'"': '\\"',
|
|
"\\": '\\\\',
|
|
'\x0b': '\\u000b' //ie doesn't handle \v
|
|
},
|
|
charToReplace = /[\\\"\x00-\x1f\x7f-\uffff]/g,
|
|
encodeString = function(s) {
|
|
return '"' + s.replace(charToReplace, function(a) {
|
|
var c = m[a];
|
|
return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
|
}) + '"';
|
|
},
|
|
encodeArray = function(o) {
|
|
var a = ["[", ""],
|
|
// Note empty string in case there are no serializable members.
|
|
len = o.length,
|
|
i;
|
|
for (i = 0; i < len; i += 1) {
|
|
a.push(doEncode(o[i]), ',');
|
|
}
|
|
// Overwrite trailing comma (or empty string)
|
|
a[a.length - 1] = ']';
|
|
return a.join("");
|
|
},
|
|
encodeObject = function(o) {
|
|
var a = ["{", ""],
|
|
// Note empty string in case there are no serializable members.
|
|
i;
|
|
for (i in o) {
|
|
if (!useHasOwn || o.hasOwnProperty(i)) {
|
|
a.push(doEncode(i), ":", doEncode(o[i]), ',');
|
|
}
|
|
}
|
|
// Overwrite trailing comma (or empty string)
|
|
a[a.length - 1] = '}';
|
|
return a.join("");
|
|
};
|
|
|
|
/**
|
|
* Encodes a Date. This returns the actual string which is inserted into the JSON string as the literal expression.
|
|
* __The returned value includes enclosing double quotation marks.__
|
|
*
|
|
* The default return format is "yyyy-mm-ddThh:mm:ss".
|
|
*
|
|
* To override this:
|
|
*
|
|
* Ext.JSON.encodeDate = function(d) {
|
|
* return Ext.Date.format(d, '"Y-m-d"');
|
|
* };
|
|
*
|
|
* @param {Date} d The Date to encode.
|
|
* @return {String} The string literal to use in a JSON string.
|
|
*/
|
|
this.encodeDate = function(o) {
|
|
return '"' + o.getFullYear() + "-"
|
|
+ pad(o.getMonth() + 1) + "-"
|
|
+ pad(o.getDate()) + "T"
|
|
+ pad(o.getHours()) + ":"
|
|
+ pad(o.getMinutes()) + ":"
|
|
+ pad(o.getSeconds()) + '"';
|
|
};
|
|
|
|
/**
|
|
* Encodes an Object, Array or other value.
|
|
* @param {Object} o The variable to encode.
|
|
* @return {String} The JSON string.
|
|
* @method
|
|
*/
|
|
this.encode = function() {
|
|
var ec;
|
|
return function(o) {
|
|
if (!ec) {
|
|
// setup encoding function on first access
|
|
ec = isNative() ? JSON.stringify : doEncode;
|
|
}
|
|
return ec(o);
|
|
};
|
|
}();
|
|
|
|
|
|
/**
|
|
* Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws a Error unless the safe option is set.
|
|
* @param {String} json The JSON string.
|
|
* @param {Boolean} safe (optional) Whether to return `null` or throw an exception if the JSON is invalid.
|
|
* @return {Object/null} The resulting object.
|
|
* @method
|
|
*/
|
|
this.decode = function() {
|
|
var dc;
|
|
return function(json, safe) {
|
|
if (!dc) {
|
|
// setup decoding function on first access
|
|
dc = isNative() ? JSON.parse : doDecode;
|
|
}
|
|
try {
|
|
return dc(json);
|
|
} catch (e) {
|
|
if (safe === true) {
|
|
return null;
|
|
}
|
|
Ext.Error.raise({
|
|
sourceClass: "Ext.JSON",
|
|
sourceMethod: "decode",
|
|
msg: "You're trying to decode an invalid JSON String: " + json
|
|
});
|
|
}
|
|
};
|
|
}();
|
|
|
|
})();
|
|
/**
|
|
* Shorthand for {@link Ext.JSON#encode}.
|
|
* @member Ext
|
|
* @method encode
|
|
* @alias Ext.JSON#encode
|
|
*/
|
|
Ext.encode = Ext.JSON.encode;
|
|
/**
|
|
* Shorthand for {@link Ext.JSON#decode}.
|
|
* @member Ext
|
|
* @method decode
|
|
* @alias Ext.JSON#decode
|
|
*/
|
|
Ext.decode = Ext.JSON.decode;
|
|
|
|
|
|
//@tag foundation,core
|
|
//@define Ext.Error
|
|
//@require Ext.JSON
|
|
|
|
Ext.Error = {
|
|
raise: function(object) {
|
|
throw new Error(object.msg);
|
|
}
|
|
};
|
|
|
|
//@tag foundation,core
|
|
//@define Ext.Date
|
|
//@require Ext.Error
|
|
|
|
/**
|
|
*
|
|
*/
|
|
Ext.Date = {
|
|
/** @ignore */
|
|
now: Date.now,
|
|
|
|
/**
|
|
* @private
|
|
* Private for now
|
|
*/
|
|
toString: function(date) {
|
|
if (!date) {
|
|
date = new Date();
|
|
}
|
|
|
|
var pad = Ext.String.leftPad;
|
|
|
|
return date.getFullYear() + "-"
|
|
+ pad(date.getMonth() + 1, 2, '0') + "-"
|
|
+ pad(date.getDate(), 2, '0') + "T"
|
|
+ pad(date.getHours(), 2, '0') + ":"
|
|
+ pad(date.getMinutes(), 2, '0') + ":"
|
|
+ pad(date.getSeconds(), 2, '0');
|
|
}
|
|
};
|
|
|
|
|
|
//@tag foundation,core
|
|
//@define Ext.Base
|
|
//@require Ext.Date
|
|
|
|
/**
|
|
* @class Ext.Base
|
|
*
|
|
* @author Jacky Nguyen <jacky@sencha.com>
|
|
* @aside guide class_system
|
|
* @aside video class-system
|
|
*
|
|
* The root of all classes created with {@link Ext#define}.
|
|
*
|
|
* Ext.Base is the building block of all Ext classes. All classes in Ext inherit from Ext.Base. All prototype and static
|
|
* members of this class are inherited by all other classes.
|
|
*
|
|
* See the [Class System Guide](#!/guide/class_system) for more.
|
|
*
|
|
*/
|
|
(function(flexSetter) {
|
|
|
|
var noArgs = [],
|
|
Base = function(){};
|
|
|
|
// These static properties will be copied to every newly created class with {@link Ext#define}
|
|
Ext.apply(Base, {
|
|
$className: 'Ext.Base',
|
|
|
|
$isClass: true,
|
|
|
|
/**
|
|
* Create a new instance of this Class.
|
|
*
|
|
* Ext.define('My.cool.Class', {
|
|
* // ...
|
|
* });
|
|
*
|
|
* My.cool.Class.create({
|
|
* someConfig: true
|
|
* });
|
|
*
|
|
* All parameters are passed to the constructor of the class.
|
|
*
|
|
* @return {Object} the created instance.
|
|
* @static
|
|
* @inheritable
|
|
*/
|
|
create: function() {
|
|
return Ext.create.apply(Ext, [this].concat(Array.prototype.slice.call(arguments, 0)));
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @static
|
|
* @inheritable
|
|
*/
|
|
extend: function(parent) {
|
|
var parentPrototype = parent.prototype,
|
|
prototype, i, ln, name, statics;
|
|
|
|
prototype = this.prototype = Ext.Object.chain(parentPrototype);
|
|
prototype.self = this;
|
|
|
|
this.superclass = prototype.superclass = parentPrototype;
|
|
|
|
if (!parent.$isClass) {
|
|
Ext.apply(prototype, Ext.Base.prototype);
|
|
prototype.constructor = function() {
|
|
parentPrototype.constructor.apply(this, arguments);
|
|
};
|
|
}
|
|
|
|
//<feature classSystem.inheritableStatics>
|
|
// Statics inheritance
|
|
statics = parentPrototype.$inheritableStatics;
|
|
|
|
if (statics) {
|
|
for (i = 0,ln = statics.length; i < ln; i++) {
|
|
name = statics[i];
|
|
|
|
if (!this.hasOwnProperty(name)) {
|
|
this[name] = parent[name];
|
|
}
|
|
}
|
|
}
|
|
//</feature>
|
|
|
|
if (parent.$onExtended) {
|
|
this.$onExtended = parent.$onExtended.slice();
|
|
}
|
|
|
|
//<feature classSystem.config>
|
|
prototype.config = prototype.defaultConfig = new prototype.configClass;
|
|
prototype.initConfigList = prototype.initConfigList.slice();
|
|
prototype.initConfigMap = Ext.Object.chain(prototype.initConfigMap);
|
|
//</feature>
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @static
|
|
* @inheritable
|
|
*/
|
|
'$onExtended': [],
|
|
|
|
/**
|
|
* @private
|
|
* @static
|
|
* @inheritable
|
|
*/
|
|
triggerExtended: function() {
|
|
var callbacks = this.$onExtended,
|
|
ln = callbacks.length,
|
|
i, callback;
|
|
|
|
if (ln > 0) {
|
|
for (i = 0; i < ln; i++) {
|
|
callback = callbacks[i];
|
|
callback.fn.apply(callback.scope || this, arguments);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @static
|
|
* @inheritable
|
|
*/
|
|
onExtended: function(fn, scope) {
|
|
this.$onExtended.push({
|
|
fn: fn,
|
|
scope: scope
|
|
});
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @static
|
|
* @inheritable
|
|
*/
|
|
addConfig: function(config, fullMerge) {
|
|
var prototype = this.prototype,
|
|
initConfigList = prototype.initConfigList,
|
|
initConfigMap = prototype.initConfigMap,
|
|
defaultConfig = prototype.defaultConfig,
|
|
hasInitConfigItem, name, value;
|
|
|
|
fullMerge = Boolean(fullMerge);
|
|
|
|
for (name in config) {
|
|
if (config.hasOwnProperty(name) && (fullMerge || !(name in defaultConfig))) {
|
|
value = config[name];
|
|
hasInitConfigItem = initConfigMap[name];
|
|
|
|
if (value !== null) {
|
|
if (!hasInitConfigItem) {
|
|
initConfigMap[name] = true;
|
|
initConfigList.push(name);
|
|
}
|
|
}
|
|
else if (hasInitConfigItem) {
|
|
initConfigMap[name] = false;
|
|
Ext.Array.remove(initConfigList, name);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fullMerge) {
|
|
Ext.merge(defaultConfig, config);
|
|
}
|
|
else {
|
|
Ext.mergeIf(defaultConfig, config);
|
|
}
|
|
|
|
prototype.configClass = Ext.Object.classify(defaultConfig);
|
|
},
|
|
|
|
/**
|
|
* Add / override static properties of this class.
|
|
*
|
|
* Ext.define('My.cool.Class', {
|
|
* // this.se
|
|
* });
|
|
*
|
|
* My.cool.Class.addStatics({
|
|
* someProperty: 'someValue', // My.cool.Class.someProperty = 'someValue'
|
|
* method1: function() { }, // My.cool.Class.method1 = function() { ... };
|
|
* method2: function() { } // My.cool.Class.method2 = function() { ... };
|
|
* });
|
|
*
|
|
* @param {Object} members
|
|
* @return {Ext.Base} this
|
|
* @static
|
|
* @inheritable
|
|
*/
|
|
addStatics: function(members) {
|
|
var member, name;
|
|
//<debug>
|
|
var className = Ext.getClassName(this);
|
|
//</debug>
|
|
|
|
for (name in members) {
|
|
if (members.hasOwnProperty(name)) {
|
|
member = members[name];
|
|
//<debug>
|
|
if (typeof member == 'function') {
|
|
member.displayName = className + '.' + name;
|
|
}
|
|
//</debug>
|
|
this[name] = member;
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @static
|
|
* @inheritable
|
|
*/
|
|
addInheritableStatics: function(members) {
|
|
var inheritableStatics,
|
|
hasInheritableStatics,
|
|
prototype = this.prototype,
|
|
name, member;
|
|
|
|
inheritableStatics = prototype.$inheritableStatics;
|
|
hasInheritableStatics = prototype.$hasInheritableStatics;
|
|
|
|
if (!inheritableStatics) {
|
|
inheritableStatics = prototype.$inheritableStatics = [];
|
|
hasInheritableStatics = prototype.$hasInheritableStatics = {};
|
|
}
|
|
|
|
//<debug>
|
|
var className = Ext.getClassName(this);
|
|
//</debug>
|
|
|
|
for (name in members) {
|
|
if (members.hasOwnProperty(name)) {
|
|
member = members[name];
|
|
//<debug>
|
|
if (typeof member == 'function') {
|
|
member.displayName = className + '.' + name;
|
|
}
|
|
//</debug>
|
|
this[name] = member;
|
|
|
|
if (!hasInheritableStatics[name]) {
|
|
hasInheritableStatics[name] = true;
|
|
inheritableStatics.push(name);
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Add methods / properties to the prototype of this class.
|
|
*
|
|
* @example
|
|
* Ext.define('My.awesome.Cat', {
|
|
* constructor: function() {
|
|
* // ...
|
|
* }
|
|
* });
|
|
*
|
|
* My.awesome.Cat.addMembers({
|
|
* meow: function() {
|
|
* alert('Meowww...');
|
|
* }
|
|
* });
|
|
*
|
|
* var kitty = new My.awesome.Cat();
|
|
* kitty.meow();
|
|
*
|
|
* @param {Object} members
|
|
* @static
|
|
* @inheritable
|
|
*/
|
|
addMembers: function(members) {
|
|
var prototype = this.prototype,
|
|
names = [],
|
|
name, member;
|
|
|
|
//<debug>
|
|
var className = this.$className || '';
|
|
//</debug>
|
|
|
|
for (name in members) {
|
|
if (members.hasOwnProperty(name)) {
|
|
member = members[name];
|
|
|
|
if (typeof member == 'function' && !member.$isClass && member !== Ext.emptyFn) {
|
|
member.$owner = this;
|
|
member.$name = name;
|
|
//<debug>
|
|
member.displayName = className + '#' + name;
|
|
//</debug>
|
|
}
|
|
|
|
prototype[name] = member;
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @static
|
|
* @inheritable
|
|
*/
|
|
addMember: function(name, member) {
|
|
if (typeof member == 'function' && !member.$isClass && member !== Ext.emptyFn) {
|
|
member.$owner = this;
|
|
member.$name = name;
|
|
//<debug>
|
|
member.displayName = (this.$className || '') + '#' + name;
|
|
//</debug>
|
|
}
|
|
|
|
this.prototype[name] = member;
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @static
|
|
* @inheritable
|
|
*/
|
|
implement: function() {
|
|
this.addMembers.apply(this, arguments);
|
|
},
|
|
|
|
/**
|
|
* Borrow another class' members to the prototype of this class.
|
|
*
|
|
* Ext.define('Bank', {
|
|
* money: '$$$',
|
|
* printMoney: function() {
|
|
* alert('$$$$$$$');
|
|
* }
|
|
* });
|
|
*
|
|
* Ext.define('Thief', {
|
|
* // ...
|
|
* });
|
|
*
|
|
* Thief.borrow(Bank, ['money', 'printMoney']);
|
|
*
|
|
* var steve = new Thief();
|
|
*
|
|
* alert(steve.money); // alerts '$$$'
|
|
* steve.printMoney(); // alerts '$$$$$$$'
|
|
*
|
|
* @param {Ext.Base} fromClass The class to borrow members from
|
|
* @param {Array/String} members The names of the members to borrow
|
|
* @return {Ext.Base} this
|
|
* @static
|
|
* @inheritable
|
|
* @private
|
|
*/
|
|
borrow: function(fromClass, members) {
|
|
var prototype = this.prototype,
|
|
fromPrototype = fromClass.prototype,
|
|
//<debug>
|
|
className = Ext.getClassName(this),
|
|
//</debug>
|
|
i, ln, name, fn, toBorrow;
|
|
|
|
members = Ext.Array.from(members);
|
|
|
|
for (i = 0,ln = members.length; i < ln; i++) {
|
|
name = members[i];
|
|
|
|
toBorrow = fromPrototype[name];
|
|
|
|
if (typeof toBorrow == 'function') {
|
|
fn = function() {
|
|
return toBorrow.apply(this, arguments);
|
|
};
|
|
|
|
//<debug>
|
|
if (className) {
|
|
fn.displayName = className + '#' + name;
|
|
}
|
|
//</debug>
|
|
|
|
fn.$owner = this;
|
|
fn.$name = name;
|
|
|
|
prototype[name] = fn;
|
|
}
|
|
else {
|
|
prototype[name] = toBorrow;
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Override members of this class. Overridden methods can be invoked via
|
|
* {@link Ext.Base#callParent}.
|
|
*
|
|
* Ext.define('My.Cat', {
|
|
* constructor: function() {
|
|
* alert("I'm a cat!");
|
|
* }
|
|
* });
|
|
*
|
|
* My.Cat.override({
|
|
* constructor: function() {
|
|
* alert("I'm going to be a cat!");
|
|
*
|
|
* var instance = this.callParent(arguments);
|
|
*
|
|
* alert("Meeeeoooowwww");
|
|
*
|
|
* return instance;
|
|
* }
|
|
* });
|
|
*
|
|
* var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
|
|
* // alerts "I'm a cat!"
|
|
* // alerts "Meeeeoooowwww"
|
|
*
|
|
* As of 2.1, direct use of this method is deprecated. Use {@link Ext#define Ext.define}
|
|
* instead:
|
|
*
|
|
* Ext.define('My.CatOverride', {
|
|
* override: 'My.Cat',
|
|
*
|
|
* constructor: function() {
|
|
* alert("I'm going to be a cat!");
|
|
*
|
|
* var instance = this.callParent(arguments);
|
|
*
|
|
* alert("Meeeeoooowwww");
|
|
*
|
|
* return instance;
|
|
* }
|
|
* });
|
|
*
|
|
* The above accomplishes the same result but can be managed by the {@link Ext.Loader}
|
|
* which can properly order the override and its target class and the build process
|
|
* can determine whether the override is needed based on the required state of the
|
|
* target class (My.Cat).
|
|
*
|
|
* @param {Object} members The properties to add to this class. This should be
|
|
* specified as an object literal containing one or more properties.
|
|
* @return {Ext.Base} this class
|
|
* @static
|
|
* @inheritable
|
|
* @deprecated 2.1.0 Please use {@link Ext#define Ext.define} instead
|
|
*/
|
|
override: function(members) {
|
|
var me = this,
|
|
enumerables = Ext.enumerables,
|
|
target = me.prototype,
|
|
cloneFunction = Ext.Function.clone,
|
|
name, index, member, statics, names, previous;
|
|
|
|
if (arguments.length === 2) {
|
|
name = members;
|
|
members = {};
|
|
members[name] = arguments[1];
|
|
enumerables = null;
|
|
}
|
|
|
|
do {
|
|
names = []; // clean slate for prototype (1st pass) and static (2nd pass)
|
|
statics = null; // not needed 1st pass, but needs to be cleared for 2nd pass
|
|
|
|
for (name in members) { // hasOwnProperty is checked in the next loop...
|
|
if (name == 'statics') {
|
|
statics = members[name];
|
|
}
|
|
else if (name == 'config') {
|
|
me.addConfig(members[name], true);
|
|
}
|
|
else {
|
|
names.push(name);
|
|
}
|
|
}
|
|
|
|
if (enumerables) {
|
|
names.push.apply(names, enumerables);
|
|
}
|
|
|
|
for (index = names.length; index--; ) {
|
|
name = names[index];
|
|
|
|
if (members.hasOwnProperty(name)) {
|
|
member = members[name];
|
|
|
|
if (typeof member == 'function' && !member.$className && member !== Ext.emptyFn) {
|
|
if (typeof member.$owner != 'undefined') {
|
|
member = cloneFunction(member);
|
|
}
|
|
|
|
//<debug>
|
|
var className = me.$className;
|
|
if (className) {
|
|
member.displayName = className + '#' + name;
|
|
}
|
|
//</debug>
|
|
|
|
member.$owner = me;
|
|
member.$name = name;
|
|
|
|
previous = target[name];
|
|
if (previous) {
|
|
member.$previous = previous;
|
|
}
|
|
}
|
|
|
|
target[name] = member;
|
|
}
|
|
}
|
|
|
|
target = me; // 2nd pass is for statics
|
|
members = statics; // statics will be null on 2nd pass
|
|
} while (members);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* @protected
|
|
* @static
|
|
* @inheritable
|
|
*/
|
|
callParent: function(args) {
|
|
var method;
|
|
|
|
// This code is intentionally inlined for the least amount of debugger stepping
|
|
return (method = this.callParent.caller) && (method.$previous ||
|
|
((method = method.$owner ? method : method.caller) &&
|
|
method.$owner.superclass.$class[method.$name])).apply(this, args || noArgs);
|
|
},
|
|
|
|
//<feature classSystem.mixins>
|
|
/**
|
|
* Used internally by the mixins pre-processor
|
|
* @private
|
|
* @static
|
|
* @inheritable
|
|
*/
|
|
mixin: function(name, mixinClass) {
|
|
var mixin = mixinClass.prototype,
|
|
prototype = this.prototype,
|
|
key;
|
|
|
|
if (typeof mixin.onClassMixedIn != 'undefined') {
|
|
mixin.onClassMixedIn.call(mixinClass, this);
|
|
}
|
|
|
|
if (!prototype.hasOwnProperty('mixins')) {
|
|
if ('mixins' in prototype) {
|
|
prototype.mixins = Ext.Object.chain(prototype.mixins);
|
|
}
|
|
else {
|
|
prototype.mixins = {};
|
|
}
|
|
}
|
|
|
|
for (key in mixin) {
|
|
if (key === 'mixins') {
|
|
Ext.merge(prototype.mixins, mixin[key]);
|
|
}
|
|
else if (typeof prototype[key] == 'undefined' && key != 'mixinId' && key != 'config') {
|
|
prototype[key] = mixin[key];
|
|
}
|
|
}
|
|
|
|
//<feature classSystem.config>
|
|
if ('config' in mixin) {
|
|
this.addConfig(mixin.config, false);
|
|
}
|
|
//</feature>
|
|
|
|
prototype.mixins[name] = mixin;
|
|
},
|
|
//</feature>
|
|
|
|
/**
|
|
* Get the current class' name in string format.
|
|
*
|
|
* Ext.define('My.cool.Class', {
|
|
* constructor: function() {
|
|
* alert(this.self.getName()); // alerts 'My.cool.Class'
|
|
* }
|
|
* });
|
|
*
|
|
* My.cool.Class.getName(); // 'My.cool.Class'
|
|
*
|
|
* @return {String} className
|
|
* @static
|
|
* @inheritable
|
|
*/
|
|
getName: function() {
|
|
return Ext.getClassName(this);
|
|
},
|
|
|
|
/**
|
|
* Create aliases for existing prototype methods. Example:
|
|
*
|
|
* Ext.define('My.cool.Class', {
|
|
* method1: function() { },
|
|
* method2: function() { }
|
|
* });
|
|
*
|
|
* var test = new My.cool.Class();
|
|
*
|
|
* My.cool.Class.createAlias({
|
|
* method3: 'method1',
|
|
* method4: 'method2'
|
|
* });
|
|
*
|
|
* test.method3(); // test.method1()
|
|
*
|
|
* My.cool.Class.createAlias('method5', 'method3');
|
|
*
|
|
* test.method5(); // test.method3() -> test.method1()
|
|
*
|
|
* @param {String/Object} alias The new method name, or an object to set multiple aliases. See
|
|
* {@link Ext.Function#flexSetter flexSetter}
|
|
* @param {String/Object} origin The original method name
|
|
* @static
|
|
* @inheritable
|
|
* @method
|
|
*/
|
|
createAlias: flexSetter(function(alias, origin) {
|
|
this.override(alias, function() {
|
|
return this[origin].apply(this, arguments);
|
|
});
|
|
}),
|
|
|
|
/**
|
|
* @private
|
|
* @static
|
|
* @inheritable
|
|
*/
|
|
addXtype: function(xtype) {
|
|
var prototype = this.prototype,
|
|
xtypesMap = prototype.xtypesMap,
|
|
xtypes = prototype.xtypes,
|
|
xtypesChain = prototype.xtypesChain;
|
|
|
|
if (!prototype.hasOwnProperty('xtypesMap')) {
|
|
xtypesMap = prototype.xtypesMap = Ext.merge({}, prototype.xtypesMap || {});
|
|
xtypes = prototype.xtypes = prototype.xtypes ? [].concat(prototype.xtypes) : [];
|
|
xtypesChain = prototype.xtypesChain = prototype.xtypesChain ? [].concat(prototype.xtypesChain) : [];
|
|
prototype.xtype = xtype;
|
|
}
|
|
|
|
if (!xtypesMap[xtype]) {
|
|
xtypesMap[xtype] = true;
|
|
xtypes.push(xtype);
|
|
xtypesChain.push(xtype);
|
|
Ext.ClassManager.setAlias(this, 'widget.' + xtype);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
});
|
|
|
|
Base.implement({
|
|
isInstance: true,
|
|
|
|
$className: 'Ext.Base',
|
|
|
|
configClass: Ext.emptyFn,
|
|
|
|
initConfigList: [],
|
|
|
|
initConfigMap: {},
|
|
|
|
/**
|
|
* Get the reference to the class from which this object was instantiated. Note that unlike {@link Ext.Base#self},
|
|
* `this.statics()` is scope-independent and it always returns the class from which it was called, regardless of what
|
|
* `this` points to during run-time
|
|
*
|
|
* Ext.define('My.Cat', {
|
|
* statics: {
|
|
* totalCreated: 0,
|
|
* speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
|
|
* },
|
|
*
|
|
* constructor: function() {
|
|
* var statics = this.statics();
|
|
*
|
|
* alert(statics.speciesName); // always equals to 'Cat' no matter what 'this' refers to
|
|
* // equivalent to: My.Cat.speciesName
|
|
*
|
|
* alert(this.self.speciesName); // dependent on 'this'
|
|
*
|
|
* statics.totalCreated++;
|
|
* },
|
|
*
|
|
* clone: function() {
|
|
* var cloned = new this.self(); // dependent on 'this'
|
|
*
|
|
* cloned.groupName = this.statics().speciesName; // equivalent to: My.Cat.speciesName
|
|
*
|
|
* return cloned;
|
|
* }
|
|
* });
|
|
*
|
|
*
|
|
* Ext.define('My.SnowLeopard', {
|
|
* extend: 'My.Cat',
|
|
*
|
|
* statics: {
|
|
* speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
|
|
* },
|
|
*
|
|
* constructor: function() {
|
|
* this.callParent();
|
|
* }
|
|
* });
|
|
*
|
|
* var cat = new My.Cat(); // alerts 'Cat', then alerts 'Cat'
|
|
*
|
|
* var snowLeopard = new My.SnowLeopard(); // alerts 'Cat', then alerts 'Snow Leopard'
|
|
*
|
|
* var clone = snowLeopard.clone();
|
|
* alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
|
|
* alert(clone.groupName); // alerts 'Cat'
|
|
*
|
|
* alert(My.Cat.totalCreated); // alerts 3
|
|
*
|
|
* @protected
|
|
* @return {Ext.Class}
|
|
*/
|
|
statics: function() {
|
|
var method = this.statics.caller,
|
|
self = this.self;
|
|
|
|
if (!method) {
|
|
return self;
|
|
}
|
|
|
|
return method.$owner;
|
|
},
|
|
|
|
/**
|
|
* Call the "parent" method of the current method. That is the method previously
|
|
* overridden by derivation or by an override (see {@link Ext#define}).
|
|
*
|
|
* Ext.define('My.Base', {
|
|
* constructor: function (x) {
|
|
* this.x = x;
|
|
* },
|
|
*
|
|
* statics: {
|
|
* method: function (x) {
|
|
* return x;
|
|
* }
|
|
* }
|
|
* });
|
|
*
|
|
* Ext.define('My.Derived', {
|
|
* extend: 'My.Base',
|
|
*
|
|
* constructor: function () {
|
|
* this.callParent([21]);
|
|
* }
|
|
* });
|
|
*
|
|
* var obj = new My.Derived();
|
|
*
|
|
* alert(obj.x); // alerts 21
|
|
*
|
|
* This can be used with an override as follows:
|
|
*
|
|
* Ext.define('My.DerivedOverride', {
|
|
* override: 'My.Derived',
|
|
*
|
|
* constructor: function (x) {
|
|
* this.callParent([x*2]); // calls original My.Derived constructor
|
|
* }
|
|
* });
|
|
*
|
|
* var obj = new My.Derived();
|
|
*
|
|
* alert(obj.x); // now alerts 42
|
|
*
|
|
* This also works with static methods.
|
|
*
|
|
* Ext.define('My.Derived2', {
|
|
* extend: 'My.Base',
|
|
*
|
|
* statics: {
|
|
* method: function (x) {
|
|
* return this.callParent([x*2]); // calls My.Base.method
|
|
* }
|
|
* }
|
|
* });
|
|
*
|
|
* alert(My.Base.method(10)); // alerts 10
|
|
* alert(My.Derived2.method(10)); // alerts 20
|
|
*
|
|
* Lastly, it also works with overridden static methods.
|
|
*
|
|
* Ext.define('My.Derived2Override', {
|
|
* override: 'My.Derived2',
|
|
*
|
|
* statics: {
|
|
* method: function (x) {
|
|
* return this.callParent([x*2]); // calls My.Derived2.method
|
|
* }
|
|
* }
|
|
* });
|
|
*
|
|
* alert(My.Derived2.method(10)); // now alerts 40
|
|
*
|
|
* To override a method and replace it and also call the superclass method, use
|
|
* {@link #callSuper}. This is often done to patch a method to fix a bug.
|
|
*
|
|
* @protected
|
|
* @param {Array/Arguments} args The arguments, either an array or the `arguments` object
|
|
* from the current method, for example: `this.callParent(arguments)`
|
|
* @return {Object} Returns the result of calling the parent method
|
|
*/
|
|
callParent: function(args) {
|
|
// NOTE: this code is deliberately as few expressions (and no function calls)
|
|
// as possible so that a debugger can skip over this noise with the minimum number
|
|
// of steps. Basically, just hit Step Into until you are where you really wanted
|
|
// to be.
|
|
var method,
|
|
superMethod = (method = this.callParent.caller) && (method.$previous ||
|
|
((method = method.$owner ? method : method.caller) &&
|
|
method.$owner.superclass[method.$name]));
|
|
|
|
//<debug error>
|
|
if (!superMethod) {
|
|
method = this.callParent.caller;
|
|
var parentClass, methodName;
|
|
|
|
if (!method.$owner) {
|
|
if (!method.caller) {
|
|
throw new Error("Attempting to call a protected method from the public scope, which is not allowed");
|
|
}
|
|
|
|
method = method.caller;
|
|
}
|
|
|
|
parentClass = method.$owner.superclass;
|
|
methodName = method.$name;
|
|
|
|
if (!(methodName in parentClass)) {
|
|
throw new Error("this.callParent() was called but there's no such method (" + methodName +
|
|
") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")");
|
|
}
|
|
}
|
|
//</debug>
|
|
|
|
return superMethod.apply(this, args || noArgs);
|
|
},
|
|
|
|
/**
|
|
* This method is used by an override to call the superclass method but bypass any
|
|
* overridden method. This is often done to "patch" a method that contains a bug
|
|
* but for whatever reason cannot be fixed directly.
|
|
*
|
|
* Consider:
|
|
*
|
|
* Ext.define('Ext.some.Class', {
|
|
* method: function () {
|
|
* console.log('Good');
|
|
* }
|
|
* });
|
|
*
|
|
* Ext.define('Ext.some.DerivedClass', {
|
|
* method: function () {
|
|
* console.log('Bad');
|
|
*
|
|
* // ... logic but with a bug ...
|
|
*
|
|
* this.callParent();
|
|
* }
|
|
* });
|
|
*
|
|
* To patch the bug in `DerivedClass.method`, the typical solution is to create an
|
|
* override:
|
|
*
|
|
* Ext.define('App.paches.DerivedClass', {
|
|
* override: 'Ext.some.DerivedClass',
|
|
*
|
|
* method: function () {
|
|
* console.log('Fixed');
|
|
*
|
|
* // ... logic but with bug fixed ...
|
|
*
|
|
* this.callSuper();
|
|
* }
|
|
* });
|
|
*
|
|
* The patch method cannot use `callParent` to call the superclass `method` since
|
|
* that would call the overridden method containing the bug. In other words, the
|
|
* above patch would only produce "Fixed" then "Good" in the console log, whereas,
|
|
* using `callParent` would produce "Fixed" then "Bad" then "Good".
|
|
*
|
|
* @protected
|
|
* @param {Array/Arguments} args The arguments, either an array or the `arguments` object
|
|
* from the current method, for example: `this.callSuper(arguments)`
|
|
* @return {Object} Returns the result of calling the superclass method
|
|
*/
|
|
callSuper: function(args) {
|
|
var method,
|
|
superMethod = (method = this.callSuper.caller) && ((method = method.$owner ? method : method.caller) &&
|
|
method.$owner.superclass[method.$name]);
|
|
|
|
//<debug error>
|
|
if (!superMethod) {
|
|
method = this.callSuper.caller;
|
|
var parentClass, methodName;
|
|
|
|
if (!method.$owner) {
|
|
if (!method.caller) {
|
|
throw new Error("Attempting to call a protected method from the public scope, which is not allowed");
|
|
}
|
|
|
|
method = method.caller;
|
|
}
|
|
|
|
parentClass = method.$owner.superclass;
|
|
methodName = method.$name;
|
|
|
|
if (!(methodName in parentClass)) {
|
|
throw new Error("this.callSuper() was called but there's no such method (" + methodName +
|
|
") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")");
|
|
}
|
|
}
|
|
//</debug>
|
|
|
|
return superMethod.apply(this, args || noArgs);
|
|
},
|
|
|
|
/**
|
|
* Call the original method that was previously overridden with {@link Ext.Base#override},
|
|
*
|
|
* This method is deprecated as {@link #callParent} does the same thing.
|
|
*
|
|
* Ext.define('My.Cat', {
|
|
* constructor: function() {
|
|
* alert("I'm a cat!");
|
|
* }
|
|
* });
|
|
*
|
|
* My.Cat.override({
|
|
* constructor: function() {
|
|
* alert("I'm going to be a cat!");
|
|
*
|
|
* var instance = this.callOverridden();
|
|
*
|
|
* alert("Meeeeoooowwww");
|
|
*
|
|
* return instance;
|
|
* }
|
|
* });
|
|
*
|
|
* var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
|
|
* // alerts "I'm a cat!"
|
|
* // alerts "Meeeeoooowwww"
|
|
*
|
|
* @param {Array/Arguments} args The arguments, either an array or the `arguments` object
|
|
* from the current method, for example: `this.callOverridden(arguments)`
|
|
* @return {Object} Returns the result of calling the overridden method
|
|
* @protected
|
|
* @deprecated Use callParent instead
|
|
*/
|
|
callOverridden: function(args) {
|
|
var method;
|
|
|
|
return (method = this.callOverridden.caller) && method.$previous.apply(this, args || noArgs);
|
|
},
|
|
|
|
/**
|
|
* @property {Ext.Class} self
|
|
*
|
|
* Get the reference to the current class from which this object was instantiated. Unlike {@link Ext.Base#statics},
|
|
* `this.self` is scope-dependent and it's meant to be used for dynamic inheritance. See {@link Ext.Base#statics}
|
|
* for a detailed comparison
|
|
*
|
|
* Ext.define('My.Cat', {
|
|
* statics: {
|
|
* speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
|
|
* },
|
|
*
|
|
* constructor: function() {
|
|
* alert(this.self.speciesName); // dependent on 'this'
|
|
* },
|
|
*
|
|
* clone: function() {
|
|
* return new this.self();
|
|
* }
|
|
* });
|
|
*
|
|
*
|
|
* Ext.define('My.SnowLeopard', {
|
|
* extend: 'My.Cat',
|
|
* statics: {
|
|
* speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
|
|
* }
|
|
* });
|
|
*
|
|
* var cat = new My.Cat(); // alerts 'Cat'
|
|
* var snowLeopard = new My.SnowLeopard(); // alerts 'Snow Leopard'
|
|
*
|
|
* var clone = snowLeopard.clone();
|
|
* alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
|
|
*
|
|
* @protected
|
|
*/
|
|
self: Base,
|
|
|
|
// Default constructor, simply returns `this`
|
|
constructor: function() {
|
|
return this;
|
|
},
|
|
|
|
//<feature classSystem.config>
|
|
|
|
wasInstantiated: false,
|
|
|
|
/**
|
|
* Initialize configuration for this class. a typical example:
|
|
*
|
|
* Ext.define('My.awesome.Class', {
|
|
* // The default config
|
|
* config: {
|
|
* name: 'Awesome',
|
|
* isAwesome: true
|
|
* },
|
|
*
|
|
* constructor: function(config) {
|
|
* this.initConfig(config);
|
|
* }
|
|
* });
|
|
*
|
|
* var awesome = new My.awesome.Class({
|
|
* name: 'Super Awesome'
|
|
* });
|
|
*
|
|
* alert(awesome.getName()); // 'Super Awesome'
|
|
*
|
|
* @protected
|
|
* @param {Object} instanceConfig
|
|
* @return {Object} mixins The mixin prototypes as key - value pairs
|
|
*/
|
|
initConfig: function(instanceConfig) {
|
|
//<debug>
|
|
// if (instanceConfig && instanceConfig.breakOnInitConfig) {
|
|
// debugger;
|
|
// }
|
|
//</debug>
|
|
var configNameCache = Ext.Class.configNameCache,
|
|
prototype = this.self.prototype,
|
|
initConfigList = this.initConfigList,
|
|
initConfigMap = this.initConfigMap,
|
|
config = new this.configClass,
|
|
defaultConfig = this.defaultConfig,
|
|
i, ln, name, value, nameMap, getName;
|
|
|
|
this.initConfig = Ext.emptyFn;
|
|
|
|
this.initialConfig = instanceConfig || {};
|
|
|
|
if (instanceConfig) {
|
|
Ext.merge(config, instanceConfig);
|
|
}
|
|
|
|
this.config = config;
|
|
|
|
// Optimize initConfigList *once* per class based on the existence of apply* and update* methods
|
|
// Happens only once during the first instantiation
|
|
if (!prototype.hasOwnProperty('wasInstantiated')) {
|
|
prototype.wasInstantiated = true;
|
|
|
|
for (i = 0,ln = initConfigList.length; i < ln; i++) {
|
|
name = initConfigList[i];
|
|
nameMap = configNameCache[name];
|
|
value = defaultConfig[name];
|
|
|
|
if (!(nameMap.apply in prototype)
|
|
&& !(nameMap.update in prototype)
|
|
&& prototype[nameMap.set].$isDefault
|
|
&& typeof value != 'object') {
|
|
prototype[nameMap.internal] = defaultConfig[name];
|
|
initConfigMap[name] = false;
|
|
Ext.Array.remove(initConfigList, name);
|
|
i--;
|
|
ln--;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (instanceConfig) {
|
|
initConfigList = initConfigList.slice();
|
|
|
|
for (name in instanceConfig) {
|
|
if (name in defaultConfig && !initConfigMap[name]) {
|
|
initConfigList.push(name);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Point all getters to the initGetters
|
|
for (i = 0,ln = initConfigList.length; i < ln; i++) {
|
|
name = initConfigList[i];
|
|
nameMap = configNameCache[name];
|
|
this[nameMap.get] = this[nameMap.initGet];
|
|
}
|
|
|
|
this.beforeInitConfig(config);
|
|
|
|
for (i = 0,ln = initConfigList.length; i < ln; i++) {
|
|
name = initConfigList[i];
|
|
nameMap = configNameCache[name];
|
|
getName = nameMap.get;
|
|
|
|
if (this.hasOwnProperty(getName)) {
|
|
this[nameMap.set].call(this, config[name]);
|
|
delete this[getName];
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
beforeInitConfig: Ext.emptyFn,
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
getCurrentConfig: function() {
|
|
var defaultConfig = this.defaultConfig,
|
|
configNameCache = Ext.Class.configNameCache,
|
|
config = {},
|
|
name, nameMap;
|
|
|
|
for (name in defaultConfig) {
|
|
nameMap = configNameCache[name];
|
|
config[name] = this[nameMap.get].call(this);
|
|
}
|
|
|
|
return config;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
setConfig: function(config, applyIfNotSet) {
|
|
if (!config) {
|
|
return this;
|
|
}
|
|
|
|
var configNameCache = Ext.Class.configNameCache,
|
|
currentConfig = this.config,
|
|
defaultConfig = this.defaultConfig,
|
|
initialConfig = this.initialConfig,
|
|
configList = [],
|
|
name, i, ln, nameMap;
|
|
|
|
applyIfNotSet = Boolean(applyIfNotSet);
|
|
|
|
for (name in config) {
|
|
if ((applyIfNotSet && (name in initialConfig))) {
|
|
continue;
|
|
}
|
|
|
|
currentConfig[name] = config[name];
|
|
|
|
if (name in defaultConfig) {
|
|
configList.push(name);
|
|
nameMap = configNameCache[name];
|
|
this[nameMap.get] = this[nameMap.initGet];
|
|
}
|
|
}
|
|
|
|
for (i = 0,ln = configList.length; i < ln; i++) {
|
|
name = configList[i];
|
|
nameMap = configNameCache[name];
|
|
this[nameMap.set].call(this, config[name]);
|
|
delete this[nameMap.get];
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
set: function(name, value) {
|
|
return this[Ext.Class.configNameCache[name].set].call(this, value);
|
|
},
|
|
|
|
get: function(name) {
|
|
return this[Ext.Class.configNameCache[name].get].call(this);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
getConfig: function(name) {
|
|
return this[Ext.Class.configNameCache[name].get].call(this);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
hasConfig: function(name) {
|
|
return (name in this.defaultConfig);
|
|
},
|
|
|
|
/**
|
|
* Returns the initial configuration passed to constructor.
|
|
*
|
|
* @param {String} [name] When supplied, value for particular configuration
|
|
* option is returned, otherwise the full config object is returned.
|
|
* @return {Object/Mixed}
|
|
*/
|
|
getInitialConfig: function(name) {
|
|
var config = this.config;
|
|
|
|
if (!name) {
|
|
return config;
|
|
}
|
|
else {
|
|
return config[name];
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
onConfigUpdate: function(names, callback, scope) {
|
|
var self = this.self,
|
|
//<debug>
|
|
className = self.$className,
|
|
//</debug>
|
|
i, ln, name,
|
|
updaterName, updater, newUpdater;
|
|
|
|
names = Ext.Array.from(names);
|
|
|
|
scope = scope || this;
|
|
|
|
for (i = 0,ln = names.length; i < ln; i++) {
|
|
name = names[i];
|
|
updaterName = 'update' + Ext.String.capitalize(name);
|
|
updater = this[updaterName] || Ext.emptyFn;
|
|
newUpdater = function() {
|
|
updater.apply(this, arguments);
|
|
scope[callback].apply(scope, arguments);
|
|
};
|
|
newUpdater.$name = updaterName;
|
|
newUpdater.$owner = self;
|
|
//<debug>
|
|
newUpdater.displayName = className + '#' + updaterName;
|
|
//</debug>
|
|
|
|
this[updaterName] = newUpdater;
|
|
}
|
|
},
|
|
//</feature>
|
|
|
|
/**
|
|
* @private
|
|
* @param name
|
|
* @param value
|
|
* @return {Mixed}
|
|
*/
|
|
link: function(name, value) {
|
|
this.$links = {};
|
|
this.link = this.doLink;
|
|
return this.link.apply(this, arguments);
|
|
},
|
|
|
|
doLink: function(name, value) {
|
|
this.$links[name] = true;
|
|
|
|
this[name] = value;
|
|
|
|
return value;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
unlink: function() {
|
|
var i, ln, link, value;
|
|
|
|
for (i = 0, ln = arguments.length; i < ln; i++) {
|
|
link = arguments[i];
|
|
if (this.hasOwnProperty(link)) {
|
|
value = this[link];
|
|
if (value) {
|
|
if (value.isInstance && !value.isDestroyed) {
|
|
value.destroy();
|
|
}
|
|
else if (value.parentNode && 'nodeType' in value) {
|
|
value.parentNode.removeChild(value);
|
|
}
|
|
}
|
|
delete this[link];
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* @protected
|
|
*/
|
|
destroy: function() {
|
|
this.destroy = Ext.emptyFn;
|
|
this.isDestroyed = true;
|
|
|
|
if (this.hasOwnProperty('$links')) {
|
|
this.unlink.apply(this, Ext.Object.getKeys(this.$links));
|
|
delete this.$links;
|
|
}
|
|
}
|
|
});
|
|
|
|
Ext.Base = Base;
|
|
|
|
})(Ext.Function.flexSetter);
|
|
|
|
//@tag foundation,core
|
|
//@define Ext.Class
|
|
//@require Ext.Base
|
|
|
|
/**
|
|
* @class Ext.Class
|
|
*
|
|
* @author Jacky Nguyen <jacky@sencha.com>
|
|
* @aside guide class_system
|
|
* @aside video class-system
|
|
*
|
|
* Handles class creation throughout the framework. This is a low level factory that is used by Ext.ClassManager and generally
|
|
* should not be used directly. If you choose to use Ext.Class you will lose out on the namespace, aliasing and dependency loading
|
|
* features made available by Ext.ClassManager. The only time you would use Ext.Class directly is to create an anonymous class.
|
|
*
|
|
* If you wish to create a class you should use {@link Ext#define Ext.define} which aliases
|
|
* {@link Ext.ClassManager#create Ext.ClassManager.create} to enable namespacing and dynamic dependency resolution.
|
|
*
|
|
* Ext.Class is the factory and **not** the superclass of everything. For the base class that **all** Ext classes inherit
|
|
* from, see {@link Ext.Base}.
|
|
*/
|
|
(function() {
|
|
var ExtClass,
|
|
Base = Ext.Base,
|
|
baseStaticMembers = [],
|
|
baseStaticMember, baseStaticMemberLength;
|
|
|
|
for (baseStaticMember in Base) {
|
|
if (Base.hasOwnProperty(baseStaticMember)) {
|
|
baseStaticMembers.push(baseStaticMember);
|
|
}
|
|
}
|
|
|
|
baseStaticMemberLength = baseStaticMembers.length;
|
|
|
|
/**
|
|
* @method constructor
|
|
* Creates a new anonymous class.
|
|
*
|
|
* @param {Object} data An object represent the properties of this class.
|
|
* @param {Function} onCreated (optional) The callback function to be executed when this class is fully created.
|
|
* Note that the creation process can be asynchronous depending on the pre-processors used.
|
|
*
|
|
* @return {Ext.Base} The newly created class
|
|
*/
|
|
Ext.Class = ExtClass = function(Class, data, onCreated) {
|
|
if (typeof Class != 'function') {
|
|
onCreated = data;
|
|
data = Class;
|
|
Class = null;
|
|
}
|
|
|
|
if (!data) {
|
|
data = {};
|
|
}
|
|
|
|
Class = ExtClass.create(Class);
|
|
|
|
ExtClass.process(Class, data, onCreated);
|
|
|
|
return Class;
|
|
};
|
|
|
|
Ext.apply(ExtClass, {
|
|
/**
|
|
* @private
|
|
* @static
|
|
*/
|
|
onBeforeCreated: function(Class, data, hooks) {
|
|
Class.addMembers(data);
|
|
|
|
hooks.onCreated.call(Class, Class);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @static
|
|
*/
|
|
create: function(Class) {
|
|
var name, i;
|
|
|
|
if (!Class) {
|
|
Class = function() {
|
|
return this.constructor.apply(this, arguments);
|
|
};
|
|
}
|
|
|
|
for (i = 0; i < baseStaticMemberLength; i++) {
|
|
name = baseStaticMembers[i];
|
|
Class[name] = Base[name];
|
|
}
|
|
|
|
return Class;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @static
|
|
*/
|
|
process: function(Class, data, onCreated) {
|
|
var preprocessorStack = data.preprocessors || ExtClass.defaultPreprocessors,
|
|
preprocessors = this.preprocessors,
|
|
hooks = {
|
|
onBeforeCreated: this.onBeforeCreated,
|
|
onCreated: onCreated || Ext.emptyFn
|
|
},
|
|
index = 0,
|
|
name, preprocessor, properties,
|
|
i, ln, fn, property, process;
|
|
|
|
delete data.preprocessors;
|
|
|
|
process = function(Class, data, hooks) {
|
|
fn = null;
|
|
|
|
while (fn === null) {
|
|
name = preprocessorStack[index++];
|
|
|
|
if (name) {
|
|
preprocessor = preprocessors[name];
|
|
properties = preprocessor.properties;
|
|
|
|
if (properties === true) {
|
|
fn = preprocessor.fn;
|
|
}
|
|
else {
|
|
for (i = 0,ln = properties.length; i < ln; i++) {
|
|
property = properties[i];
|
|
|
|
if (data.hasOwnProperty(property)) {
|
|
fn = preprocessor.fn;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
hooks.onBeforeCreated.apply(this, arguments);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (fn.call(this, Class, data, hooks, process) !== false) {
|
|
process.apply(this, arguments);
|
|
}
|
|
};
|
|
|
|
process.call(this, Class, data, hooks);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @static
|
|
*/
|
|
preprocessors: {},
|
|
|
|
/**
|
|
* Register a new pre-processor to be used during the class creation process.
|
|
*
|
|
* @private
|
|
* @static
|
|
* @param {String} name The pre-processor's name.
|
|
* @param {Function} fn The callback function to be executed. Typical format:
|
|
*
|
|
* function(cls, data, fn) {
|
|
* // Your code here
|
|
*
|
|
* // Execute this when the processing is finished.
|
|
* // Asynchronous processing is perfectly OK
|
|
* if (fn) {
|
|
* fn.call(this, cls, data);
|
|
* }
|
|
* });
|
|
*
|
|
* @param {Function} fn.cls The created class.
|
|
* @param {Object} fn.data The set of properties passed in {@link Ext.Class} constructor.
|
|
* @param {Function} fn.fn The callback function that __must__ to be executed when this pre-processor finishes,
|
|
* regardless of whether the processing is synchronous or asynchronous.
|
|
*
|
|
* @return {Ext.Class} this
|
|
*/
|
|
registerPreprocessor: function(name, fn, properties, position, relativeTo) {
|
|
if (!position) {
|
|
position = 'last';
|
|
}
|
|
|
|
if (!properties) {
|
|
properties = [name];
|
|
}
|
|
|
|
this.preprocessors[name] = {
|
|
name: name,
|
|
properties: properties || false,
|
|
fn: fn
|
|
};
|
|
|
|
this.setDefaultPreprocessorPosition(name, position, relativeTo);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Retrieve a pre-processor callback function by its name, which has been registered before.
|
|
*
|
|
* @private
|
|
* @static
|
|
* @param {String} name
|
|
* @return {Function} preprocessor
|
|
*/
|
|
getPreprocessor: function(name) {
|
|
return this.preprocessors[name];
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @static
|
|
*/
|
|
getPreprocessors: function() {
|
|
return this.preprocessors;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @static
|
|
*/
|
|
defaultPreprocessors: [],
|
|
|
|
/**
|
|
* Retrieve the array stack of default pre-processors.
|
|
* @private
|
|
* @static
|
|
* @return {Function} defaultPreprocessors
|
|
*/
|
|
getDefaultPreprocessors: function() {
|
|
return this.defaultPreprocessors;
|
|
},
|
|
|
|
/**
|
|
* Set the default array stack of default pre-processors.
|
|
*
|
|
* @private
|
|
* @static
|
|
* @param {Array} preprocessors
|
|
* @return {Ext.Class} this
|
|
*/
|
|
setDefaultPreprocessors: function(preprocessors) {
|
|
this.defaultPreprocessors = Ext.Array.from(preprocessors);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Insert this pre-processor at a specific position in the stack, optionally relative to
|
|
* any existing pre-processor. For example:
|
|
*
|
|
* Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {
|
|
* // Your code here
|
|
*
|
|
* if (fn) {
|
|
* fn.call(this, cls, data);
|
|
* }
|
|
* }).insertDefaultPreprocessor('debug', 'last');
|
|
*
|
|
* @private
|
|
* @static
|
|
* @param {String} name The pre-processor name. Note that it needs to be registered with
|
|
* {@link Ext.Class#registerPreprocessor registerPreprocessor} before this.
|
|
* @param {String} offset The insertion position. Four possible values are:
|
|
* 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument).
|
|
* @param {String} relativeName
|
|
* @return {Ext.Class} this
|
|
*/
|
|
setDefaultPreprocessorPosition: function(name, offset, relativeName) {
|
|
var defaultPreprocessors = this.defaultPreprocessors,
|
|
index;
|
|
|
|
if (typeof offset == 'string') {
|
|
if (offset === 'first') {
|
|
defaultPreprocessors.unshift(name);
|
|
|
|
return this;
|
|
}
|
|
else if (offset === 'last') {
|
|
defaultPreprocessors.push(name);
|
|
|
|
return this;
|
|
}
|
|
|
|
offset = (offset === 'after') ? 1 : -1;
|
|
}
|
|
|
|
index = Ext.Array.indexOf(defaultPreprocessors, relativeName);
|
|
|
|
if (index !== -1) {
|
|
Ext.Array.splice(defaultPreprocessors, Math.max(0, index + offset), 0, name);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @static
|
|
*/
|
|
configNameCache: {},
|
|
|
|
/**
|
|
* @private
|
|
* @static
|
|
*/
|
|
getConfigNameMap: function(name) {
|
|
var cache = this.configNameCache,
|
|
map = cache[name],
|
|
capitalizedName;
|
|
|
|
if (!map) {
|
|
capitalizedName = name.charAt(0).toUpperCase() + name.substr(1);
|
|
|
|
map = cache[name] = {
|
|
name: name,
|
|
internal: '_' + name,
|
|
initializing: 'is' + capitalizedName + 'Initializing',
|
|
apply: 'apply' + capitalizedName,
|
|
update: 'update' + capitalizedName,
|
|
set: 'set' + capitalizedName,
|
|
get: 'get' + capitalizedName,
|
|
initGet: 'initGet' + capitalizedName,
|
|
doSet : 'doSet' + capitalizedName,
|
|
changeEvent: name.toLowerCase() + 'change'
|
|
}
|
|
}
|
|
|
|
return map;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @static
|
|
*/
|
|
generateSetter: function(nameMap) {
|
|
var internalName = nameMap.internal,
|
|
getName = nameMap.get,
|
|
applyName = nameMap.apply,
|
|
updateName = nameMap.update,
|
|
setter;
|
|
|
|
setter = function(value) {
|
|
var oldValue = this[internalName],
|
|
applier = this[applyName],
|
|
updater = this[updateName];
|
|
|
|
delete this[getName];
|
|
|
|
if (applier) {
|
|
value = applier.call(this, value, oldValue);
|
|
}
|
|
|
|
if (typeof value != 'undefined') {
|
|
this[internalName] = value;
|
|
|
|
if (updater && value !== oldValue) {
|
|
updater.call(this, value, oldValue);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
setter.$isDefault = true;
|
|
|
|
return setter;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @static
|
|
*/
|
|
generateInitGetter: function(nameMap) {
|
|
var name = nameMap.name,
|
|
setName = nameMap.set,
|
|
getName = nameMap.get,
|
|
initializingName = nameMap.initializing;
|
|
|
|
return function() {
|
|
this[initializingName] = true;
|
|
delete this[getName];
|
|
|
|
this[setName].call(this, this.config[name]);
|
|
delete this[initializingName];
|
|
|
|
return this[getName].apply(this, arguments);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @static
|
|
*/
|
|
generateGetter: function(nameMap) {
|
|
var internalName = nameMap.internal;
|
|
|
|
return function() {
|
|
return this[internalName];
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @cfg {String} extend
|
|
* The parent class that this class extends. For example:
|
|
*
|
|
* @example
|
|
* Ext.define('Person', {
|
|
* say: function(text) {
|
|
* alert(text);
|
|
* }
|
|
* });
|
|
*
|
|
* Ext.define('Developer', {
|
|
* extend: 'Person',
|
|
* say: function(text) {
|
|
* this.callParent(["print " + text]);
|
|
* }
|
|
* });
|
|
*
|
|
* var person1 = Ext.create("Person");
|
|
* person1.say("Bill");
|
|
*
|
|
* var developer1 = Ext.create("Developer");
|
|
* developer1.say("Ted");
|
|
*/
|
|
ExtClass.registerPreprocessor('extend', function(Class, data) {
|
|
var Base = Ext.Base,
|
|
extend = data.extend,
|
|
Parent;
|
|
|
|
delete data.extend;
|
|
|
|
if (extend && extend !== Object) {
|
|
Parent = extend;
|
|
}
|
|
else {
|
|
Parent = Base;
|
|
}
|
|
|
|
Class.extend(Parent);
|
|
|
|
Class.triggerExtended.apply(Class, arguments);
|
|
|
|
if (data.onClassExtended) {
|
|
Class.onExtended(data.onClassExtended, Class);
|
|
delete data.onClassExtended;
|
|
}
|
|
|
|
}, true);
|
|
|
|
//<feature classSystem.statics>
|
|
/**
|
|
* @cfg {Object} statics
|
|
* List of static methods for this class. For example:
|
|
*
|
|
* Ext.define('Computer', {
|
|
* statics: {
|
|
* factory: function(brand) {
|
|
* // 'this' in static methods refer to the class itself
|
|
* return new this(brand);
|
|
* }
|
|
* },
|
|
*
|
|
* constructor: function() {
|
|
* // ...
|
|
* }
|
|
* });
|
|
*
|
|
* var dellComputer = Computer.factory('Dell');
|
|
*/
|
|
ExtClass.registerPreprocessor('statics', function(Class, data) {
|
|
Class.addStatics(data.statics);
|
|
|
|
delete data.statics;
|
|
});
|
|
//</feature>
|
|
|
|
//<feature classSystem.inheritableStatics>
|
|
/**
|
|
* @cfg {Object} inheritableStatics
|
|
* List of inheritable static methods for this class.
|
|
* Otherwise just like {@link #statics} but subclasses inherit these methods.
|
|
*/
|
|
ExtClass.registerPreprocessor('inheritableStatics', function(Class, data) {
|
|
Class.addInheritableStatics(data.inheritableStatics);
|
|
|
|
delete data.inheritableStatics;
|
|
});
|
|
//</feature>
|
|
|
|
//<feature classSystem.config>
|
|
/**
|
|
* @cfg {Object} config
|
|
*
|
|
* List of configuration options with their default values.
|
|
*
|
|
* __Note:__ You need to make sure {@link Ext.Base#initConfig} is called from your constructor if you are defining
|
|
* your own class or singleton, unless you are extending a Component. Otherwise the generated getter and setter
|
|
* methods will not be initialized.
|
|
*
|
|
* Each config item will have its own setter and getter method automatically generated inside the class prototype
|
|
* during class creation time, if the class does not have those methods explicitly defined.
|
|
*
|
|
* As an example, let's convert the name property of a Person class to be a config item, then add extra age and
|
|
* gender items.
|
|
*
|
|
* Ext.define('My.sample.Person', {
|
|
* config: {
|
|
* name: 'Mr. Unknown',
|
|
* age: 0,
|
|
* gender: 'Male'
|
|
* },
|
|
*
|
|
* constructor: function(config) {
|
|
* this.initConfig(config);
|
|
*
|
|
* return this;
|
|
* }
|
|
*
|
|
* // ...
|
|
* });
|
|
*
|
|
* Within the class, this.name still has the default value of "Mr. Unknown". However, it's now publicly accessible
|
|
* without sacrificing encapsulation, via setter and getter methods.
|
|
*
|
|
* var jacky = new Person({
|
|
* name: "Jacky",
|
|
* age: 35
|
|
* });
|
|
*
|
|
* alert(jacky.getAge()); // alerts 35
|
|
* alert(jacky.getGender()); // alerts "Male"
|
|
*
|
|
* jacky.walk(10); // alerts "Jacky is walking 10 steps"
|
|
*
|
|
* jacky.setName("Mr. Nguyen");
|
|
* alert(jacky.getName()); // alerts "Mr. Nguyen"
|
|
*
|
|
* jacky.walk(10); // alerts "Mr. Nguyen is walking 10 steps"
|
|
*
|
|
* Notice that we changed the class constructor to invoke this.initConfig() and pass in the provided config object.
|
|
* Two key things happened:
|
|
*
|
|
* - The provided config object when the class is instantiated is recursively merged with the default config object.
|
|
* - All corresponding setter methods are called with the merged values.
|
|
*
|
|
* Beside storing the given values, throughout the frameworks, setters generally have two key responsibilities:
|
|
*
|
|
* - Filtering / validation / transformation of the given value before it's actually stored within the instance.
|
|
* - Notification (such as firing events) / post-processing after the value has been set, or changed from a
|
|
* previous value.
|
|
*
|
|
* By standardize this common pattern, the default generated setters provide two extra template methods that you
|
|
* can put your own custom logics into, i.e: an "applyFoo" and "updateFoo" method for a "foo" config item, which are
|
|
* executed before and after the value is actually set, respectively. Back to the example class, let's validate that
|
|
* age must be a valid positive number, and fire an 'agechange' if the value is modified.
|
|
*
|
|
* Ext.define('My.sample.Person', {
|
|
* config: {
|
|
* // ...
|
|
* },
|
|
*
|
|
* constructor: {
|
|
* // ...
|
|
* },
|
|
*
|
|
* applyAge: function(age) {
|
|
* if (typeof age !== 'number' || age < 0) {
|
|
* console.warn("Invalid age, must be a positive number");
|
|
* return;
|
|
* }
|
|
*
|
|
* return age;
|
|
* },
|
|
*
|
|
* updateAge: function(newAge, oldAge) {
|
|
* // age has changed from "oldAge" to "newAge"
|
|
* this.fireEvent('agechange', this, newAge, oldAge);
|
|
* }
|
|
*
|
|
* // ...
|
|
* });
|
|
*
|
|
* var jacky = new Person({
|
|
* name: "Jacky",
|
|
* age: 'invalid'
|
|
* });
|
|
*
|
|
* alert(jacky.getAge()); // alerts 0
|
|
*
|
|
* alert(jacky.setAge(-100)); // alerts 0
|
|
* alert(jacky.getAge()); // alerts 0
|
|
*
|
|
* alert(jacky.setAge(35)); // alerts 0
|
|
* alert(jacky.getAge()); // alerts 35
|
|
*
|
|
* In other words, when leveraging the config feature, you mostly never need to define setter and getter methods
|
|
* explicitly. Instead, "apply*" and "update*" methods should be implemented where necessary. Your code will be
|
|
* consistent throughout and only contain the minimal logic that you actually care about.
|
|
*
|
|
* When it comes to inheritance, the default config of the parent class is automatically, recursively merged with
|
|
* the child's default config. The same applies for mixins.
|
|
*/
|
|
ExtClass.registerPreprocessor('config', function(Class, data) {
|
|
var config = data.config,
|
|
prototype = Class.prototype,
|
|
defaultConfig = prototype.config,
|
|
nameMap, name, setName, getName, initGetName, internalName, value;
|
|
|
|
delete data.config;
|
|
|
|
for (name in config) {
|
|
// Once per config item, per class hierarchy
|
|
if (config.hasOwnProperty(name) && !(name in defaultConfig)) {
|
|
value = config[name];
|
|
nameMap = this.getConfigNameMap(name);
|
|
setName = nameMap.set;
|
|
getName = nameMap.get;
|
|
initGetName = nameMap.initGet;
|
|
internalName = nameMap.internal;
|
|
|
|
data[initGetName] = this.generateInitGetter(nameMap);
|
|
|
|
if (value === null && !data.hasOwnProperty(internalName)) {
|
|
data[internalName] = null;
|
|
}
|
|
|
|
if (!data.hasOwnProperty(getName)) {
|
|
data[getName] = this.generateGetter(nameMap);
|
|
}
|
|
|
|
if (!data.hasOwnProperty(setName)) {
|
|
data[setName] = this.generateSetter(nameMap);
|
|
}
|
|
}
|
|
}
|
|
|
|
Class.addConfig(config, true);
|
|
});
|
|
//</feature>
|
|
|
|
//<feature classSystem.mixins>
|
|
/**
|
|
* @cfg {Object} mixins
|
|
* List of classes to mix into this class. For example:
|
|
*
|
|
* Ext.define('CanSing', {
|
|
* sing: function() {
|
|
* alert("I'm on the highway to hell...");
|
|
* }
|
|
* });
|
|
*
|
|
* Ext.define('Musician', {
|
|
* extend: 'Person',
|
|
*
|
|
* mixins: {
|
|
* canSing: 'CanSing'
|
|
* }
|
|
* });
|
|
*/
|
|
ExtClass.registerPreprocessor('mixins', function(Class, data, hooks) {
|
|
var mixins = data.mixins,
|
|
name, mixin, i, ln;
|
|
|
|
delete data.mixins;
|
|
|
|
Ext.Function.interceptBefore(hooks, 'onCreated', function() {
|
|
if (mixins instanceof Array) {
|
|
for (i = 0,ln = mixins.length; i < ln; i++) {
|
|
mixin = mixins[i];
|
|
name = mixin.prototype.mixinId || mixin.$className;
|
|
|
|
Class.mixin(name, mixin);
|
|
}
|
|
}
|
|
else {
|
|
for (name in mixins) {
|
|
if (mixins.hasOwnProperty(name)) {
|
|
Class.mixin(name, mixins[name]);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
//</feature>
|
|
|
|
//<feature classSystem.backwardsCompatible>
|
|
// Backwards compatible
|
|
Ext.extend = function(Class, Parent, members) {
|
|
if (arguments.length === 2 && Ext.isObject(Parent)) {
|
|
members = Parent;
|
|
Parent = Class;
|
|
Class = null;
|
|
}
|
|
|
|
var cls;
|
|
|
|
if (!Parent) {
|
|
throw new Error("[Ext.extend] Attempting to extend from a class which has not been loaded on the page.");
|
|
}
|
|
|
|
members.extend = Parent;
|
|
members.preprocessors = [
|
|
'extend'
|
|
//<feature classSystem.statics>
|
|
,'statics'
|
|
//</feature>
|
|
//<feature classSystem.inheritableStatics>
|
|
,'inheritableStatics'
|
|
//</feature>
|
|
//<feature classSystem.mixins>
|
|
,'mixins'
|
|
//</feature>
|
|
//<feature classSystem.config>
|
|
,'config'
|
|
//</feature>
|
|
];
|
|
|
|
if (Class) {
|
|
cls = new ExtClass(Class, members);
|
|
}
|
|
else {
|
|
cls = new ExtClass(members);
|
|
}
|
|
|
|
cls.prototype.override = function(o) {
|
|
for (var m in o) {
|
|
if (o.hasOwnProperty(m)) {
|
|
this[m] = o[m];
|
|
}
|
|
}
|
|
};
|
|
|
|
return cls;
|
|
};
|
|
//</feature>
|
|
})();
|
|
|
|
//@tag foundation,core
|
|
//@define Ext.ClassManager
|
|
//@require Ext.Class
|
|
|
|
/**
|
|
* @class Ext.ClassManager
|
|
*
|
|
* @author Jacky Nguyen <jacky@sencha.com>
|
|
* @aside guide class_system
|
|
* @aside video class-system
|
|
*
|
|
* Ext.ClassManager manages all classes and handles mapping from string class name to
|
|
* actual class objects throughout the whole framework. It is not generally accessed directly, rather through
|
|
* these convenient shorthands:
|
|
*
|
|
* - {@link Ext#define Ext.define}
|
|
* - {@link Ext.ClassManager#create Ext.create}
|
|
* - {@link Ext#widget Ext.widget}
|
|
* - {@link Ext#getClass Ext.getClass}
|
|
* - {@link Ext#getClassName Ext.getClassName}
|
|
*
|
|
* ## Basic syntax:
|
|
*
|
|
* Ext.define(className, properties);
|
|
*
|
|
* in which `properties` is an object represent a collection of properties that apply to the class. See
|
|
* {@link Ext.ClassManager#create} for more detailed instructions.
|
|
*
|
|
* @example
|
|
* Ext.define('Person', {
|
|
* name: 'Unknown',
|
|
*
|
|
* constructor: function(name) {
|
|
* if (name) {
|
|
* this.name = name;
|
|
* }
|
|
*
|
|
* return this;
|
|
* },
|
|
*
|
|
* eat: function(foodType) {
|
|
* alert("I'm eating: " + foodType);
|
|
*
|
|
* return this;
|
|
* }
|
|
* });
|
|
*
|
|
* var aaron = new Person("Aaron");
|
|
* aaron.eat("Sandwich"); // alert("I'm eating: Sandwich");
|
|
*
|
|
* Ext.Class has a powerful set of extensible {@link Ext.Class#registerPreprocessor pre-processors} which takes care of
|
|
* everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc.
|
|
*
|
|
* ## Inheritance:
|
|
*
|
|
* Ext.define('Developer', {
|
|
* extend: 'Person',
|
|
*
|
|
* constructor: function(name, isGeek) {
|
|
* this.isGeek = isGeek;
|
|
*
|
|
* // Apply a method from the parent class' prototype
|
|
* this.callParent([name]);
|
|
*
|
|
* return this;
|
|
*
|
|
* },
|
|
*
|
|
* code: function(language) {
|
|
* alert("I'm coding in: " + language);
|
|
*
|
|
* this.eat("Bugs");
|
|
*
|
|
* return this;
|
|
* }
|
|
* });
|
|
*
|
|
* var jacky = new Developer("Jacky", true);
|
|
* jacky.code("JavaScript"); // alert("I'm coding in: JavaScript");
|
|
* // alert("I'm eating: Bugs");
|
|
*
|
|
* See {@link Ext.Base#callParent} for more details on calling superclass' methods
|
|
*
|
|
* ## Mixins:
|
|
*
|
|
* Ext.define('CanPlayGuitar', {
|
|
* playGuitar: function() {
|
|
* alert("F#...G...D...A");
|
|
* }
|
|
* });
|
|
*
|
|
* Ext.define('CanComposeSongs', {
|
|
* composeSongs: function() { }
|
|
* });
|
|
*
|
|
* Ext.define('CanSing', {
|
|
* sing: function() {
|
|
* alert("I'm on the highway to hell...");
|
|
* }
|
|
* });
|
|
*
|
|
* Ext.define('Musician', {
|
|
* extend: 'Person',
|
|
*
|
|
* mixins: {
|
|
* canPlayGuitar: 'CanPlayGuitar',
|
|
* canComposeSongs: 'CanComposeSongs',
|
|
* canSing: 'CanSing'
|
|
* }
|
|
* });
|
|
*
|
|
* Ext.define('CoolPerson', {
|
|
* extend: 'Person',
|
|
*
|
|
* mixins: {
|
|
* canPlayGuitar: 'CanPlayGuitar',
|
|
* canSing: 'CanSing'
|
|
* },
|
|
*
|
|
* sing: function() {
|
|
* alert("Ahem...");
|
|
*
|
|
* this.mixins.canSing.sing.call(this);
|
|
*
|
|
* alert("[Playing guitar at the same time...]");
|
|
*
|
|
* this.playGuitar();
|
|
* }
|
|
* });
|
|
*
|
|
* var me = new CoolPerson("Jacky");
|
|
*
|
|
* me.sing(); // alert("Ahem...");
|
|
* // alert("I'm on the highway to hell...");
|
|
* // alert("[Playing guitar at the same time...]");
|
|
* // alert("F#...G...D...A");
|
|
*
|
|
* ## Config:
|
|
*
|
|
* Ext.define('SmartPhone', {
|
|
* config: {
|
|
* hasTouchScreen: false,
|
|
* operatingSystem: 'Other',
|
|
* price: 500
|
|
* },
|
|
*
|
|
* isExpensive: false,
|
|
*
|
|
* constructor: function(config) {
|
|
* this.initConfig(config);
|
|
*
|
|
* return this;
|
|
* },
|
|
*
|
|
* applyPrice: function(price) {
|
|
* this.isExpensive = (price > 500);
|
|
*
|
|
* return price;
|
|
* },
|
|
*
|
|
* applyOperatingSystem: function(operatingSystem) {
|
|
* if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) {
|
|
* return 'Other';
|
|
* }
|
|
*
|
|
* return operatingSystem;
|
|
* }
|
|
* });
|
|
*
|
|
* var iPhone = new SmartPhone({
|
|
* hasTouchScreen: true,
|
|
* operatingSystem: 'iOS'
|
|
* });
|
|
*
|
|
* iPhone.getPrice(); // 500;
|
|
* iPhone.getOperatingSystem(); // 'iOS'
|
|
* iPhone.getHasTouchScreen(); // true;
|
|
*
|
|
* iPhone.isExpensive; // false;
|
|
* iPhone.setPrice(600);
|
|
* iPhone.getPrice(); // 600
|
|
* iPhone.isExpensive; // true;
|
|
*
|
|
* iPhone.setOperatingSystem('AlienOS');
|
|
* iPhone.getOperatingSystem(); // 'Other'
|
|
*
|
|
* ## Statics:
|
|
*
|
|
* Ext.define('Computer', {
|
|
* statics: {
|
|
* factory: function(brand) {
|
|
* // 'this' in static methods refer to the class itself
|
|
* return new this(brand);
|
|
* }
|
|
* },
|
|
*
|
|
* constructor: function() { }
|
|
* });
|
|
*
|
|
* var dellComputer = Computer.factory('Dell');
|
|
*
|
|
* Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing
|
|
* static properties within class methods
|
|
*
|
|
* @singleton
|
|
*/
|
|
(function(Class, alias, arraySlice, arrayFrom, global) {
|
|
var Manager = Ext.ClassManager = {
|
|
|
|
/**
|
|
* @property classes
|
|
* @type Object
|
|
* All classes which were defined through the ClassManager. Keys are the
|
|
* name of the classes and the values are references to the classes.
|
|
* @private
|
|
*/
|
|
classes: {},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
existCache: {},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
namespaceRewrites: [{
|
|
from: 'Ext.',
|
|
to: Ext
|
|
}],
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
maps: {
|
|
alternateToName: {},
|
|
aliasToName: {},
|
|
nameToAliases: {},
|
|
nameToAlternates: {}
|
|
},
|
|
|
|
/** @private */
|
|
enableNamespaceParseCache: true,
|
|
|
|
/** @private */
|
|
namespaceParseCache: {},
|
|
|
|
/** @private */
|
|
instantiators: [],
|
|
|
|
/**
|
|
* Checks if a class has already been created.
|
|
*
|
|
* @param {String} className
|
|
* @return {Boolean} exist
|
|
*/
|
|
isCreated: function(className) {
|
|
var existCache = this.existCache,
|
|
i, ln, part, root, parts;
|
|
|
|
//<debug error>
|
|
if (typeof className != 'string' || className.length < 1) {
|
|
throw new Error("[Ext.ClassManager] Invalid classname, must be a string and must not be empty");
|
|
}
|
|
//</debug>
|
|
|
|
if (this.classes[className] || existCache[className]) {
|
|
return true;
|
|
}
|
|
|
|
root = global;
|
|
parts = this.parseNamespace(className);
|
|
|
|
for (i = 0, ln = parts.length; i < ln; i++) {
|
|
part = parts[i];
|
|
|
|
if (typeof part != 'string') {
|
|
root = part;
|
|
} else {
|
|
if (!root || !root[part]) {
|
|
return false;
|
|
}
|
|
|
|
root = root[part];
|
|
}
|
|
}
|
|
|
|
existCache[className] = true;
|
|
|
|
this.triggerCreated(className);
|
|
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
createdListeners: [],
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
nameCreatedListeners: {},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
triggerCreated: function(className) {
|
|
var listeners = this.createdListeners,
|
|
nameListeners = this.nameCreatedListeners,
|
|
alternateNames = this.maps.nameToAlternates[className],
|
|
names = [className],
|
|
i, ln, j, subLn, listener, name;
|
|
|
|
for (i = 0,ln = listeners.length; i < ln; i++) {
|
|
listener = listeners[i];
|
|
listener.fn.call(listener.scope, className);
|
|
}
|
|
|
|
if (alternateNames) {
|
|
names.push.apply(names, alternateNames);
|
|
}
|
|
|
|
for (i = 0,ln = names.length; i < ln; i++) {
|
|
name = names[i];
|
|
listeners = nameListeners[name];
|
|
|
|
if (listeners) {
|
|
for (j = 0,subLn = listeners.length; j < subLn; j++) {
|
|
listener = listeners[j];
|
|
listener.fn.call(listener.scope, name);
|
|
}
|
|
delete nameListeners[name];
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
onCreated: function(fn, scope, className) {
|
|
var listeners = this.createdListeners,
|
|
nameListeners = this.nameCreatedListeners,
|
|
listener = {
|
|
fn: fn,
|
|
scope: scope
|
|
};
|
|
|
|
if (className) {
|
|
if (this.isCreated(className)) {
|
|
fn.call(scope, className);
|
|
return;
|
|
}
|
|
|
|
if (!nameListeners[className]) {
|
|
nameListeners[className] = [];
|
|
}
|
|
|
|
nameListeners[className].push(listener);
|
|
}
|
|
else {
|
|
listeners.push(listener);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Supports namespace rewriting.
|
|
* @private
|
|
*/
|
|
parseNamespace: function(namespace) {
|
|
//<debug error>
|
|
if (typeof namespace != 'string') {
|
|
throw new Error("[Ext.ClassManager] Invalid namespace, must be a string");
|
|
}
|
|
//</debug>
|
|
|
|
var cache = this.namespaceParseCache;
|
|
|
|
if (this.enableNamespaceParseCache) {
|
|
if (cache.hasOwnProperty(namespace)) {
|
|
return cache[namespace];
|
|
}
|
|
}
|
|
|
|
var parts = [],
|
|
rewrites = this.namespaceRewrites,
|
|
root = global,
|
|
name = namespace,
|
|
rewrite, from, to, i, ln;
|
|
|
|
for (i = 0, ln = rewrites.length; i < ln; i++) {
|
|
rewrite = rewrites[i];
|
|
from = rewrite.from;
|
|
to = rewrite.to;
|
|
|
|
if (name === from || name.substring(0, from.length) === from) {
|
|
name = name.substring(from.length);
|
|
|
|
if (typeof to != 'string') {
|
|
root = to;
|
|
} else {
|
|
parts = parts.concat(to.split('.'));
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
parts.push(root);
|
|
|
|
parts = parts.concat(name.split('.'));
|
|
|
|
if (this.enableNamespaceParseCache) {
|
|
cache[namespace] = parts;
|
|
}
|
|
|
|
return parts;
|
|
},
|
|
|
|
/**
|
|
* Creates a namespace and assign the `value` to the created object.
|
|
*
|
|
* Ext.ClassManager.setNamespace('MyCompany.pkg.Example', someObject);
|
|
* alert(MyCompany.pkg.Example === someObject); // alerts true
|
|
*
|
|
* @param {String} name
|
|
* @param {Mixed} value
|
|
*/
|
|
setNamespace: function(name, value) {
|
|
var root = global,
|
|
parts = this.parseNamespace(name),
|
|
ln = parts.length - 1,
|
|
leaf = parts[ln],
|
|
i, part;
|
|
|
|
for (i = 0; i < ln; i++) {
|
|
part = parts[i];
|
|
|
|
if (typeof part != 'string') {
|
|
root = part;
|
|
} else {
|
|
if (!root[part]) {
|
|
root[part] = {};
|
|
}
|
|
|
|
root = root[part];
|
|
}
|
|
}
|
|
|
|
root[leaf] = value;
|
|
|
|
return root[leaf];
|
|
},
|
|
|
|
/**
|
|
* The new Ext.ns, supports namespace rewriting.
|
|
* @private
|
|
*/
|
|
createNamespaces: function() {
|
|
var root = global,
|
|
parts, part, i, j, ln, subLn;
|
|
|
|
for (i = 0, ln = arguments.length; i < ln; i++) {
|
|
parts = this.parseNamespace(arguments[i]);
|
|
|
|
for (j = 0, subLn = parts.length; j < subLn; j++) {
|
|
part = parts[j];
|
|
|
|
if (typeof part != 'string') {
|
|
root = part;
|
|
} else {
|
|
if (!root[part]) {
|
|
root[part] = {};
|
|
}
|
|
|
|
root = root[part];
|
|
}
|
|
}
|
|
}
|
|
|
|
return root;
|
|
},
|
|
|
|
/**
|
|
* Sets a name reference to a class.
|
|
*
|
|
* @param {String} name
|
|
* @param {Object} value
|
|
* @return {Ext.ClassManager} this
|
|
*/
|
|
set: function(name, value) {
|
|
var me = this,
|
|
maps = me.maps,
|
|
nameToAlternates = maps.nameToAlternates,
|
|
targetName = me.getName(value),
|
|
alternates;
|
|
|
|
me.classes[name] = me.setNamespace(name, value);
|
|
|
|
if (targetName && targetName !== name) {
|
|
maps.alternateToName[name] = targetName;
|
|
alternates = nameToAlternates[targetName] || (nameToAlternates[targetName] = []);
|
|
alternates.push(name);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Retrieve a class by its name.
|
|
*
|
|
* @param {String} name
|
|
* @return {Ext.Class} class
|
|
*/
|
|
get: function(name) {
|
|
var classes = this.classes;
|
|
|
|
if (classes[name]) {
|
|
return classes[name];
|
|
}
|
|
|
|
var root = global,
|
|
parts = this.parseNamespace(name),
|
|
part, i, ln;
|
|
|
|
for (i = 0, ln = parts.length; i < ln; i++) {
|
|
part = parts[i];
|
|
|
|
if (typeof part != 'string') {
|
|
root = part;
|
|
} else {
|
|
if (!root || !root[part]) {
|
|
return null;
|
|
}
|
|
|
|
root = root[part];
|
|
}
|
|
}
|
|
|
|
return root;
|
|
},
|
|
|
|
/**
|
|
* Register the alias for a class.
|
|
*
|
|
* @param {Ext.Class/String} cls a reference to a class or a `className`.
|
|
* @param {String} alias Alias to use when referring to this class.
|
|
*/
|
|
setAlias: function(cls, alias) {
|
|
var aliasToNameMap = this.maps.aliasToName,
|
|
nameToAliasesMap = this.maps.nameToAliases,
|
|
className;
|
|
|
|
if (typeof cls == 'string') {
|
|
className = cls;
|
|
} else {
|
|
className = this.getName(cls);
|
|
}
|
|
|
|
if (alias && aliasToNameMap[alias] !== className) {
|
|
//<debug info>
|
|
if (aliasToNameMap[alias]) {
|
|
Ext.Logger.info("[Ext.ClassManager] Overriding existing alias: '" + alias + "' " +
|
|
"of: '" + aliasToNameMap[alias] + "' with: '" + className + "'. Be sure it's intentional.");
|
|
}
|
|
//</debug>
|
|
|
|
aliasToNameMap[alias] = className;
|
|
}
|
|
|
|
if (!nameToAliasesMap[className]) {
|
|
nameToAliasesMap[className] = [];
|
|
}
|
|
|
|
if (alias) {
|
|
Ext.Array.include(nameToAliasesMap[className], alias);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Adds a batch of class name to alias mappings
|
|
* @param {Object} aliases The set of mappings of the form
|
|
* className : [values...]
|
|
*/
|
|
addNameAliasMappings: function(aliases){
|
|
var aliasToNameMap = this.maps.aliasToName,
|
|
nameToAliasesMap = this.maps.nameToAliases,
|
|
className, aliasList, alias, i;
|
|
|
|
for (className in aliases) {
|
|
aliasList = nameToAliasesMap[className] ||
|
|
(nameToAliasesMap[className] = []);
|
|
|
|
for (i = 0; i < aliases[className].length; i++) {
|
|
alias = aliases[className][i];
|
|
if (!aliasToNameMap[alias]) {
|
|
aliasToNameMap[alias] = className;
|
|
aliasList.push(alias);
|
|
}
|
|
}
|
|
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
*
|
|
* @param {Object} alternates The set of mappings of the form
|
|
* className : [values...]
|
|
*/
|
|
addNameAlternateMappings: function(alternates) {
|
|
var alternateToName = this.maps.alternateToName,
|
|
nameToAlternates = this.maps.nameToAlternates,
|
|
className, aliasList, alternate, i;
|
|
|
|
for (className in alternates) {
|
|
aliasList = nameToAlternates[className] ||
|
|
(nameToAlternates[className] = []);
|
|
|
|
for (i = 0; i < alternates[className].length; i++) {
|
|
alternate = alternates[className];
|
|
if (!alternateToName[alternate]) {
|
|
alternateToName[alternate] = className;
|
|
aliasList.push(alternate);
|
|
}
|
|
}
|
|
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Get a reference to the class by its alias.
|
|
*
|
|
* @param {String} alias
|
|
* @return {Ext.Class} class
|
|
*/
|
|
getByAlias: function(alias) {
|
|
return this.get(this.getNameByAlias(alias));
|
|
},
|
|
|
|
/**
|
|
* Get the name of a class by its alias.
|
|
*
|
|
* @param {String} alias
|
|
* @return {String} className
|
|
*/
|
|
getNameByAlias: function(alias) {
|
|
return this.maps.aliasToName[alias] || '';
|
|
},
|
|
|
|
/**
|
|
* Get the name of a class by its alternate name.
|
|
*
|
|
* @param {String} alternate
|
|
* @return {String} className
|
|
*/
|
|
getNameByAlternate: function(alternate) {
|
|
return this.maps.alternateToName[alternate] || '';
|
|
},
|
|
|
|
/**
|
|
* Get the aliases of a class by the class name
|
|
*
|
|
* @param {String} name
|
|
* @return {Array} aliases
|
|
*/
|
|
getAliasesByName: function(name) {
|
|
return this.maps.nameToAliases[name] || [];
|
|
},
|
|
|
|
/**
|
|
* Get the name of the class by its reference or its instance;
|
|
* usually invoked by the shorthand {@link Ext#getClassName Ext.getClassName}
|
|
*
|
|
* Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action"
|
|
*
|
|
* @param {Ext.Class/Object} object
|
|
* @return {String} className
|
|
*/
|
|
getName: function(object) {
|
|
return object && object.$className || '';
|
|
},
|
|
|
|
/**
|
|
* Get the class of the provided object; returns null if it's not an instance
|
|
* of any class created with Ext.define. This is usually invoked by the shorthand {@link Ext#getClass Ext.getClass}.
|
|
*
|
|
* var component = new Ext.Component();
|
|
*
|
|
* Ext.ClassManager.getClass(component); // returns Ext.Component
|
|
*
|
|
* @param {Object} object
|
|
* @return {Ext.Class} class
|
|
*/
|
|
getClass: function(object) {
|
|
return object && object.self || null;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
create: function(className, data, createdFn) {
|
|
//<debug error>
|
|
if (typeof className != 'string') {
|
|
throw new Error("[Ext.define] Invalid class name '" + className + "' specified, must be a non-empty string");
|
|
}
|
|
//</debug>
|
|
|
|
data.$className = className;
|
|
|
|
return new Class(data, function() {
|
|
var postprocessorStack = data.postprocessors || Manager.defaultPostprocessors,
|
|
registeredPostprocessors = Manager.postprocessors,
|
|
index = 0,
|
|
postprocessors = [],
|
|
postprocessor, process, i, ln, j, subLn, postprocessorProperties, postprocessorProperty;
|
|
|
|
delete data.postprocessors;
|
|
|
|
for (i = 0,ln = postprocessorStack.length; i < ln; i++) {
|
|
postprocessor = postprocessorStack[i];
|
|
|
|
if (typeof postprocessor == 'string') {
|
|
postprocessor = registeredPostprocessors[postprocessor];
|
|
postprocessorProperties = postprocessor.properties;
|
|
|
|
if (postprocessorProperties === true) {
|
|
postprocessors.push(postprocessor.fn);
|
|
}
|
|
else if (postprocessorProperties) {
|
|
for (j = 0,subLn = postprocessorProperties.length; j < subLn; j++) {
|
|
postprocessorProperty = postprocessorProperties[j];
|
|
|
|
if (data.hasOwnProperty(postprocessorProperty)) {
|
|
postprocessors.push(postprocessor.fn);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
postprocessors.push(postprocessor);
|
|
}
|
|
}
|
|
|
|
process = function(clsName, cls, clsData) {
|
|
postprocessor = postprocessors[index++];
|
|
|
|
if (!postprocessor) {
|
|
Manager.set(className, cls);
|
|
|
|
if (createdFn) {
|
|
createdFn.call(cls, cls);
|
|
}
|
|
|
|
Manager.triggerCreated(className);
|
|
return;
|
|
}
|
|
|
|
if (postprocessor.call(this, clsName, cls, clsData, process) !== false) {
|
|
process.apply(this, arguments);
|
|
}
|
|
};
|
|
|
|
process.call(Manager, className, this, data);
|
|
});
|
|
},
|
|
|
|
createOverride: function(className, data) {
|
|
var overriddenClassName = data.override,
|
|
requires = Ext.Array.from(data.requires);
|
|
|
|
delete data.override;
|
|
delete data.requires;
|
|
|
|
this.existCache[className] = true;
|
|
|
|
Ext.require(requires, function() {
|
|
// Override the target class right after it's created
|
|
this.onCreated(function() {
|
|
this.get(overriddenClassName).override(data);
|
|
|
|
// This push the overridding file itself into Ext.Loader.history
|
|
// Hence if the target class never exists, the overriding file will
|
|
// never be included in the build
|
|
this.triggerCreated(className);
|
|
}, this, overriddenClassName);
|
|
}, this);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Instantiate a class by its alias; usually invoked by the convenient shorthand {@link Ext#createByAlias Ext.createByAlias}
|
|
* If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
|
|
* attempt to load the class via synchronous loading.
|
|
*
|
|
* var window = Ext.ClassManager.instantiateByAlias('widget.window', { width: 600, height: 800 });
|
|
*
|
|
* @param {String} alias
|
|
* @param {Mixed...} args Additional arguments after the alias will be passed to the class constructor.
|
|
* @return {Object} instance
|
|
*/
|
|
instantiateByAlias: function() {
|
|
var alias = arguments[0],
|
|
args = arraySlice.call(arguments),
|
|
className = this.getNameByAlias(alias);
|
|
|
|
if (!className) {
|
|
className = this.maps.aliasToName[alias];
|
|
|
|
//<debug error>
|
|
if (!className) {
|
|
throw new Error("[Ext.createByAlias] Cannot create an instance of unrecognized alias: " + alias);
|
|
}
|
|
//</debug>
|
|
|
|
//<debug warn>
|
|
Ext.Logger.warn("[Ext.Loader] Synchronously loading '" + className + "'; consider adding " +
|
|
"Ext.require('" + alias + "') above Ext.onReady");
|
|
//</debug>
|
|
|
|
Ext.syncRequire(className);
|
|
}
|
|
|
|
args[0] = className;
|
|
|
|
return this.instantiate.apply(this, args);
|
|
},
|
|
|
|
/**
|
|
* Instantiate a class by either full name, alias or alternate name; usually invoked by the convenient
|
|
* shorthand {@link Ext.ClassManager#create Ext.create}.
|
|
*
|
|
* If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
|
|
* attempt to load the class via synchronous loading.
|
|
*
|
|
* For example, all these three lines return the same result:
|
|
*
|
|
* // alias
|
|
* var formPanel = Ext.create('widget.formpanel', { width: 600, height: 800 });
|
|
*
|
|
* // alternate name
|
|
* var formPanel = Ext.create('Ext.form.FormPanel', { width: 600, height: 800 });
|
|
*
|
|
* // full class name
|
|
* var formPanel = Ext.create('Ext.form.Panel', { width: 600, height: 800 });
|
|
*
|
|
* @param {String} name
|
|
* @param {Mixed} args Additional arguments after the name will be passed to the class' constructor.
|
|
* @return {Object} instance
|
|
*/
|
|
instantiate: function() {
|
|
var name = arguments[0],
|
|
args = arraySlice.call(arguments, 1),
|
|
alias = name,
|
|
possibleName, cls;
|
|
|
|
if (typeof name != 'function') {
|
|
//<debug error>
|
|
if ((typeof name != 'string' || name.length < 1)) {
|
|
throw new Error("[Ext.create] Invalid class name or alias '" + name + "' specified, must be a non-empty string");
|
|
}
|
|
//</debug>
|
|
|
|
cls = this.get(name);
|
|
}
|
|
else {
|
|
cls = name;
|
|
}
|
|
|
|
// No record of this class name, it's possibly an alias, so look it up
|
|
if (!cls) {
|
|
possibleName = this.getNameByAlias(name);
|
|
|
|
if (possibleName) {
|
|
name = possibleName;
|
|
|
|
cls = this.get(name);
|
|
}
|
|
}
|
|
|
|
// Still no record of this class name, it's possibly an alternate name, so look it up
|
|
if (!cls) {
|
|
possibleName = this.getNameByAlternate(name);
|
|
|
|
if (possibleName) {
|
|
name = possibleName;
|
|
|
|
cls = this.get(name);
|
|
}
|
|
}
|
|
|
|
// Still not existing at this point, try to load it via synchronous mode as the last resort
|
|
if (!cls) {
|
|
//<debug warn>
|
|
Ext.Logger.warn("[Ext.Loader] Synchronously loading '" + name + "'; consider adding '" +
|
|
((possibleName) ? alias : name) + "' explicitly as a require of the corresponding class");
|
|
//</debug>
|
|
|
|
Ext.syncRequire(name);
|
|
|
|
cls = this.get(name);
|
|
}
|
|
|
|
//<debug error>
|
|
if (!cls) {
|
|
throw new Error("[Ext.create] Cannot create an instance of unrecognized class name / alias: " + alias);
|
|
}
|
|
|
|
if (typeof cls != 'function') {
|
|
throw new Error("[Ext.create] '" + name + "' is a singleton and cannot be instantiated");
|
|
}
|
|
//</debug>
|
|
|
|
return this.getInstantiator(args.length)(cls, args);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @param name
|
|
* @param args
|
|
*/
|
|
dynInstantiate: function(name, args) {
|
|
args = arrayFrom(args, true);
|
|
args.unshift(name);
|
|
|
|
return this.instantiate.apply(this, args);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @param length
|
|
*/
|
|
getInstantiator: function(length) {
|
|
var instantiators = this.instantiators,
|
|
instantiator;
|
|
|
|
instantiator = instantiators[length];
|
|
|
|
if (!instantiator) {
|
|
var i = length,
|
|
args = [];
|
|
|
|
for (i = 0; i < length; i++) {
|
|
args.push('a[' + i + ']');
|
|
}
|
|
|
|
instantiator = instantiators[length] = new Function('c', 'a', 'return new c(' + args.join(',') + ')');
|
|
//<debug>
|
|
instantiator.displayName = "Ext.ClassManager.instantiate" + length;
|
|
//</debug>
|
|
}
|
|
|
|
return instantiator;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
postprocessors: {},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
defaultPostprocessors: [],
|
|
|
|
/**
|
|
* Register a post-processor function.
|
|
*
|
|
* @private
|
|
* @param {String} name
|
|
* @param {Function} postprocessor
|
|
*/
|
|
registerPostprocessor: function(name, fn, properties, position, relativeTo) {
|
|
if (!position) {
|
|
position = 'last';
|
|
}
|
|
|
|
if (!properties) {
|
|
properties = [name];
|
|
}
|
|
|
|
this.postprocessors[name] = {
|
|
name: name,
|
|
properties: properties || false,
|
|
fn: fn
|
|
};
|
|
|
|
this.setDefaultPostprocessorPosition(name, position, relativeTo);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Set the default post processors array stack which are applied to every class.
|
|
*
|
|
* @private
|
|
* @param {String/Array} The name of a registered post processor or an array of registered names.
|
|
* @return {Ext.ClassManager} this
|
|
*/
|
|
setDefaultPostprocessors: function(postprocessors) {
|
|
this.defaultPostprocessors = arrayFrom(postprocessors);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Insert this post-processor at a specific position in the stack, optionally relative to
|
|
* any existing post-processor
|
|
*
|
|
* @private
|
|
* @param {String} name The post-processor name. Note that it needs to be registered with
|
|
* {@link Ext.ClassManager#registerPostprocessor} before this
|
|
* @param {String} offset The insertion position. Four possible values are:
|
|
* 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
|
|
* @param {String} relativeName
|
|
* @return {Ext.ClassManager} this
|
|
*/
|
|
setDefaultPostprocessorPosition: function(name, offset, relativeName) {
|
|
var defaultPostprocessors = this.defaultPostprocessors,
|
|
index;
|
|
|
|
if (typeof offset == 'string') {
|
|
if (offset === 'first') {
|
|
defaultPostprocessors.unshift(name);
|
|
|
|
return this;
|
|
}
|
|
else if (offset === 'last') {
|
|
defaultPostprocessors.push(name);
|
|
|
|
return this;
|
|
}
|
|
|
|
offset = (offset === 'after') ? 1 : -1;
|
|
}
|
|
|
|
index = Ext.Array.indexOf(defaultPostprocessors, relativeName);
|
|
|
|
if (index !== -1) {
|
|
Ext.Array.splice(defaultPostprocessors, Math.max(0, index + offset), 0, name);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Converts a string expression to an array of matching class names. An expression can either refers to class aliases
|
|
* or class names. Expressions support wildcards:
|
|
*
|
|
* // returns ['Ext.window.Window']
|
|
* var window = Ext.ClassManager.getNamesByExpression('widget.window');
|
|
*
|
|
* // returns ['widget.panel', 'widget.window', ...]
|
|
* var allWidgets = Ext.ClassManager.getNamesByExpression('widget.*');
|
|
*
|
|
* // returns ['Ext.data.Store', 'Ext.data.ArrayProxy', ...]
|
|
* var allData = Ext.ClassManager.getNamesByExpression('Ext.data.*');
|
|
*
|
|
* @param {String} expression
|
|
* @return {Array} classNames
|
|
*/
|
|
getNamesByExpression: function(expression) {
|
|
var nameToAliasesMap = this.maps.nameToAliases,
|
|
names = [],
|
|
name, alias, aliases, possibleName, regex, i, ln;
|
|
|
|
//<debug error>
|
|
if (typeof expression != 'string' || expression.length < 1) {
|
|
throw new Error("[Ext.ClassManager.getNamesByExpression] Expression " + expression + " is invalid, must be a non-empty string");
|
|
}
|
|
//</debug>
|
|
|
|
if (expression.indexOf('*') !== -1) {
|
|
expression = expression.replace(/\*/g, '(.*?)');
|
|
regex = new RegExp('^' + expression + '$');
|
|
|
|
for (name in nameToAliasesMap) {
|
|
if (nameToAliasesMap.hasOwnProperty(name)) {
|
|
aliases = nameToAliasesMap[name];
|
|
|
|
if (name.search(regex) !== -1) {
|
|
names.push(name);
|
|
}
|
|
else {
|
|
for (i = 0, ln = aliases.length; i < ln; i++) {
|
|
alias = aliases[i];
|
|
|
|
if (alias.search(regex) !== -1) {
|
|
names.push(name);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
possibleName = this.getNameByAlias(expression);
|
|
|
|
if (possibleName) {
|
|
names.push(possibleName);
|
|
} else {
|
|
possibleName = this.getNameByAlternate(expression);
|
|
|
|
if (possibleName) {
|
|
names.push(possibleName);
|
|
} else {
|
|
names.push(expression);
|
|
}
|
|
}
|
|
}
|
|
|
|
return names;
|
|
}
|
|
};
|
|
|
|
//<feature classSystem.alias>
|
|
/**
|
|
* @cfg {String[]} alias
|
|
* @member Ext.Class
|
|
* List of short aliases for class names. Most useful for defining xtypes for widgets:
|
|
*
|
|
* Ext.define('MyApp.CoolPanel', {
|
|
* extend: 'Ext.panel.Panel',
|
|
* alias: ['widget.coolpanel'],
|
|
* title: 'Yeah!'
|
|
* });
|
|
*
|
|
* // Using Ext.create
|
|
* Ext.create('widget.coolpanel');
|
|
*
|
|
* // Using the shorthand for widgets and in xtypes
|
|
* Ext.widget('panel', {
|
|
* items: [
|
|
* {xtype: 'coolpanel', html: 'Foo'},
|
|
* {xtype: 'coolpanel', html: 'Bar'}
|
|
* ]
|
|
* });
|
|
*/
|
|
Manager.registerPostprocessor('alias', function(name, cls, data) {
|
|
var aliases = data.alias,
|
|
i, ln;
|
|
|
|
for (i = 0,ln = aliases.length; i < ln; i++) {
|
|
alias = aliases[i];
|
|
|
|
this.setAlias(cls, alias);
|
|
}
|
|
|
|
}, ['xtype', 'alias']);
|
|
//</feature>
|
|
|
|
//<feature classSystem.singleton>
|
|
/**
|
|
* @cfg {Boolean} singleton
|
|
* @member Ext.Class
|
|
* When set to true, the class will be instantiated as singleton. For example:
|
|
*
|
|
* Ext.define('Logger', {
|
|
* singleton: true,
|
|
* log: function(msg) {
|
|
* console.log(msg);
|
|
* }
|
|
* });
|
|
*
|
|
* Logger.log('Hello');
|
|
*/
|
|
Manager.registerPostprocessor('singleton', function(name, cls, data, fn) {
|
|
fn.call(this, name, new cls(), data);
|
|
return false;
|
|
});
|
|
//</feature>
|
|
|
|
//<feature classSystem.alternateClassName>
|
|
/**
|
|
* @cfg {String/String[]} alternateClassName
|
|
* @member Ext.Class
|
|
* Defines alternate names for this class. For example:
|
|
*
|
|
* @example
|
|
* Ext.define('Developer', {
|
|
* alternateClassName: ['Coder', 'Hacker'],
|
|
* code: function(msg) {
|
|
* alert('Typing... ' + msg);
|
|
* }
|
|
* });
|
|
*
|
|
* var joe = Ext.create('Developer');
|
|
* joe.code('stackoverflow');
|
|
*
|
|
* var rms = Ext.create('Hacker');
|
|
* rms.code('hack hack');
|
|
*/
|
|
Manager.registerPostprocessor('alternateClassName', function(name, cls, data) {
|
|
var alternates = data.alternateClassName,
|
|
i, ln, alternate;
|
|
|
|
if (!(alternates instanceof Array)) {
|
|
alternates = [alternates];
|
|
}
|
|
|
|
for (i = 0, ln = alternates.length; i < ln; i++) {
|
|
alternate = alternates[i];
|
|
|
|
//<debug error>
|
|
if (typeof alternate != 'string') {
|
|
throw new Error("[Ext.define] Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string");
|
|
}
|
|
//</debug>
|
|
|
|
this.set(alternate, cls);
|
|
}
|
|
});
|
|
//</feature>
|
|
|
|
Ext.apply(Ext, {
|
|
/**
|
|
* Instantiate a class by either full name, alias or alternate name.
|
|
*
|
|
* If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
|
|
* attempt to load the class via synchronous loading.
|
|
*
|
|
* For example, all these three lines return the same result:
|
|
*
|
|
* // alias
|
|
* var formPanel = Ext.create('widget.formpanel', { width: 600, height: 800 });
|
|
*
|
|
* // alternate name
|
|
* var formPanel = Ext.create('Ext.form.FormPanel', { width: 600, height: 800 });
|
|
*
|
|
* // full class name
|
|
* var formPanel = Ext.create('Ext.form.Panel', { width: 600, height: 800 });
|
|
*
|
|
* @param {String} name
|
|
* @param {Mixed} args Additional arguments after the name will be passed to the class' constructor.
|
|
* @return {Object} instance
|
|
* @member Ext
|
|
*/
|
|
create: alias(Manager, 'instantiate'),
|
|
|
|
/**
|
|
* Convenient shorthand to create a widget by its xtype, also see {@link Ext.ClassManager#instantiateByAlias}
|
|
*
|
|
* var button = Ext.widget('button'); // Equivalent to Ext.create('widget.button')
|
|
* var panel = Ext.widget('panel'); // Equivalent to Ext.create('widget.panel')
|
|
*
|
|
* @member Ext
|
|
* @method widget
|
|
*/
|
|
widget: function(name) {
|
|
var args = arraySlice.call(arguments);
|
|
args[0] = 'widget.' + name;
|
|
|
|
return Manager.instantiateByAlias.apply(Manager, args);
|
|
},
|
|
|
|
/**
|
|
* Convenient shorthand, see {@link Ext.ClassManager#instantiateByAlias}.
|
|
* @member Ext
|
|
* @method createByAlias
|
|
*/
|
|
createByAlias: alias(Manager, 'instantiateByAlias'),
|
|
|
|
/**
|
|
* Defines a class or override. A basic class is defined like this:
|
|
*
|
|
* Ext.define('My.awesome.Class', {
|
|
* someProperty: 'something',
|
|
*
|
|
* someMethod: function(s) {
|
|
* console.log(s + this.someProperty);
|
|
* }
|
|
* });
|
|
*
|
|
* var obj = new My.awesome.Class();
|
|
*
|
|
* obj.someMethod('Say '); // logs 'Say something' to the console
|
|
*
|
|
* To defines an override, include the `override` property. The content of an
|
|
* override is aggregated with the specified class in order to extend or modify
|
|
* that class. This can be as simple as setting default property values or it can
|
|
* extend and/or replace methods. This can also extend the statics of the class.
|
|
*
|
|
* One use for an override is to break a large class into manageable pieces.
|
|
*
|
|
* // File: /src/app/Panel.js
|
|
* Ext.define('My.app.Panel', {
|
|
* extend: 'Ext.panel.Panel',
|
|
* requires: [
|
|
* 'My.app.PanelPart2',
|
|
* 'My.app.PanelPart3'
|
|
* ],
|
|
*
|
|
* constructor: function (config) {
|
|
* this.callParent(arguments); // calls Ext.panel.Panel's constructor
|
|
* // ...
|
|
* },
|
|
*
|
|
* statics: {
|
|
* method: function () {
|
|
* return 'abc';
|
|
* }
|
|
* }
|
|
* });
|
|
*
|
|
* // File: /src/app/PanelPart2.js
|
|
* Ext.define('My.app.PanelPart2', {
|
|
* override: 'My.app.Panel',
|
|
*
|
|
* constructor: function (config) {
|
|
* this.callParent(arguments); // calls My.app.Panel's constructor
|
|
* // ...
|
|
* }
|
|
* });
|
|
*
|
|
* Another use for an override is to provide optional parts of classes that can be
|
|
* independently required. In this case, the class may even be unaware of the
|
|
* override altogether.
|
|
*
|
|
* Ext.define('My.ux.CoolTip', {
|
|
* override: 'Ext.tip.ToolTip',
|
|
*
|
|
* constructor: function (config) {
|
|
* this.callParent(arguments); // calls Ext.tip.ToolTip's constructor
|
|
* // ...
|
|
* }
|
|
* });
|
|
*
|
|
* The above override can now be required as normal.
|
|
*
|
|
* Ext.define('My.app.App', {
|
|
* requires: [
|
|
* 'My.ux.CoolTip'
|
|
* ]
|
|
* });
|
|
*
|
|
* Overrides can also contain statics:
|
|
*
|
|
* Ext.define('My.app.BarMod', {
|
|
* override: 'Ext.foo.Bar',
|
|
*
|
|
* statics: {
|
|
* method: function (x) {
|
|
* return this.callParent([x * 2]); // call Ext.foo.Bar.method
|
|
* }
|
|
* }
|
|
* });
|
|
*
|
|
* __IMPORTANT:__ An override is only included in a build if the class it overrides is
|
|
* required. Otherwise, the override, like the target class, is not included.
|
|
*
|
|
* @param {String} className The class name to create in string dot-namespaced format, for example:
|
|
* 'My.very.awesome.Class', 'FeedViewer.plugin.CoolPager'
|
|
*
|
|
* It is highly recommended to follow this simple convention:
|
|
* - The root and the class name are 'CamelCased'
|
|
* - Everything else is lower-cased
|
|
*
|
|
* @param {Object} data The key - value pairs of properties to apply to this class. Property names can be of
|
|
* any valid strings, except those in the reserved listed below:
|
|
*
|
|
* - `mixins`
|
|
* - `statics`
|
|
* - `config`
|
|
* - `alias`
|
|
* - `self`
|
|
* - `singleton`
|
|
* - `alternateClassName`
|
|
* - `override`
|
|
*
|
|
* @param {Function} [createdFn] Optional callback to execute after the class (or override)
|
|
* is created. The execution scope (`this`) will be the newly created class itself.
|
|
* @return {Ext.Base}
|
|
*
|
|
* @member Ext
|
|
* @method define
|
|
*/
|
|
define: function (className, data, createdFn) {
|
|
if ('override' in data) {
|
|
return Manager.createOverride.apply(Manager, arguments);
|
|
}
|
|
|
|
return Manager.create.apply(Manager, arguments);
|
|
},
|
|
|
|
/**
|
|
* Convenient shorthand for {@link Ext.ClassManager#getName}.
|
|
* @member Ext
|
|
* @method getClassName
|
|
* @inheritdoc Ext.ClassManager#getName
|
|
*/
|
|
getClassName: alias(Manager, 'getName'),
|
|
|
|
/**
|
|
* Returns the display name for object. This name is looked for in order from the following places:
|
|
*
|
|
* - `displayName` field of the object.
|
|
* - `$name` and `$class` fields of the object.
|
|
* - '$className` field of the object.
|
|
*
|
|
* This method is used by {@link Ext.Logger#log} to display information about objects.
|
|
*
|
|
* @param {Mixed} [object] The object who's display name to determine.
|
|
* @return {String} The determined display name, or "Anonymous" if none found.
|
|
* @member Ext
|
|
*/
|
|
getDisplayName: function(object) {
|
|
if (object) {
|
|
if (object.displayName) {
|
|
return object.displayName;
|
|
}
|
|
|
|
if (object.$name && object.$class) {
|
|
return Ext.getClassName(object.$class) + '#' + object.$name;
|
|
}
|
|
|
|
if (object.$className) {
|
|
return object.$className;
|
|
}
|
|
}
|
|
|
|
return 'Anonymous';
|
|
},
|
|
|
|
/**
|
|
* Convenient shorthand, see {@link Ext.ClassManager#getClass}.
|
|
* @member Ext
|
|
* @method getClass
|
|
*/
|
|
getClass: alias(Manager, 'getClass'),
|
|
|
|
/**
|
|
* Creates namespaces to be used for scoping variables and classes so that they are not global.
|
|
* Specifying the last node of a namespace implicitly creates all other nodes. Usage:
|
|
*
|
|
* Ext.namespace('Company', 'Company.data');
|
|
*
|
|
* // equivalent and preferable to the above syntax
|
|
* Ext.namespace('Company.data');
|
|
*
|
|
* Company.Widget = function() {
|
|
* // ...
|
|
* };
|
|
*
|
|
* Company.data.CustomStore = function(config) {
|
|
* // ...
|
|
* };
|
|
*
|
|
* @param {String} namespace1
|
|
* @param {String} namespace2
|
|
* @param {String} etc
|
|
* @return {Object} The namespace object. If multiple arguments are passed, this will be the last namespace created.
|
|
* @member Ext
|
|
* @method namespace
|
|
*/
|
|
namespace: alias(Manager, 'createNamespaces')
|
|
});
|
|
|
|
/**
|
|
* Old name for {@link Ext#widget}.
|
|
* @deprecated 4.0.0 Please use {@link Ext#widget} instead.
|
|
* @method createWidget
|
|
* @member Ext
|
|
*/
|
|
Ext.createWidget = Ext.widget;
|
|
|
|
/**
|
|
* Convenient alias for {@link Ext#namespace Ext.namespace}.
|
|
* @member Ext
|
|
* @method ns
|
|
*/
|
|
Ext.ns = Ext.namespace;
|
|
|
|
Class.registerPreprocessor('className', function(cls, data) {
|
|
if (data.$className) {
|
|
cls.$className = data.$className;
|
|
//<debug>
|
|
cls.displayName = cls.$className;
|
|
//</debug>
|
|
}
|
|
}, true, 'first');
|
|
|
|
Class.registerPreprocessor('alias', function(cls, data) {
|
|
var prototype = cls.prototype,
|
|
xtypes = arrayFrom(data.xtype),
|
|
aliases = arrayFrom(data.alias),
|
|
widgetPrefix = 'widget.',
|
|
widgetPrefixLength = widgetPrefix.length,
|
|
xtypesChain = Array.prototype.slice.call(prototype.xtypesChain || []),
|
|
xtypesMap = Ext.merge({}, prototype.xtypesMap || {}),
|
|
i, ln, alias, xtype;
|
|
|
|
for (i = 0,ln = aliases.length; i < ln; i++) {
|
|
alias = aliases[i];
|
|
|
|
//<debug error>
|
|
if (typeof alias != 'string' || alias.length < 1) {
|
|
throw new Error("[Ext.define] Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string");
|
|
}
|
|
//</debug>
|
|
|
|
if (alias.substring(0, widgetPrefixLength) === widgetPrefix) {
|
|
xtype = alias.substring(widgetPrefixLength);
|
|
Ext.Array.include(xtypes, xtype);
|
|
}
|
|
}
|
|
|
|
cls.xtype = data.xtype = xtypes[0];
|
|
data.xtypes = xtypes;
|
|
|
|
for (i = 0,ln = xtypes.length; i < ln; i++) {
|
|
xtype = xtypes[i];
|
|
|
|
if (!xtypesMap[xtype]) {
|
|
xtypesMap[xtype] = true;
|
|
xtypesChain.push(xtype);
|
|
}
|
|
}
|
|
|
|
data.xtypesChain = xtypesChain;
|
|
data.xtypesMap = xtypesMap;
|
|
|
|
Ext.Function.interceptAfter(data, 'onClassCreated', function() {
|
|
var mixins = prototype.mixins,
|
|
key, mixin;
|
|
|
|
for (key in mixins) {
|
|
if (mixins.hasOwnProperty(key)) {
|
|
mixin = mixins[key];
|
|
|
|
xtypes = mixin.xtypes;
|
|
|
|
if (xtypes) {
|
|
for (i = 0,ln = xtypes.length; i < ln; i++) {
|
|
xtype = xtypes[i];
|
|
|
|
if (!xtypesMap[xtype]) {
|
|
xtypesMap[xtype] = true;
|
|
xtypesChain.push(xtype);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
for (i = 0,ln = xtypes.length; i < ln; i++) {
|
|
xtype = xtypes[i];
|
|
|
|
//<debug error>
|
|
if (typeof xtype != 'string' || xtype.length < 1) {
|
|
throw new Error("[Ext.define] Invalid xtype of: '" + xtype + "' for class: '" + name + "'; must be a valid non-empty string");
|
|
}
|
|
//</debug>
|
|
|
|
Ext.Array.include(aliases, widgetPrefix + xtype);
|
|
}
|
|
|
|
data.alias = aliases;
|
|
|
|
}, ['xtype', 'alias']);
|
|
|
|
})(Ext.Class, Ext.Function.alias, Array.prototype.slice, Ext.Array.from, Ext.global);
|
|
|
|
//@tag foundation,core
|
|
//@define Ext.Loader
|
|
//@require Ext.ClassManager
|
|
|
|
/**
|
|
* @class Ext.Loader
|
|
*
|
|
* @author Jacky Nguyen <jacky@sencha.com>
|
|
* @docauthor Jacky Nguyen <jacky@sencha.com>
|
|
* @aside guide mvc_dependencies
|
|
*
|
|
* Ext.Loader is the heart of the new dynamic dependency loading capability in Ext JS 4+. It is most commonly used
|
|
* via the {@link Ext#require} shorthand. Ext.Loader supports both asynchronous and synchronous loading
|
|
* approaches, and leverage their advantages for the best development flow.
|
|
* We'll discuss about the pros and cons of each approach.
|
|
*
|
|
* __Note:__ The Loader is only enabled by default in development versions of the library (eg sencha-touch-debug.js). To
|
|
* explicitly enable the loader, use `Ext.Loader.setConfig({ enabled: true });` before the start of your script.
|
|
*
|
|
* ## Asynchronous Loading
|
|
*
|
|
* - Advantages:
|
|
* + Cross-domain
|
|
* + No web server needed: you can run the application via the file system protocol (i.e: `file://path/to/your/index
|
|
* .html`)
|
|
* + Best possible debugging experience: error messages come with the exact file name and line number
|
|
*
|
|
* - Disadvantages:
|
|
* + Dependencies need to be specified before-hand
|
|
*
|
|
* ### Method 1: Explicitly include what you need: ###
|
|
*
|
|
* // Syntax
|
|
* // Ext.require({String/Array} expressions);
|
|
*
|
|
* // Example: Single alias
|
|
* Ext.require('widget.window');
|
|
*
|
|
* // Example: Single class name
|
|
* Ext.require('Ext.window.Window');
|
|
*
|
|
* // Example: Multiple aliases / class names mix
|
|
* Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);
|
|
*
|
|
* // Wildcards
|
|
* Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);
|
|
*
|
|
* ### Method 2: Explicitly exclude what you don't need: ###
|
|
*
|
|
* // Syntax: Note that it must be in this chaining format.
|
|
* // Ext.exclude({String/Array} expressions)
|
|
* // .require({String/Array} expressions);
|
|
*
|
|
* // Include everything except Ext.data.*
|
|
* Ext.exclude('Ext.data.*').require('*');
|
|
*
|
|
* // Include all widgets except widget.checkbox*,
|
|
* // which will match widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.
|
|
* Ext.exclude('widget.checkbox*').require('widget.*');
|
|
*
|
|
* # Synchronous Loading on Demand #
|
|
*
|
|
* - *Advantages:*
|
|
* + There's no need to specify dependencies before-hand, which is always the convenience of including ext-all.js
|
|
* before
|
|
*
|
|
* - *Disadvantages:*
|
|
* + Not as good debugging experience since file name won't be shown (except in Firebug at the moment)
|
|
* + Must be from the same domain due to XHR restriction
|
|
* + Need a web server, same reason as above
|
|
*
|
|
* There's one simple rule to follow: Instantiate everything with Ext.create instead of the `new` keyword
|
|
*
|
|
* Ext.create('widget.window', {}); // Instead of new Ext.window.Window({...});
|
|
*
|
|
* Ext.create('Ext.window.Window', {}); // Same as above, using full class name instead of alias
|
|
*
|
|
* Ext.widget('window', {}); // Same as above, all you need is the traditional `xtype`
|
|
*
|
|
* Behind the scene, {@link Ext.ClassManager} will automatically check whether the given class name / alias has already
|
|
* existed on the page. If it's not, Ext.Loader will immediately switch itself to synchronous mode and automatic load the given
|
|
* class and all its dependencies.
|
|
*
|
|
* # Hybrid Loading - The Best of Both Worlds #
|
|
*
|
|
* It has all the advantages combined from asynchronous and synchronous loading. The development flow is simple:
|
|
*
|
|
* ### Step 1: Start writing your application using synchronous approach. ###
|
|
* Ext.Loader will automatically fetch all dependencies on demand as they're
|
|
* needed during run-time. For example:
|
|
*
|
|
* Ext.onReady(function(){
|
|
* var window = Ext.createWidget('window', {
|
|
* width: 500,
|
|
* height: 300,
|
|
* layout: {
|
|
* type: 'border',
|
|
* padding: 5
|
|
* },
|
|
* title: 'Hello Dialog',
|
|
* items: [{
|
|
* title: 'Navigation',
|
|
* collapsible: true,
|
|
* region: 'west',
|
|
* width: 200,
|
|
* html: 'Hello',
|
|
* split: true
|
|
* }, {
|
|
* title: 'TabPanel',
|
|
* region: 'center'
|
|
* }]
|
|
* });
|
|
*
|
|
* window.show();
|
|
* });
|
|
*
|
|
* ### Step 2: Along the way, when you need better debugging ability, watch the console for warnings like these: ###
|
|
*
|
|
* [Ext.Loader] Synchronously loading 'Ext.window.Window'; consider adding Ext.require('Ext.window.Window') before your application's code
|
|
* ClassManager.js:432
|
|
* [Ext.Loader] Synchronously loading 'Ext.layout.container.Border'; consider adding Ext.require('Ext.layout.container.Border') before your application's code
|
|
*
|
|
* Simply copy and paste the suggested code above `Ext.onReady`, i.e:
|
|
*
|
|
* Ext.require('Ext.window.Window');
|
|
* Ext.require('Ext.layout.container.Border');
|
|
*
|
|
* Ext.onReady(function () {
|
|
* // ...
|
|
* });
|
|
*
|
|
* Everything should now load via asynchronous mode.
|
|
*
|
|
* # Deployment #
|
|
*
|
|
* It's important to note that dynamic loading should only be used during development on your local machines.
|
|
* During production, all dependencies should be combined into one single JavaScript file. Ext.Loader makes
|
|
* the whole process of transitioning from / to between development / maintenance and production as easy as
|
|
* possible. Internally {@link Ext.Loader#history Ext.Loader.history} maintains the list of all dependencies your application
|
|
* needs in the exact loading sequence. It's as simple as concatenating all files in this array into one,
|
|
* then include it on top of your application.
|
|
*
|
|
* This process will be automated with Sencha Command, to be released and documented towards Ext JS 4 Final.
|
|
*
|
|
* @singleton
|
|
*/
|
|
(function(Manager, Class, flexSetter, alias, pass, arrayFrom, arrayErase, arrayInclude) {
|
|
|
|
var
|
|
dependencyProperties = ['extend', 'mixins', 'requires'],
|
|
Loader,
|
|
setPathCount = 0;;
|
|
|
|
Loader = Ext.Loader = {
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
isInHistory: {},
|
|
|
|
/**
|
|
* An array of class names to keep track of the dependency loading order.
|
|
* This is not guaranteed to be the same every time due to the asynchronous
|
|
* nature of the Loader.
|
|
*
|
|
* @property history
|
|
* @type Array
|
|
*/
|
|
history: [],
|
|
|
|
/**
|
|
* Configuration
|
|
* @private
|
|
*/
|
|
config: {
|
|
/**
|
|
* Whether or not to enable the dynamic dependency loading feature.
|
|
* @cfg {Boolean} enabled
|
|
*/
|
|
enabled: true,
|
|
|
|
/**
|
|
* @cfg {Boolean} disableCaching
|
|
* Appends current timestamp to script files to prevent caching.
|
|
*/
|
|
disableCaching: true,
|
|
|
|
/**
|
|
* @cfg {String} disableCachingParam
|
|
* The get parameter name for the cache buster's timestamp.
|
|
*/
|
|
disableCachingParam: '_dc',
|
|
|
|
/**
|
|
* @cfg {Object} paths
|
|
* The mapping from namespaces to file paths.
|
|
*
|
|
* {
|
|
* 'Ext': '.', // This is set by default, Ext.layout.container.Container will be
|
|
* // loaded from ./layout/Container.js
|
|
*
|
|
* 'My': './src/my_own_folder' // My.layout.Container will be loaded from
|
|
* // ./src/my_own_folder/layout/Container.js
|
|
* }
|
|
*
|
|
* Note that all relative paths are relative to the current HTML document.
|
|
* If not being specified, for example, `Other.awesome.Class`
|
|
* will simply be loaded from `./Other/awesome/Class.js`.
|
|
*/
|
|
paths: {
|
|
'Ext': '.'
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Set the configuration for the loader. This should be called right after ext-(debug).js
|
|
* is included in the page, and before Ext.onReady. i.e:
|
|
*
|
|
* <script type="text/javascript" src="ext-core-debug.js"></script>
|
|
* <script type="text/javascript">
|
|
* Ext.Loader.setConfig({
|
|
* enabled: true,
|
|
* paths: {
|
|
* 'My': 'my_own_path'
|
|
* }
|
|
* });
|
|
* <script>
|
|
* <script type="text/javascript">
|
|
* Ext.require(...);
|
|
*
|
|
* Ext.onReady(function() {
|
|
* // application code here
|
|
* });
|
|
* </script>
|
|
*
|
|
* Refer to config options of {@link Ext.Loader} for the list of possible properties.
|
|
*
|
|
* @param {Object} config The config object to override the default values.
|
|
* @return {Ext.Loader} this
|
|
*/
|
|
setConfig: function(name, value) {
|
|
if (Ext.isObject(name) && arguments.length === 1) {
|
|
Ext.merge(this.config, name);
|
|
}
|
|
else {
|
|
this.config[name] = (Ext.isObject(value)) ? Ext.merge(this.config[name], value) : value;
|
|
}
|
|
setPathCount += 1;
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Get the config value corresponding to the specified name. If no name is given, will return the config object.
|
|
* @param {String} name The config property name.
|
|
* @return {Object/Mixed}
|
|
*/
|
|
getConfig: function(name) {
|
|
if (name) {
|
|
return this.config[name];
|
|
}
|
|
|
|
return this.config;
|
|
},
|
|
|
|
/**
|
|
* Sets the path of a namespace.
|
|
* For example:
|
|
*
|
|
* Ext.Loader.setPath('Ext', '.');
|
|
*
|
|
* @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}
|
|
* @param {String} [path] See {@link Ext.Function#flexSetter flexSetter}
|
|
* @return {Ext.Loader} this
|
|
* @method
|
|
*/
|
|
setPath: flexSetter(function(name, path) {
|
|
this.config.paths[name] = path;
|
|
setPathCount += 1;
|
|
return this;
|
|
}),
|
|
|
|
/**
|
|
* Sets a batch of path entries
|
|
*
|
|
* @param {Object } paths a set of className: path mappings
|
|
* @return {Ext.Loader} this
|
|
*/
|
|
addClassPathMappings: function(paths) {
|
|
var name;
|
|
|
|
if(setPathCount == 0){
|
|
Loader.config.paths = paths;
|
|
} else {
|
|
for(name in paths){
|
|
Loader.config.paths[name] = paths[name];
|
|
}
|
|
}
|
|
setPathCount++;
|
|
return Loader;
|
|
},
|
|
|
|
/**
|
|
* Translates a className to a file path by adding the
|
|
* the proper prefix and converting the .'s to /'s. For example:
|
|
*
|
|
* Ext.Loader.setPath('My', '/path/to/My');
|
|
*
|
|
* alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
|
|
*
|
|
* Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
|
|
*
|
|
* Ext.Loader.setPath({
|
|
* 'My': '/path/to/lib',
|
|
* 'My.awesome': '/other/path/for/awesome/stuff',
|
|
* 'My.awesome.more': '/more/awesome/path'
|
|
* });
|
|
*
|
|
* alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
|
|
*
|
|
* alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
|
|
*
|
|
* alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
|
|
*
|
|
* alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
|
|
*
|
|
* @param {String} className
|
|
* @return {String} path
|
|
*/
|
|
getPath: function(className) {
|
|
var path = '',
|
|
paths = this.config.paths,
|
|
prefix = this.getPrefix(className);
|
|
|
|
if (prefix.length > 0) {
|
|
if (prefix === className) {
|
|
return paths[prefix];
|
|
}
|
|
|
|
path = paths[prefix];
|
|
className = className.substring(prefix.length + 1);
|
|
}
|
|
|
|
if (path.length > 0) {
|
|
path += '/';
|
|
}
|
|
|
|
return path.replace(/\/\.\//g, '/') + className.replace(/\./g, "/") + '.js';
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @param {String} className
|
|
*/
|
|
getPrefix: function(className) {
|
|
var paths = this.config.paths,
|
|
prefix, deepestPrefix = '';
|
|
|
|
if (paths.hasOwnProperty(className)) {
|
|
return className;
|
|
}
|
|
|
|
for (prefix in paths) {
|
|
if (paths.hasOwnProperty(prefix) && prefix + '.' === className.substring(0, prefix.length + 1)) {
|
|
if (prefix.length > deepestPrefix.length) {
|
|
deepestPrefix = prefix;
|
|
}
|
|
}
|
|
}
|
|
|
|
return deepestPrefix;
|
|
},
|
|
|
|
/**
|
|
* Loads all classes by the given names and all their direct dependencies; optionally executes the given callback function when
|
|
* finishes, within the optional scope. This method is aliased by {@link Ext#require Ext.require} for convenience.
|
|
* @param {String/Array} expressions Can either be a string or an array of string.
|
|
* @param {Function} fn (optional) The callback function.
|
|
* @param {Object} scope (optional) The execution scope (`this`) of the callback function.
|
|
* @param {String/Array} excludes (optional) Classes to be excluded, useful when being used with expressions.
|
|
*/
|
|
require: function(expressions, fn, scope, excludes) {
|
|
if (fn) {
|
|
fn.call(scope);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Synchronously loads all classes by the given names and all their direct dependencies; optionally executes the given callback function when finishes, within the optional scope. This method is aliased by {@link Ext#syncRequire} for convenience
|
|
* @param {String/Array} expressions Can either be a string or an array of string
|
|
* @param {Function} fn (optional) The callback function
|
|
* @param {Object} scope (optional) The execution scope (`this`) of the callback function
|
|
* @param {String/Array} excludes (optional) Classes to be excluded, useful when being used with expressions
|
|
*/
|
|
syncRequire: function() {},
|
|
|
|
/**
|
|
* Explicitly exclude files from being loaded. Useful when used in conjunction with a broad include expression.
|
|
* Can be chained with more `require` and `exclude` methods, eg:
|
|
*
|
|
* Ext.exclude('Ext.data.*').require('*');
|
|
*
|
|
* Ext.exclude('widget.button*').require('widget.*');
|
|
*
|
|
* @param {Array} excludes
|
|
* @return {Object} object contains `require` method for chaining.
|
|
*/
|
|
exclude: function(excludes) {
|
|
var me = this;
|
|
|
|
return {
|
|
require: function(expressions, fn, scope) {
|
|
return me.require(expressions, fn, scope, excludes);
|
|
},
|
|
|
|
syncRequire: function(expressions, fn, scope) {
|
|
return me.syncRequire(expressions, fn, scope, excludes);
|
|
}
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Add a new listener to be executed when all required scripts are fully loaded.
|
|
*
|
|
* @param {Function} fn The function callback to be executed.
|
|
* @param {Object} scope The execution scope (`this`) of the callback function.
|
|
* @param {Boolean} withDomReady Whether or not to wait for document DOM ready as well.
|
|
*/
|
|
onReady: function(fn, scope, withDomReady, options) {
|
|
var oldFn;
|
|
|
|
if (withDomReady !== false && Ext.onDocumentReady) {
|
|
oldFn = fn;
|
|
|
|
fn = function() {
|
|
Ext.onDocumentReady(oldFn, scope, options);
|
|
};
|
|
}
|
|
|
|
fn.call(scope);
|
|
}
|
|
};
|
|
|
|
//<feature classSystem.loader>
|
|
Ext.apply(Loader, {
|
|
/**
|
|
* @private
|
|
*/
|
|
documentHead: typeof document != 'undefined' && (document.head || document.getElementsByTagName('head')[0]),
|
|
|
|
/**
|
|
* Flag indicating whether there are still files being loaded
|
|
* @private
|
|
*/
|
|
isLoading: false,
|
|
|
|
/**
|
|
* Maintain the queue for all dependencies. Each item in the array is an object of the format:
|
|
*
|
|
* {
|
|
* requires: [...], // The required classes for this queue item
|
|
* callback: function() { ... } // The function to execute when all classes specified in requires exist
|
|
* }
|
|
* @private
|
|
*/
|
|
queue: [],
|
|
|
|
/**
|
|
* Maintain the list of files that have already been handled so that they never get double-loaded
|
|
* @private
|
|
*/
|
|
isClassFileLoaded: {},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
isFileLoaded: {},
|
|
|
|
/**
|
|
* Maintain the list of listeners to execute when all required scripts are fully loaded
|
|
* @private
|
|
*/
|
|
readyListeners: [],
|
|
|
|
/**
|
|
* Contains optional dependencies to be loaded last
|
|
* @private
|
|
*/
|
|
optionalRequires: [],
|
|
|
|
/**
|
|
* Map of fully qualified class names to an array of dependent classes.
|
|
* @private
|
|
*/
|
|
requiresMap: {},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
numPendingFiles: 0,
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
numLoadedFiles: 0,
|
|
|
|
/** @private */
|
|
hasFileLoadError: false,
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
classNameToFilePathMap: {},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
syncModeEnabled: false,
|
|
|
|
scriptElements: {},
|
|
|
|
/**
|
|
* Refresh all items in the queue. If all dependencies for an item exist during looping,
|
|
* it will execute the callback and call refreshQueue again. Triggers onReady when the queue is
|
|
* empty
|
|
* @private
|
|
*/
|
|
refreshQueue: function() {
|
|
var queue = this.queue,
|
|
ln = queue.length,
|
|
i, item, j, requires, references;
|
|
|
|
if (ln === 0) {
|
|
this.triggerReady();
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < ln; i++) {
|
|
item = queue[i];
|
|
|
|
if (item) {
|
|
requires = item.requires;
|
|
references = item.references;
|
|
|
|
// Don't bother checking when the number of files loaded
|
|
// is still less than the array length
|
|
if (requires.length > this.numLoadedFiles) {
|
|
continue;
|
|
}
|
|
|
|
j = 0;
|
|
|
|
do {
|
|
if (Manager.isCreated(requires[j])) {
|
|
// Take out from the queue
|
|
arrayErase(requires, j, 1);
|
|
}
|
|
else {
|
|
j++;
|
|
}
|
|
} while (j < requires.length);
|
|
|
|
if (item.requires.length === 0) {
|
|
arrayErase(queue, i, 1);
|
|
item.callback.call(item.scope);
|
|
this.refreshQueue();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Inject a script element to document's head, call onLoad and onError accordingly
|
|
* @private
|
|
*/
|
|
injectScriptElement: function(url, onLoad, onError, scope) {
|
|
var script = document.createElement('script'),
|
|
me = this,
|
|
onLoadFn = function() {
|
|
me.cleanupScriptElement(script);
|
|
onLoad.call(scope);
|
|
},
|
|
onErrorFn = function() {
|
|
me.cleanupScriptElement(script);
|
|
onError.call(scope);
|
|
};
|
|
|
|
script.type = 'text/javascript';
|
|
script.src = url;
|
|
script.onload = onLoadFn;
|
|
script.onerror = onErrorFn;
|
|
script.onreadystatechange = function() {
|
|
if (this.readyState === 'loaded' || this.readyState === 'complete') {
|
|
onLoadFn();
|
|
}
|
|
};
|
|
|
|
this.documentHead.appendChild(script);
|
|
|
|
return script;
|
|
},
|
|
|
|
removeScriptElement: function(url) {
|
|
var scriptElements = this.scriptElements;
|
|
|
|
if (scriptElements[url]) {
|
|
this.cleanupScriptElement(scriptElements[url], true);
|
|
delete scriptElements[url];
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
cleanupScriptElement: function(script, remove) {
|
|
script.onload = null;
|
|
script.onreadystatechange = null;
|
|
script.onerror = null;
|
|
|
|
if (remove) {
|
|
this.documentHead.removeChild(script);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Load a script file, supports both asynchronous and synchronous approaches
|
|
*
|
|
* @param {String} url
|
|
* @param {Function} onLoad
|
|
* @param {Object} scope
|
|
* @param {Boolean} synchronous
|
|
* @private
|
|
*/
|
|
loadScriptFile: function(url, onLoad, onError, scope, synchronous) {
|
|
var me = this,
|
|
isFileLoaded = this.isFileLoaded,
|
|
scriptElements = this.scriptElements,
|
|
noCacheUrl = url + (this.getConfig('disableCaching') ? ('?' + this.getConfig('disableCachingParam') + '=' + Ext.Date.now()) : ''),
|
|
xhr, status, content, onScriptError;
|
|
|
|
if (isFileLoaded[url]) {
|
|
return this;
|
|
}
|
|
|
|
scope = scope || this;
|
|
|
|
this.isLoading = true;
|
|
|
|
if (!synchronous) {
|
|
onScriptError = function() {
|
|
//<debug error>
|
|
onError.call(scope, "Failed loading '" + url + "', please verify that the file exists", synchronous);
|
|
//</debug>
|
|
};
|
|
|
|
if (!Ext.isReady && Ext.onDocumentReady) {
|
|
Ext.onDocumentReady(function() {
|
|
if (!isFileLoaded[url]) {
|
|
scriptElements[url] = me.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
scriptElements[url] = this.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
|
|
}
|
|
}
|
|
else {
|
|
if (typeof XMLHttpRequest != 'undefined') {
|
|
xhr = new XMLHttpRequest();
|
|
} else {
|
|
xhr = new ActiveXObject('Microsoft.XMLHTTP');
|
|
}
|
|
|
|
try {
|
|
xhr.open('GET', noCacheUrl, false);
|
|
xhr.send(null);
|
|
}
|
|
catch (e) {
|
|
//<debug error>
|
|
onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; It's likely that the file is either " +
|
|
"being loaded from a different domain or from the local file system whereby cross origin " +
|
|
"requests are not allowed due to security reasons. Use asynchronous loading with " +
|
|
"Ext.require instead.", synchronous);
|
|
//</debug>
|
|
}
|
|
|
|
status = (xhr.status == 1223) ? 204 : xhr.status;
|
|
content = xhr.responseText;
|
|
|
|
if ((status >= 200 && status < 300) || status == 304 || (status == 0 && content.length > 0)) {
|
|
// Debugger friendly, file names are still shown even though they're eval'ed code
|
|
// Breakpoints work on both Firebug and Chrome's Web Inspector
|
|
Ext.globalEval(content + "\n//@ sourceURL=" + url);
|
|
onLoad.call(scope);
|
|
}
|
|
else {
|
|
//<debug>
|
|
onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; please " +
|
|
"verify that the file exists. " +
|
|
"XHR status code: " + status, synchronous);
|
|
//</debug>
|
|
}
|
|
|
|
// Prevent potential IE memory leak
|
|
xhr = null;
|
|
}
|
|
},
|
|
|
|
// documented above
|
|
syncRequire: function() {
|
|
var syncModeEnabled = this.syncModeEnabled;
|
|
|
|
if (!syncModeEnabled) {
|
|
this.syncModeEnabled = true;
|
|
}
|
|
|
|
this.require.apply(this, arguments);
|
|
|
|
if (!syncModeEnabled) {
|
|
this.syncModeEnabled = false;
|
|
}
|
|
|
|
this.refreshQueue();
|
|
},
|
|
|
|
// documented above
|
|
require: function(expressions, fn, scope, excludes) {
|
|
var excluded = {},
|
|
included = {},
|
|
queue = this.queue,
|
|
classNameToFilePathMap = this.classNameToFilePathMap,
|
|
isClassFileLoaded = this.isClassFileLoaded,
|
|
excludedClassNames = [],
|
|
possibleClassNames = [],
|
|
classNames = [],
|
|
references = [],
|
|
callback,
|
|
syncModeEnabled,
|
|
filePath, expression, exclude, className,
|
|
possibleClassName, i, j, ln, subLn;
|
|
|
|
if (excludes) {
|
|
excludes = arrayFrom(excludes);
|
|
|
|
for (i = 0,ln = excludes.length; i < ln; i++) {
|
|
exclude = excludes[i];
|
|
|
|
if (typeof exclude == 'string' && exclude.length > 0) {
|
|
excludedClassNames = Manager.getNamesByExpression(exclude);
|
|
|
|
for (j = 0,subLn = excludedClassNames.length; j < subLn; j++) {
|
|
excluded[excludedClassNames[j]] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
expressions = arrayFrom(expressions);
|
|
|
|
if (fn) {
|
|
if (fn.length > 0) {
|
|
callback = function() {
|
|
var classes = [],
|
|
i, ln, name;
|
|
|
|
for (i = 0,ln = references.length; i < ln; i++) {
|
|
name = references[i];
|
|
classes.push(Manager.get(name));
|
|
}
|
|
|
|
return fn.apply(this, classes);
|
|
};
|
|
}
|
|
else {
|
|
callback = fn;
|
|
}
|
|
}
|
|
else {
|
|
callback = Ext.emptyFn;
|
|
}
|
|
|
|
scope = scope || Ext.global;
|
|
|
|
for (i = 0,ln = expressions.length; i < ln; i++) {
|
|
expression = expressions[i];
|
|
|
|
if (typeof expression == 'string' && expression.length > 0) {
|
|
possibleClassNames = Manager.getNamesByExpression(expression);
|
|
subLn = possibleClassNames.length;
|
|
|
|
for (j = 0; j < subLn; j++) {
|
|
possibleClassName = possibleClassNames[j];
|
|
|
|
if (excluded[possibleClassName] !== true) {
|
|
references.push(possibleClassName);
|
|
|
|
if (!Manager.isCreated(possibleClassName) && !included[possibleClassName]) {
|
|
included[possibleClassName] = true;
|
|
classNames.push(possibleClassName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the dynamic dependency feature is not being used, throw an error
|
|
// if the dependencies are not defined
|
|
if (classNames.length > 0) {
|
|
if (!this.config.enabled) {
|
|
throw new Error("Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " +
|
|
"Missing required class" + ((classNames.length > 1) ? "es" : "") + ": " + classNames.join(', '));
|
|
}
|
|
}
|
|
else {
|
|
callback.call(scope);
|
|
return this;
|
|
}
|
|
|
|
syncModeEnabled = this.syncModeEnabled;
|
|
|
|
if (!syncModeEnabled) {
|
|
queue.push({
|
|
requires: classNames.slice(), // this array will be modified as the queue is processed,
|
|
// so we need a copy of it
|
|
callback: callback,
|
|
scope: scope
|
|
});
|
|
}
|
|
|
|
ln = classNames.length;
|
|
|
|
for (i = 0; i < ln; i++) {
|
|
className = classNames[i];
|
|
|
|
filePath = this.getPath(className);
|
|
|
|
// If we are synchronously loading a file that has already been asynchronously loaded before
|
|
// we need to destroy the script tag and revert the count
|
|
// This file will then be forced loaded in synchronous
|
|
if (syncModeEnabled && isClassFileLoaded.hasOwnProperty(className)) {
|
|
this.numPendingFiles--;
|
|
this.removeScriptElement(filePath);
|
|
delete isClassFileLoaded[className];
|
|
}
|
|
|
|
if (!isClassFileLoaded.hasOwnProperty(className)) {
|
|
isClassFileLoaded[className] = false;
|
|
|
|
classNameToFilePathMap[className] = filePath;
|
|
|
|
this.numPendingFiles++;
|
|
|
|
this.loadScriptFile(
|
|
filePath,
|
|
pass(this.onFileLoaded, [className, filePath], this),
|
|
pass(this.onFileLoadError, [className, filePath]),
|
|
this,
|
|
syncModeEnabled
|
|
);
|
|
}
|
|
}
|
|
|
|
if (syncModeEnabled) {
|
|
callback.call(scope);
|
|
|
|
if (ln === 1) {
|
|
return Manager.get(className);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @param {String} className
|
|
* @param {String} filePath
|
|
*/
|
|
onFileLoaded: function(className, filePath) {
|
|
this.numLoadedFiles++;
|
|
|
|
this.isClassFileLoaded[className] = true;
|
|
this.isFileLoaded[filePath] = true;
|
|
|
|
this.numPendingFiles--;
|
|
|
|
if (this.numPendingFiles === 0) {
|
|
this.refreshQueue();
|
|
}
|
|
|
|
//<debug>
|
|
if (!this.syncModeEnabled && this.numPendingFiles === 0 && this.isLoading && !this.hasFileLoadError) {
|
|
var queue = this.queue,
|
|
missingClasses = [],
|
|
missingPaths = [],
|
|
requires,
|
|
i, ln, j, subLn;
|
|
|
|
for (i = 0,ln = queue.length; i < ln; i++) {
|
|
requires = queue[i].requires;
|
|
|
|
for (j = 0,subLn = requires.length; j < subLn; j++) {
|
|
if (this.isClassFileLoaded[requires[j]]) {
|
|
missingClasses.push(requires[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (missingClasses.length < 1) {
|
|
return;
|
|
}
|
|
|
|
missingClasses = Ext.Array.filter(Ext.Array.unique(missingClasses), function(item) {
|
|
return !this.requiresMap.hasOwnProperty(item);
|
|
}, this);
|
|
|
|
for (i = 0,ln = missingClasses.length; i < ln; i++) {
|
|
missingPaths.push(this.classNameToFilePathMap[missingClasses[i]]);
|
|
}
|
|
|
|
throw new Error("The following classes are not declared even if their files have been " +
|
|
"loaded: '" + missingClasses.join("', '") + "'. Please check the source code of their " +
|
|
"corresponding files for possible typos: '" + missingPaths.join("', '"));
|
|
}
|
|
//</debug>
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
onFileLoadError: function(className, filePath, errorMessage, isSynchronous) {
|
|
this.numPendingFiles--;
|
|
this.hasFileLoadError = true;
|
|
|
|
//<debug error>
|
|
throw new Error("[Ext.Loader] " + errorMessage);
|
|
//</debug>
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
addOptionalRequires: function(requires) {
|
|
var optionalRequires = this.optionalRequires,
|
|
i, ln, require;
|
|
|
|
requires = arrayFrom(requires);
|
|
|
|
for (i = 0, ln = requires.length; i < ln; i++) {
|
|
require = requires[i];
|
|
|
|
arrayInclude(optionalRequires, require);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
triggerReady: function(force) {
|
|
var readyListeners = this.readyListeners,
|
|
optionalRequires = this.optionalRequires,
|
|
listener;
|
|
|
|
if (this.isLoading || force) {
|
|
this.isLoading = false;
|
|
|
|
if (optionalRequires.length !== 0) {
|
|
// Clone then empty the array to eliminate potential recursive loop issue
|
|
optionalRequires = optionalRequires.slice();
|
|
|
|
// Empty the original array
|
|
this.optionalRequires.length = 0;
|
|
|
|
this.require(optionalRequires, pass(this.triggerReady, [true], this), this);
|
|
return this;
|
|
}
|
|
|
|
while (readyListeners.length) {
|
|
listener = readyListeners.shift();
|
|
listener.fn.call(listener.scope);
|
|
|
|
if (this.isLoading) {
|
|
return this;
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
// duplicate definition (documented above)
|
|
onReady: function(fn, scope, withDomReady, options) {
|
|
var oldFn;
|
|
|
|
if (withDomReady !== false && Ext.onDocumentReady) {
|
|
oldFn = fn;
|
|
|
|
fn = function() {
|
|
Ext.onDocumentReady(oldFn, scope, options);
|
|
};
|
|
}
|
|
|
|
if (!this.isLoading) {
|
|
fn.call(scope);
|
|
}
|
|
else {
|
|
this.readyListeners.push({
|
|
fn: fn,
|
|
scope: scope
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @param {String} className
|
|
*/
|
|
historyPush: function(className) {
|
|
var isInHistory = this.isInHistory;
|
|
|
|
if (className && this.isClassFileLoaded.hasOwnProperty(className) && !isInHistory[className]) {
|
|
isInHistory[className] = true;
|
|
this.history.push(className);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
});
|
|
|
|
//</feature>
|
|
|
|
/**
|
|
* Convenient alias of {@link Ext.Loader#require}. Please see the introduction documentation of
|
|
* {@link Ext.Loader} for examples.
|
|
* @member Ext
|
|
* @method require
|
|
* @inheritdoc Ext.Loader#require
|
|
*/
|
|
Ext.require = alias(Loader, 'require');
|
|
|
|
/**
|
|
* Synchronous version of {@link Ext#require}, convenient alias of {@link Ext.Loader#syncRequire}.
|
|
* @member Ext
|
|
* @method syncRequire
|
|
* @inheritdoc Ext.Loader#syncRequire
|
|
*/
|
|
Ext.syncRequire = alias(Loader, 'syncRequire');
|
|
|
|
/**
|
|
* Convenient shortcut to {@link Ext.Loader#exclude}.
|
|
* @member Ext
|
|
* @method exclude
|
|
* @inheritdoc Ext.Loader#exclude
|
|
*/
|
|
Ext.exclude = alias(Loader, 'exclude');
|
|
|
|
/**
|
|
* Adds a listener to be notified when the document is ready and all dependencies are loaded.
|
|
*
|
|
* @param {Function} fn The method the event invokes.
|
|
* @param {Object} [scope] The scope in which the handler function executes. Defaults to the browser window.
|
|
* @param {Boolean} [options] Options object as passed to {@link Ext.Element#addListener}. It is recommended
|
|
* that the options `{single: true}` be used so that the handler is removed on first invocation.
|
|
* @member Ext
|
|
* @method onReady
|
|
*/
|
|
Ext.onReady = function(fn, scope, options) {
|
|
Loader.onReady(fn, scope, true, options);
|
|
};
|
|
|
|
Class.registerPreprocessor('loader', function(cls, data, hooks, continueFn) {
|
|
var me = this,
|
|
dependencies = [],
|
|
className = Manager.getName(cls),
|
|
i, j, ln, subLn, value, propertyName, propertyValue;
|
|
|
|
/*
|
|
Loop through the dependencyProperties, look for string class names and push
|
|
them into a stack, regardless of whether the property's value is a string, array or object. For example:
|
|
{
|
|
extend: 'Ext.MyClass',
|
|
requires: ['Ext.some.OtherClass'],
|
|
mixins: {
|
|
observable: 'Ext.mixin.Observable';
|
|
}
|
|
}
|
|
which will later be transformed into:
|
|
{
|
|
extend: Ext.MyClass,
|
|
requires: [Ext.some.OtherClass],
|
|
mixins: {
|
|
observable: Ext.mixin.Observable;
|
|
}
|
|
}
|
|
*/
|
|
|
|
for (i = 0,ln = dependencyProperties.length; i < ln; i++) {
|
|
propertyName = dependencyProperties[i];
|
|
|
|
if (data.hasOwnProperty(propertyName)) {
|
|
propertyValue = data[propertyName];
|
|
|
|
if (typeof propertyValue == 'string') {
|
|
dependencies.push(propertyValue);
|
|
}
|
|
else if (propertyValue instanceof Array) {
|
|
for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
|
|
value = propertyValue[j];
|
|
|
|
if (typeof value == 'string') {
|
|
dependencies.push(value);
|
|
}
|
|
}
|
|
}
|
|
else if (typeof propertyValue != 'function') {
|
|
for (j in propertyValue) {
|
|
if (propertyValue.hasOwnProperty(j)) {
|
|
value = propertyValue[j];
|
|
|
|
if (typeof value == 'string') {
|
|
dependencies.push(value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dependencies.length === 0) {
|
|
return;
|
|
}
|
|
|
|
//<feature classSystem.loader>
|
|
//<debug error>
|
|
var deadlockPath = [],
|
|
requiresMap = Loader.requiresMap,
|
|
detectDeadlock;
|
|
|
|
/*
|
|
Automatically detect deadlocks before-hand,
|
|
will throw an error with detailed path for ease of debugging. Examples of deadlock cases:
|
|
|
|
- A extends B, then B extends A
|
|
- A requires B, B requires C, then C requires A
|
|
|
|
The detectDeadlock function will recursively transverse till the leaf, hence it can detect deadlocks
|
|
no matter how deep the path is.
|
|
*/
|
|
|
|
if (className) {
|
|
requiresMap[className] = dependencies;
|
|
//<debug>
|
|
if (!Loader.requiredByMap) Loader.requiredByMap = {};
|
|
Ext.Array.each(dependencies, function(dependency){
|
|
if (!Loader.requiredByMap[dependency]) Loader.requiredByMap[dependency] = [];
|
|
Loader.requiredByMap[dependency].push(className);
|
|
});
|
|
//</debug>
|
|
detectDeadlock = function(cls) {
|
|
deadlockPath.push(cls);
|
|
|
|
if (requiresMap[cls]) {
|
|
if (Ext.Array.contains(requiresMap[cls], className)) {
|
|
throw new Error("Deadlock detected while loading dependencies! '" + className + "' and '" +
|
|
deadlockPath[1] + "' " + "mutually require each other. Path: " +
|
|
deadlockPath.join(' -> ') + " -> " + deadlockPath[0]);
|
|
}
|
|
|
|
for (i = 0,ln = requiresMap[cls].length; i < ln; i++) {
|
|
detectDeadlock(requiresMap[cls][i]);
|
|
}
|
|
}
|
|
};
|
|
|
|
detectDeadlock(className);
|
|
}
|
|
|
|
//</debug>
|
|
//</feature>
|
|
|
|
Loader.require(dependencies, function() {
|
|
for (i = 0,ln = dependencyProperties.length; i < ln; i++) {
|
|
propertyName = dependencyProperties[i];
|
|
|
|
if (data.hasOwnProperty(propertyName)) {
|
|
propertyValue = data[propertyName];
|
|
|
|
if (typeof propertyValue == 'string') {
|
|
data[propertyName] = Manager.get(propertyValue);
|
|
}
|
|
else if (propertyValue instanceof Array) {
|
|
for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
|
|
value = propertyValue[j];
|
|
|
|
if (typeof value == 'string') {
|
|
data[propertyName][j] = Manager.get(value);
|
|
}
|
|
}
|
|
}
|
|
else if (typeof propertyValue != 'function') {
|
|
for (var k in propertyValue) {
|
|
if (propertyValue.hasOwnProperty(k)) {
|
|
value = propertyValue[k];
|
|
|
|
if (typeof value == 'string') {
|
|
data[propertyName][k] = Manager.get(value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
continueFn.call(me, cls, data, hooks);
|
|
});
|
|
|
|
return false;
|
|
}, true, 'after', 'className');
|
|
|
|
//<feature classSystem.loader>
|
|
/**
|
|
* @cfg {String[]} uses
|
|
* @member Ext.Class
|
|
* List of optional classes to load together with this class. These aren't necessarily loaded before
|
|
* this class is created, but are guaranteed to be available before Ext.onReady listeners are
|
|
* invoked
|
|
*/
|
|
Manager.registerPostprocessor('uses', function(name, cls, data) {
|
|
var uses = arrayFrom(data.uses),
|
|
items = [],
|
|
i, ln, item;
|
|
|
|
for (i = 0,ln = uses.length; i < ln; i++) {
|
|
item = uses[i];
|
|
|
|
if (typeof item == 'string') {
|
|
items.push(item);
|
|
}
|
|
}
|
|
|
|
Loader.addOptionalRequires(items);
|
|
});
|
|
|
|
Manager.onCreated(function(className) {
|
|
this.historyPush(className);
|
|
}, Loader);
|
|
//</feature>
|
|
|
|
})(Ext.ClassManager, Ext.Class, Ext.Function.flexSetter, Ext.Function.alias,
|
|
Ext.Function.pass, Ext.Array.from, Ext.Array.erase, Ext.Array.include);
|
|
|
|
// initalize the default path of the framework
|
|
// trimmed down version of sench-touch-debug-suffix.js
|
|
// with alias / alternates removed, as those are handled separately by
|
|
// compiler-generated metadata
|
|
(function() {
|
|
var scripts = document.getElementsByTagName('script'),
|
|
currentScript = scripts[scripts.length - 1],
|
|
src = currentScript.src,
|
|
path = src.substring(0, src.lastIndexOf('/') + 1),
|
|
Loader = Ext.Loader;
|
|
|
|
//<debug>
|
|
// if we're running in dev mode out of the repo src tree, then this
|
|
// file will potentially be loaded from the touch/src/core/class folder
|
|
// so we'll need to adjust for that
|
|
if(src.indexOf("src/core/class/") != -1) {
|
|
path = path + "../../../";
|
|
}
|
|
//</debug>
|
|
|
|
|
|
Loader.setConfig({
|
|
enabled: true,
|
|
disableCaching: !/[?&](cache|breakpoint)/i.test(location.search),
|
|
paths: {
|
|
'Ext' : path + 'src'
|
|
}
|
|
});
|
|
|
|
})();
|
|
|
|
//@tag dom,core
|
|
//@define Ext.EventManager
|
|
//@define Ext.core.EventManager
|
|
//@require Ext.Loader
|
|
|
|
/**
|
|
* @class Ext.EventManager
|
|
*
|
|
* This object has been deprecated in Sencha Touch 2.0.0. Please refer to the method documentation for specific alternatives.
|
|
*
|
|
* @deprecated 2.0.0
|
|
* @singleton
|
|
* @private
|
|
*/
|
|
|
|
|
|
//@tag dom,core
|
|
//@define Ext-more
|
|
//@require Ext.EventManager
|
|
|
|
/**
|
|
* @class Ext
|
|
*
|
|
* Ext is the global namespace for the whole Sencha Touch framework. Every class, function and configuration for the
|
|
* whole framework exists under this single global variable. The Ext singleton itself contains a set of useful helper
|
|
* functions (like {@link #apply}, {@link #min} and others), but most of the framework that you use day to day exists
|
|
* in specialized classes (for example {@link Ext.Panel}, {@link Ext.Carousel} and others).
|
|
*
|
|
* If you are new to Sencha Touch we recommend starting with the [Getting Started Guide][getting_started] to
|
|
* get a feel for how the framework operates. After that, use the more focused guides on subjects like panels, forms and data
|
|
* to broaden your understanding. The MVC guides take you through the process of building full applications using the
|
|
* framework, and detail how to deploy them to production.
|
|
*
|
|
* The functions listed below are mostly utility functions used internally by many of the classes shipped in the
|
|
* framework, but also often useful in your own apps.
|
|
*
|
|
* A method that is crucial to beginning your application is {@link #setup Ext.setup}. Please refer to it's documentation, or the
|
|
* [Getting Started Guide][getting_started] as a reference on beginning your application.
|
|
*
|
|
* Ext.setup({
|
|
* onReady: function() {
|
|
* Ext.Viewport.add({
|
|
* xtype: 'component',
|
|
* html: 'Hello world!'
|
|
* });
|
|
* }
|
|
* });
|
|
*
|
|
* [getting_started]: #!/guide/getting_started
|
|
*/
|
|
Ext.setVersion('touch', '2.1.0');
|
|
|
|
Ext.apply(Ext, {
|
|
/**
|
|
* The version of the framework
|
|
* @type String
|
|
*/
|
|
version: Ext.getVersion('touch'),
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
idSeed: 0,
|
|
|
|
/**
|
|
* Repaints the whole page. This fixes frequently encountered painting issues in mobile Safari.
|
|
*/
|
|
repaint: function() {
|
|
var mask = Ext.getBody().createChild({
|
|
cls: Ext.baseCSSPrefix + 'mask ' + Ext.baseCSSPrefix + 'mask-transparent'
|
|
});
|
|
setTimeout(function() {
|
|
mask.destroy();
|
|
}, 0);
|
|
},
|
|
|
|
/**
|
|
* Generates unique ids. If the element already has an `id`, it is unchanged.
|
|
* @param {Mixed} el (optional) The element to generate an id for.
|
|
* @param {String} [prefix=ext-gen] (optional) The `id` prefix.
|
|
* @return {String} The generated `id`.
|
|
*/
|
|
id: function(el, prefix) {
|
|
if (el && el.id) {
|
|
return el.id;
|
|
}
|
|
|
|
el = Ext.getDom(el) || {};
|
|
|
|
if (el === document || el === document.documentElement) {
|
|
el.id = 'ext-application';
|
|
}
|
|
else if (el === document.body) {
|
|
el.id = 'ext-viewport';
|
|
}
|
|
else if (el === window) {
|
|
el.id = 'ext-window';
|
|
}
|
|
|
|
el.id = el.id || ((prefix || 'ext-element-') + (++Ext.idSeed));
|
|
|
|
return el.id;
|
|
},
|
|
|
|
/**
|
|
* Returns the current document body as an {@link Ext.Element}.
|
|
* @return {Ext.Element} The document body.
|
|
*/
|
|
getBody: function() {
|
|
if (!Ext.documentBodyElement) {
|
|
if (!document.body) {
|
|
throw new Error("[Ext.getBody] document.body does not exist at this point");
|
|
}
|
|
|
|
Ext.documentBodyElement = Ext.get(document.body);
|
|
}
|
|
|
|
return Ext.documentBodyElement;
|
|
},
|
|
|
|
/**
|
|
* Returns the current document head as an {@link Ext.Element}.
|
|
* @return {Ext.Element} The document head.
|
|
*/
|
|
getHead: function() {
|
|
if (!Ext.documentHeadElement) {
|
|
Ext.documentHeadElement = Ext.get(document.head || document.getElementsByTagName('head')[0]);
|
|
}
|
|
|
|
return Ext.documentHeadElement;
|
|
},
|
|
|
|
/**
|
|
* Returns the current HTML document object as an {@link Ext.Element}.
|
|
* @return {Ext.Element} The document.
|
|
*/
|
|
getDoc: function() {
|
|
if (!Ext.documentElement) {
|
|
Ext.documentElement = Ext.get(document);
|
|
}
|
|
|
|
return Ext.documentElement;
|
|
},
|
|
|
|
/**
|
|
* This is shorthand reference to {@link Ext.ComponentMgr#get}.
|
|
* Looks up an existing {@link Ext.Component Component} by {@link Ext.Component#getId id}
|
|
* @param {String} id The component {@link Ext.Component#getId id}
|
|
* @return {Ext.Component} The Component, `undefined` if not found, or `null` if a
|
|
* Class was found.
|
|
*/
|
|
getCmp: function(id) {
|
|
return Ext.ComponentMgr.get(id);
|
|
},
|
|
|
|
/**
|
|
* Copies a set of named properties from the source object to the destination object.
|
|
*
|
|
* Example:
|
|
*
|
|
* ImageComponent = Ext.extend(Ext.Component, {
|
|
* initComponent: function() {
|
|
* this.autoEl = { tag: 'img' };
|
|
* MyComponent.superclass.initComponent.apply(this, arguments);
|
|
* this.initialBox = Ext.copyTo({}, this.initialConfig, 'x,y,width,height');
|
|
* }
|
|
* });
|
|
*
|
|
* Important note: To borrow class prototype methods, use {@link Ext.Base#borrow} instead.
|
|
*
|
|
* @param {Object} dest The destination object.
|
|
* @param {Object} source The source object.
|
|
* @param {String/String[]} names Either an Array of property names, or a comma-delimited list
|
|
* of property names to copy.
|
|
* @param {Boolean} [usePrototypeKeys=false] (optional) Pass `true` to copy keys off of the prototype as well as the instance.
|
|
* @return {Object} The modified object.
|
|
*/
|
|
copyTo : function(dest, source, names, usePrototypeKeys) {
|
|
if (typeof names == 'string') {
|
|
names = names.split(/[,;\s]/);
|
|
}
|
|
Ext.each (names, function(name) {
|
|
if (usePrototypeKeys || source.hasOwnProperty(name)) {
|
|
dest[name] = source[name];
|
|
}
|
|
}, this);
|
|
return dest;
|
|
},
|
|
|
|
/**
|
|
* Attempts to destroy any objects passed to it by removing all event listeners, removing them from the
|
|
* DOM (if applicable) and calling their destroy functions (if available). This method is primarily
|
|
* intended for arguments of type {@link Ext.Element} and {@link Ext.Component}.
|
|
* Any number of elements and/or components can be passed into this function in a single
|
|
* call as separate arguments.
|
|
* @param {Mixed...} args An {@link Ext.Element}, {@link Ext.Component}, or an Array of either of these to destroy.
|
|
*/
|
|
destroy: function() {
|
|
var args = arguments,
|
|
ln = args.length,
|
|
i, item;
|
|
|
|
for (i = 0; i < ln; i++) {
|
|
item = args[i];
|
|
|
|
if (item) {
|
|
if (Ext.isArray(item)) {
|
|
this.destroy.apply(this, item);
|
|
}
|
|
else if (Ext.isFunction(item.destroy)) {
|
|
item.destroy();
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Return the dom node for the passed String (id), dom node, or Ext.Element.
|
|
* Here are some examples:
|
|
*
|
|
* // gets dom node based on id
|
|
* var elDom = Ext.getDom('elId');
|
|
*
|
|
* // gets dom node based on the dom node
|
|
* var elDom1 = Ext.getDom(elDom);
|
|
*
|
|
* // If we don't know if we are working with an
|
|
* // Ext.Element or a dom node use Ext.getDom
|
|
* function(el){
|
|
* var dom = Ext.getDom(el);
|
|
* // do something with the dom node
|
|
* }
|
|
*
|
|
* __Note:__ the dom node to be found actually needs to exist (be rendered, etc)
|
|
* when this method is called to be successful.
|
|
* @param {Mixed} el
|
|
* @return {HTMLElement}
|
|
*/
|
|
getDom: function(el) {
|
|
if (!el || !document) {
|
|
return null;
|
|
}
|
|
|
|
return el.dom ? el.dom : (typeof el == 'string' ? document.getElementById(el) : el);
|
|
},
|
|
|
|
/**
|
|
* Removes this element from the document, removes all DOM event listeners, and deletes the cache reference.
|
|
* All DOM event listeners are removed from this element.
|
|
* @param {HTMLElement} node The node to remove.
|
|
*/
|
|
removeNode: function(node) {
|
|
if (node && node.parentNode && node.tagName != 'BODY') {
|
|
Ext.get(node).clearListeners();
|
|
node.parentNode.removeChild(node);
|
|
delete Ext.cache[node.id];
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
defaultSetupConfig: {
|
|
eventPublishers: {
|
|
dom: {
|
|
xclass: 'Ext.event.publisher.Dom'
|
|
},
|
|
touchGesture: {
|
|
xclass: 'Ext.event.publisher.TouchGesture',
|
|
recognizers: {
|
|
drag: {
|
|
xclass: 'Ext.event.recognizer.Drag'
|
|
},
|
|
tap: {
|
|
xclass: 'Ext.event.recognizer.Tap'
|
|
},
|
|
doubleTap: {
|
|
xclass: 'Ext.event.recognizer.DoubleTap'
|
|
},
|
|
longPress: {
|
|
xclass: 'Ext.event.recognizer.LongPress'
|
|
},
|
|
swipe: {
|
|
xclass: 'Ext.event.recognizer.HorizontalSwipe'
|
|
},
|
|
pinch: {
|
|
xclass: 'Ext.event.recognizer.Pinch'
|
|
},
|
|
rotate: {
|
|
xclass: 'Ext.event.recognizer.Rotate'
|
|
}
|
|
}
|
|
},
|
|
componentDelegation: {
|
|
xclass: 'Ext.event.publisher.ComponentDelegation'
|
|
},
|
|
componentPaint: {
|
|
xclass: 'Ext.event.publisher.ComponentPaint'
|
|
},
|
|
// componentSize: {
|
|
// xclass: 'Ext.event.publisher.ComponentSize'
|
|
// },
|
|
elementPaint: {
|
|
xclass: 'Ext.event.publisher.ElementPaint'
|
|
},
|
|
elementSize: {
|
|
xclass: 'Ext.event.publisher.ElementSize'
|
|
}
|
|
//<feature charts>
|
|
,seriesItemEvents: {
|
|
xclass: 'Ext.chart.series.ItemPublisher'
|
|
}
|
|
//</feature>
|
|
},
|
|
|
|
//<feature logger>
|
|
logger: {
|
|
enabled: true,
|
|
xclass: 'Ext.log.Logger',
|
|
minPriority: 'deprecate',
|
|
writers: {
|
|
console: {
|
|
xclass: 'Ext.log.writer.Console',
|
|
throwOnErrors: true,
|
|
formatter: {
|
|
xclass: 'Ext.log.formatter.Default'
|
|
}
|
|
}
|
|
}
|
|
},
|
|
//</feature>
|
|
|
|
animator: {
|
|
xclass: 'Ext.fx.Runner'
|
|
},
|
|
|
|
viewport: {
|
|
xclass: 'Ext.viewport.Viewport'
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
isSetup: false,
|
|
|
|
/**
|
|
* This indicate the start timestamp of current cycle.
|
|
* It is only reliable during dom-event-initiated cycles and
|
|
* {@link Ext.draw.Animator} initiated cycles.
|
|
*/
|
|
frameStartTime: +new Date(),
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
setupListeners: [],
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
onSetup: function(fn, scope) {
|
|
if (Ext.isSetup) {
|
|
fn.call(scope);
|
|
}
|
|
else {
|
|
Ext.setupListeners.push({
|
|
fn: fn,
|
|
scope: scope
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Ext.setup() is the entry-point to initialize a Sencha Touch application. Note that if your application makes
|
|
* use of MVC architecture, use {@link Ext#application} instead.
|
|
*
|
|
* This method accepts one single argument in object format. The most basic use of Ext.setup() is as follows:
|
|
*
|
|
* Ext.setup({
|
|
* onReady: function() {
|
|
* // ...
|
|
* }
|
|
* });
|
|
*
|
|
* This sets up the viewport, initializes the event system, instantiates a default animation runner, and a default
|
|
* logger (during development). When all of that is ready, it invokes the callback function given to the `onReady` key.
|
|
*
|
|
* The default scope (`this`) of `onReady` is the main viewport. By default the viewport instance is stored in
|
|
* {@link Ext.Viewport}. For example, this snippet adds a 'Hello World' button that is centered on the screen:
|
|
*
|
|
* Ext.setup({
|
|
* onReady: function() {
|
|
* this.add({
|
|
* xtype: 'button',
|
|
* centered: true,
|
|
* text: 'Hello world!'
|
|
* }); // Equivalent to Ext.Viewport.add(...)
|
|
* }
|
|
* });
|
|
*
|
|
* @param {Object} config An object with the following config options:
|
|
*
|
|
* @param {Function} config.onReady
|
|
* A function to be called when the application is ready. Your application logic should be here.
|
|
*
|
|
* @param {Object} config.viewport
|
|
* A custom config object to be used when creating the global {@link Ext.Viewport} instance. Please refer to the
|
|
* {@link Ext.Viewport} documentation for more information.
|
|
*
|
|
* Ext.setup({
|
|
* viewport: {
|
|
* width: 500,
|
|
* height: 500
|
|
* },
|
|
* onReady: function() {
|
|
* // ...
|
|
* }
|
|
* });
|
|
*
|
|
* @param {String/Object} config.icon
|
|
* Specifies a set of URLs to the application icon for different device form factors. This icon is displayed
|
|
* when the application is added to the device's Home Screen.
|
|
*
|
|
* Ext.setup({
|
|
* icon: {
|
|
* 57: 'resources/icons/Icon.png',
|
|
* 72: 'resources/icons/Icon~ipad.png',
|
|
* 114: 'resources/icons/Icon@2x.png',
|
|
* 144: 'resources/icons/Icon~ipad@2x.png'
|
|
* },
|
|
* onReady: function() {
|
|
* // ...
|
|
* }
|
|
* });
|
|
*
|
|
* Each key represents the dimension of the icon as a square shape. For example: '57' is the key for a 57 x 57
|
|
* icon image. Here is the breakdown of each dimension and its device target:
|
|
*
|
|
* - 57: Non-retina iPhone, iPod touch, and all Android devices
|
|
* - 72: Retina iPhone and iPod touch
|
|
* - 114: Non-retina iPad (first and second generation)
|
|
* - 144: Retina iPad (third generation)
|
|
*
|
|
* Note that the dimensions of the icon images must be exactly 57x57, 72x72, 114x114 and 144x144 respectively.
|
|
*
|
|
* It is highly recommended that you provide all these different sizes to accommodate a full range of
|
|
* devices currently available. However if you only have one icon in one size, make it 57x57 in size and
|
|
* specify it as a string value. This same icon will be used on all supported devices.
|
|
*
|
|
* Ext.setup({
|
|
* icon: 'resources/icons/Icon.png',
|
|
* onReady: function() {
|
|
* // ...
|
|
* }
|
|
* });
|
|
*
|
|
* @param {Object} config.startupImage
|
|
* Specifies a set of URLs to the application startup images for different device form factors. This image is
|
|
* displayed when the application is being launched from the Home Screen icon. Note that this currently only applies
|
|
* to iOS devices.
|
|
*
|
|
* Ext.setup({
|
|
* startupImage: {
|
|
* '320x460': 'resources/startup/320x460.jpg',
|
|
* '640x920': 'resources/startup/640x920.png',
|
|
* '640x1096': 'resources/startup/640x1096.png',
|
|
* '768x1004': 'resources/startup/768x1004.png',
|
|
* '748x1024': 'resources/startup/748x1024.png',
|
|
* '1536x2008': 'resources/startup/1536x2008.png',
|
|
* '1496x2048': 'resources/startup/1496x2048.png'
|
|
* },
|
|
* onReady: function() {
|
|
* // ...
|
|
* }
|
|
* });
|
|
*
|
|
* Each key represents the dimension of the image. For example: '320x460' is the key for a 320px x 460px image.
|
|
* Here is the breakdown of each dimension and its device target:
|
|
*
|
|
* - 320x460: Non-retina iPhone, iPod touch, and all Android devices
|
|
* - 640x920: Retina iPhone and iPod touch
|
|
* - 640x1096: iPhone 5 and iPod touch (fifth generation)
|
|
* - 768x1004: Non-retina iPad (first and second generation) in portrait orientation
|
|
* - 748x1024: Non-retina iPad (first and second generation) in landscape orientation
|
|
* - 1536x2008: Retina iPad (third generation) in portrait orientation
|
|
* - 1496x2048: Retina iPad (third generation) in landscape orientation
|
|
*
|
|
* Please note that there's no automatic fallback mechanism for the startup images. In other words, if you don't specify
|
|
* a valid image for a certain device, nothing will be displayed while the application is being launched on that device.
|
|
*
|
|
* @param {Boolean} isIconPrecomposed
|
|
* True to not having a glossy effect added to the icon by the OS, which will preserve its exact look. This currently
|
|
* only applies to iOS devices.
|
|
*
|
|
* @param {String} statusBarStyle
|
|
* The style of status bar to be shown on applications added to the iOS home screen. Valid options are:
|
|
*
|
|
* * `default`
|
|
* * `black`
|
|
* * `black-translucent`
|
|
*
|
|
* @param {String[]} config.requires
|
|
* An array of required classes for your application which will be automatically loaded before `onReady` is invoked.
|
|
* Please refer to {@link Ext.Loader} and {@link Ext.Loader#require} for more information.
|
|
*
|
|
* Ext.setup({
|
|
* requires: ['Ext.Button', 'Ext.tab.Panel'],
|
|
* onReady: function() {
|
|
* // ...
|
|
* }
|
|
* });
|
|
*
|
|
* @param {Object} config.eventPublishers
|
|
* Sencha Touch, by default, includes various {@link Ext.event.recognizer.Recognizer} subclasses to recognize events fired
|
|
* in your application. The list of default recognizers can be found in the documentation for
|
|
* {@link Ext.event.recognizer.Recognizer}.
|
|
*
|
|
* To change the default recognizers, you can use the following syntax:
|
|
*
|
|
* Ext.setup({
|
|
* eventPublishers: {
|
|
* touchGesture: {
|
|
* recognizers: {
|
|
* swipe: {
|
|
* // this will include both vertical and horizontal swipe recognizers
|
|
* xclass: 'Ext.event.recognizer.Swipe'
|
|
* }
|
|
* }
|
|
* }
|
|
* },
|
|
* onReady: function() {
|
|
* // ...
|
|
* }
|
|
* });
|
|
*
|
|
* You can also disable recognizers using this syntax:
|
|
*
|
|
* Ext.setup({
|
|
* eventPublishers: {
|
|
* touchGesture: {
|
|
* recognizers: {
|
|
* swipe: null,
|
|
* pinch: null,
|
|
* rotate: null
|
|
* }
|
|
* }
|
|
* },
|
|
* onReady: function() {
|
|
* // ...
|
|
* }
|
|
* });
|
|
*/
|
|
setup: function(config) {
|
|
var defaultSetupConfig = Ext.defaultSetupConfig,
|
|
emptyFn = Ext.emptyFn,
|
|
onReady = config.onReady || emptyFn,
|
|
onUpdated = config.onUpdated || emptyFn,
|
|
scope = config.scope,
|
|
requires = Ext.Array.from(config.requires),
|
|
extOnReady = Ext.onReady,
|
|
head = Ext.getHead(),
|
|
callback, viewport, precomposed;
|
|
|
|
Ext.setup = function() {
|
|
throw new Error("Ext.setup has already been called before");
|
|
};
|
|
|
|
delete config.requires;
|
|
delete config.onReady;
|
|
delete config.onUpdated;
|
|
delete config.scope;
|
|
|
|
Ext.require(['Ext.event.Dispatcher']);
|
|
|
|
callback = function() {
|
|
var listeners = Ext.setupListeners,
|
|
ln = listeners.length,
|
|
i, listener;
|
|
|
|
delete Ext.setupListeners;
|
|
Ext.isSetup = true;
|
|
|
|
for (i = 0; i < ln; i++) {
|
|
listener = listeners[i];
|
|
listener.fn.call(listener.scope);
|
|
}
|
|
|
|
Ext.onReady = extOnReady;
|
|
Ext.onReady(onReady, scope);
|
|
};
|
|
|
|
Ext.onUpdated = onUpdated;
|
|
Ext.onReady = function(fn, scope) {
|
|
var origin = onReady;
|
|
|
|
onReady = function() {
|
|
origin();
|
|
Ext.onReady(fn, scope);
|
|
};
|
|
};
|
|
|
|
config = Ext.merge({}, defaultSetupConfig, config);
|
|
|
|
Ext.onDocumentReady(function() {
|
|
Ext.factoryConfig(config, function(data) {
|
|
Ext.event.Dispatcher.getInstance().setPublishers(data.eventPublishers);
|
|
|
|
if (data.logger) {
|
|
Ext.Logger = data.logger;
|
|
}
|
|
|
|
if (data.animator) {
|
|
Ext.Animator = data.animator;
|
|
}
|
|
|
|
if (data.viewport) {
|
|
Ext.Viewport = viewport = data.viewport;
|
|
|
|
if (!scope) {
|
|
scope = viewport;
|
|
}
|
|
|
|
Ext.require(requires, function() {
|
|
Ext.Viewport.on('ready', callback, null, {single: true});
|
|
});
|
|
}
|
|
else {
|
|
Ext.require(requires, callback);
|
|
}
|
|
});
|
|
});
|
|
|
|
function addMeta(name, content) {
|
|
var meta = document.createElement('meta');
|
|
meta.setAttribute('name', name);
|
|
meta.setAttribute('content', content);
|
|
head.append(meta);
|
|
}
|
|
|
|
function addIcon(href, sizes, precomposed) {
|
|
var link = document.createElement('link');
|
|
link.setAttribute('rel', 'apple-touch-icon' + (precomposed ? '-precomposed' : ''));
|
|
link.setAttribute('href', href);
|
|
if (sizes) {
|
|
link.setAttribute('sizes', sizes);
|
|
}
|
|
head.append(link);
|
|
}
|
|
|
|
function addStartupImage(href, media) {
|
|
var link = document.createElement('link');
|
|
link.setAttribute('rel', 'apple-touch-startup-image');
|
|
link.setAttribute('href', href);
|
|
if (media) {
|
|
link.setAttribute('media', media);
|
|
}
|
|
head.append(link);
|
|
}
|
|
|
|
var icon = config.icon,
|
|
isIconPrecomposed = Boolean(config.isIconPrecomposed),
|
|
startupImage = config.startupImage || {},
|
|
statusBarStyle = config.statusBarStyle,
|
|
devicePixelRatio = window.devicePixelRatio || 1;
|
|
|
|
if (navigator.standalone) {
|
|
addMeta('viewport', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0');
|
|
}
|
|
else {
|
|
addMeta('viewport', 'initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0');
|
|
}
|
|
addMeta('apple-mobile-web-app-capable', 'yes');
|
|
addMeta('apple-touch-fullscreen', 'yes');
|
|
|
|
// status bar style
|
|
if (statusBarStyle) {
|
|
addMeta('apple-mobile-web-app-status-bar-style', statusBarStyle);
|
|
}
|
|
|
|
if (Ext.isString(icon)) {
|
|
icon = {
|
|
57: icon,
|
|
72: icon,
|
|
114: icon,
|
|
144: icon
|
|
};
|
|
}
|
|
else if (!icon) {
|
|
icon = {};
|
|
}
|
|
|
|
|
|
if (Ext.os.is.iPad) {
|
|
if (devicePixelRatio >= 2) {
|
|
// Retina iPad - Landscape
|
|
if ('1496x2048' in startupImage) {
|
|
addStartupImage(startupImage['1496x2048'], '(orientation: landscape)');
|
|
}
|
|
// Retina iPad - Portrait
|
|
if ('1536x2008' in startupImage) {
|
|
addStartupImage(startupImage['1536x2008'], '(orientation: portrait)');
|
|
}
|
|
|
|
// Retina iPad
|
|
if ('144' in icon) {
|
|
addIcon(icon['144'], '144x144', isIconPrecomposed);
|
|
}
|
|
}
|
|
else {
|
|
// Non-Retina iPad - Landscape
|
|
if ('748x1024' in startupImage) {
|
|
addStartupImage(startupImage['748x1024'], '(orientation: landscape)');
|
|
}
|
|
// Non-Retina iPad - Portrait
|
|
if ('768x1004' in startupImage) {
|
|
addStartupImage(startupImage['768x1004'], '(orientation: portrait)');
|
|
}
|
|
|
|
// Non-Retina iPad
|
|
if ('72' in icon) {
|
|
addIcon(icon['72'], '72x72', isIconPrecomposed);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// Retina iPhone, iPod touch with iOS version >= 4.3
|
|
if (devicePixelRatio >= 2 && Ext.os.version.gtEq('4.3')) {
|
|
if (Ext.os.is.iPhone5) {
|
|
addStartupImage(startupImage['640x1096']);
|
|
} else {
|
|
addStartupImage(startupImage['640x920']);
|
|
}
|
|
|
|
// Retina iPhone and iPod touch
|
|
if ('114' in icon) {
|
|
addIcon(icon['114'], '114x114', isIconPrecomposed);
|
|
}
|
|
}
|
|
else {
|
|
addStartupImage(startupImage['320x460']);
|
|
|
|
// Non-Retina iPhone, iPod touch, and Android devices
|
|
if ('57' in icon) {
|
|
addIcon(icon['57'], null, isIconPrecomposed);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @member Ext
|
|
* @method application
|
|
*
|
|
* Loads Ext.app.Application class and starts it up with given configuration after the page is ready.
|
|
*
|
|
* Ext.application({
|
|
* launch: function() {
|
|
* alert('Application launched!');
|
|
* }
|
|
* });
|
|
*
|
|
* See {@link Ext.app.Application} for details.
|
|
*
|
|
* @param {Object} config An object with the following config options:
|
|
*
|
|
* @param {Function} config.launch
|
|
* A function to be called when the application is ready. Your application logic should be here. Please see {@link Ext.app.Application}
|
|
* for details.
|
|
*
|
|
* @param {Object} config.viewport
|
|
* An object to be used when creating the global {@link Ext.Viewport} instance. Please refer to the {@link Ext.Viewport}
|
|
* documentation for more information.
|
|
*
|
|
* Ext.application({
|
|
* viewport: {
|
|
* layout: 'vbox'
|
|
* },
|
|
* launch: function() {
|
|
* Ext.Viewport.add({
|
|
* flex: 1,
|
|
* html: 'top (flex: 1)'
|
|
* });
|
|
*
|
|
* Ext.Viewport.add({
|
|
* flex: 4,
|
|
* html: 'bottom (flex: 4)'
|
|
* });
|
|
* }
|
|
* });
|
|
*
|
|
* @param {String/Object} config.icon
|
|
* Specifies a set of URLs to the application icon for different device form factors. This icon is displayed
|
|
* when the application is added to the device's Home Screen.
|
|
*
|
|
* Ext.application({
|
|
* icon: {
|
|
* 57: 'resources/icons/Icon.png',
|
|
* 72: 'resources/icons/Icon~ipad.png',
|
|
* 114: 'resources/icons/Icon@2x.png',
|
|
* 144: 'resources/icons/Icon~ipad@2x.png'
|
|
* },
|
|
* launch: function() {
|
|
* // ...
|
|
* }
|
|
* });
|
|
*
|
|
* Each key represents the dimension of the icon as a square shape. For example: '57' is the key for a 57 x 57
|
|
* icon image. Here is the breakdown of each dimension and its device target:
|
|
*
|
|
* - 57: Non-retina iPhone, iPod touch, and all Android devices
|
|
* - 72: Retina iPhone and iPod touch
|
|
* - 114: Non-retina iPad (first and second generation)
|
|
* - 144: Retina iPad (third generation)
|
|
*
|
|
* Note that the dimensions of the icon images must be exactly 57x57, 72x72, 114x114 and 144x144 respectively.
|
|
*
|
|
* It is highly recommended that you provide all these different sizes to accommodate a full range of
|
|
* devices currently available. However if you only have one icon in one size, make it 57x57 in size and
|
|
* specify it as a string value. This same icon will be used on all supported devices.
|
|
*
|
|
* Ext.setup({
|
|
* icon: 'resources/icons/Icon.png',
|
|
* onReady: function() {
|
|
* // ...
|
|
* }
|
|
* });
|
|
*
|
|
* @param {Object} config.startupImage
|
|
* Specifies a set of URLs to the application startup images for different device form factors. This image is
|
|
* displayed when the application is being launched from the Home Screen icon. Note that this currently only applies
|
|
* to iOS devices.
|
|
*
|
|
* Ext.application({
|
|
* startupImage: {
|
|
* '320x460': 'resources/startup/320x460.jpg',
|
|
* '640x920': 'resources/startup/640x920.png',
|
|
* '640x1096': 'resources/startup/640x1096.png',
|
|
* '768x1004': 'resources/startup/768x1004.png',
|
|
* '748x1024': 'resources/startup/748x1024.png',
|
|
* '1536x2008': 'resources/startup/1536x2008.png',
|
|
* '1496x2048': 'resources/startup/1496x2048.png'
|
|
* },
|
|
* launch: function() {
|
|
* // ...
|
|
* }
|
|
* });
|
|
*
|
|
* Each key represents the dimension of the image. For example: '320x460' is the key for a 320px x 460px image.
|
|
* Here is the breakdown of each dimension and its device target:
|
|
*
|
|
* - 320x460: Non-retina iPhone, iPod touch, and all Android devices
|
|
* - 640x920: Retina iPhone and iPod touch
|
|
* - 640x1096: iPhone 5 and iPod touch (fifth generation)
|
|
* - 768x1004: Non-retina iPad (first and second generation) in portrait orientation
|
|
* - 748x1024: Non-retina iPad (first and second generation) in landscape orientation
|
|
* - 1536x2008: Retina iPad (third generation) in portrait orientation
|
|
* - 1496x2048: Retina iPad (third generation) in landscape orientation
|
|
*
|
|
* Please note that there's no automatic fallback mechanism for the startup images. In other words, if you don't specify
|
|
* a valid image for a certain device, nothing will be displayed while the application is being launched on that device.
|
|
*
|
|
* @param {Boolean} config.isIconPrecomposed
|
|
* True to not having a glossy effect added to the icon by the OS, which will preserve its exact look. This currently
|
|
* only applies to iOS devices.
|
|
*
|
|
* @param {String} config.statusBarStyle
|
|
* The style of status bar to be shown on applications added to the iOS home screen. Valid options are:
|
|
*
|
|
* * `default`
|
|
* * `black`
|
|
* * `black-translucent`
|
|
*
|
|
* @param {String[]} config.requires
|
|
* An array of required classes for your application which will be automatically loaded if {@link Ext.Loader#enabled} is set
|
|
* to `true`. Please refer to {@link Ext.Loader} and {@link Ext.Loader#require} for more information.
|
|
*
|
|
* Ext.application({
|
|
* requires: ['Ext.Button', 'Ext.tab.Panel'],
|
|
* launch: function() {
|
|
* // ...
|
|
* }
|
|
* });
|
|
*
|
|
* @param {Object} config.eventPublishers
|
|
* Sencha Touch, by default, includes various {@link Ext.event.recognizer.Recognizer} subclasses to recognize events fired
|
|
* in your application. The list of default recognizers can be found in the documentation for {@link Ext.event.recognizer.Recognizer}.
|
|
*
|
|
* To change the default recognizers, you can use the following syntax:
|
|
*
|
|
* Ext.application({
|
|
* eventPublishers: {
|
|
* touchGesture: {
|
|
* recognizers: {
|
|
* swipe: {
|
|
* // this will include both vertical and horizontal swipe recognizers
|
|
* xclass: 'Ext.event.recognizer.Swipe'
|
|
* }
|
|
* }
|
|
* }
|
|
* },
|
|
* launch: function() {
|
|
* // ...
|
|
* }
|
|
* });
|
|
*
|
|
* You can also disable recognizers using this syntax:
|
|
*
|
|
* Ext.application({
|
|
* eventPublishers: {
|
|
* touchGesture: {
|
|
* recognizers: {
|
|
* swipe: null,
|
|
* pinch: null,
|
|
* rotate: null
|
|
* }
|
|
* }
|
|
* },
|
|
* launch: function() {
|
|
* // ...
|
|
* }
|
|
* });
|
|
*/
|
|
application: function(config) {
|
|
var appName = config.name,
|
|
onReady, scope, requires;
|
|
|
|
if (!config) {
|
|
config = {};
|
|
}
|
|
|
|
if (!Ext.Loader.config.paths[appName]) {
|
|
Ext.Loader.setPath(appName, config.appFolder || 'app');
|
|
}
|
|
|
|
requires = Ext.Array.from(config.requires);
|
|
config.requires = ['Ext.app.Application'];
|
|
|
|
onReady = config.onReady;
|
|
scope = config.scope;
|
|
|
|
config.onReady = function() {
|
|
config.requires = requires;
|
|
new Ext.app.Application(config);
|
|
|
|
if (onReady) {
|
|
onReady.call(scope);
|
|
}
|
|
};
|
|
|
|
Ext.setup(config);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @param config
|
|
* @param callback
|
|
* @member Ext
|
|
*/
|
|
factoryConfig: function(config, callback) {
|
|
var isSimpleObject = Ext.isSimpleObject(config);
|
|
|
|
if (isSimpleObject && config.xclass) {
|
|
var className = config.xclass;
|
|
|
|
delete config.xclass;
|
|
|
|
Ext.require(className, function() {
|
|
Ext.factoryConfig(config, function(cfg) {
|
|
callback(Ext.create(className, cfg));
|
|
});
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
var isArray = Ext.isArray(config),
|
|
keys = [],
|
|
key, value, i, ln;
|
|
|
|
if (isSimpleObject || isArray) {
|
|
if (isSimpleObject) {
|
|
for (key in config) {
|
|
if (config.hasOwnProperty(key)) {
|
|
value = config[key];
|
|
if (Ext.isSimpleObject(value) || Ext.isArray(value)) {
|
|
keys.push(key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (i = 0,ln = config.length; i < ln; i++) {
|
|
value = config[i];
|
|
|
|
if (Ext.isSimpleObject(value) || Ext.isArray(value)) {
|
|
keys.push(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
i = 0;
|
|
ln = keys.length;
|
|
|
|
if (ln === 0) {
|
|
callback(config);
|
|
return;
|
|
}
|
|
|
|
function fn(value) {
|
|
config[key] = value;
|
|
i++;
|
|
factory();
|
|
}
|
|
|
|
function factory() {
|
|
if (i >= ln) {
|
|
callback(config);
|
|
return;
|
|
}
|
|
|
|
key = keys[i];
|
|
value = config[key];
|
|
|
|
Ext.factoryConfig(value, fn);
|
|
}
|
|
|
|
factory();
|
|
return;
|
|
}
|
|
|
|
callback(config);
|
|
},
|
|
|
|
/**
|
|
* A global factory method to instantiate a class from a config object. For example, these two calls are equivalent:
|
|
*
|
|
* Ext.factory({ text: 'My Button' }, 'Ext.Button');
|
|
* Ext.create('Ext.Button', { text: 'My Button' });
|
|
*
|
|
* If an existing instance is also specified, it will be updated with the supplied config object. This is useful
|
|
* if you need to either create or update an object, depending on if an instance already exists. For example:
|
|
*
|
|
* var button;
|
|
* button = Ext.factory({ text: 'New Button' }, 'Ext.Button', button); // Button created
|
|
* button = Ext.factory({ text: 'Updated Button' }, 'Ext.Button', button); // Button updated
|
|
*
|
|
* @param {Object} config The config object to instantiate or update an instance with.
|
|
* @param {String} classReference The class to instantiate from.
|
|
* @param {Object} [instance] The instance to update.
|
|
* @param [aliasNamespace]
|
|
* @member Ext
|
|
*/
|
|
factory: function(config, classReference, instance, aliasNamespace) {
|
|
var manager = Ext.ClassManager,
|
|
newInstance;
|
|
|
|
// If config is falsy or a valid instance, destroy the current instance
|
|
// (if it exists) and replace with the new one
|
|
if (!config || config.isInstance) {
|
|
if (instance && instance !== config) {
|
|
instance.destroy();
|
|
}
|
|
|
|
return config;
|
|
}
|
|
|
|
if (aliasNamespace) {
|
|
// If config is a string value, treat it as an alias
|
|
if (typeof config == 'string') {
|
|
return manager.instantiateByAlias(aliasNamespace + '.' + config);
|
|
}
|
|
// Same if 'type' is given in config
|
|
else if (Ext.isObject(config) && 'type' in config) {
|
|
return manager.instantiateByAlias(aliasNamespace + '.' + config.type, config);
|
|
}
|
|
}
|
|
|
|
if (config === true) {
|
|
return instance || manager.instantiate(classReference);
|
|
}
|
|
|
|
//<debug error>
|
|
if (!Ext.isObject(config)) {
|
|
Ext.Logger.error("Invalid config, must be a valid config object");
|
|
}
|
|
//</debug>
|
|
|
|
if ('xtype' in config) {
|
|
newInstance = manager.instantiateByAlias('widget.' + config.xtype, config);
|
|
}
|
|
else if ('xclass' in config) {
|
|
newInstance = manager.instantiate(config.xclass, config);
|
|
}
|
|
|
|
if (newInstance) {
|
|
if (instance) {
|
|
instance.destroy();
|
|
}
|
|
|
|
return newInstance;
|
|
}
|
|
|
|
if (instance) {
|
|
return instance.setConfig(config);
|
|
}
|
|
|
|
return manager.instantiate(classReference, config);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @member Ext
|
|
*/
|
|
deprecateClassMember: function(cls, oldName, newName, message) {
|
|
return this.deprecateProperty(cls.prototype, oldName, newName, message);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @member Ext
|
|
*/
|
|
deprecateClassMembers: function(cls, members) {
|
|
var prototype = cls.prototype,
|
|
oldName, newName;
|
|
|
|
for (oldName in members) {
|
|
if (members.hasOwnProperty(oldName)) {
|
|
newName = members[oldName];
|
|
|
|
this.deprecateProperty(prototype, oldName, newName);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @member Ext
|
|
*/
|
|
deprecateProperty: function(object, oldName, newName, message) {
|
|
if (!message) {
|
|
message = "'" + oldName + "' is deprecated";
|
|
}
|
|
if (newName) {
|
|
message += ", please use '" + newName + "' instead";
|
|
}
|
|
|
|
if (newName) {
|
|
Ext.Object.defineProperty(object, oldName, {
|
|
get: function() {
|
|
//<debug warn>
|
|
Ext.Logger.deprecate(message, 1);
|
|
//</debug>
|
|
return this[newName];
|
|
},
|
|
set: function(value) {
|
|
//<debug warn>
|
|
Ext.Logger.deprecate(message, 1);
|
|
//</debug>
|
|
|
|
this[newName] = value;
|
|
},
|
|
configurable: true
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @member Ext
|
|
*/
|
|
deprecatePropertyValue: function(object, name, value, message) {
|
|
Ext.Object.defineProperty(object, name, {
|
|
get: function() {
|
|
//<debug warn>
|
|
Ext.Logger.deprecate(message, 1);
|
|
//</debug>
|
|
return value;
|
|
},
|
|
configurable: true
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @member Ext
|
|
*/
|
|
deprecateMethod: function(object, name, method, message) {
|
|
object[name] = function() {
|
|
//<debug warn>
|
|
Ext.Logger.deprecate(message, 2);
|
|
//</debug>
|
|
if (method) {
|
|
return method.apply(this, arguments);
|
|
}
|
|
};
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @member Ext
|
|
*/
|
|
deprecateClassMethod: function(cls, name, method, message) {
|
|
if (typeof name != 'string') {
|
|
var from, to;
|
|
|
|
for (from in name) {
|
|
if (name.hasOwnProperty(from)) {
|
|
to = name[from];
|
|
Ext.deprecateClassMethod(cls, from, to);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
var isLateBinding = typeof method == 'string',
|
|
member;
|
|
|
|
if (!message) {
|
|
message = "'" + name + "()' is deprecated, please use '" + (isLateBinding ? method : method.name) +
|
|
"()' instead";
|
|
}
|
|
|
|
if (isLateBinding) {
|
|
member = function() {
|
|
//<debug warn>
|
|
Ext.Logger.deprecate(message, this);
|
|
//</debug>
|
|
|
|
return this[method].apply(this, arguments);
|
|
};
|
|
}
|
|
else {
|
|
member = function() {
|
|
//<debug warn>
|
|
Ext.Logger.deprecate(message, this);
|
|
//</debug>
|
|
|
|
return method.apply(this, arguments);
|
|
};
|
|
}
|
|
|
|
if (name in cls.prototype) {
|
|
Ext.Object.defineProperty(cls.prototype, name, {
|
|
value: null,
|
|
writable: true,
|
|
configurable: true
|
|
});
|
|
}
|
|
|
|
cls.addMember(name, member);
|
|
},
|
|
|
|
//<debug>
|
|
/**
|
|
* Useful snippet to show an exact, narrowed-down list of top-level Components that are not yet destroyed.
|
|
* @private
|
|
*/
|
|
showLeaks: function() {
|
|
var map = Ext.ComponentManager.all.map,
|
|
leaks = [],
|
|
parent;
|
|
|
|
Ext.Object.each(map, function(id, component) {
|
|
while ((parent = component.getParent()) && map.hasOwnProperty(parent.getId())) {
|
|
component = parent;
|
|
}
|
|
|
|
if (leaks.indexOf(component) === -1) {
|
|
leaks.push(component);
|
|
}
|
|
});
|
|
|
|
console.log(leaks);
|
|
},
|
|
//</debug>
|
|
|
|
/**
|
|
* True when the document is fully initialized and ready for action
|
|
* @type Boolean
|
|
* @member Ext
|
|
* @private
|
|
*/
|
|
isReady : false,
|
|
|
|
/**
|
|
* @private
|
|
* @member Ext
|
|
*/
|
|
readyListeners: [],
|
|
|
|
/**
|
|
* @private
|
|
* @member Ext
|
|
*/
|
|
triggerReady: function() {
|
|
var listeners = Ext.readyListeners,
|
|
i, ln, listener;
|
|
|
|
if (!Ext.isReady) {
|
|
Ext.isReady = true;
|
|
|
|
for (i = 0,ln = listeners.length; i < ln; i++) {
|
|
listener = listeners[i];
|
|
listener.fn.call(listener.scope);
|
|
}
|
|
delete Ext.readyListeners;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @member Ext
|
|
*/
|
|
onDocumentReady: function(fn, scope) {
|
|
if (Ext.isReady) {
|
|
fn.call(scope);
|
|
}
|
|
else {
|
|
var triggerFn = Ext.triggerReady;
|
|
|
|
Ext.readyListeners.push({
|
|
fn: fn,
|
|
scope: scope
|
|
});
|
|
|
|
if (Ext.browser.is.PhoneGap && !Ext.os.is.Desktop) {
|
|
if (!Ext.readyListenerAttached) {
|
|
Ext.readyListenerAttached = true;
|
|
document.addEventListener('deviceready', triggerFn, false);
|
|
}
|
|
}
|
|
else {
|
|
if (document.readyState.match(/interactive|complete|loaded/) !== null) {
|
|
triggerFn();
|
|
}
|
|
else if (!Ext.readyListenerAttached) {
|
|
Ext.readyListenerAttached = true;
|
|
window.addEventListener('DOMContentLoaded', triggerFn, false);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Calls function after specified delay, or right away when delay == 0.
|
|
* @param {Function} callback The callback to execute.
|
|
* @param {Object} scope (optional) The scope to execute in.
|
|
* @param {Array} args (optional) The arguments to pass to the function.
|
|
* @param {Number} delay (optional) Pass a number to delay the call by a number of milliseconds.
|
|
* @member Ext
|
|
*/
|
|
callback: function(callback, scope, args, delay) {
|
|
if (Ext.isFunction(callback)) {
|
|
args = args || [];
|
|
scope = scope || window;
|
|
if (delay) {
|
|
Ext.defer(callback, delay, scope, args);
|
|
} else {
|
|
callback.apply(scope, args);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
//<debug>
|
|
Ext.Object.defineProperty(Ext, 'Msg', {
|
|
get: function() {
|
|
Ext.Logger.error("Using Ext.Msg without requiring Ext.MessageBox");
|
|
return null;
|
|
},
|
|
set: function(value) {
|
|
Ext.Object.defineProperty(Ext, 'Msg', {
|
|
value: value
|
|
});
|
|
return value;
|
|
},
|
|
configurable: true
|
|
});
|
|
//</debug>
|
|
|
|
|
|
//@tag dom,core
|
|
//@require Ext-more
|
|
|
|
/**
|
|
* Provides information about browser.
|
|
*
|
|
* Should not be manually instantiated unless for unit-testing.
|
|
* Access the global instance stored in {@link Ext.browser} instead.
|
|
* @private
|
|
*/
|
|
Ext.define('Ext.env.Browser', {
|
|
requires: ['Ext.Version'],
|
|
|
|
statics: {
|
|
browserNames: {
|
|
ie: 'IE',
|
|
firefox: 'Firefox',
|
|
safari: 'Safari',
|
|
chrome: 'Chrome',
|
|
opera: 'Opera',
|
|
dolfin: 'Dolfin',
|
|
webosbrowser: 'webOSBrowser',
|
|
chromeMobile: 'ChromeMobile',
|
|
silk: 'Silk',
|
|
other: 'Other'
|
|
},
|
|
engineNames: {
|
|
webkit: 'WebKit',
|
|
gecko: 'Gecko',
|
|
presto: 'Presto',
|
|
trident: 'Trident',
|
|
other: 'Other'
|
|
},
|
|
enginePrefixes: {
|
|
webkit: 'AppleWebKit/',
|
|
gecko: 'Gecko/',
|
|
presto: 'Presto/',
|
|
trident: 'Trident/'
|
|
},
|
|
browserPrefixes: {
|
|
ie: 'MSIE ',
|
|
firefox: 'Firefox/',
|
|
chrome: 'Chrome/',
|
|
safari: 'Version/',
|
|
opera: 'Opera/',
|
|
dolfin: 'Dolfin/',
|
|
webosbrowser: 'wOSBrowser/',
|
|
chromeMobile: 'CrMo/',
|
|
silk: 'Silk/'
|
|
}
|
|
},
|
|
|
|
styleDashPrefixes: {
|
|
WebKit: '-webkit-',
|
|
Gecko: '-moz-',
|
|
Trident: '-ms-',
|
|
Presto: '-o-',
|
|
Other: ''
|
|
},
|
|
|
|
stylePrefixes: {
|
|
WebKit: 'Webkit',
|
|
Gecko: 'Moz',
|
|
Trident: 'ms',
|
|
Presto: 'O',
|
|
Other: ''
|
|
},
|
|
|
|
propertyPrefixes: {
|
|
WebKit: 'webkit',
|
|
Gecko: 'moz',
|
|
Trident: 'ms',
|
|
Presto: 'o',
|
|
Other: ''
|
|
},
|
|
|
|
// scope: Ext.env.Browser.prototype
|
|
|
|
/**
|
|
* A "hybrid" property, can be either accessed as a method call, for example:
|
|
*
|
|
* if (Ext.browser.is('IE')) {
|
|
* // ...
|
|
* }
|
|
*
|
|
* Or as an object with Boolean properties, for example:
|
|
*
|
|
* if (Ext.browser.is.IE) {
|
|
* // ...
|
|
* }
|
|
*
|
|
* Versions can be conveniently checked as well. For example:
|
|
*
|
|
* if (Ext.browser.is.IE6) {
|
|
* // Equivalent to (Ext.browser.is.IE && Ext.browser.version.equals(6))
|
|
* }
|
|
*
|
|
* __Note:__ Only {@link Ext.Version#getMajor major component} and {@link Ext.Version#getShortVersion simplified}
|
|
* value of the version are available via direct property checking.
|
|
*
|
|
* Supported values are:
|
|
*
|
|
* - IE
|
|
* - Firefox
|
|
* - Safari
|
|
* - Chrome
|
|
* - Opera
|
|
* - WebKit
|
|
* - Gecko
|
|
* - Presto
|
|
* - Trident
|
|
* - WebView
|
|
* - Other
|
|
*
|
|
* @param {String} value The OS name to check.
|
|
* @return {Boolean}
|
|
*/
|
|
is: Ext.emptyFn,
|
|
|
|
/**
|
|
* The full name of the current browser.
|
|
* Possible values are:
|
|
*
|
|
* - IE
|
|
* - Firefox
|
|
* - Safari
|
|
* - Chrome
|
|
* - Opera
|
|
* - Other
|
|
* @type String
|
|
* @readonly
|
|
*/
|
|
name: null,
|
|
|
|
/**
|
|
* Refer to {@link Ext.Version}.
|
|
* @type Ext.Version
|
|
* @readonly
|
|
*/
|
|
version: null,
|
|
|
|
/**
|
|
* The full name of the current browser's engine.
|
|
* Possible values are:
|
|
*
|
|
* - WebKit
|
|
* - Gecko
|
|
* - Presto
|
|
* - Trident
|
|
* - Other
|
|
* @type String
|
|
* @readonly
|
|
*/
|
|
engineName: null,
|
|
|
|
/**
|
|
* Refer to {@link Ext.Version}.
|
|
* @type Ext.Version
|
|
* @readonly
|
|
*/
|
|
engineVersion: null,
|
|
|
|
setFlag: function(name, value) {
|
|
if (typeof value == 'undefined') {
|
|
value = true;
|
|
}
|
|
|
|
this.is[name] = value;
|
|
this.is[name.toLowerCase()] = value;
|
|
|
|
return this;
|
|
},
|
|
|
|
constructor: function(userAgent) {
|
|
/**
|
|
* @property {String}
|
|
* Browser User Agent string.
|
|
*/
|
|
this.userAgent = userAgent;
|
|
|
|
is = this.is = function(name) {
|
|
return is[name] === true;
|
|
};
|
|
|
|
var statics = this.statics(),
|
|
browserMatch = userAgent.match(new RegExp('((?:' + Ext.Object.getValues(statics.browserPrefixes).join(')|(?:') + '))([\\w\\._]+)')),
|
|
engineMatch = userAgent.match(new RegExp('((?:' + Ext.Object.getValues(statics.enginePrefixes).join(')|(?:') + '))([\\w\\._]+)')),
|
|
browserNames = statics.browserNames,
|
|
browserName = browserNames.other,
|
|
engineNames = statics.engineNames,
|
|
engineName = engineNames.other,
|
|
browserVersion = '',
|
|
engineVersion = '',
|
|
isWebView = false,
|
|
is, i, name;
|
|
|
|
if (browserMatch) {
|
|
browserName = browserNames[Ext.Object.getKey(statics.browserPrefixes, browserMatch[1])];
|
|
|
|
browserVersion = new Ext.Version(browserMatch[2]);
|
|
}
|
|
|
|
if (engineMatch) {
|
|
engineName = engineNames[Ext.Object.getKey(statics.enginePrefixes, engineMatch[1])];
|
|
engineVersion = new Ext.Version(engineMatch[2]);
|
|
}
|
|
|
|
// Facebook changes the userAgent when you view a website within their iOS app. For some reason, the strip out information
|
|
// about the browser, so we have to detect that and fake it...
|
|
if (userAgent.match(/FB/) && browserName == "Other") {
|
|
browserName = browserNames.safari;
|
|
engineName = engineNames.webkit;
|
|
}
|
|
|
|
if (userAgent.match(/Android.*Chrome/g)) {
|
|
browserName = 'ChromeMobile';
|
|
}
|
|
|
|
Ext.apply(this, {
|
|
engineName: engineName,
|
|
engineVersion: engineVersion,
|
|
name: browserName,
|
|
version: browserVersion
|
|
});
|
|
|
|
this.setFlag(browserName);
|
|
|
|
if (browserVersion) {
|
|
this.setFlag(browserName + (browserVersion.getMajor() || ''));
|
|
this.setFlag(browserName + browserVersion.getShortVersion());
|
|
}
|
|
|
|
for (i in browserNames) {
|
|
if (browserNames.hasOwnProperty(i)) {
|
|
name = browserNames[i];
|
|
|
|
this.setFlag(name, browserName === name);
|
|
}
|
|
}
|
|
|
|
this.setFlag(name);
|
|
|
|
if (engineVersion) {
|
|
this.setFlag(engineName + (engineVersion.getMajor() || ''));
|
|
this.setFlag(engineName + engineVersion.getShortVersion());
|
|
}
|
|
|
|
for (i in engineNames) {
|
|
if (engineNames.hasOwnProperty(i)) {
|
|
name = engineNames[i];
|
|
|
|
this.setFlag(name, engineName === name);
|
|
}
|
|
}
|
|
|
|
this.setFlag('Standalone', !!navigator.standalone);
|
|
|
|
if (typeof window.PhoneGap != 'undefined' || typeof window.Cordova != 'undefined' || typeof window.cordova != 'undefined') {
|
|
isWebView = true;
|
|
this.setFlag('PhoneGap');
|
|
}
|
|
else if (!!window.isNK) {
|
|
isWebView = true;
|
|
this.setFlag('Sencha');
|
|
}
|
|
|
|
// Check if running in UIWebView
|
|
if (/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)(?!.*FBAN)/i.test(userAgent)) {
|
|
isWebView = true;
|
|
}
|
|
|
|
// Flag to check if it we are in the WebView
|
|
this.setFlag('WebView', isWebView);
|
|
|
|
/**
|
|
* @property {Boolean}
|
|
* `true` if browser is using strict mode.
|
|
*/
|
|
this.isStrict = document.compatMode == "CSS1Compat";
|
|
|
|
/**
|
|
* @property {Boolean}
|
|
* `true` if page is running over SSL.
|
|
*/
|
|
this.isSecure = /^https/i.test(window.location.protocol);
|
|
|
|
return this;
|
|
},
|
|
|
|
getStyleDashPrefix: function() {
|
|
return this.styleDashPrefixes[this.engineName];
|
|
},
|
|
|
|
getStylePrefix: function() {
|
|
return this.stylePrefixes[this.engineName];
|
|
},
|
|
|
|
getVendorProperyName: function(name) {
|
|
var prefix = this.propertyPrefixes[this.engineName];
|
|
|
|
if (prefix.length > 0) {
|
|
return prefix + Ext.String.capitalize(name);
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
}, function() {
|
|
/**
|
|
* @class Ext.browser
|
|
* @extends Ext.env.Browser
|
|
* @singleton
|
|
* Provides useful information about the current browser.
|
|
*
|
|
* Example:
|
|
*
|
|
* if (Ext.browser.is.IE) {
|
|
* // IE specific code here
|
|
* }
|
|
*
|
|
* if (Ext.browser.is.WebKit) {
|
|
* // WebKit specific code here
|
|
* }
|
|
*
|
|
* console.log("Version " + Ext.browser.version);
|
|
*
|
|
* For a full list of supported values, refer to {@link #is} property/method.
|
|
*
|
|
* @aside guide environment_package
|
|
*/
|
|
var browserEnv = Ext.browser = new this(Ext.global.navigator.userAgent);
|
|
|
|
});
|
|
|
|
//@tag dom,core
|
|
//@require Ext.env.Browser
|
|
|
|
/**
|
|
* Provides information about operating system environment.
|
|
*
|
|
* Should not be manually instantiated unless for unit-testing.
|
|
* Access the global instance stored in {@link Ext.os} instead.
|
|
* @private
|
|
*/
|
|
Ext.define('Ext.env.OS', {
|
|
|
|
requires: ['Ext.Version'],
|
|
|
|
statics: {
|
|
names: {
|
|
ios: 'iOS',
|
|
android: 'Android',
|
|
webos: 'webOS',
|
|
blackberry: 'BlackBerry',
|
|
rimTablet: 'RIMTablet',
|
|
mac: 'MacOS',
|
|
win: 'Windows',
|
|
linux: 'Linux',
|
|
bada: 'Bada',
|
|
other: 'Other'
|
|
},
|
|
prefixes: {
|
|
ios: 'i(?:Pad|Phone|Pod)(?:.*)CPU(?: iPhone)? OS ',
|
|
android: '(Android |HTC_|Silk/)', // Some HTC devices ship with an OSX userAgent by default,
|
|
// so we need to add a direct check for HTC_
|
|
blackberry: 'BlackBerry(?:.*)Version\/',
|
|
rimTablet: 'RIM Tablet OS ',
|
|
webos: '(?:webOS|hpwOS)\/',
|
|
bada: 'Bada\/'
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A "hybrid" property, can be either accessed as a method call, i.e:
|
|
*
|
|
* if (Ext.os.is('Android')) {
|
|
* // ...
|
|
* }
|
|
*
|
|
* or as an object with boolean properties, i.e:
|
|
*
|
|
* if (Ext.os.is.Android) {
|
|
* // ...
|
|
* }
|
|
*
|
|
* Versions can be conveniently checked as well. For example:
|
|
*
|
|
* if (Ext.os.is.Android2) {
|
|
* // Equivalent to (Ext.os.is.Android && Ext.os.version.equals(2))
|
|
* }
|
|
*
|
|
* if (Ext.os.is.iOS32) {
|
|
* // Equivalent to (Ext.os.is.iOS && Ext.os.version.equals(3.2))
|
|
* }
|
|
*
|
|
* Note that only {@link Ext.Version#getMajor major component} and {@link Ext.Version#getShortVersion simplified}
|
|
* value of the version are available via direct property checking. Supported values are:
|
|
*
|
|
* - iOS
|
|
* - iPad
|
|
* - iPhone
|
|
* - iPhone5 (also true for 4in iPods).
|
|
* - iPod
|
|
* - Android
|
|
* - WebOS
|
|
* - BlackBerry
|
|
* - Bada
|
|
* - MacOS
|
|
* - Windows
|
|
* - Linux
|
|
* - Other
|
|
* @param {String} value The OS name to check.
|
|
* @return {Boolean}
|
|
*/
|
|
is: Ext.emptyFn,
|
|
|
|
/**
|
|
* @property {String} [name=null]
|
|
* @readonly
|
|
* The full name of the current operating system. Possible values are:
|
|
*
|
|
* - iOS
|
|
* - Android
|
|
* - WebOS
|
|
* - BlackBerry,
|
|
* - MacOS
|
|
* - Windows
|
|
* - Linux
|
|
* - Other
|
|
*/
|
|
name: null,
|
|
|
|
/**
|
|
* @property {Ext.Version} [version=null]
|
|
* Refer to {@link Ext.Version}
|
|
* @readonly
|
|
*/
|
|
version: null,
|
|
|
|
setFlag: function(name, value) {
|
|
if (typeof value == 'undefined') {
|
|
value = true;
|
|
}
|
|
|
|
this.is[name] = value;
|
|
this.is[name.toLowerCase()] = value;
|
|
|
|
return this;
|
|
},
|
|
|
|
constructor: function(userAgent, platform) {
|
|
var statics = this.statics(),
|
|
names = statics.names,
|
|
prefixes = statics.prefixes,
|
|
name,
|
|
version = '',
|
|
i, prefix, match, item, is;
|
|
|
|
is = this.is = function(name) {
|
|
return this.is[name] === true;
|
|
};
|
|
|
|
for (i in prefixes) {
|
|
if (prefixes.hasOwnProperty(i)) {
|
|
prefix = prefixes[i];
|
|
|
|
match = userAgent.match(new RegExp('(?:'+prefix+')([^\\s;]+)'));
|
|
|
|
if (match) {
|
|
name = names[i];
|
|
|
|
// This is here because some HTC android devices show an OSX Snow Leopard userAgent by default.
|
|
// And the Kindle Fire doesn't have any indicator of Android as the OS in its User Agent
|
|
if (match[1] && (match[1] == "HTC_" || match[1] == "Silk/")) {
|
|
version = new Ext.Version("2.3");
|
|
} else {
|
|
version = new Ext.Version(match[match.length - 1]);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!name) {
|
|
name = names[(userAgent.toLowerCase().match(/mac|win|linux/) || ['other'])[0]];
|
|
version = new Ext.Version('');
|
|
}
|
|
|
|
this.name = name;
|
|
this.version = version;
|
|
|
|
if (platform) {
|
|
this.setFlag(platform.replace(/ simulator$/i, ''));
|
|
}
|
|
|
|
this.setFlag(name);
|
|
|
|
if (version) {
|
|
this.setFlag(name + (version.getMajor() || ''));
|
|
this.setFlag(name + version.getShortVersion());
|
|
}
|
|
|
|
for (i in names) {
|
|
if (names.hasOwnProperty(i)) {
|
|
item = names[i];
|
|
|
|
if (!is.hasOwnProperty(name)) {
|
|
this.setFlag(item, (name === item));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Detect if the device is the iPhone 5.
|
|
if (this.name == "iOS" && window.screen.height == 568) {
|
|
this.setFlag('iPhone5');
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
}, function() {
|
|
|
|
var navigation = Ext.global.navigator,
|
|
userAgent = navigation.userAgent,
|
|
osEnv, osName, deviceType;
|
|
|
|
|
|
/**
|
|
* @class Ext.os
|
|
* @extends Ext.env.OS
|
|
* @singleton
|
|
* Provides useful information about the current operating system environment.
|
|
*
|
|
* Example:
|
|
*
|
|
* if (Ext.os.is.Windows) {
|
|
* // Windows specific code here
|
|
* }
|
|
*
|
|
* if (Ext.os.is.iOS) {
|
|
* // iPad, iPod, iPhone, etc.
|
|
* }
|
|
*
|
|
* console.log("Version " + Ext.os.version);
|
|
*
|
|
* For a full list of supported values, refer to the {@link #is} property/method.
|
|
*
|
|
* @aside guide environment_package
|
|
*/
|
|
Ext.os = osEnv = new this(userAgent, navigation.platform);
|
|
|
|
osName = osEnv.name;
|
|
|
|
var search = window.location.search.match(/deviceType=(Tablet|Phone)/),
|
|
nativeDeviceType = window.deviceType;
|
|
|
|
// Override deviceType by adding a get variable of deviceType. NEEDED FOR DOCS APP.
|
|
// E.g: example/kitchen-sink.html?deviceType=Phone
|
|
if (search && search[1]) {
|
|
deviceType = search[1];
|
|
}
|
|
else if (nativeDeviceType === 'iPhone') {
|
|
deviceType = 'Phone';
|
|
}
|
|
else if (nativeDeviceType === 'iPad') {
|
|
deviceType = 'Tablet';
|
|
}
|
|
else {
|
|
if (!osEnv.is.Android && !osEnv.is.iOS && /Windows|Linux|MacOS/.test(osName)) {
|
|
deviceType = 'Desktop';
|
|
|
|
// always set it to false when you are on a desktop
|
|
Ext.browser.is.WebView = false;
|
|
}
|
|
else if (osEnv.is.iPad || osEnv.is.Android3 || (osEnv.is.Android4 && userAgent.search(/mobile/i) == -1)) {
|
|
deviceType = 'Tablet';
|
|
}
|
|
else {
|
|
deviceType = 'Phone';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @property {String} deviceType
|
|
* The generic type of the current device.
|
|
*
|
|
* Possible values:
|
|
*
|
|
* - Phone
|
|
* - Tablet
|
|
* - Desktop
|
|
*
|
|
* For testing purposes the deviceType can be overridden by adding
|
|
* a deviceType parameter to the URL of the page, like so:
|
|
*
|
|
* http://localhost/mypage.html?deviceType=Tablet
|
|
*
|
|
*/
|
|
osEnv.setFlag(deviceType, true);
|
|
osEnv.deviceType = deviceType;
|
|
|
|
|
|
/**
|
|
* @class Ext.is
|
|
* Used to detect if the current browser supports a certain feature, and the type of the current browser.
|
|
* @deprecated 2.0.0
|
|
* Please refer to the {@link Ext.browser}, {@link Ext.os} and {@link Ext.feature} classes instead.
|
|
*/
|
|
});
|
|
|
|
//@tag dom,core
|
|
|
|
/**
|
|
* Provides information about browser.
|
|
*
|
|
* Should not be manually instantiated unless for unit-testing.
|
|
* Access the global instance stored in {@link Ext.browser} instead.
|
|
* @private
|
|
*/
|
|
Ext.define('Ext.env.Feature', {
|
|
|
|
requires: ['Ext.env.Browser', 'Ext.env.OS'],
|
|
|
|
constructor: function() {
|
|
this.testElements = {};
|
|
|
|
this.has = function(name) {
|
|
return !!this.has[name];
|
|
};
|
|
|
|
return this;
|
|
},
|
|
|
|
getTestElement: function(tag, createNew) {
|
|
if (tag === undefined) {
|
|
tag = 'div';
|
|
}
|
|
else if (typeof tag !== 'string') {
|
|
return tag;
|
|
}
|
|
|
|
if (createNew) {
|
|
return document.createElement(tag);
|
|
}
|
|
|
|
if (!this.testElements[tag]) {
|
|
this.testElements[tag] = document.createElement(tag);
|
|
}
|
|
|
|
return this.testElements[tag];
|
|
},
|
|
|
|
isStyleSupported: function(name, tag) {
|
|
var elementStyle = this.getTestElement(tag).style,
|
|
cName = Ext.String.capitalize(name);
|
|
|
|
if (typeof elementStyle[name] !== 'undefined'
|
|
|| typeof elementStyle[Ext.browser.getStylePrefix(name) + cName] !== 'undefined') {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
isEventSupported: function(name, tag) {
|
|
if (tag === undefined) {
|
|
tag = window;
|
|
}
|
|
|
|
var element = this.getTestElement(tag),
|
|
eventName = 'on' + name.toLowerCase(),
|
|
isSupported = (eventName in element);
|
|
|
|
if (!isSupported) {
|
|
if (element.setAttribute && element.removeAttribute) {
|
|
element.setAttribute(eventName, '');
|
|
isSupported = typeof element[eventName] === 'function';
|
|
|
|
if (typeof element[eventName] !== 'undefined') {
|
|
element[eventName] = undefined;
|
|
}
|
|
|
|
element.removeAttribute(eventName);
|
|
}
|
|
}
|
|
|
|
return isSupported;
|
|
},
|
|
|
|
getSupportedPropertyName: function(object, name) {
|
|
var vendorName = Ext.browser.getVendorProperyName(name);
|
|
|
|
if (vendorName in object) {
|
|
return vendorName;
|
|
}
|
|
else if (name in object) {
|
|
return name;
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
registerTest: Ext.Function.flexSetter(function(name, fn) {
|
|
this.has[name] = fn.call(this);
|
|
|
|
return this;
|
|
})
|
|
|
|
}, function() {
|
|
|
|
/**
|
|
* @class Ext.feature
|
|
* @extend Ext.env.Feature
|
|
* @singleton
|
|
*
|
|
* A simple class to verify if a browser feature exists or not on the current device.
|
|
*
|
|
* if (Ext.feature.has.Canvas) {
|
|
* // do some cool things with canvas here
|
|
* }
|
|
*
|
|
* See the {@link #has} property/method for details of the features that can be detected.
|
|
*
|
|
* @aside guide environment_package
|
|
*/
|
|
Ext.feature = new this;
|
|
|
|
var has = Ext.feature.has;
|
|
|
|
/**
|
|
* @method has
|
|
* @member Ext.feature
|
|
* Verifies if a browser feature exists or not on the current device.
|
|
*
|
|
* A "hybrid" property, can be either accessed as a method call, i.e:
|
|
*
|
|
* if (Ext.feature.has('Canvas')) {
|
|
* // ...
|
|
* }
|
|
*
|
|
* or as an object with boolean properties, i.e:
|
|
*
|
|
* if (Ext.feature.has.Canvas) {
|
|
* // ...
|
|
* }
|
|
*
|
|
* Possible properties/parameter values:
|
|
*
|
|
* - Canvas
|
|
* - Svg
|
|
* - Vml
|
|
* - Touch - supports touch events (`touchstart`).
|
|
* - Orientation - supports different orientations.
|
|
* - OrientationChange - supports the `orientationchange` event.
|
|
* - DeviceMotion - supports the `devicemotion` event.
|
|
* - Geolocation
|
|
* - SqlDatabase
|
|
* - WebSockets
|
|
* - Range - supports [DOM document fragments.][1]
|
|
* - CreateContextualFragment - supports HTML fragment parsing using [range.createContextualFragment()][2].
|
|
* - History - supports history management with [history.pushState()][3].
|
|
* - CssTransforms
|
|
* - Css3dTransforms
|
|
* - CssAnimations
|
|
* - CssTransitions
|
|
* - Audio - supports the `<audio>` tag.
|
|
* - Video - supports the `<video>` tag.
|
|
* - ClassList - supports the HTML5 classList API.
|
|
* - LocalStorage - LocalStorage is supported and can be written to.
|
|
*
|
|
* [1]: https://developer.mozilla.org/en/DOM/range
|
|
* [2]: https://developer.mozilla.org/en/DOM/range.createContextualFragment
|
|
* [3]: https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history#The_pushState().C2.A0method
|
|
*
|
|
* @param {String} value The feature name to check.
|
|
* @return {Boolean}
|
|
*/
|
|
Ext.feature.registerTest({
|
|
Canvas: function() {
|
|
var element = this.getTestElement('canvas');
|
|
return !!(element && element.getContext && element.getContext('2d'));
|
|
},
|
|
|
|
Svg: function() {
|
|
var doc = document;
|
|
|
|
return !!(doc.createElementNS && !!doc.createElementNS("http:/" + "/www.w3.org/2000/svg", "svg").createSVGRect);
|
|
},
|
|
|
|
Vml: function() {
|
|
var element = this.getTestElement(),
|
|
ret = false;
|
|
|
|
element.innerHTML = "<!--[if vml]><br><![endif]-->";
|
|
ret = (element.childNodes.length === 1);
|
|
element.innerHTML = "";
|
|
|
|
return ret;
|
|
},
|
|
|
|
Touch: function() {
|
|
return this.isEventSupported('touchstart') && !(Ext.os && Ext.os.name.match(/Windows|MacOS|Linux/) && !Ext.os.is.BlackBerry6);
|
|
},
|
|
|
|
Orientation: function() {
|
|
return ('orientation' in window) && this.isEventSupported('orientationchange');
|
|
},
|
|
|
|
OrientationChange: function() {
|
|
return this.isEventSupported('orientationchange');
|
|
},
|
|
|
|
DeviceMotion: function() {
|
|
return this.isEventSupported('devicemotion');
|
|
},
|
|
|
|
Geolocation: function() {
|
|
return 'geolocation' in window.navigator;
|
|
},
|
|
|
|
SqlDatabase: function() {
|
|
return 'openDatabase' in window;
|
|
},
|
|
|
|
WebSockets: function() {
|
|
return 'WebSocket' in window;
|
|
},
|
|
|
|
Range: function() {
|
|
return !!document.createRange;
|
|
},
|
|
|
|
CreateContextualFragment: function() {
|
|
var range = !!document.createRange ? document.createRange() : false;
|
|
return range && !!range.createContextualFragment;
|
|
},
|
|
|
|
History: function() {
|
|
return ('history' in window && 'pushState' in window.history);
|
|
},
|
|
|
|
CssTransforms: function() {
|
|
return this.isStyleSupported('transform');
|
|
},
|
|
|
|
Css3dTransforms: function() {
|
|
// See https://sencha.jira.com/browse/TOUCH-1544
|
|
return this.has('CssTransforms') && this.isStyleSupported('perspective') && !Ext.os.is.Android2;
|
|
},
|
|
|
|
CssAnimations: function() {
|
|
return this.isStyleSupported('animationName');
|
|
},
|
|
|
|
CssTransitions: function() {
|
|
return this.isStyleSupported('transitionProperty');
|
|
},
|
|
|
|
Audio: function() {
|
|
return !!this.getTestElement('audio').canPlayType;
|
|
},
|
|
|
|
Video: function() {
|
|
return !!this.getTestElement('video').canPlayType;
|
|
},
|
|
|
|
ClassList: function() {
|
|
return "classList" in this.getTestElement();
|
|
},
|
|
|
|
LocalStorage : function() {
|
|
var supported = false;
|
|
|
|
try {
|
|
if ('localStorage' in window && window['localStorage'] !== null) {
|
|
//this should throw an error in private browsing mode in iOS
|
|
localStorage.setItem('sencha-localstorage-test', 'test success');
|
|
//clean up if setItem worked
|
|
localStorage.removeItem('sencha-localstorage-test');
|
|
supported = true;
|
|
}
|
|
} catch ( e ) {}
|
|
|
|
return supported;
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
//@tag dom,core
|
|
//@define Ext.DomQuery
|
|
//@define Ext.core.DomQuery
|
|
//@require Ext.env.Feature
|
|
|
|
/**
|
|
* @class Ext.DomQuery
|
|
* @alternateClassName Ext.dom.Query
|
|
*
|
|
* Provides functionality to select elements on the page based on a CSS selector. Delegates to
|
|
* document.querySelectorAll. More information can be found at
|
|
* [http://www.w3.org/TR/css3-selectors/](http://www.w3.org/TR/css3-selectors/)
|
|
*
|
|
* All selectors, attribute filters and pseudos below can be combined infinitely in any order. For example
|
|
* `div.foo:nth-child(odd)[@foo=bar].bar:first` would be a perfectly valid selector.
|
|
*
|
|
* ## Element Selectors:
|
|
*
|
|
* * \* any element
|
|
* * E an element with the tag E
|
|
* * E F All descendant elements of E that have the tag F
|
|
* * E > F or E/F all direct children elements of E that have the tag F
|
|
* * E + F all elements with the tag F that are immediately preceded by an element with the tag E
|
|
* * E ~ F all elements with the tag F that are preceded by a sibling element with the tag E
|
|
*
|
|
* ## Attribute Selectors:
|
|
*
|
|
* The use of @ and quotes are optional. For example, div[@foo='bar'] is also a valid attribute selector.
|
|
*
|
|
* * E[foo] has an attribute "foo"
|
|
* * E[foo=bar] has an attribute "foo" that equals "bar"
|
|
* * E[foo^=bar] has an attribute "foo" that starts with "bar"
|
|
* * E[foo$=bar] has an attribute "foo" that ends with "bar"
|
|
* * E[foo*=bar] has an attribute "foo" that contains the substring "bar"
|
|
* * E[foo%=2] has an attribute "foo" that is evenly divisible by 2
|
|
* * E[foo!=bar] has an attribute "foo" that does not equal "bar"
|
|
*
|
|
* ## Pseudo Classes:
|
|
*
|
|
* * E:first-child E is the first child of its parent
|
|
* * E:last-child E is the last child of its parent
|
|
* * E:nth-child(n) E is the nth child of its parent (1 based as per the spec)
|
|
* * E:nth-child(odd) E is an odd child of its parent
|
|
* * E:nth-child(even) E is an even child of its parent
|
|
* * E:only-child E is the only child of its parent
|
|
* * E:checked E is an element that is has a checked attribute that is true (e.g. a radio or checkbox)
|
|
* * E:first the first E in the resultset
|
|
* * E:last the last E in the resultset
|
|
* * E:nth(n) the nth E in the resultset (1 based)
|
|
* * E:odd shortcut for :nth-child(odd)
|
|
* * E:even shortcut for :nth-child(even)
|
|
* * E:not(S) an E element that does not match simple selector S
|
|
* * E:has(S) an E element that has a descendant that matches simple selector S
|
|
* * E:next(S) an E element whose next sibling matches simple selector S
|
|
* * E:prev(S) an E element whose previous sibling matches simple selector S
|
|
* * E:any(S1|S2|S2) an E element which matches any of the simple selectors S1, S2 or S3//\\
|
|
*
|
|
* ## CSS Value Selectors:
|
|
*
|
|
* * E{display=none} CSS value "display" that equals "none"
|
|
* * E{display^=none} CSS value "display" that starts with "none"
|
|
* * E{display$=none} CSS value "display" that ends with "none"
|
|
* * E{display*=none} CSS value "display" that contains the substring "none"
|
|
* * E{display%=2} CSS value "display" that is evenly divisible by 2
|
|
* * E{display!=none} CSS value "display" that does not equal "none"
|
|
*/
|
|
Ext.define('Ext.dom.Query', {
|
|
/**
|
|
* Selects a group of elements.
|
|
* @param {String} selector The selector/xpath query (can be a comma separated list of selectors)
|
|
* @param {HTMLElement/String} [root] The start of the query (defaults to document).
|
|
* @return {HTMLElement[]} An Array of DOM elements which match the selector. If there are
|
|
* no matches, and empty Array is returned.
|
|
*/
|
|
select: function(q, root) {
|
|
var results = [],
|
|
nodes,
|
|
i,
|
|
j,
|
|
qlen,
|
|
nlen;
|
|
|
|
root = root || document;
|
|
|
|
if (typeof root == 'string') {
|
|
root = document.getElementById(root);
|
|
}
|
|
|
|
q = q.split(",");
|
|
|
|
for (i = 0,qlen = q.length; i < qlen; i++) {
|
|
if (typeof q[i] == 'string') {
|
|
|
|
//support for node attribute selection
|
|
if (q[i][0] == '@') {
|
|
nodes = root.getAttributeNode(q[i].substring(1));
|
|
results.push(nodes);
|
|
}
|
|
else {
|
|
nodes = root.querySelectorAll(q[i]);
|
|
|
|
for (j = 0,nlen = nodes.length; j < nlen; j++) {
|
|
results.push(nodes[j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return results;
|
|
},
|
|
|
|
/**
|
|
* Selects a single element.
|
|
* @param {String} selector The selector/xpath query
|
|
* @param {HTMLElement/String} [root] The start of the query (defaults to document).
|
|
* @return {HTMLElement} The DOM element which matched the selector.
|
|
*/
|
|
selectNode: function(q, root) {
|
|
return this.select(q, root)[0];
|
|
},
|
|
|
|
/**
|
|
* Returns true if the passed element(s) match the passed simple selector (e.g. div.some-class or span:first-child)
|
|
* @param {String/HTMLElement/Array} el An element id, element or array of elements
|
|
* @param {String} selector The simple selector to test
|
|
* @return {Boolean}
|
|
*/
|
|
is: function(el, q) {
|
|
if (typeof el == "string") {
|
|
el = document.getElementById(el);
|
|
}
|
|
return this.select(q).indexOf(el) !== -1;
|
|
},
|
|
|
|
isXml: function(el) {
|
|
var docEl = (el ? el.ownerDocument || el : 0).documentElement;
|
|
return docEl ? docEl.nodeName !== "HTML" : false;
|
|
}
|
|
|
|
}, function() {
|
|
Ext.ns('Ext.core');
|
|
Ext.core.DomQuery = Ext.DomQuery = new this();
|
|
Ext.query = Ext.Function.alias(Ext.DomQuery, 'select');
|
|
});
|
|
|
|
//@tag dom,core
|
|
//@define Ext.DomHelper
|
|
//@require Ext.dom.Query
|
|
|
|
/**
|
|
* @class Ext.DomHelper
|
|
* @alternateClassName Ext.dom.Helper
|
|
*
|
|
* The DomHelper class provides a layer of abstraction from DOM and transparently supports creating elements via DOM or
|
|
* using HTML fragments. It also has the ability to create HTML fragment templates from your DOM building code.
|
|
*
|
|
* ## DomHelper element specification object
|
|
*
|
|
* A specification object is used when creating elements. Attributes of this object are assumed to be element
|
|
* attributes, except for 4 special attributes:
|
|
*
|
|
* * **tag**: The tag name of the element
|
|
* * **children (or cn)**: An array of the same kind of element definition objects to be created and appended. These
|
|
* can be nested as deep as you want.
|
|
* * **cls**: The class attribute of the element. This will end up being either the "class" attribute on a HTML
|
|
* fragment or className for a DOM node, depending on whether DomHelper is using fragments or DOM.
|
|
* * **html**: The innerHTML for the element
|
|
*
|
|
* ## Insertion methods
|
|
*
|
|
* Commonly used insertion methods:
|
|
*
|
|
* * {@link #append}
|
|
* * {@link #insertBefore}
|
|
* * {@link #insertAfter}
|
|
* * {@link #overwrite}
|
|
* * {@link #insertHtml}
|
|
*
|
|
* ## Example
|
|
*
|
|
* This is an example, where an unordered list with 3 children items is appended to an existing element with id
|
|
* 'my-div':
|
|
*
|
|
* var dh = Ext.DomHelper; // create shorthand alias
|
|
* // specification object
|
|
* var spec = {
|
|
* id: 'my-ul',
|
|
* tag: 'ul',
|
|
* cls: 'my-list',
|
|
* // append children after creating
|
|
* children: [ // may also specify 'cn' instead of 'children'
|
|
* {tag: 'li', id: 'item0', html: 'List Item 0'},
|
|
* {tag: 'li', id: 'item1', html: 'List Item 1'},
|
|
* {tag: 'li', id: 'item2', html: 'List Item 2'}
|
|
* ]
|
|
* };
|
|
* var list = dh.append(
|
|
* 'my-div', // the context element 'my-div' can either be the id or the actual node
|
|
* spec // the specification object
|
|
* );
|
|
*
|
|
* Element creation specification parameters in this class may also be passed as an Array of specification objects.
|
|
* This can be used to insert multiple sibling nodes into an existing container very efficiently. For example, to add
|
|
* more list items to the example above:
|
|
*
|
|
* dh.append('my-ul', [
|
|
* {tag: 'li', id: 'item3', html: 'List Item 3'},
|
|
* {tag: 'li', id: 'item4', html: 'List Item 4'}
|
|
* ]);
|
|
*
|
|
* ## Templating
|
|
*
|
|
* The real power is in the built-in templating. Instead of creating or appending any elements, createTemplate returns
|
|
* a Template object which can be used over and over to insert new elements. Revisiting the example above, we could
|
|
* utilize templating this time:
|
|
*
|
|
* // create the node
|
|
* var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
|
|
* // get template
|
|
* var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
|
|
*
|
|
* for(var i = 0; i < 5; i++){
|
|
* tpl.append(list, i); // use template to append to the actual node
|
|
* }
|
|
*
|
|
* An example using a template:
|
|
*
|
|
* var html = '"{0}" href="{1}" class="nav">{2}';
|
|
*
|
|
* var tpl = new Ext.DomHelper.createTemplate(html);
|
|
* tpl.append('blog-roll', ['link1', 'http://www.tommymaintz.com/', "Tommy's Site"]);
|
|
* tpl.append('blog-roll', ['link2', 'http://www.avins.org/', "Jamie's Site"]);
|
|
*
|
|
* The same example using named parameters:
|
|
*
|
|
* var html = '"{id}" href="{url}" class="nav">{text}';
|
|
*
|
|
* var tpl = new Ext.DomHelper.createTemplate(html);
|
|
* tpl.append('blog-roll', {
|
|
* id: 'link1',
|
|
* url: 'http://www.tommymaintz.com/',
|
|
* text: "Tommy's Site"
|
|
* });
|
|
* tpl.append('blog-roll', {
|
|
* id: 'link2',
|
|
* url: 'http://www.avins.org/',
|
|
* text: "Jamie's Site"
|
|
* });
|
|
*
|
|
* ## Compiling Templates
|
|
*
|
|
* Templates are applied using regular expressions. The performance is great, but if you are adding a bunch of DOM
|
|
* elements using the same template, you can increase performance even further by "compiling" the template. The way
|
|
* "compile()" works is the template is parsed and broken up at the different variable points and a dynamic function is
|
|
* created and eval'ed. The generated function performs string concatenation of these parts and the passed variables
|
|
* instead of using regular expressions.
|
|
*
|
|
* var html = '"{id}" href="{url}" class="nav">{text}';
|
|
*
|
|
* var tpl = new Ext.DomHelper.createTemplate(html);
|
|
* tpl.compile();
|
|
*
|
|
* // ... use template like normal
|
|
*
|
|
* ## Performance Boost
|
|
*
|
|
* DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead of DOM can
|
|
* significantly boost performance.
|
|
*
|
|
* Element creation specification parameters may also be strings. If useDom is false, then the string is used as
|
|
* innerHTML. If useDom is true, a string specification results in the creation of a text node. Usage:
|
|
*
|
|
* Ext.DomHelper.useDom = true; // force it to use DOM; reduces performance
|
|
*
|
|
*/
|
|
Ext.define('Ext.dom.Helper', {
|
|
emptyTags : /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
|
|
confRe : /tag|children|cn|html|tpl|tplData$/i,
|
|
endRe : /end/i,
|
|
|
|
attribXlat: { cls : 'class', htmlFor : 'for' },
|
|
|
|
closeTags: {},
|
|
|
|
decamelizeName : function () {
|
|
var camelCaseRe = /([a-z])([A-Z])/g,
|
|
cache = {};
|
|
|
|
function decamel (match, p1, p2) {
|
|
return p1 + '-' + p2.toLowerCase();
|
|
}
|
|
|
|
return function (s) {
|
|
return cache[s] || (cache[s] = s.replace(camelCaseRe, decamel));
|
|
};
|
|
}(),
|
|
|
|
generateMarkup: function(spec, buffer) {
|
|
var me = this,
|
|
attr, val, tag, i, closeTags;
|
|
|
|
if (typeof spec == "string") {
|
|
buffer.push(spec);
|
|
} else if (Ext.isArray(spec)) {
|
|
for (i = 0; i < spec.length; i++) {
|
|
if (spec[i]) {
|
|
me.generateMarkup(spec[i], buffer);
|
|
}
|
|
}
|
|
} else {
|
|
tag = spec.tag || 'div';
|
|
buffer.push('<', tag);
|
|
|
|
for (attr in spec) {
|
|
if (spec.hasOwnProperty(attr)) {
|
|
val = spec[attr];
|
|
if (!me.confRe.test(attr)) {
|
|
if (typeof val == "object") {
|
|
buffer.push(' ', attr, '="');
|
|
me.generateStyles(val, buffer).push('"');
|
|
} else {
|
|
buffer.push(' ', me.attribXlat[attr] || attr, '="', val, '"');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now either just close the tag or try to add children and close the tag.
|
|
if (me.emptyTags.test(tag)) {
|
|
buffer.push('/>');
|
|
} else {
|
|
buffer.push('>');
|
|
|
|
// Apply the tpl html, and cn specifications
|
|
if ((val = spec.tpl)) {
|
|
val.applyOut(spec.tplData, buffer);
|
|
}
|
|
if ((val = spec.html)) {
|
|
buffer.push(val);
|
|
}
|
|
if ((val = spec.cn || spec.children)) {
|
|
me.generateMarkup(val, buffer);
|
|
}
|
|
|
|
// we generate a lot of close tags, so cache them rather than push 3 parts
|
|
closeTags = me.closeTags;
|
|
buffer.push(closeTags[tag] || (closeTags[tag] = '</' + tag + '>'));
|
|
}
|
|
}
|
|
|
|
return buffer;
|
|
},
|
|
|
|
/**
|
|
* Converts the styles from the given object to text. The styles are CSS style names
|
|
* with their associated value.
|
|
*
|
|
* The basic form of this method returns a string:
|
|
*
|
|
* var s = Ext.DomHelper.generateStyles({
|
|
* backgroundColor: 'red'
|
|
* });
|
|
*
|
|
* // s = 'background-color:red;'
|
|
*
|
|
* Alternatively, this method can append to an output array.
|
|
*
|
|
* var buf = [];
|
|
*
|
|
* // ...
|
|
*
|
|
* Ext.DomHelper.generateStyles({
|
|
* backgroundColor: 'red'
|
|
* }, buf);
|
|
*
|
|
* In this case, the style text is pushed on to the array and the array is returned.
|
|
*
|
|
* @param {Object} styles The object describing the styles.
|
|
* @param {String[]} [buffer] The output buffer.
|
|
* @return {String/String[]} If buffer is passed, it is returned. Otherwise the style
|
|
* string is returned.
|
|
*/
|
|
generateStyles: function (styles, buffer) {
|
|
var a = buffer || [],
|
|
name;
|
|
|
|
for (name in styles) {
|
|
if (styles.hasOwnProperty(name)) {
|
|
a.push(this.decamelizeName(name), ':', styles[name], ';');
|
|
}
|
|
}
|
|
|
|
return buffer || a.join('');
|
|
},
|
|
|
|
/**
|
|
* Returns the markup for the passed Element(s) config.
|
|
* @param {Object} spec The DOM object spec (and children).
|
|
* @return {String}
|
|
*/
|
|
markup: function(spec) {
|
|
if (typeof spec == "string") {
|
|
return spec;
|
|
}
|
|
|
|
var buf = this.generateMarkup(spec, []);
|
|
return buf.join('');
|
|
},
|
|
|
|
/**
|
|
* Applies a style specification to an element.
|
|
* @param {String/HTMLElement} el The element to apply styles to
|
|
* @param {String/Object/Function} styles A style specification string e.g. 'width:100px', or object in the form {width:'100px'}, or
|
|
* a function which returns such a specification.
|
|
*/
|
|
applyStyles: function(el, styles) {
|
|
Ext.fly(el).applyStyles(styles);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* Fix for browsers which no longer support createContextualFragment
|
|
*/
|
|
createContextualFragment: function(html){
|
|
var div = document.createElement("div"),
|
|
fragment = document.createDocumentFragment(),
|
|
i = 0,
|
|
length, childNodes;
|
|
|
|
div.innerHTML = html;
|
|
childNodes = div.childNodes;
|
|
length = childNodes.length;
|
|
|
|
for (; i < length; i++) {
|
|
fragment.appendChild(childNodes[i].cloneNode(true));
|
|
}
|
|
|
|
return fragment;
|
|
},
|
|
|
|
/**
|
|
* Inserts an HTML fragment into the DOM.
|
|
* @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
|
|
*
|
|
* For example take the following HTML: `<div>Contents</div>`
|
|
*
|
|
* Using different `where` values inserts element to the following places:
|
|
*
|
|
* - beforeBegin: `<HERE><div>Contents</div>`
|
|
* - afterBegin: `<div><HERE>Contents</div>`
|
|
* - beforeEnd: `<div>Contents<HERE></div>`
|
|
* - afterEnd: `<div>Contents</div><HERE>`
|
|
*
|
|
* @param {HTMLElement/TextNode} el The context element
|
|
* @param {String} html The HTML fragment
|
|
* @return {HTMLElement} The new node
|
|
*/
|
|
insertHtml: function(where, el, html) {
|
|
var setStart, range, frag, rangeEl, isBeforeBegin, isAfterBegin;
|
|
|
|
where = where.toLowerCase();
|
|
|
|
if (Ext.isTextNode(el)) {
|
|
if (where == 'afterbegin' ) {
|
|
where = 'beforebegin';
|
|
}
|
|
else if (where == 'beforeend') {
|
|
where = 'afterend';
|
|
}
|
|
}
|
|
|
|
isBeforeBegin = where == 'beforebegin';
|
|
isAfterBegin = where == 'afterbegin';
|
|
|
|
range = Ext.feature.has.CreateContextualFragment ? el.ownerDocument.createRange() : undefined;
|
|
setStart = 'setStart' + (this.endRe.test(where) ? 'After' : 'Before');
|
|
|
|
if (isBeforeBegin || where == 'afterend') {
|
|
if (range) {
|
|
range[setStart](el);
|
|
frag = range.createContextualFragment(html);
|
|
}
|
|
else {
|
|
frag = this.createContextualFragment(html);
|
|
}
|
|
el.parentNode.insertBefore(frag, isBeforeBegin ? el : el.nextSibling);
|
|
return el[(isBeforeBegin ? 'previous' : 'next') + 'Sibling'];
|
|
}
|
|
else {
|
|
rangeEl = (isAfterBegin ? 'first' : 'last') + 'Child';
|
|
if (el.firstChild) {
|
|
if (range) {
|
|
range[setStart](el[rangeEl]);
|
|
frag = range.createContextualFragment(html);
|
|
} else {
|
|
frag = this.createContextualFragment(html);
|
|
}
|
|
|
|
if (isAfterBegin) {
|
|
el.insertBefore(frag, el.firstChild);
|
|
} else {
|
|
el.appendChild(frag);
|
|
}
|
|
} else {
|
|
el.innerHTML = html;
|
|
}
|
|
return el[rangeEl];
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Creates new DOM element(s) and inserts them before el.
|
|
* @param {String/HTMLElement/Ext.Element} el The context element
|
|
* @param {Object/String} o The DOM object spec (and children) or raw HTML blob
|
|
* @param {Boolean} [returnElement] true to return a Ext.Element
|
|
* @return {HTMLElement/Ext.Element} The new node
|
|
*/
|
|
insertBefore: function(el, o, returnElement) {
|
|
return this.doInsert(el, o, returnElement, 'beforebegin');
|
|
},
|
|
|
|
/**
|
|
* Creates new DOM element(s) and inserts them after el.
|
|
* @param {String/HTMLElement/Ext.Element} el The context element
|
|
* @param {Object} o The DOM object spec (and children)
|
|
* @param {Boolean} [returnElement] true to return a Ext.Element
|
|
* @return {HTMLElement/Ext.Element} The new node
|
|
*/
|
|
insertAfter: function(el, o, returnElement) {
|
|
return this.doInsert(el, o, returnElement, 'afterend');
|
|
},
|
|
|
|
/**
|
|
* Creates new DOM element(s) and inserts them as the first child of el.
|
|
* @param {String/HTMLElement/Ext.Element} el The context element
|
|
* @param {Object/String} o The DOM object spec (and children) or raw HTML blob
|
|
* @param {Boolean} [returnElement] true to return a Ext.Element
|
|
* @return {HTMLElement/Ext.Element} The new node
|
|
*/
|
|
insertFirst: function(el, o, returnElement) {
|
|
return this.doInsert(el, o, returnElement, 'afterbegin');
|
|
},
|
|
|
|
/**
|
|
* Creates new DOM element(s) and appends them to el.
|
|
* @param {String/HTMLElement/Ext.Element} el The context element
|
|
* @param {Object/String} o The DOM object spec (and children) or raw HTML blob
|
|
* @param {Boolean} [returnElement] true to return a Ext.Element
|
|
* @return {HTMLElement/Ext.Element} The new node
|
|
*/
|
|
append: function(el, o, returnElement) {
|
|
return this.doInsert(el, o, returnElement, 'beforeend');
|
|
},
|
|
|
|
/**
|
|
* Creates new DOM element(s) and overwrites the contents of el with them.
|
|
* @param {String/HTMLElement/Ext.Element} el The context element
|
|
* @param {Object/String} o The DOM object spec (and children) or raw HTML blob
|
|
* @param {Boolean} [returnElement] true to return a Ext.Element
|
|
* @return {HTMLElement/Ext.Element} The new node
|
|
*/
|
|
overwrite: function(el, o, returnElement) {
|
|
el = Ext.getDom(el);
|
|
el.innerHTML = this.markup(o);
|
|
return returnElement ? Ext.get(el.firstChild) : el.firstChild;
|
|
},
|
|
|
|
doInsert: function(el, o, returnElement, pos) {
|
|
var newNode = this.insertHtml(pos, Ext.getDom(el), this.markup(o));
|
|
return returnElement ? Ext.get(newNode, true) : newNode;
|
|
},
|
|
|
|
/**
|
|
* Creates a new Ext.Template from the DOM object spec.
|
|
* @param {Object} o The DOM object spec (and children)
|
|
* @return {Ext.Template} The new template
|
|
*/
|
|
createTemplate: function(o) {
|
|
var html = this.markup(o);
|
|
return new Ext.Template(html);
|
|
}
|
|
}, function() {
|
|
Ext.ns('Ext.core');
|
|
Ext.core.DomHelper = Ext.DomHelper = new this;
|
|
});
|
|
|
|
//@tag dom,core
|
|
//@require Ext.dom.Helper
|
|
|
|
/**
|
|
* An Identifiable mixin.
|
|
* @private
|
|
*/
|
|
Ext.define('Ext.mixin.Identifiable', {
|
|
statics: {
|
|
uniqueIds: {}
|
|
},
|
|
|
|
isIdentifiable: true,
|
|
|
|
mixinId: 'identifiable',
|
|
|
|
idCleanRegex: /\.|[^\w\-]/g,
|
|
|
|
defaultIdPrefix: 'ext-',
|
|
|
|
defaultIdSeparator: '-',
|
|
|
|
getOptimizedId: function() {
|
|
return this.id;
|
|
},
|
|
|
|
getUniqueId: function() {
|
|
var id = this.id,
|
|
prototype, separator, xtype, uniqueIds, prefix;
|
|
|
|
if (!id) {
|
|
prototype = this.self.prototype;
|
|
separator = this.defaultIdSeparator;
|
|
|
|
uniqueIds = Ext.mixin.Identifiable.uniqueIds;
|
|
|
|
if (!prototype.hasOwnProperty('identifiablePrefix')) {
|
|
xtype = this.xtype;
|
|
|
|
if (xtype) {
|
|
prefix = this.defaultIdPrefix + xtype + separator;
|
|
}
|
|
else {
|
|
prefix = prototype.$className.replace(this.idCleanRegex, separator).toLowerCase() + separator;
|
|
}
|
|
|
|
prototype.identifiablePrefix = prefix;
|
|
}
|
|
|
|
prefix = this.identifiablePrefix;
|
|
|
|
if (!uniqueIds.hasOwnProperty(prefix)) {
|
|
uniqueIds[prefix] = 0;
|
|
}
|
|
|
|
id = this.id = prefix + (++uniqueIds[prefix]);
|
|
}
|
|
|
|
this.getUniqueId = this.getOptimizedId;
|
|
|
|
return id;
|
|
},
|
|
|
|
setId: function(id) {
|
|
this.id = id;
|
|
},
|
|
|
|
/**
|
|
* Retrieves the id of this component. Will autogenerate an id if one has not already been set.
|
|
* @return {String} id
|
|
*/
|
|
getId: function() {
|
|
var id = this.id;
|
|
|
|
if (!id) {
|
|
id = this.getUniqueId();
|
|
}
|
|
|
|
this.getId = this.getOptimizedId;
|
|
|
|
return id;
|
|
}
|
|
});
|
|
|
|
//@tag dom,core
|
|
//@define Ext.Element-all
|
|
//@define Ext.Element
|
|
|
|
/**
|
|
* Encapsulates a DOM element, adding simple DOM manipulation facilities, normalizing for browser differences.
|
|
*
|
|
* All instances of this class inherit the methods of Ext.Fx making visual effects easily available to all DOM elements.
|
|
*
|
|
* Note that the events documented in this class are not Ext events, they encapsulate browser events. To access the
|
|
* underlying browser event, see {@link Ext.EventObject#browserEvent}. Some older browsers may not support the full range of
|
|
* events. Which events are supported is beyond the control of Sencha Touch.
|
|
*
|
|
* ## Usage
|
|
*
|
|
* // by id
|
|
* var el = Ext.get("my-div");
|
|
*
|
|
* // by DOM element reference
|
|
* var el = Ext.get(myDivElement);
|
|
*
|
|
* ## Composite (Collections of) Elements
|
|
*
|
|
* For working with collections of Elements, see {@link Ext.CompositeElement}.
|
|
*
|
|
* @mixins Ext.mixin.Observable
|
|
*/
|
|
Ext.define('Ext.dom.Element', {
|
|
alternateClassName: 'Ext.Element',
|
|
|
|
mixins: [
|
|
'Ext.mixin.Identifiable'
|
|
],
|
|
|
|
requires: [
|
|
'Ext.dom.Query',
|
|
'Ext.dom.Helper'
|
|
],
|
|
|
|
observableType: 'element',
|
|
|
|
xtype: 'element',
|
|
|
|
statics: {
|
|
CREATE_ATTRIBUTES: {
|
|
style: 'style',
|
|
className: 'className',
|
|
cls: 'cls',
|
|
classList: 'classList',
|
|
text: 'text',
|
|
hidden: 'hidden',
|
|
html: 'html',
|
|
children: 'children'
|
|
},
|
|
|
|
create: function(attributes, domNode) {
|
|
var ATTRIBUTES = this.CREATE_ATTRIBUTES,
|
|
element, elementStyle, tag, value, name, i, ln;
|
|
|
|
if (!attributes) {
|
|
attributes = {};
|
|
}
|
|
|
|
if (attributes.isElement) {
|
|
return attributes.dom;
|
|
}
|
|
else if ('nodeType' in attributes) {
|
|
return attributes;
|
|
}
|
|
|
|
if (typeof attributes == 'string') {
|
|
return document.createTextNode(attributes);
|
|
}
|
|
|
|
tag = attributes.tag;
|
|
|
|
if (!tag) {
|
|
tag = 'div';
|
|
}
|
|
if (attributes.namespace) {
|
|
element = document.createElementNS(attributes.namespace, tag);
|
|
} else {
|
|
element = document.createElement(tag);
|
|
}
|
|
elementStyle = element.style;
|
|
|
|
for (name in attributes) {
|
|
if (name != 'tag') {
|
|
value = attributes[name];
|
|
|
|
switch (name) {
|
|
case ATTRIBUTES.style:
|
|
if (typeof value == 'string') {
|
|
element.setAttribute(name, value);
|
|
}
|
|
else {
|
|
for (i in value) {
|
|
if (value.hasOwnProperty(i)) {
|
|
elementStyle[i] = value[i];
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ATTRIBUTES.className:
|
|
case ATTRIBUTES.cls:
|
|
element.className = value;
|
|
break;
|
|
|
|
case ATTRIBUTES.classList:
|
|
element.className = value.join(' ');
|
|
break;
|
|
|
|
case ATTRIBUTES.text:
|
|
element.textContent = value;
|
|
break;
|
|
|
|
case ATTRIBUTES.hidden:
|
|
if (value) {
|
|
element.style.display = 'none';
|
|
}
|
|
break;
|
|
|
|
case ATTRIBUTES.html:
|
|
element.innerHTML = value;
|
|
break;
|
|
|
|
case ATTRIBUTES.children:
|
|
for (i = 0,ln = value.length; i < ln; i++) {
|
|
element.appendChild(this.create(value[i], true));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
element.setAttribute(name, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (domNode) {
|
|
return element;
|
|
}
|
|
else {
|
|
return this.get(element);
|
|
}
|
|
},
|
|
|
|
documentElement: null,
|
|
|
|
cache: {},
|
|
|
|
/**
|
|
* Retrieves Ext.dom.Element objects. {@link Ext#get} is alias for {@link Ext.dom.Element#get}.
|
|
*
|
|
* **This method does not retrieve {@link Ext.Element Element}s.** This method retrieves Ext.dom.Element
|
|
* objects which encapsulate DOM elements. To retrieve a Element by its ID, use {@link Ext.ElementManager#get}.
|
|
*
|
|
* Uses simple caching to consistently return the same object. Automatically fixes if an object was recreated with
|
|
* the same id via AJAX or DOM.
|
|
*
|
|
* @param {String/HTMLElement/Ext.Element} el The `id` of the node, a DOM Node or an existing Element.
|
|
* @return {Ext.dom.Element} The Element object (or `null` if no matching element was found).
|
|
* @static
|
|
* @inheritable
|
|
*/
|
|
get: function(element) {
|
|
var cache = this.cache,
|
|
instance, dom, id;
|
|
|
|
if (!element) {
|
|
return null;
|
|
}
|
|
|
|
if (typeof element == 'string') {
|
|
if (cache.hasOwnProperty(element)) {
|
|
return cache[element];
|
|
}
|
|
|
|
if (!(dom = document.getElementById(element))) {
|
|
return null;
|
|
}
|
|
|
|
cache[element] = instance = new this(dom);
|
|
|
|
return instance;
|
|
}
|
|
|
|
if ('tagName' in element) { // dom element
|
|
id = element.id;
|
|
|
|
if (cache.hasOwnProperty(id)) {
|
|
return cache[id];
|
|
}
|
|
|
|
instance = new this(element);
|
|
cache[instance.getId()] = instance;
|
|
|
|
return instance;
|
|
}
|
|
|
|
if (element.isElement) {
|
|
return element;
|
|
}
|
|
|
|
if (element.isComposite) {
|
|
return element;
|
|
}
|
|
|
|
if (Ext.isArray(element)) {
|
|
return this.select(element);
|
|
}
|
|
|
|
if (element === document) {
|
|
// create a bogus element object representing the document object
|
|
if (!this.documentElement) {
|
|
this.documentElement = new this(document.documentElement);
|
|
this.documentElement.setId('ext-application');
|
|
}
|
|
|
|
return this.documentElement;
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
data: function(element, key, value) {
|
|
var cache = Ext.cache,
|
|
id, data;
|
|
|
|
element = this.get(element);
|
|
|
|
if (!element) {
|
|
return null;
|
|
}
|
|
|
|
id = element.id;
|
|
|
|
data = cache[id].data;
|
|
|
|
if (!data) {
|
|
cache[id].data = data = {};
|
|
}
|
|
|
|
if (arguments.length == 2) {
|
|
return data[key];
|
|
}
|
|
else {
|
|
return (data[key] = value);
|
|
}
|
|
}
|
|
},
|
|
|
|
isElement: true,
|
|
|
|
|
|
/**
|
|
* @event painted
|
|
* Fires whenever this Element actually becomes visible (painted) on the screen. This is useful when you need to
|
|
* perform 'read' operations on the DOM element, i.e: calculating natural sizes and positioning.
|
|
*
|
|
* __Note:__ This event is not available to be used with event delegation. Instead `painted` only fires if you explicitly
|
|
* add at least one listener to it, for performance reasons.
|
|
*
|
|
* @param {Ext.Element} this The component instance.
|
|
*/
|
|
|
|
/**
|
|
* @event resize
|
|
* Important note: For the best performance on mobile devices, use this only when you absolutely need to monitor
|
|
* a Element's size.
|
|
*
|
|
* __Note:__ This event is not available to be used with event delegation. Instead `resize` only fires if you explicitly
|
|
* add at least one listener to it, for performance reasons.
|
|
*
|
|
* @param {Ext.Element} this The component instance.
|
|
*/
|
|
|
|
constructor: function(dom) {
|
|
if (typeof dom == 'string') {
|
|
dom = document.getElementById(dom);
|
|
}
|
|
|
|
if (!dom) {
|
|
throw new Error("Invalid domNode reference or an id of an existing domNode: " + dom);
|
|
}
|
|
|
|
/**
|
|
* The DOM element
|
|
* @property dom
|
|
* @type HTMLElement
|
|
*/
|
|
this.dom = dom;
|
|
|
|
this.getUniqueId();
|
|
},
|
|
|
|
attach: function (dom) {
|
|
this.dom = dom;
|
|
this.id = dom.id;
|
|
return this;
|
|
},
|
|
|
|
getUniqueId: function() {
|
|
var id = this.id,
|
|
dom;
|
|
|
|
if (!id) {
|
|
dom = this.dom;
|
|
|
|
if (dom.id.length > 0) {
|
|
this.id = id = dom.id;
|
|
}
|
|
else {
|
|
dom.id = id = this.mixins.identifiable.getUniqueId.call(this);
|
|
}
|
|
|
|
this.self.cache[id] = this;
|
|
}
|
|
|
|
return id;
|
|
},
|
|
|
|
setId: function(id) {
|
|
var currentId = this.id,
|
|
cache = this.self.cache;
|
|
|
|
if (currentId) {
|
|
delete cache[currentId];
|
|
}
|
|
|
|
this.dom.id = id;
|
|
|
|
/**
|
|
* The DOM element ID
|
|
* @property id
|
|
* @type String
|
|
*/
|
|
this.id = id;
|
|
|
|
cache[id] = this;
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Sets the `innerHTML` of this element.
|
|
* @param {String} html The new HTML.
|
|
*/
|
|
setHtml: function(html) {
|
|
this.dom.innerHTML = html;
|
|
},
|
|
|
|
/**
|
|
* Returns the `innerHTML` of an element.
|
|
* @return {String}
|
|
*/
|
|
getHtml: function() {
|
|
return this.dom.innerHTML;
|
|
},
|
|
|
|
setText: function(text) {
|
|
this.dom.textContent = text;
|
|
},
|
|
|
|
redraw: function() {
|
|
var dom = this.dom,
|
|
domStyle = dom.style;
|
|
|
|
domStyle.display = 'none';
|
|
dom.offsetHeight;
|
|
domStyle.display = '';
|
|
},
|
|
|
|
isPainted: function() {
|
|
var dom = this.dom;
|
|
return Boolean(dom && dom.offsetParent);
|
|
},
|
|
|
|
/**
|
|
* Sets the passed attributes as attributes of this element (a style attribute can be a string, object or function).
|
|
* @param {Object} attributes The object with the attributes.
|
|
* @param {Boolean} [useSet=true] `false` to override the default `setAttribute` to use expandos.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
set: function(attributes, useSet) {
|
|
var dom = this.dom,
|
|
attribute, value;
|
|
|
|
for (attribute in attributes) {
|
|
if (attributes.hasOwnProperty(attribute)) {
|
|
value = attributes[attribute];
|
|
|
|
if (attribute == 'style') {
|
|
this.applyStyles(value);
|
|
}
|
|
else if (attribute == 'cls') {
|
|
dom.className = value;
|
|
}
|
|
else if (useSet !== false) {
|
|
if (value === undefined) {
|
|
dom.removeAttribute(attribute);
|
|
} else {
|
|
dom.setAttribute(attribute, value);
|
|
}
|
|
}
|
|
else {
|
|
dom[attribute] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Returns `true` if this element matches the passed simple selector (e.g. 'div.some-class' or 'span:first-child').
|
|
* @param {String} selector The simple selector to test.
|
|
* @return {Boolean} `true` if this element matches the selector, else `false`.
|
|
*/
|
|
is: function(selector) {
|
|
return Ext.DomQuery.is(this.dom, selector);
|
|
},
|
|
|
|
/**
|
|
* Returns the value of the `value` attribute.
|
|
* @param {Boolean} asNumber `true` to parse the value as a number.
|
|
* @return {String/Number}
|
|
*/
|
|
getValue: function(asNumber) {
|
|
var value = this.dom.value;
|
|
|
|
return asNumber ? parseInt(value, 10) : value;
|
|
},
|
|
|
|
/**
|
|
* Returns the value of an attribute from the element's underlying DOM node.
|
|
* @param {String} name The attribute name.
|
|
* @param {String} [namespace] The namespace in which to look for the attribute.
|
|
* @return {String} The attribute value.
|
|
*/
|
|
getAttribute: function(name, namespace) {
|
|
var dom = this.dom;
|
|
|
|
return dom.getAttributeNS(namespace, name) || dom.getAttribute(namespace + ":" + name)
|
|
|| dom.getAttribute(name) || dom[name];
|
|
},
|
|
|
|
setSizeState: function(state) {
|
|
var classes = ['x-sized', 'x-unsized', 'x-stretched'],
|
|
states = [true, false, null],
|
|
index = states.indexOf(state),
|
|
addedClass;
|
|
|
|
if (index !== -1) {
|
|
addedClass = classes[index];
|
|
classes.splice(index, 1);
|
|
this.addCls(addedClass);
|
|
}
|
|
|
|
this.removeCls(classes);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Removes this element's DOM reference. Note that event and cache removal is handled at {@link Ext#removeNode}
|
|
*/
|
|
destroy: function() {
|
|
this.isDestroyed = true;
|
|
|
|
var cache = Ext.Element.cache,
|
|
dom = this.dom;
|
|
|
|
if (dom && dom.parentNode && dom.tagName != 'BODY') {
|
|
dom.parentNode.removeChild(dom);
|
|
}
|
|
|
|
delete cache[this.id];
|
|
delete this.dom;
|
|
}
|
|
|
|
}, function(Element) {
|
|
Ext.elements = Ext.cache = Element.cache;
|
|
|
|
this.addStatics({
|
|
Fly: new Ext.Class({
|
|
extend: Element,
|
|
|
|
constructor: function(dom) {
|
|
this.dom = dom;
|
|
}
|
|
}),
|
|
|
|
_flyweights: {},
|
|
|
|
/**
|
|
* Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference
|
|
* to this element - the dom node can be overwritten by other code. {@link Ext#fly} is alias for
|
|
* {@link Ext.dom.Element#fly}.
|
|
*
|
|
* Use this to make one-time references to DOM elements which are not going to be accessed again either by
|
|
* application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link
|
|
* Ext#get Ext.get} will be more appropriate to take advantage of the caching provided by the {@link Ext.dom.Element}
|
|
* class.
|
|
*
|
|
* @param {String/HTMLElement} element The DOM node or `id`.
|
|
* @param {String} [named] Allows for creation of named reusable flyweights to prevent conflicts (e.g.
|
|
* internally Ext uses "_global").
|
|
* @return {Ext.dom.Element} The shared Element object (or `null` if no matching element was found).
|
|
* @static
|
|
*/
|
|
fly: function(element, named) {
|
|
var fly = null,
|
|
flyweights = Element._flyweights,
|
|
cachedElement;
|
|
|
|
named = named || '_global';
|
|
|
|
element = Ext.getDom(element);
|
|
|
|
if (element) {
|
|
fly = flyweights[named] || (flyweights[named] = new Element.Fly());
|
|
fly.dom = element;
|
|
fly.isSynchronized = false;
|
|
cachedElement = Ext.cache[element.id];
|
|
if (cachedElement && cachedElement.isElement) {
|
|
cachedElement.isSynchronized = false;
|
|
}
|
|
}
|
|
|
|
return fly;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @member Ext
|
|
* @method get
|
|
* @alias Ext.dom.Element#get
|
|
*/
|
|
Ext.get = function(element) {
|
|
return Element.get.call(Element, element);
|
|
};
|
|
|
|
/**
|
|
* @member Ext
|
|
* @method fly
|
|
* @alias Ext.dom.Element#fly
|
|
*/
|
|
Ext.fly = function() {
|
|
return Element.fly.apply(Element, arguments);
|
|
};
|
|
|
|
Ext.ClassManager.onCreated(function() {
|
|
Element.mixin('observable', Ext.mixin.Observable);
|
|
}, null, 'Ext.mixin.Observable');
|
|
|
|
|
|
});
|
|
|
|
//@tag dom,core
|
|
//@define Ext.Element-all
|
|
//@define Ext.Element-static
|
|
//@require Ext.Element
|
|
|
|
/**
|
|
* @class Ext.dom.Element
|
|
*/
|
|
Ext.dom.Element.addStatics({
|
|
numberRe: /\d+$/,
|
|
unitRe: /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
|
|
camelRe: /(-[a-z])/gi,
|
|
cssRe: /([a-z0-9-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi,
|
|
opacityRe: /alpha\(opacity=(.*)\)/i,
|
|
propertyCache: {},
|
|
defaultUnit: "px",
|
|
borders: {l: 'border-left-width', r: 'border-right-width', t: 'border-top-width', b: 'border-bottom-width'},
|
|
paddings: {l: 'padding-left', r: 'padding-right', t: 'padding-top', b: 'padding-bottom'},
|
|
margins: {l: 'margin-left', r: 'margin-right', t: 'margin-top', b: 'margin-bottom'},
|
|
|
|
/**
|
|
* Test if size has a unit, otherwise appends the passed unit string, or the default for this Element.
|
|
* @param {Object} size The size to set.
|
|
* @param {String} units The units to append to a numeric size value.
|
|
* @return {String}
|
|
* @private
|
|
* @static
|
|
*/
|
|
addUnits: function(size, units) {
|
|
// Size set to a value which means "auto"
|
|
if (size === "" || size == "auto" || size === undefined || size === null) {
|
|
return size || '';
|
|
}
|
|
|
|
// Otherwise, warn if it's not a valid CSS measurement
|
|
if (Ext.isNumber(size) || this.numberRe.test(size)) {
|
|
return size + (units || this.defaultUnit || 'px');
|
|
}
|
|
else if (!this.unitRe.test(size)) {
|
|
//<debug>
|
|
Ext.Logger.warn("Warning, size detected (" + size + ") not a valid property value on Element.addUnits.");
|
|
//</debug>
|
|
return size || '';
|
|
}
|
|
|
|
return size;
|
|
},
|
|
|
|
/**
|
|
* @static
|
|
* @return {Boolean}
|
|
* @private
|
|
*/
|
|
isAncestor: function(p, c) {
|
|
var ret = false;
|
|
|
|
p = Ext.getDom(p);
|
|
c = Ext.getDom(c);
|
|
if (p && c) {
|
|
if (p.contains) {
|
|
return p.contains(c);
|
|
} else if (p.compareDocumentPosition) {
|
|
return !!(p.compareDocumentPosition(c) & 16);
|
|
} else {
|
|
while ((c = c.parentNode)) {
|
|
ret = c == p || ret;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
* Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
|
|
* (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
|
|
* @static
|
|
* @param {Number/String} box The encoded margins
|
|
* @return {Object} An object with margin sizes for top, right, bottom and left containing the unit
|
|
*/
|
|
parseBox: function(box) {
|
|
if (typeof box != 'string') {
|
|
box = box.toString();
|
|
}
|
|
|
|
var parts = box.split(' '),
|
|
ln = parts.length;
|
|
|
|
if (ln == 1) {
|
|
parts[1] = parts[2] = parts[3] = parts[0];
|
|
}
|
|
else if (ln == 2) {
|
|
parts[2] = parts[0];
|
|
parts[3] = parts[1];
|
|
}
|
|
else if (ln == 3) {
|
|
parts[3] = parts[1];
|
|
}
|
|
|
|
return {
|
|
top: parts[0] || 0,
|
|
right: parts[1] || 0,
|
|
bottom: parts[2] || 0,
|
|
left: parts[3] || 0
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
|
|
* (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
|
|
* @static
|
|
* @param {Number/String} box The encoded margins
|
|
* @param {String} units The type of units to add
|
|
* @return {String} An string with unitized (px if units is not specified) metrics for top, right, bottom and left
|
|
*/
|
|
unitizeBox: function(box, units) {
|
|
var me = this;
|
|
box = me.parseBox(box);
|
|
|
|
return me.addUnits(box.top, units) + ' ' +
|
|
me.addUnits(box.right, units) + ' ' +
|
|
me.addUnits(box.bottom, units) + ' ' +
|
|
me.addUnits(box.left, units);
|
|
},
|
|
|
|
// @private
|
|
camelReplaceFn: function(m, a) {
|
|
return a.charAt(1).toUpperCase();
|
|
},
|
|
|
|
/**
|
|
* Normalizes CSS property keys from dash delimited to camel case JavaScript Syntax.
|
|
* For example:
|
|
*
|
|
* - border-width -> borderWidth
|
|
* - padding-top -> paddingTop
|
|
*
|
|
* @static
|
|
* @param {String} prop The property to normalize
|
|
* @return {String} The normalized string
|
|
*/
|
|
normalize: function(prop) {
|
|
// TODO: Mobile optimization?
|
|
// if (prop == 'float') {
|
|
// prop = Ext.supports.Float ? 'cssFloat' : 'styleFloat';
|
|
// }
|
|
return this.propertyCache[prop] || (this.propertyCache[prop] = prop.replace(this.camelRe, this.camelReplaceFn));
|
|
},
|
|
|
|
/**
|
|
* Returns the top Element that is located at the passed coordinates
|
|
* @static
|
|
* @param {Number} x The x coordinate
|
|
* @param {Number} y The y coordinate
|
|
* @return {String} The found Element
|
|
*/
|
|
fromPoint: function(x, y) {
|
|
return Ext.get(document.elementFromPoint(x, y));
|
|
},
|
|
|
|
/**
|
|
* Converts a CSS string into an object with a property for each style.
|
|
*
|
|
* The sample code below would return an object with 2 properties, one
|
|
* for background-color and one for color.
|
|
*
|
|
* var css = 'background-color: red;color: blue; ';
|
|
* console.log(Ext.dom.Element.parseStyles(css));
|
|
*
|
|
* @static
|
|
* @param {String} styles A CSS string
|
|
* @return {Object} styles
|
|
*/
|
|
parseStyles: function(styles) {
|
|
var out = {},
|
|
cssRe = this.cssRe,
|
|
matches;
|
|
|
|
if (styles) {
|
|
// Since we're using the g flag on the regex, we need to set the lastIndex.
|
|
// This automatically happens on some implementations, but not others, see:
|
|
// http://stackoverflow.com/questions/2645273/javascript-regular-expression-literal-persists-between-function-calls
|
|
// http://blog.stevenlevithan.com/archives/fixing-javascript-regexp
|
|
cssRe.lastIndex = 0;
|
|
while ((matches = cssRe.exec(styles))) {
|
|
out[matches[1]] = matches[2];
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
});
|
|
|
|
|
|
//@tag dom,core
|
|
//@define Ext.Element-all
|
|
//@define Ext.Element-alignment
|
|
//@require Ext.Element-static
|
|
|
|
/**
|
|
* @class Ext.dom.Element
|
|
*/
|
|
|
|
//@tag dom,core
|
|
//@define Ext.Element-all
|
|
//@define Ext.Element-insertion
|
|
//@require Ext.Element-alignment
|
|
|
|
/**
|
|
* @class Ext.dom.Element
|
|
*/
|
|
Ext.dom.Element.addMembers({
|
|
|
|
/**
|
|
* Appends the passed element(s) to this element.
|
|
* @param {HTMLElement/Ext.dom.Element} element a DOM Node or an existing Element.
|
|
* @return {Ext.dom.Element} This element.
|
|
*/
|
|
appendChild: function(element) {
|
|
this.dom.appendChild(Ext.getDom(element));
|
|
|
|
return this;
|
|
},
|
|
|
|
removeChild: function(element) {
|
|
this.dom.removeChild(Ext.getDom(element));
|
|
|
|
return this;
|
|
},
|
|
|
|
append: function() {
|
|
this.appendChild.apply(this, arguments);
|
|
},
|
|
|
|
/**
|
|
* Appends this element to the passed element.
|
|
* @param {String/HTMLElement/Ext.dom.Element} el The new parent element.
|
|
* The id of the node, a DOM Node or an existing Element.
|
|
* @return {Ext.dom.Element} This element.
|
|
*/
|
|
appendTo: function(el) {
|
|
Ext.getDom(el).appendChild(this.dom);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Inserts this element before the passed element in the DOM.
|
|
* @param {String/HTMLElement/Ext.dom.Element} el The element before which this element will be inserted.
|
|
* The id of the node, a DOM Node or an existing Element.
|
|
* @return {Ext.dom.Element} This element.
|
|
*/
|
|
insertBefore: function(el) {
|
|
el = Ext.getDom(el);
|
|
el.parentNode.insertBefore(this.dom, el);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Inserts this element after the passed element in the DOM.
|
|
* @param {String/HTMLElement/Ext.dom.Element} el The element to insert after.
|
|
* The `id` of the node, a DOM Node or an existing Element.
|
|
* @return {Ext.dom.Element} This element.
|
|
*/
|
|
insertAfter: function(el) {
|
|
el = Ext.getDom(el);
|
|
el.parentNode.insertBefore(this.dom, el.nextSibling);
|
|
return this;
|
|
},
|
|
|
|
|
|
/**
|
|
* Inserts an element as the first child of this element.
|
|
* @param {String/HTMLElement/Ext.dom.Element} element The `id` or element to insert.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
insertFirst: function(element) {
|
|
var elementDom = Ext.getDom(element),
|
|
dom = this.dom,
|
|
firstChild = dom.firstChild;
|
|
|
|
if (!firstChild) {
|
|
dom.appendChild(elementDom);
|
|
}
|
|
else {
|
|
dom.insertBefore(elementDom, firstChild);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Inserts (or creates) the passed element (or DomHelper config) as a sibling of this element
|
|
* @param {String/HTMLElement/Ext.dom.Element/Object/Array} el The id, element to insert or a DomHelper config
|
|
* to create and insert *or* an array of any of those.
|
|
* @param {String} [where=before] (optional) 'before' or 'after'.
|
|
* @param {Boolean} returnDom (optional) `true` to return the raw DOM element instead of Ext.dom.Element.
|
|
* @return {Ext.dom.Element} The inserted Element. If an array is passed, the last inserted element is returned.
|
|
*/
|
|
insertSibling: function(el, where, returnDom) {
|
|
var me = this, rt,
|
|
isAfter = (where || 'before').toLowerCase() == 'after',
|
|
insertEl;
|
|
|
|
if (Ext.isArray(el)) {
|
|
insertEl = me;
|
|
Ext.each(el, function(e) {
|
|
rt = Ext.fly(insertEl, '_internal').insertSibling(e, where, returnDom);
|
|
if (isAfter) {
|
|
insertEl = rt;
|
|
}
|
|
});
|
|
return rt;
|
|
}
|
|
|
|
el = el || {};
|
|
|
|
if (el.nodeType || el.dom) {
|
|
rt = me.dom.parentNode.insertBefore(Ext.getDom(el), isAfter ? me.dom.nextSibling : me.dom);
|
|
if (!returnDom) {
|
|
rt = Ext.get(rt);
|
|
}
|
|
} else {
|
|
if (isAfter && !me.dom.nextSibling) {
|
|
rt = Ext.core.DomHelper.append(me.dom.parentNode, el, !returnDom);
|
|
} else {
|
|
rt = Ext.core.DomHelper[isAfter ? 'insertAfter' : 'insertBefore'](me.dom, el, !returnDom);
|
|
}
|
|
}
|
|
return rt;
|
|
},
|
|
|
|
/**
|
|
* Replaces the passed element with this element.
|
|
* @param {String/HTMLElement/Ext.dom.Element} el The element to replace.
|
|
* The id of the node, a DOM Node or an existing Element.
|
|
* @return {Ext.dom.Element} This element.
|
|
*/
|
|
replace: function(element) {
|
|
element = Ext.getDom(element);
|
|
|
|
element.parentNode.replaceChild(this.dom, element);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Replaces this element with the passed element.
|
|
* @param {String/HTMLElement/Ext.dom.Element/Object} el The new element (id of the node, a DOM Node
|
|
* or an existing Element) or a DomHelper config of an element to create.
|
|
* @return {Ext.dom.Element} This element.
|
|
*/
|
|
replaceWith: function(el) {
|
|
var me = this;
|
|
|
|
if (el.nodeType || el.dom || typeof el == 'string') {
|
|
el = Ext.get(el);
|
|
me.dom.parentNode.insertBefore(el, me.dom);
|
|
} else {
|
|
el = Ext.core.DomHelper.insertBefore(me.dom, el);
|
|
}
|
|
|
|
delete Ext.cache[me.id];
|
|
Ext.removeNode(me.dom);
|
|
me.id = Ext.id(me.dom = el);
|
|
Ext.dom.Element.addToCache(me.isFlyweight ? new Ext.dom.Element(me.dom) : me);
|
|
return me;
|
|
},
|
|
|
|
doReplaceWith: function(element) {
|
|
var dom = this.dom;
|
|
dom.parentNode.replaceChild(Ext.getDom(element), dom);
|
|
},
|
|
|
|
/**
|
|
* Creates the passed DomHelper config and appends it to this element or optionally inserts it before the passed child element.
|
|
* @param {Object} config DomHelper element config object. If no tag is specified (e.g., `{tag:'input'}`) then a div will be
|
|
* automatically generated with the specified attributes.
|
|
* @param {HTMLElement} insertBefore (optional) a child element of this element.
|
|
* @param {Boolean} returnDom (optional) `true` to return the dom node instead of creating an Element.
|
|
* @return {Ext.dom.Element} The new child element.
|
|
*/
|
|
createChild: function(config, insertBefore, returnDom) {
|
|
config = config || {tag: 'div'};
|
|
if (insertBefore) {
|
|
return Ext.core.DomHelper.insertBefore(insertBefore, config, returnDom !== true);
|
|
}
|
|
else {
|
|
return Ext.core.DomHelper[!this.dom.firstChild ? 'insertFirst' : 'append'](this.dom, config, returnDom !== true);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Creates and wraps this element with another element.
|
|
* @param {Object} [config] (optional) DomHelper element config object for the wrapper element or `null` for an empty div
|
|
* @param {Boolean} [domNode] (optional) `true` to return the raw DOM element instead of Ext.dom.Element.
|
|
* @return {HTMLElement/Ext.dom.Element} The newly created wrapper element.
|
|
*/
|
|
wrap: function(config, domNode) {
|
|
var dom = this.dom,
|
|
wrapper = this.self.create(config, domNode),
|
|
wrapperDom = (domNode) ? wrapper : wrapper.dom,
|
|
parentNode = dom.parentNode;
|
|
|
|
if (parentNode) {
|
|
parentNode.insertBefore(wrapperDom, dom);
|
|
}
|
|
|
|
wrapperDom.appendChild(dom);
|
|
|
|
return wrapper;
|
|
},
|
|
|
|
wrapAllChildren: function(config) {
|
|
var dom = this.dom,
|
|
children = dom.childNodes,
|
|
wrapper = this.self.create(config),
|
|
wrapperDom = wrapper.dom;
|
|
|
|
while (children.length > 0) {
|
|
wrapperDom.appendChild(dom.firstChild);
|
|
}
|
|
|
|
dom.appendChild(wrapperDom);
|
|
|
|
return wrapper;
|
|
},
|
|
|
|
unwrapAllChildren: function() {
|
|
var dom = this.dom,
|
|
children = dom.childNodes,
|
|
parentNode = dom.parentNode;
|
|
|
|
if (parentNode) {
|
|
while (children.length > 0) {
|
|
parentNode.insertBefore(dom, dom.firstChild);
|
|
}
|
|
|
|
this.destroy();
|
|
}
|
|
},
|
|
|
|
unwrap: function() {
|
|
var dom = this.dom,
|
|
parentNode = dom.parentNode,
|
|
grandparentNode;
|
|
|
|
if (parentNode) {
|
|
grandparentNode = parentNode.parentNode;
|
|
grandparentNode.insertBefore(dom, parentNode);
|
|
grandparentNode.removeChild(parentNode);
|
|
}
|
|
else {
|
|
grandparentNode = document.createDocumentFragment();
|
|
grandparentNode.appendChild(dom);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
detach: function() {
|
|
var dom = this.dom;
|
|
|
|
if (dom && dom.parentNode && dom.tagName !== 'BODY') {
|
|
dom.parentNode.removeChild(dom);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Inserts an HTML fragment into this element.
|
|
* @param {String} where Where to insert the HTML in relation to this element - 'beforeBegin', 'afterBegin', 'beforeEnd', 'afterEnd'.
|
|
* See {@link Ext.DomHelper#insertHtml} for details.
|
|
* @param {String} html The HTML fragment
|
|
* @param {Boolean} [returnEl=false] (optional) `true` to return an Ext.dom.Element.
|
|
* @return {HTMLElement/Ext.dom.Element} The inserted node (or nearest related if more than 1 inserted).
|
|
*/
|
|
insertHtml: function(where, html, returnEl) {
|
|
var el = Ext.core.DomHelper.insertHtml(where, this.dom, html);
|
|
return returnEl ? Ext.get(el) : el;
|
|
}
|
|
});
|
|
|
|
//@tag dom,core
|
|
//@define Ext.Element-all
|
|
//@define Ext.Element-position
|
|
//@require Ext.Element-insertion
|
|
|
|
/**
|
|
* @class Ext.dom.Element
|
|
*/
|
|
Ext.dom.Element.override({
|
|
|
|
/**
|
|
* Gets the current X position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (`display:none` or elements not appended return `false`).
|
|
* @return {Number} The X position of the element
|
|
*/
|
|
getX: function(el) {
|
|
return this.getXY(el)[0];
|
|
},
|
|
|
|
/**
|
|
* Gets the current Y position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (`display:none` or elements not appended return `false`).
|
|
* @return {Number} The Y position of the element
|
|
*/
|
|
getY: function(el) {
|
|
return this.getXY(el)[1];
|
|
},
|
|
|
|
/**
|
|
* Gets the current position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (`display:none` or elements not appended return `false`).
|
|
* @return {Array} The XY position of the element
|
|
*/
|
|
|
|
getXY: function() {
|
|
var rect = this.dom.getBoundingClientRect(),
|
|
round = Math.round;
|
|
|
|
return [round(rect.left + window.pageXOffset), round(rect.top + window.pageYOffset)];
|
|
},
|
|
|
|
/**
|
|
* Returns the offsets of this element from the passed element. Both element must be part of the DOM tree
|
|
* and not have `display:none` to have page coordinates.
|
|
* @param {Mixed} element The element to get the offsets from.
|
|
* @return {Array} The XY page offsets (e.g. [100, -200])
|
|
*/
|
|
getOffsetsTo: function(el) {
|
|
var o = this.getXY(),
|
|
e = Ext.fly(el, '_internal').getXY();
|
|
return [o[0] - e[0], o[1] - e[1]];
|
|
},
|
|
|
|
/**
|
|
* Sets the X position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (`display:none` or elements not appended return `false`).
|
|
* @param {Number} The X position of the element
|
|
* @param {Boolean/Object} animate (optional) `true` for the default animation, or a standard Element animation config object.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
setX: function(x) {
|
|
return this.setXY([x, this.getY()]);
|
|
},
|
|
|
|
/**
|
|
* Sets the Y position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (`display:none` or elements not appended return `false`).
|
|
* @param {Number} The Y position of the element.
|
|
* @param {Boolean/Object} animate (optional) `true` for the default animation, or a standard Element animation config object.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
setY: function(y) {
|
|
return this.setXY([this.getX(), y]);
|
|
},
|
|
|
|
/**
|
|
* Sets the position of the element in page coordinates, regardless of how the element is positioned.
|
|
* The element must be part of the DOM tree to have page coordinates (`display:none` or elements not appended return `false`).
|
|
* @param {Array} pos Contains X & Y [x, y] values for new position (coordinates are page-based).
|
|
* @param {Boolean/Object} animate (optional) `true` for the default animation, or a standard Element animation config object.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
setXY: function(pos) {
|
|
var me = this;
|
|
|
|
if (arguments.length > 1) {
|
|
pos = [pos, arguments[1]];
|
|
}
|
|
|
|
// me.position();
|
|
var pts = me.translatePoints(pos),
|
|
style = me.dom.style;
|
|
|
|
for (pos in pts) {
|
|
if (!pts.hasOwnProperty(pos)) {
|
|
continue;
|
|
}
|
|
if (!isNaN(pts[pos])) style[pos] = pts[pos] + "px";
|
|
}
|
|
return me;
|
|
},
|
|
|
|
/**
|
|
* Gets the left X coordinate.
|
|
* @return {Number}
|
|
*/
|
|
getLeft: function() {
|
|
return parseInt(this.getStyle('left'), 10) || 0;
|
|
},
|
|
|
|
/**
|
|
* Gets the right X coordinate of the element (element X position + element width).
|
|
* @return {Number}
|
|
*/
|
|
getRight: function() {
|
|
return parseInt(this.getStyle('right'), 10) || 0;
|
|
},
|
|
|
|
/**
|
|
* Gets the top Y coordinate.
|
|
* @return {Number}
|
|
*/
|
|
getTop: function() {
|
|
return parseInt(this.getStyle('top'), 10) || 0;
|
|
},
|
|
|
|
/**
|
|
* Gets the bottom Y coordinate of the element (element Y position + element height).
|
|
* @return {Number}
|
|
*/
|
|
getBottom: function() {
|
|
return parseInt(this.getStyle('bottom'), 10) || 0;
|
|
},
|
|
|
|
/**
|
|
* Translates the passed page coordinates into left/top CSS values for this element.
|
|
* @param {Number/Array} x The page `x` or an array containing [x, y].
|
|
* @param {Number} y (optional) The page `y`, required if `x` is not an array.
|
|
* @return {Object} An object with `left` and `top` properties. e.g. `{left: (value), top: (value)}`.
|
|
*/
|
|
translatePoints: function(x, y) {
|
|
y = isNaN(x[1]) ? y : x[1];
|
|
x = isNaN(x[0]) ? x : x[0];
|
|
|
|
var me = this,
|
|
relative = me.isStyle('position', 'relative'),
|
|
o = me.getXY(),
|
|
l = parseInt(me.getStyle('left'), 10),
|
|
t = parseInt(me.getStyle('top'), 10);
|
|
|
|
l = !isNaN(l) ? l : (relative ? 0 : me.dom.offsetLeft);
|
|
t = !isNaN(t) ? t : (relative ? 0 : me.dom.offsetTop);
|
|
|
|
return {left: (x - o[0] + l), top: (y - o[1] + t)};
|
|
},
|
|
|
|
/**
|
|
* Sets the element's box. Use {@link #getBox} on another element to get a box object.
|
|
* @param {Object} box The box to fill, for example:
|
|
*
|
|
* {
|
|
* left: ...,
|
|
* top: ...,
|
|
* width: ...,
|
|
* height: ...
|
|
* }
|
|
*
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
setBox: function(box) {
|
|
var me = this,
|
|
width = box.width,
|
|
height = box.height,
|
|
top = box.top,
|
|
left = box.left;
|
|
|
|
if (left !== undefined) {
|
|
me.setLeft(left);
|
|
}
|
|
if (top !== undefined) {
|
|
me.setTop(top);
|
|
}
|
|
if (width !== undefined) {
|
|
me.setWidth(width);
|
|
}
|
|
if (height !== undefined) {
|
|
me.setHeight(height);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Return an object defining the area of this Element which can be passed to {@link #setBox} to
|
|
* set another Element's size/location to match this element.
|
|
*
|
|
* The returned object may also be addressed as an Array where index 0 contains the X position
|
|
* and index 1 contains the Y position. So the result may also be used for {@link #setXY}.
|
|
*
|
|
* @param {Boolean} contentBox (optional) If `true` a box for the content of the element is returned.
|
|
* @param {Boolean} local (optional) If `true` the element's left and top are returned instead of page x/y.
|
|
* @return {Object} An object in the format
|
|
* @return {Number} return.x The element's X position.
|
|
* @return {Number} return.y The element's Y position.
|
|
* @return {Number} return.width The element's width.
|
|
* @return {Number} return.height The element's height.
|
|
* @return {Number} return.bottom The element's lower bound.
|
|
* @return {Number} return.right The element's rightmost bound.
|
|
*/
|
|
getBox: function(contentBox, local) {
|
|
var me = this,
|
|
dom = me.dom,
|
|
width = dom.offsetWidth,
|
|
height = dom.offsetHeight,
|
|
xy, box, l, r, t, b;
|
|
|
|
if (!local) {
|
|
xy = me.getXY();
|
|
}
|
|
else if (contentBox) {
|
|
xy = [0, 0];
|
|
}
|
|
else {
|
|
xy = [parseInt(me.getStyle("left"), 10) || 0, parseInt(me.getStyle("top"), 10) || 0];
|
|
}
|
|
|
|
if (!contentBox) {
|
|
box = {
|
|
x: xy[0],
|
|
y: xy[1],
|
|
0: xy[0],
|
|
1: xy[1],
|
|
width: width,
|
|
height: height
|
|
};
|
|
}
|
|
else {
|
|
l = me.getBorderWidth.call(me, "l") + me.getPadding.call(me, "l");
|
|
r = me.getBorderWidth.call(me, "r") + me.getPadding.call(me, "r");
|
|
t = me.getBorderWidth.call(me, "t") + me.getPadding.call(me, "t");
|
|
b = me.getBorderWidth.call(me, "b") + me.getPadding.call(me, "b");
|
|
box = {
|
|
x: xy[0] + l,
|
|
y: xy[1] + t,
|
|
0: xy[0] + l,
|
|
1: xy[1] + t,
|
|
width: width - (l + r),
|
|
height: height - (t + b)
|
|
};
|
|
}
|
|
|
|
box.left = box.x;
|
|
box.top = box.y;
|
|
box.right = box.x + box.width;
|
|
box.bottom = box.y + box.height;
|
|
|
|
return box;
|
|
},
|
|
|
|
/**
|
|
* Return an object defining the area of this Element which can be passed to {@link #setBox} to
|
|
* set another Element's size/location to match this element.
|
|
* @param {Boolean} asRegion (optional) If `true` an {@link Ext.util.Region} will be returned.
|
|
* @return {Object} box An object in the format:
|
|
*
|
|
* {
|
|
* x: <Element's X position>,
|
|
* y: <Element's Y position>,
|
|
* width: <Element's width>,
|
|
* height: <Element's height>,
|
|
* bottom: <Element's lower bound>,
|
|
* right: <Element's rightmost bound>
|
|
* }
|
|
*
|
|
* The returned object may also be addressed as an Array where index 0 contains the X position
|
|
* and index 1 contains the Y position. So the result may also be used for {@link #setXY}.
|
|
*/
|
|
getPageBox: function(getRegion) {
|
|
var me = this,
|
|
el = me.dom,
|
|
w = el.offsetWidth,
|
|
h = el.offsetHeight,
|
|
xy = me.getXY(),
|
|
t = xy[1],
|
|
r = xy[0] + w,
|
|
b = xy[1] + h,
|
|
l = xy[0];
|
|
|
|
if (!el) {
|
|
return new Ext.util.Region();
|
|
}
|
|
|
|
if (getRegion) {
|
|
return new Ext.util.Region(t, r, b, l);
|
|
}
|
|
else {
|
|
return {
|
|
left: l,
|
|
top: t,
|
|
width: w,
|
|
height: h,
|
|
right: r,
|
|
bottom: b
|
|
};
|
|
}
|
|
}
|
|
});
|
|
|
|
//@tag dom,core
|
|
//@define Ext.Element-all
|
|
//@define Ext.Element-style
|
|
//@require Ext.Element-position
|
|
|
|
/**
|
|
* @class Ext.dom.Element
|
|
*/
|
|
|
|
Ext.dom.Element.addMembers({
|
|
WIDTH: 'width',
|
|
HEIGHT: 'height',
|
|
MIN_WIDTH: 'min-width',
|
|
MIN_HEIGHT: 'min-height',
|
|
MAX_WIDTH: 'max-width',
|
|
MAX_HEIGHT: 'max-height',
|
|
TOP: 'top',
|
|
RIGHT: 'right',
|
|
BOTTOM: 'bottom',
|
|
LEFT: 'left',
|
|
/**
|
|
* @property VISIBILITY
|
|
* Visibility mode constant for use with {@link #setVisibilityMode}. Use `visibility` to hide element.
|
|
*/
|
|
VISIBILITY: 1,
|
|
|
|
/**
|
|
* @property DISPLAY
|
|
* Visibility mode constant for use with {@link #setVisibilityMode}. Use `display` to hide element.
|
|
*/
|
|
DISPLAY: 2,
|
|
|
|
/**
|
|
* @property OFFSETS
|
|
* Visibility mode constant for use with {@link #setVisibilityMode}. Use offsets to hide element.
|
|
*/
|
|
OFFSETS: 3,
|
|
|
|
SEPARATOR: '-',
|
|
|
|
trimRe: /^\s+|\s+$/g,
|
|
wordsRe: /\w/g,
|
|
spacesRe: /\s+/,
|
|
styleSplitRe: /\s*(?::|;)\s*/,
|
|
transparentRe: /^(?:transparent|(?:rgba[(](?:\s*\d+\s*[,]){3}\s*0\s*[)]))$/i,
|
|
classNameSplitRegex: /[\s]+/,
|
|
|
|
borders: {
|
|
t: 'border-top-width',
|
|
r: 'border-right-width',
|
|
b: 'border-bottom-width',
|
|
l: 'border-left-width'
|
|
},
|
|
|
|
paddings: {
|
|
t: 'padding-top',
|
|
r: 'padding-right',
|
|
b: 'padding-bottom',
|
|
l: 'padding-left'
|
|
},
|
|
|
|
margins: {
|
|
t: 'margin-top',
|
|
r: 'margin-right',
|
|
b: 'margin-bottom',
|
|
l: 'margin-left'
|
|
},
|
|
|
|
/**
|
|
* @property {String} defaultUnit
|
|
* The default unit to append to CSS values where a unit isn't provided.
|
|
*/
|
|
defaultUnit: "px",
|
|
|
|
isSynchronized: false,
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
synchronize: function() {
|
|
var dom = this.dom,
|
|
hasClassMap = {},
|
|
className = dom.className,
|
|
classList, i, ln, name;
|
|
|
|
if (className.length > 0) {
|
|
classList = dom.className.split(this.classNameSplitRegex);
|
|
|
|
for (i = 0, ln = classList.length; i < ln; i++) {
|
|
name = classList[i];
|
|
hasClassMap[name] = true;
|
|
}
|
|
}
|
|
else {
|
|
classList = [];
|
|
}
|
|
|
|
this.classList = classList;
|
|
|
|
this.hasClassMap = hasClassMap;
|
|
|
|
this.isSynchronized = true;
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Adds the given CSS class(es) to this Element.
|
|
* @param {String} names The CSS class(es) to add to this element.
|
|
* @param {String} [prefix] (optional) Prefix to prepend to each class.
|
|
* @param {String} [suffix] (optional) Suffix to append to each class.
|
|
*/
|
|
addCls: function(names, prefix, suffix) {
|
|
if (!names) {
|
|
return this;
|
|
}
|
|
|
|
if (!this.isSynchronized) {
|
|
this.synchronize();
|
|
}
|
|
|
|
var dom = this.dom,
|
|
map = this.hasClassMap,
|
|
classList = this.classList,
|
|
SEPARATOR = this.SEPARATOR,
|
|
i, ln, name;
|
|
|
|
prefix = prefix ? prefix + SEPARATOR : '';
|
|
suffix = suffix ? SEPARATOR + suffix : '';
|
|
|
|
if (typeof names == 'string') {
|
|
names = names.split(this.spacesRe);
|
|
}
|
|
|
|
for (i = 0, ln = names.length; i < ln; i++) {
|
|
name = prefix + names[i] + suffix;
|
|
|
|
if (!map[name]) {
|
|
map[name] = true;
|
|
classList.push(name);
|
|
}
|
|
}
|
|
|
|
dom.className = classList.join(' ');
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Removes the given CSS class(es) from this Element.
|
|
* @param {String} names The CSS class(es) to remove from this element.
|
|
* @param {String} [prefix=''] (optional) Prefix to prepend to each class to be removed.
|
|
* @param {String} [suffix=''] (optional) Suffix to append to each class to be removed.
|
|
*/
|
|
removeCls: function(names, prefix, suffix) {
|
|
if (!names) {
|
|
return this;
|
|
}
|
|
|
|
if (!this.isSynchronized) {
|
|
this.synchronize();
|
|
}
|
|
|
|
if (!suffix) {
|
|
suffix = '';
|
|
}
|
|
|
|
var dom = this.dom,
|
|
map = this.hasClassMap,
|
|
classList = this.classList,
|
|
SEPARATOR = this.SEPARATOR,
|
|
i, ln, name;
|
|
|
|
prefix = prefix ? prefix + SEPARATOR : '';
|
|
suffix = suffix ? SEPARATOR + suffix : '';
|
|
|
|
if (typeof names == 'string') {
|
|
names = names.split(this.spacesRe);
|
|
}
|
|
|
|
for (i = 0, ln = names.length; i < ln; i++) {
|
|
name = prefix + names[i] + suffix;
|
|
|
|
if (map[name]) {
|
|
delete map[name];
|
|
Ext.Array.remove(classList, name);
|
|
}
|
|
}
|
|
|
|
dom.className = classList.join(' ');
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Replaces a CSS class on the element with another. If the old name does not exist, the new name will simply be added.
|
|
* @param {String} oldClassName The CSS class to replace.
|
|
* @param {String} newClassName The replacement CSS class.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
replaceCls: function(oldName, newName, prefix, suffix) {
|
|
return this.removeCls(oldName, prefix, suffix).addCls(newName, prefix, suffix);
|
|
},
|
|
|
|
/**
|
|
* Checks if the specified CSS class exists on this element's DOM node.
|
|
* @param {String} className The CSS class to check for.
|
|
* @return {Boolean} `true` if the class exists, else `false`.
|
|
*/
|
|
hasCls: function(name) {
|
|
if (!this.isSynchronized) {
|
|
this.synchronize();
|
|
}
|
|
|
|
return this.hasClassMap.hasOwnProperty(name);
|
|
},
|
|
|
|
/**
|
|
* Toggles the specified CSS class on this element (removes it if it already exists, otherwise adds it).
|
|
* @param {String} className The CSS class to toggle.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
toggleCls: function(className, force){
|
|
if (typeof force !== 'boolean') {
|
|
force = !this.hasCls(className);
|
|
}
|
|
|
|
return (force) ? this.addCls(className) : this.removeCls(className);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @param firstClass
|
|
* @param secondClass
|
|
* @param flag
|
|
* @param prefix
|
|
* @return {Mixed}
|
|
*/
|
|
swapCls: function(firstClass, secondClass, flag, prefix) {
|
|
if (flag === undefined) {
|
|
flag = true;
|
|
}
|
|
|
|
var addedClass = flag ? firstClass : secondClass,
|
|
removedClass = flag ? secondClass : firstClass;
|
|
|
|
if (removedClass) {
|
|
this.removeCls(prefix ? prefix + '-' + removedClass : removedClass);
|
|
}
|
|
|
|
if (addedClass) {
|
|
this.addCls(prefix ? prefix + '-' + addedClass : addedClass);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Set the width of this Element.
|
|
* @param {Number/String} width The new width.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
setWidth: function(width) {
|
|
return this.setLengthValue(this.WIDTH, width);
|
|
},
|
|
|
|
/**
|
|
* Set the height of this Element.
|
|
* @param {Number/String} height The new height.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
setHeight: function(height) {
|
|
return this.setLengthValue(this.HEIGHT, height);
|
|
},
|
|
|
|
/**
|
|
* Set the size of this Element.
|
|
*
|
|
* @param {Number/String} width The new width. This may be one of:
|
|
*
|
|
* - A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).
|
|
* - A String used to set the CSS width style. Animation may **not** be used.
|
|
* - A size object in the format `{width: widthValue, height: heightValue}`.
|
|
*
|
|
* @param {Number/String} height The new height. This may be one of:
|
|
*
|
|
* - A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels).
|
|
* - A String used to set the CSS height style. Animation may **not** be used.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
setSize: function(width, height) {
|
|
if (Ext.isObject(width)) {
|
|
// in case of object from getSize()
|
|
height = width.height;
|
|
width = width.width;
|
|
}
|
|
|
|
this.setWidth(width);
|
|
this.setHeight(height);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Set the minimum width of this Element.
|
|
* @param {Number/String} width The new minimum width.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
setMinWidth: function(width) {
|
|
return this.setLengthValue(this.MIN_WIDTH, width);
|
|
},
|
|
|
|
/**
|
|
* Set the minimum height of this Element.
|
|
* @param {Number/String} height The new minimum height.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
setMinHeight: function(height) {
|
|
return this.setLengthValue(this.MIN_HEIGHT, height);
|
|
},
|
|
|
|
/**
|
|
* Set the maximum width of this Element.
|
|
* @param {Number/String} width The new maximum width.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
setMaxWidth: function(width) {
|
|
return this.setLengthValue(this.MAX_WIDTH, width);
|
|
},
|
|
|
|
/**
|
|
* Set the maximum height of this Element.
|
|
* @param {Number/String} height The new maximum height.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
setMaxHeight: function(height) {
|
|
return this.setLengthValue(this.MAX_HEIGHT, height);
|
|
},
|
|
|
|
/**
|
|
* Sets the element's top position directly using CSS style (instead of {@link #setY}).
|
|
* @param {String} top The top CSS property value.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
setTop: function(top) {
|
|
return this.setLengthValue(this.TOP, top);
|
|
},
|
|
|
|
/**
|
|
* Sets the element's CSS right style.
|
|
* @param {String} right The right CSS property value.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
setRight: function(right) {
|
|
return this.setLengthValue(this.RIGHT, right);
|
|
},
|
|
|
|
/**
|
|
* Sets the element's CSS bottom style.
|
|
* @param {String} bottom The bottom CSS property value.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
setBottom: function(bottom) {
|
|
return this.setLengthValue(this.BOTTOM, bottom);
|
|
},
|
|
|
|
/**
|
|
* Sets the element's left position directly using CSS style (instead of {@link #setX}).
|
|
* @param {String} left The left CSS property value.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
setLeft: function(left) {
|
|
return this.setLengthValue(this.LEFT, left);
|
|
},
|
|
|
|
setMargin: function(margin) {
|
|
var domStyle = this.dom.style;
|
|
|
|
if (margin || margin === 0) {
|
|
margin = this.self.unitizeBox((margin === true) ? 5 : margin);
|
|
domStyle.setProperty('margin', margin, 'important');
|
|
}
|
|
else {
|
|
domStyle.removeProperty('margin-top');
|
|
domStyle.removeProperty('margin-right');
|
|
domStyle.removeProperty('margin-bottom');
|
|
domStyle.removeProperty('margin-left');
|
|
}
|
|
},
|
|
|
|
setPadding: function(padding) {
|
|
var domStyle = this.dom.style;
|
|
|
|
if (padding || padding === 0) {
|
|
padding = this.self.unitizeBox((padding === true) ? 5 : padding);
|
|
domStyle.setProperty('padding', padding, 'important');
|
|
}
|
|
else {
|
|
domStyle.removeProperty('padding-top');
|
|
domStyle.removeProperty('padding-right');
|
|
domStyle.removeProperty('padding-bottom');
|
|
domStyle.removeProperty('padding-left');
|
|
}
|
|
},
|
|
|
|
setBorder: function(border) {
|
|
var domStyle = this.dom.style;
|
|
|
|
if (border || border === 0) {
|
|
border = this.self.unitizeBox((border === true) ? 1 : border);
|
|
domStyle.setProperty('border-width', border, 'important');
|
|
}
|
|
else {
|
|
domStyle.removeProperty('border-top-width');
|
|
domStyle.removeProperty('border-right-width');
|
|
domStyle.removeProperty('border-bottom-width');
|
|
domStyle.removeProperty('border-left-width');
|
|
}
|
|
},
|
|
|
|
setLengthValue: function(name, value) {
|
|
var domStyle = this.dom.style;
|
|
|
|
if (value === null) {
|
|
domStyle.removeProperty(name);
|
|
return this;
|
|
}
|
|
|
|
if (typeof value == 'number') {
|
|
value = value + 'px';
|
|
}
|
|
|
|
domStyle.setProperty(name, value, 'important');
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Sets the visibility of the element (see details). If the `visibilityMode` is set to `Element.DISPLAY`, it will use
|
|
* the display property to hide the element, otherwise it uses visibility. The default is to hide and show using the `visibility` property.
|
|
* @param {Boolean} visible Whether the element is visible.
|
|
* @return {Ext.Element} this
|
|
*/
|
|
setVisible: function(visible) {
|
|
var mode = this.getVisibilityMode(),
|
|
method = visible ? 'removeCls' : 'addCls';
|
|
|
|
switch (mode) {
|
|
case this.VISIBILITY:
|
|
this.removeCls(['x-hidden-display', 'x-hidden-offsets']);
|
|
this[method]('x-hidden-visibility');
|
|
break;
|
|
|
|
case this.DISPLAY:
|
|
this.removeCls(['x-hidden-visibility', 'x-hidden-offsets']);
|
|
this[method]('x-hidden-display');
|
|
break;
|
|
|
|
case this.OFFSETS:
|
|
this.removeCls(['x-hidden-visibility', 'x-hidden-display']);
|
|
this[method]('x-hidden-offsets');
|
|
break;
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
getVisibilityMode: function() {
|
|
var dom = this.dom,
|
|
mode = Ext.dom.Element.data(dom, 'visibilityMode');
|
|
|
|
if (mode === undefined) {
|
|
Ext.dom.Element.data(dom, 'visibilityMode', mode = this.DISPLAY);
|
|
}
|
|
|
|
return mode;
|
|
},
|
|
|
|
/**
|
|
* Use this to change the visibility mode between {@link #VISIBILITY}, {@link #DISPLAY} or {@link #OFFSETS}.
|
|
*/
|
|
setVisibilityMode: function(mode) {
|
|
this.self.data(this.dom, 'visibilityMode', mode);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Shows this element.
|
|
* Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
|
|
*/
|
|
show: function() {
|
|
var dom = this.dom;
|
|
if (dom) {
|
|
dom.style.removeProperty('display');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Hides this element.
|
|
* Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
|
|
*/
|
|
hide: function() {
|
|
this.dom.style.setProperty('display', 'none', 'important');
|
|
},
|
|
|
|
setVisibility: function(isVisible) {
|
|
var domStyle = this.dom.style;
|
|
|
|
if (isVisible) {
|
|
domStyle.removeProperty('visibility');
|
|
}
|
|
else {
|
|
domStyle.setProperty('visibility', 'hidden', 'important');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* This shared object is keyed by style name (e.g., 'margin-left' or 'marginLeft'). The
|
|
* values are objects with the following properties:
|
|
*
|
|
* * `name` (String) : The actual name to be presented to the DOM. This is typically the value
|
|
* returned by {@link #normalize}.
|
|
* * `get` (Function) : A hook function that will perform the get on this style. These
|
|
* functions receive "(dom, el)" arguments. The `dom` parameter is the DOM Element
|
|
* from which to get the style. The `el` argument (may be `null`) is the Ext.Element.
|
|
* * `set` (Function) : A hook function that will perform the set on this style. These
|
|
* functions receive "(dom, value, el)" arguments. The `dom` parameter is the DOM Element
|
|
* from which to get this style. The `value` parameter is the new value for the style. The
|
|
* `el` argument (may be `null`) is the Ext.Element.
|
|
*
|
|
* The `this` pointer is the object that contains `get` or `set`, which means that
|
|
* `this.name` can be accessed if needed. The hook functions are both optional.
|
|
* @private
|
|
*/
|
|
styleHooks: {},
|
|
|
|
// @private
|
|
addStyles: function(sides, styles) {
|
|
var totalSize = 0,
|
|
sidesArr = sides.match(this.wordsRe),
|
|
i = 0,
|
|
len = sidesArr.length,
|
|
side, size;
|
|
for (; i < len; i++) {
|
|
side = sidesArr[i];
|
|
size = side && parseInt(this.getStyle(styles[side]), 10);
|
|
if (size) {
|
|
totalSize += Math.abs(size);
|
|
}
|
|
}
|
|
return totalSize;
|
|
},
|
|
|
|
/**
|
|
* Checks if the current value of a style is equal to a given value.
|
|
* @param {String} style property whose value is returned.
|
|
* @param {String} value to check against.
|
|
* @return {Boolean} `true` for when the current value equals the given value.
|
|
*/
|
|
isStyle: function(style, val) {
|
|
return this.getStyle(style) == val;
|
|
},
|
|
|
|
getStyleValue: function(name) {
|
|
return this.dom.style.getPropertyValue(name);
|
|
},
|
|
|
|
/**
|
|
* Normalizes `currentStyle` and `computedStyle`.
|
|
* @param {String} prop The style property whose value is returned.
|
|
* @return {String} The current value of the style property for this element.
|
|
*/
|
|
getStyle: function(prop) {
|
|
var me = this,
|
|
dom = me.dom,
|
|
hook = me.styleHooks[prop],
|
|
cs, result;
|
|
|
|
if (dom == document) {
|
|
return null;
|
|
}
|
|
if (!hook) {
|
|
me.styleHooks[prop] = hook = { name: Ext.dom.Element.normalize(prop) };
|
|
}
|
|
if (hook.get) {
|
|
return hook.get(dom, me);
|
|
}
|
|
|
|
cs = window.getComputedStyle(dom, '');
|
|
|
|
// why the dom.style lookup? It is not true that "style == computedStyle" as
|
|
// well as the fact that 0/false are valid answers...
|
|
result = (cs && cs[hook.name]); // || dom.style[hook.name];
|
|
|
|
// WebKit returns rgb values for transparent, how does this work n IE9+
|
|
// if (!supportsTransparentColor && result == 'rgba(0, 0, 0, 0)') {
|
|
// result = 'transparent';
|
|
// }
|
|
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* Wrapper for setting style properties, also takes single object parameter of multiple styles.
|
|
* @param {String/Object} property The style property to be set, or an object of multiple styles.
|
|
* @param {String} [value] The value to apply to the given property, or `null` if an object was passed.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
setStyle: function(prop, value) {
|
|
var me = this,
|
|
dom = me.dom,
|
|
hooks = me.styleHooks,
|
|
style = dom.style,
|
|
valueFrom = Ext.valueFrom,
|
|
name, hook;
|
|
|
|
// we don't promote the 2-arg form to object-form to avoid the overhead...
|
|
if (typeof prop == 'string') {
|
|
hook = hooks[prop];
|
|
|
|
if (!hook) {
|
|
hooks[prop] = hook = { name: Ext.dom.Element.normalize(prop) };
|
|
}
|
|
value = valueFrom(value, '');
|
|
|
|
if (hook.set) {
|
|
hook.set(dom, value, me);
|
|
} else {
|
|
style[hook.name] = value;
|
|
}
|
|
}
|
|
else {
|
|
for (name in prop) {
|
|
if (prop.hasOwnProperty(name)) {
|
|
hook = hooks[name];
|
|
|
|
if (!hook) {
|
|
hooks[name] = hook = { name: Ext.dom.Element.normalize(name) };
|
|
}
|
|
|
|
value = valueFrom(prop[name], '');
|
|
|
|
if (hook.set) {
|
|
hook.set(dom, value, me);
|
|
}
|
|
else {
|
|
style[hook.name] = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return me;
|
|
},
|
|
|
|
/**
|
|
* Returns the offset height of the element.
|
|
* @param {Boolean} [contentHeight] `true` to get the height minus borders and padding.
|
|
* @return {Number} The element's height.
|
|
*/
|
|
getHeight: function(contentHeight) {
|
|
var dom = this.dom,
|
|
height = contentHeight ? (dom.clientHeight - this.getPadding("tb")) : dom.offsetHeight;
|
|
return height > 0 ? height : 0;
|
|
},
|
|
|
|
/**
|
|
* Returns the offset width of the element.
|
|
* @param {Boolean} [contentWidth] `true` to get the width minus borders and padding.
|
|
* @return {Number} The element's width.
|
|
*/
|
|
getWidth: function(contentWidth) {
|
|
var dom = this.dom,
|
|
width = contentWidth ? (dom.clientWidth - this.getPadding("lr")) : dom.offsetWidth;
|
|
return width > 0 ? width : 0;
|
|
},
|
|
|
|
/**
|
|
* Gets the width of the border(s) for the specified side(s)
|
|
* @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
|
|
* passing `'lr'` would get the border **l**eft width + the border **r**ight width.
|
|
* @return {Number} The width of the sides passed added together
|
|
*/
|
|
getBorderWidth: function(side) {
|
|
return this.addStyles(side, this.borders);
|
|
},
|
|
|
|
/**
|
|
* Gets the width of the padding(s) for the specified side(s).
|
|
* @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
|
|
* passing `'lr'` would get the padding **l**eft + the padding **r**ight.
|
|
* @return {Number} The padding of the sides passed added together.
|
|
*/
|
|
getPadding: function(side) {
|
|
return this.addStyles(side, this.paddings);
|
|
},
|
|
|
|
/**
|
|
* More flexible version of {@link #setStyle} for setting style properties.
|
|
* @param {String/Object/Function} styles A style specification string, e.g. "width:100px", or object in the form `{width:"100px"}`, or
|
|
* a function which returns such a specification.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
applyStyles: function(styles) {
|
|
if (styles) {
|
|
var dom = this.dom,
|
|
styleType, i, len;
|
|
|
|
if (typeof styles == 'function') {
|
|
styles = styles.call();
|
|
}
|
|
styleType = typeof styles;
|
|
if (styleType == 'string') {
|
|
styles = Ext.util.Format.trim(styles).split(this.styleSplitRe);
|
|
for (i = 0, len = styles.length; i < len;) {
|
|
dom.style[Ext.dom.Element.normalize(styles[i++])] = styles[i++];
|
|
}
|
|
}
|
|
else if (styleType == 'object') {
|
|
this.setStyle(styles);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns the size of the element.
|
|
* @param {Boolean} [contentSize] `true` to get the width/size minus borders and padding.
|
|
* @return {Object} An object containing the element's size:
|
|
* @return {Number} return.width
|
|
* @return {Number} return.height
|
|
*/
|
|
getSize: function(contentSize) {
|
|
var dom = this.dom;
|
|
return {
|
|
width: Math.max(0, contentSize ? (dom.clientWidth - this.getPadding("lr")) : dom.offsetWidth),
|
|
height: Math.max(0, contentSize ? (dom.clientHeight - this.getPadding("tb")) : dom.offsetHeight)
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Forces the browser to repaint this element.
|
|
* @return {Ext.dom.Element} this
|
|
*/
|
|
repaint: function() {
|
|
var dom = this.dom;
|
|
this.addCls(Ext.baseCSSPrefix + 'repaint');
|
|
setTimeout(function() {
|
|
Ext.fly(dom).removeCls(Ext.baseCSSPrefix + 'repaint');
|
|
}, 1);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Returns an object with properties top, left, right and bottom representing the margins of this element unless sides is passed,
|
|
* then it returns the calculated width of the sides (see {@link #getPadding}).
|
|
* @param {String} [sides] Any combination of 'l', 'r', 't', 'b' to get the sum of those sides.
|
|
* @return {Object/Number}
|
|
*/
|
|
getMargin: function(side) {
|
|
var me = this,
|
|
hash = {t: "top", l: "left", r: "right", b: "bottom"},
|
|
o = {},
|
|
key;
|
|
|
|
if (!side) {
|
|
for (key in me.margins) {
|
|
o[hash[key]] = parseFloat(me.getStyle(me.margins[key])) || 0;
|
|
}
|
|
return o;
|
|
} else {
|
|
return me.addStyles.call(me, side, me.margins);
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
//@tag dom,core
|
|
//@define Ext.Element-all
|
|
//@define Ext.Element-traversal
|
|
//@require Ext.Element-style
|
|
|
|
/**
|
|
* @class Ext.dom.Element
|
|
*/
|
|
Ext.dom.Element.addMembers({
|
|
getParent: function() {
|
|
return Ext.get(this.dom.parentNode);
|
|
},
|
|
|
|
getFirstChild: function() {
|
|
return Ext.get(this.dom.firstElementChild);
|
|
},
|
|
|
|
/**
|
|
* Returns `true` if this element is an ancestor of the passed element.
|
|
* @param {HTMLElement/String} element The element to check.
|
|
* @return {Boolean} `true` if this element is an ancestor of `el`, else `false`.
|
|
*/
|
|
contains: function(element) {
|
|
if (!element) {
|
|
return false;
|
|
}
|
|
|
|
var dom = Ext.getDom(element);
|
|
|
|
// we need el-contains-itself logic here because isAncestor does not do that:
|
|
return (dom === this.dom) || this.self.isAncestor(this.dom, dom);
|
|
},
|
|
|
|
/**
|
|
* Looks at this node and then at parent nodes for a match of the passed simple selector (e.g. 'div.some-class' or 'span:first-child')
|
|
* @param {String} selector The simple selector to test.
|
|
* @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
|
|
* The max depth to search as a number or element (defaults to `50 || document.body`)
|
|
* @param {Boolean} returnEl (optional) `true` to return a Ext.Element object instead of DOM node.
|
|
* @return {HTMLElement/null} The matching DOM node (or `null` if no match was found).
|
|
*/
|
|
findParent: function(simpleSelector, maxDepth, returnEl) {
|
|
var p = this.dom,
|
|
b = document.body,
|
|
depth = 0,
|
|
stopEl;
|
|
|
|
maxDepth = maxDepth || 50;
|
|
if (isNaN(maxDepth)) {
|
|
stopEl = Ext.getDom(maxDepth);
|
|
maxDepth = Number.MAX_VALUE;
|
|
}
|
|
while (p && p.nodeType == 1 && depth < maxDepth && p != b && p != stopEl) {
|
|
if (Ext.DomQuery.is(p, simpleSelector)) {
|
|
return returnEl ? Ext.get(p) : p;
|
|
}
|
|
depth++;
|
|
p = p.parentNode;
|
|
}
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* Looks at parent nodes for a match of the passed simple selector (e.g. 'div.some-class' or 'span:first-child').
|
|
* @param {String} selector The simple selector to test.
|
|
* @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
|
|
* The max depth to search as a number or element (defaults to `10 || document.body`).
|
|
* @param {Boolean} returnEl (optional) `true` to return a Ext.Element object instead of DOM node.
|
|
* @return {HTMLElement/null} The matching DOM node (or `null` if no match was found).
|
|
*/
|
|
findParentNode: function(simpleSelector, maxDepth, returnEl) {
|
|
var p = Ext.fly(this.dom.parentNode, '_internal');
|
|
return p ? p.findParent(simpleSelector, maxDepth, returnEl) : null;
|
|
},
|
|
|
|
/**
|
|
* Walks up the dom looking for a parent node that matches the passed simple selector (e.g. 'div.some-class' or 'span:first-child').
|
|
* This is a shortcut for `findParentNode()` that always returns an Ext.dom.Element.
|
|
* @param {String} selector The simple selector to test
|
|
* @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
|
|
* The max depth to search as a number or element (defaults to `10 || document.body`).
|
|
* @return {Ext.dom.Element/null} The matching DOM node (or `null` if no match was found).
|
|
*/
|
|
up: function(simpleSelector, maxDepth) {
|
|
return this.findParentNode(simpleSelector, maxDepth, true);
|
|
},
|
|
|
|
select: function(selector, composite) {
|
|
return Ext.dom.Element.select(selector, this.dom, composite);
|
|
},
|
|
|
|
/**
|
|
* Selects child nodes based on the passed CSS selector (the selector should not contain an id).
|
|
* @param {String} selector The CSS selector.
|
|
* @return {HTMLElement[]} An array of the matched nodes.
|
|
*/
|
|
query: function(selector) {
|
|
return Ext.DomQuery.select(selector, this.dom);
|
|
},
|
|
|
|
/**
|
|
* Selects a single child at any depth below this element based on the passed CSS selector (the selector should not contain an id).
|
|
* @param {String} selector The CSS selector.
|
|
* @param {Boolean} [returnDom=false] (optional) `true` to return the DOM node instead of Ext.dom.Element.
|
|
* @return {HTMLElement/Ext.dom.Element} The child Ext.dom.Element (or DOM node if `returnDom` is `true`).
|
|
*/
|
|
down: function(selector, returnDom) {
|
|
var n = Ext.DomQuery.selectNode(selector, this.dom);
|
|
return returnDom ? n : Ext.get(n);
|
|
},
|
|
|
|
/**
|
|
* Selects a single *direct* child based on the passed CSS selector (the selector should not contain an id).
|
|
* @param {String} selector The CSS selector.
|
|
* @param {Boolean} [returnDom=false] (optional) `true` to return the DOM node instead of Ext.dom.Element.
|
|
* @return {HTMLElement/Ext.dom.Element} The child Ext.dom.Element (or DOM node if `returnDom` is `true`)
|
|
*/
|
|
child: function(selector, returnDom) {
|
|
var node,
|
|
me = this,
|
|
id;
|
|
id = Ext.get(me).id;
|
|
// Escape . or :
|
|
id = id.replace(/[\.:]/g, "\\$0");
|
|
node = Ext.DomQuery.selectNode('#' + id + " > " + selector, me.dom);
|
|
return returnDom ? node : Ext.get(node);
|
|
},
|
|
|
|
/**
|
|
* Gets the parent node for this element, optionally chaining up trying to match a selector.
|
|
* @param {String} selector (optional) Find a parent node that matches the passed simple selector.
|
|
* @param {Boolean} returnDom (optional) `true` to return a raw DOM node instead of an Ext.dom.Element.
|
|
* @return {Ext.dom.Element/HTMLElement/null} The parent node or `null`.
|
|
*/
|
|
parent: function(selector, returnDom) {
|
|
return this.matchNode('parentNode', 'parentNode', selector, returnDom);
|
|
},
|
|
|
|
/**
|
|
* Gets the next sibling, skipping text nodes.
|
|
* @param {String} selector (optional) Find the next sibling that matches the passed simple selector.
|
|
* @param {Boolean} returnDom (optional) `true` to return a raw dom node instead of an Ext.dom.Element.
|
|
* @return {Ext.dom.Element/HTMLElement/null} The next sibling or `null`.
|
|
*/
|
|
next: function(selector, returnDom) {
|
|
return this.matchNode('nextSibling', 'nextSibling', selector, returnDom);
|
|
},
|
|
|
|
/**
|
|
* Gets the previous sibling, skipping text nodes.
|
|
* @param {String} selector (optional) Find the previous sibling that matches the passed simple selector.
|
|
* @param {Boolean} returnDom (optional) `true` to return a raw DOM node instead of an Ext.dom.Element
|
|
* @return {Ext.dom.Element/HTMLElement/null} The previous sibling or `null`.
|
|
*/
|
|
prev: function(selector, returnDom) {
|
|
return this.matchNode('previousSibling', 'previousSibling', selector, returnDom);
|
|
},
|
|
|
|
|
|
/**
|
|
* Gets the first child, skipping text nodes.
|
|
* @param {String} selector (optional) Find the next sibling that matches the passed simple selector.
|
|
* @param {Boolean} returnDom (optional) `true` to return a raw DOM node instead of an Ext.dom.Element.
|
|
* @return {Ext.dom.Element/HTMLElement/null} The first child or `null`.
|
|
*/
|
|
first: function(selector, returnDom) {
|
|
return this.matchNode('nextSibling', 'firstChild', selector, returnDom);
|
|
},
|
|
|
|
/**
|
|
* Gets the last child, skipping text nodes.
|
|
* @param {String} selector (optional) Find the previous sibling that matches the passed simple selector.
|
|
* @param {Boolean} returnDom (optional) `true` to return a raw DOM node instead of an Ext.dom.Element.
|
|
* @return {Ext.dom.Element/HTMLElement/null} The last child or `null`.
|
|
*/
|
|
last: function(selector, returnDom) {
|
|
return this.matchNode('previousSibling', 'lastChild', selector, returnDom);
|
|
},
|
|
|
|
matchNode: function(dir, start, selector, returnDom) {
|
|
if (!this.dom) {
|
|
return null;
|
|
}
|
|
|
|
var n = this.dom[start];
|
|
while (n) {
|
|
if (n.nodeType == 1 && (!selector || Ext.DomQuery.is(n, selector))) {
|
|
return !returnDom ? Ext.get(n) : n;
|
|
}
|
|
n = n[dir];
|
|
}
|
|
return null;
|
|
},
|
|
|
|
isAncestor: function(element) {
|
|
return this.self.isAncestor.call(this.self, this.dom, element);
|
|
}
|
|
});
|
|
|
|
//@tag dom,core
|
|
//@require Ext.Element-all
|
|
|
|
/**
|
|
* This class encapsulates a *collection* of DOM elements, providing methods to filter members, or to perform collective
|
|
* actions upon the whole set.
|
|
*
|
|
* Although they are not listed, this class supports all of the methods of {@link Ext.dom.Element} and
|
|
* {@link Ext.Anim}. The methods from these classes will be performed on all the elements in this collection.
|
|
*
|
|
* Example:
|
|
*
|
|
* var els = Ext.select("#some-el div.some-class");
|
|
* // or select directly from an existing element
|
|
* var el = Ext.get('some-el');
|
|
* el.select('div.some-class');
|
|
*
|
|
* els.setWidth(100); // all elements become 100 width
|
|
* els.hide(true); // all elements fade out and hide
|
|
* // or
|
|
* els.setWidth(100).hide(true);
|
|
*
|
|
* @mixins Ext.dom.Element
|
|
*/
|
|
Ext.define('Ext.dom.CompositeElementLite', {
|
|
alternateClassName: ['Ext.CompositeElementLite', 'Ext.CompositeElement'],
|
|
|
|
requires: ['Ext.dom.Element'],
|
|
|
|
// We use the @mixins tag above to document that CompositeElement has
|
|
// all the same methods as Element, but the @mixins tag also pulls in
|
|
// configs and properties which we don't want, so hide them explicitly:
|
|
/** @cfg bubbleEvents @hide */
|
|
/** @cfg listeners @hide */
|
|
/** @property DISPLAY @hide */
|
|
/** @property OFFSETS @hide */
|
|
/** @property VISIBILITY @hide */
|
|
/** @property defaultUnit @hide */
|
|
/** @property dom @hide */
|
|
/** @property id @hide */
|
|
// Also hide the static #get method that also gets inherited
|
|
/** @method get @static @hide */
|
|
|
|
statics: {
|
|
/**
|
|
* @private
|
|
* @static
|
|
* Copies all of the functions from Ext.dom.Element's prototype onto CompositeElementLite's prototype.
|
|
*/
|
|
importElementMethods: function() {
|
|
|
|
}
|
|
},
|
|
|
|
constructor: function(elements, root) {
|
|
/**
|
|
* @property {HTMLElement[]} elements
|
|
* @readonly
|
|
* The Array of DOM elements which this CompositeElement encapsulates.
|
|
*
|
|
* This will not *usually* be accessed in developers' code, but developers wishing to augment the capabilities
|
|
* of the CompositeElementLite class may use it when adding methods to the class.
|
|
*
|
|
* For example to add the `nextAll` method to the class to **add** all following siblings of selected elements,
|
|
* the code would be
|
|
*
|
|
* Ext.override(Ext.dom.CompositeElementLite, {
|
|
* nextAll: function() {
|
|
* var elements = this.elements, i, l = elements.length, n, r = [], ri = -1;
|
|
*
|
|
* // Loop through all elements in this Composite, accumulating
|
|
* // an Array of all siblings.
|
|
* for (i = 0; i < l; i++) {
|
|
* for (n = elements[i].nextSibling; n; n = n.nextSibling) {
|
|
* r[++ri] = n;
|
|
* }
|
|
* }
|
|
*
|
|
* // Add all found siblings to this Composite
|
|
* return this.add(r);
|
|
* }
|
|
* });
|
|
*/
|
|
this.elements = [];
|
|
this.add(elements, root);
|
|
this.el = new Ext.dom.Element.Fly();
|
|
},
|
|
|
|
isComposite: true,
|
|
|
|
// @private
|
|
getElement: function(el) {
|
|
// Set the shared flyweight dom property to the current element
|
|
return this.el.attach(el).synchronize();
|
|
},
|
|
|
|
// @private
|
|
transformElement: function(el) {
|
|
return Ext.getDom(el);
|
|
},
|
|
|
|
/**
|
|
* Returns the number of elements in this Composite.
|
|
* @return {Number}
|
|
*/
|
|
getCount: function() {
|
|
return this.elements.length;
|
|
},
|
|
|
|
/**
|
|
* Adds elements to this Composite object.
|
|
* @param {HTMLElement[]/Ext.dom.CompositeElementLite} els Either an Array of DOM elements to add, or another Composite
|
|
* object who's elements should be added.
|
|
* @param {HTMLElement/String} [root] The root element of the query or id of the root.
|
|
* @return {Ext.dom.CompositeElementLite} This Composite object.
|
|
*/
|
|
add: function(els, root) {
|
|
var elements = this.elements,
|
|
i, ln;
|
|
|
|
if (!els) {
|
|
return this;
|
|
}
|
|
|
|
if (typeof els == "string") {
|
|
els = Ext.dom.Element.selectorFunction(els, root);
|
|
}
|
|
else if (els.isComposite) {
|
|
els = els.elements;
|
|
}
|
|
else if (!Ext.isIterable(els)) {
|
|
els = [els];
|
|
}
|
|
|
|
for (i = 0, ln = els.length; i < ln; ++i) {
|
|
elements.push(this.transformElement(els[i]));
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
invoke: function(fn, args) {
|
|
var elements = this.elements,
|
|
ln = elements.length,
|
|
element,
|
|
i;
|
|
|
|
for (i = 0; i < ln; i++) {
|
|
element = elements[i];
|
|
|
|
if (element) {
|
|
Ext.dom.Element.prototype[fn].apply(this.getElement(element), args);
|
|
}
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Returns a flyweight Element of the dom element object at the specified index.
|
|
* @param {Number} index
|
|
* @return {Ext.dom.Element}
|
|
*/
|
|
item: function(index) {
|
|
var el = this.elements[index],
|
|
out = null;
|
|
|
|
if (el) {
|
|
out = this.getElement(el);
|
|
}
|
|
|
|
return out;
|
|
},
|
|
|
|
// fixes scope with flyweight.
|
|
addListener: function(eventName, handler, scope, opt) {
|
|
var els = this.elements,
|
|
len = els.length,
|
|
i, e;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
e = els[i];
|
|
if (e) {
|
|
e.on(eventName, handler, scope || e, opt);
|
|
}
|
|
}
|
|
return this;
|
|
},
|
|
/**
|
|
* Calls the passed function for each element in this composite.
|
|
* @param {Function} fn The function to call.
|
|
* @param {Ext.dom.Element} fn.el The current Element in the iteration. **This is the flyweight
|
|
* (shared) Ext.dom.Element instance, so if you require a a reference to the dom node, use el.dom.**
|
|
* @param {Ext.dom.CompositeElementLite} fn.c This Composite object.
|
|
* @param {Number} fn.index The zero-based index in the iteration.
|
|
* @param {Object} [scope] The scope (this reference) in which the function is executed.
|
|
* Defaults to the Element.
|
|
* @return {Ext.dom.CompositeElementLite} this
|
|
*/
|
|
each: function(fn, scope) {
|
|
var me = this,
|
|
els = me.elements,
|
|
len = els.length,
|
|
i, e;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
e = els[i];
|
|
if (e) {
|
|
e = this.getElement(e);
|
|
if (fn.call(scope || e, e, me, i) === false) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return me;
|
|
},
|
|
|
|
/**
|
|
* Clears this Composite and adds the elements passed.
|
|
* @param {HTMLElement[]/Ext.dom.CompositeElementLite} els Either an array of DOM elements, or another Composite from which
|
|
* to fill this Composite.
|
|
* @return {Ext.dom.CompositeElementLite} this
|
|
*/
|
|
fill: function(els) {
|
|
var me = this;
|
|
me.elements = [];
|
|
me.add(els);
|
|
return me;
|
|
},
|
|
|
|
/**
|
|
* Filters this composite to only elements that match the passed selector.
|
|
* @param {String/Function} selector A string CSS selector or a comparison function. The comparison function will be
|
|
* called with the following arguments:
|
|
* @param {Ext.dom.Element} selector.el The current DOM element.
|
|
* @param {Number} selector.index The current index within the collection.
|
|
* @return {Ext.dom.CompositeElementLite} this
|
|
*/
|
|
filter: function(selector) {
|
|
var els = [],
|
|
me = this,
|
|
fn = Ext.isFunction(selector) ? selector
|
|
: function(el) {
|
|
return el.is(selector);
|
|
};
|
|
|
|
me.each(function(el, self, i) {
|
|
if (fn(el, i) !== false) {
|
|
els[els.length] = me.transformElement(el);
|
|
}
|
|
});
|
|
|
|
me.elements = els;
|
|
return me;
|
|
},
|
|
|
|
/**
|
|
* Find the index of the passed element within the composite collection.
|
|
* @param {String/HTMLElement/Ext.Element/Number} el The id of an element, or an Ext.dom.Element, or an HtmlElement
|
|
* to find within the composite collection.
|
|
* @return {Number} The index of the passed Ext.dom.Element in the composite collection, or -1 if not found.
|
|
*/
|
|
indexOf: function(el) {
|
|
return Ext.Array.indexOf(this.elements, this.transformElement(el));
|
|
},
|
|
|
|
/**
|
|
* Replaces the specified element with the passed element.
|
|
* @param {String/HTMLElement/Ext.Element/Number} el The id of an element, the Element itself, the index of the
|
|
* element in this composite to replace.
|
|
* @param {String/Ext.Element} replacement The id of an element or the Element itself.
|
|
* @param {Boolean} [domReplace] `true` to remove and replace the element in the document too.
|
|
* @return {Ext.dom.CompositeElementLite} this
|
|
*/
|
|
replaceElement: function(el, replacement, domReplace) {
|
|
var index = !isNaN(el) ? el : this.indexOf(el),
|
|
d;
|
|
if (index > -1) {
|
|
replacement = Ext.getDom(replacement);
|
|
if (domReplace) {
|
|
d = this.elements[index];
|
|
d.parentNode.insertBefore(replacement, d);
|
|
Ext.removeNode(d);
|
|
}
|
|
Ext.Array.splice(this.elements, index, 1, replacement);
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Removes all elements.
|
|
*/
|
|
clear: function() {
|
|
this.elements = [];
|
|
},
|
|
|
|
addElements: function(els, root) {
|
|
if (!els) {
|
|
return this;
|
|
}
|
|
|
|
if (typeof els == "string") {
|
|
els = Ext.dom.Element.selectorFunction(els, root);
|
|
}
|
|
|
|
var yels = this.elements;
|
|
|
|
Ext.each(els, function(e) {
|
|
yels.push(Ext.get(e));
|
|
});
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Returns the first Element
|
|
* @return {Ext.dom.Element}
|
|
*/
|
|
first: function() {
|
|
return this.item(0);
|
|
},
|
|
|
|
/**
|
|
* Returns the last Element
|
|
* @return {Ext.dom.Element}
|
|
*/
|
|
last: function() {
|
|
return this.item(this.getCount() - 1);
|
|
},
|
|
|
|
/**
|
|
* Returns `true` if this composite contains the passed element
|
|
* @param {String/HTMLElement/Ext.Element/Number} el The id of an element, or an Ext.Element, or an HtmlElement to
|
|
* find within the composite collection.
|
|
* @return {Boolean}
|
|
*/
|
|
contains: function(el) {
|
|
return this.indexOf(el) != -1;
|
|
},
|
|
|
|
/**
|
|
* Removes the specified element(s).
|
|
* @param {String/HTMLElement/Ext.Element/Number} el The id of an element, the Element itself, the index of the
|
|
* element in this composite or an array of any of those.
|
|
* @param {Boolean} [removeDom] `true` to also remove the element from the document
|
|
* @return {Ext.dom.CompositeElementLite} this
|
|
*/
|
|
removeElement: function(keys, removeDom) {
|
|
var me = this,
|
|
elements = this.elements,
|
|
el;
|
|
|
|
Ext.each(keys, function(val) {
|
|
if ((el = (elements[val] || elements[val = me.indexOf(val)]))) {
|
|
if (removeDom) {
|
|
if (el.dom) {
|
|
el.remove();
|
|
}
|
|
else {
|
|
Ext.removeNode(el);
|
|
}
|
|
}
|
|
Ext.Array.erase(elements, val, 1);
|
|
}
|
|
});
|
|
|
|
return this;
|
|
}
|
|
|
|
}, function() {
|
|
var Element = Ext.dom.Element,
|
|
elementPrototype = Element.prototype,
|
|
prototype = this.prototype,
|
|
name;
|
|
|
|
for (name in elementPrototype) {
|
|
if (typeof elementPrototype[name] == 'function'){
|
|
(function(key) {
|
|
prototype[key] = prototype[key] || function() {
|
|
return this.invoke(key, arguments);
|
|
};
|
|
}).call(prototype, name);
|
|
}
|
|
}
|
|
|
|
prototype.on = prototype.addListener;
|
|
|
|
if (Ext.DomQuery){
|
|
Element.selectorFunction = Ext.DomQuery.select;
|
|
}
|
|
|
|
/**
|
|
* Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
|
|
* to be applied to many related elements in one statement through the returned
|
|
* {@link Ext.dom.CompositeElementLite CompositeElementLite} object.
|
|
* @param {String/HTMLElement[]} selector The CSS selector or an array of elements
|
|
* @param {HTMLElement/String} [root] The root element of the query or id of the root
|
|
* @return {Ext.dom.CompositeElementLite}
|
|
* @member Ext.dom.Element
|
|
* @method select
|
|
*/
|
|
Element.select = function(selector, root) {
|
|
var elements;
|
|
|
|
if (typeof selector == "string") {
|
|
elements = Element.selectorFunction(selector, root);
|
|
}
|
|
else if (selector.length !== undefined) {
|
|
elements = selector;
|
|
}
|
|
else {
|
|
//<debug>
|
|
throw new Error("[Ext.select] Invalid selector specified: " + selector);
|
|
//</debug>
|
|
}
|
|
|
|
return new Ext.CompositeElementLite(elements);
|
|
};
|
|
|
|
/**
|
|
* @member Ext
|
|
* @method select
|
|
* @alias Ext.dom.Element#select
|
|
*/
|
|
Ext.select = function() {
|
|
return Element.select.apply(Element, arguments);
|
|
};
|
|
});
|
|
|
|
Ext.ClassManager.addNameAlternateMappings({
|
|
"Ext.app.Profile": [],
|
|
"Ext.event.recognizer.MultiTouch": [],
|
|
"Ext.fx.Runner": [],
|
|
"Ext.chart.grid.CircularGrid": [],
|
|
"Ext.mixin.Templatable": [],
|
|
"Ext.event.recognizer.Pinch": [],
|
|
"Ext.util.Format": [],
|
|
"Ext.direct.JsonProvider": [],
|
|
"Ext.data.identifier.Simple": [],
|
|
"Ext.dataview.DataView": [
|
|
"Ext.DataView"
|
|
],
|
|
"Ext.field.Hidden": [
|
|
"Ext.form.Hidden"
|
|
],
|
|
"Ext.field.Number": [
|
|
"Ext.form.Number"
|
|
],
|
|
"Ext.chart.series.CandleStick": [],
|
|
"Ext.device.Connection": [],
|
|
"Ext.data.Model": [
|
|
"Ext.data.Record"
|
|
],
|
|
"Ext.data.reader.Reader": [
|
|
"Ext.data.Reader",
|
|
"Ext.data.DataReader"
|
|
],
|
|
"Ext.Sheet": [],
|
|
"Ext.tab.Tab": [
|
|
"Ext.Tab"
|
|
],
|
|
"Ext.chart.series.sprite.StackedCartesian": [],
|
|
"Ext.util.Grouper": [],
|
|
"Ext.util.translatable.CssPosition": [],
|
|
"Ext.util.paintmonitor.Abstract": [],
|
|
"Ext.direct.RemotingProvider": [],
|
|
"Ext.data.NodeInterface": [
|
|
"Ext.data.Node"
|
|
],
|
|
"Ext.chart.interactions.PanZoom": [],
|
|
"Ext.util.PositionMap": [],
|
|
"Ext.chart.series.ItemPublisher": [],
|
|
"Ext.util.Sortable": [],
|
|
"Ext.chart.series.sprite.AbstractRadial": [],
|
|
"Ext.fx.runner.Css": [],
|
|
"Ext.fx.runner.CssTransition": [],
|
|
"Ext.draw.Group": [],
|
|
"Ext.XTemplateCompiler": [],
|
|
"Ext.util.Wrapper": [],
|
|
"Ext.app.Router": [],
|
|
"Ext.direct.Transaction": [
|
|
"Ext.Direct.Transaction"
|
|
],
|
|
"Ext.util.Offset": [],
|
|
"Ext.device.device.Abstract": [],
|
|
"Ext.mixin.Mixin": [],
|
|
"Ext.fx.animation.FadeOut": [],
|
|
"Ext.util.Geolocation": [
|
|
"Ext.util.GeoLocation"
|
|
],
|
|
"Ext.ComponentManager": [
|
|
"Ext.ComponentMgr"
|
|
],
|
|
"Ext.util.sizemonitor.OverflowChange": [],
|
|
"Ext.event.publisher.ElementSize": [],
|
|
"Ext.tab.Bar": [
|
|
"Ext.TabBar"
|
|
],
|
|
"Ext.event.Dom": [],
|
|
"Ext.app.Application": [],
|
|
"Ext.dataview.List": [
|
|
"Ext.List"
|
|
],
|
|
"Ext.util.translatable.Dom": [],
|
|
"Ext.fx.layout.card.Scroll": [],
|
|
"Ext.draw.LimitedCache": [],
|
|
"Ext.device.geolocation.Sencha": [],
|
|
"Ext.dataview.ListItemHeader": [],
|
|
"Ext.event.publisher.TouchGesture": [],
|
|
"Ext.data.SortTypes": [],
|
|
"Ext.device.contacts.Abstract": [],
|
|
"Ext.device.push.Sencha": [],
|
|
"Ext.fx.animation.WipeOut": [],
|
|
"Ext.slider.Slider": [],
|
|
"Ext.Component": [
|
|
"Ext.lib.Component"
|
|
],
|
|
"Ext.device.communicator.Default": [],
|
|
"Ext.fx.runner.CssAnimation": [],
|
|
"Ext.chart.axis.Axis": [],
|
|
"Ext.fx.animation.Cube": [],
|
|
"Ext.chart.Markers": [],
|
|
"Ext.chart.series.sprite.Radar": [],
|
|
"Ext.device.device.Simulator": [],
|
|
"Ext.Ajax": [],
|
|
"Ext.dataview.component.ListItem": [],
|
|
"Ext.util.Filter": [],
|
|
"Ext.layout.wrapper.Inner": [],
|
|
"Ext.draw.Animator": [],
|
|
"Ext.device.geolocation.Simulator": [],
|
|
"Ext.data.association.BelongsTo": [
|
|
"Ext.data.BelongsToAssociation"
|
|
],
|
|
"Ext.draw.Surface": [],
|
|
"Ext.scroll.indicator.ScrollPosition": [],
|
|
"Ext.field.Email": [
|
|
"Ext.form.Email"
|
|
],
|
|
"Ext.fx.layout.card.Abstract": [],
|
|
"Ext.event.Controller": [],
|
|
"Ext.dataview.component.Container": [],
|
|
"Ext.log.writer.Remote": [],
|
|
"Ext.fx.layout.card.Style": [],
|
|
"Ext.device.purchases.Sencha": [],
|
|
"Ext.chart.axis.segmenter.Segmenter": [],
|
|
"Ext.viewport.Android": [],
|
|
"Ext.log.formatter.Identity": [],
|
|
"Ext.chart.interactions.ItemHighlight": [],
|
|
"Ext.picker.Picker": [
|
|
"Ext.Picker"
|
|
],
|
|
"Ext.data.Batch": [],
|
|
"Ext.draw.modifier.Animation": [],
|
|
"Ext.chart.AbstractChart": [],
|
|
"Ext.tab.Panel": [
|
|
"Ext.TabPanel"
|
|
],
|
|
"Ext.draw.Path": [],
|
|
"Ext.scroll.indicator.Throttled": [],
|
|
"Ext.fx.animation.SlideOut": [],
|
|
"Ext.device.connection.Sencha": [],
|
|
"Ext.fx.layout.card.Pop": [],
|
|
"Ext.chart.axis.layout.Discrete": [],
|
|
"Ext.data.Field": [],
|
|
"Ext.chart.series.Gauge": [],
|
|
"Ext.data.StoreManager": [
|
|
"Ext.StoreMgr",
|
|
"Ext.data.StoreMgr",
|
|
"Ext.StoreManager"
|
|
],
|
|
"Ext.fx.animation.PopOut": [],
|
|
"Ext.chart.label.Callout": [],
|
|
"Ext.device.push.Abstract": [],
|
|
"Ext.util.DelayedTask": [],
|
|
"Ext.fx.easing.Momentum": [],
|
|
"Ext.fx.easing.Abstract": [],
|
|
"Ext.Title": [],
|
|
"Ext.event.recognizer.Drag": [],
|
|
"Ext.field.TextArea": [
|
|
"Ext.form.TextArea"
|
|
],
|
|
"Ext.fx.Easing": [],
|
|
"Ext.chart.series.sprite.Scatter": [],
|
|
"Ext.data.reader.Array": [
|
|
"Ext.data.ArrayReader"
|
|
],
|
|
"Ext.picker.Date": [
|
|
"Ext.DatePicker"
|
|
],
|
|
"Ext.data.proxy.JsonP": [
|
|
"Ext.data.ScriptTagProxy"
|
|
],
|
|
"Ext.device.communicator.Android": [],
|
|
"Ext.chart.series.Area": [],
|
|
"Ext.device.device.PhoneGap": [],
|
|
"Ext.field.Checkbox": [
|
|
"Ext.form.Checkbox"
|
|
],
|
|
"Ext.chart.Legend": [],
|
|
"Ext.Media": [],
|
|
"Ext.TitleBar": [],
|
|
"Ext.chart.interactions.RotatePie3D": [],
|
|
"Ext.draw.gradient.Linear": [],
|
|
"Ext.util.TapRepeater": [],
|
|
"Ext.event.Touch": [],
|
|
"Ext.mixin.Bindable": [],
|
|
"Ext.data.proxy.Server": [
|
|
"Ext.data.ServerProxy"
|
|
],
|
|
"Ext.chart.series.Cartesian": [],
|
|
"Ext.util.sizemonitor.Scroll": [],
|
|
"Ext.data.ResultSet": [],
|
|
"Ext.data.association.HasMany": [
|
|
"Ext.data.HasManyAssociation"
|
|
],
|
|
"Ext.draw.TimingFunctions": [],
|
|
"Ext.draw.engine.Canvas": [],
|
|
"Ext.data.proxy.Ajax": [
|
|
"Ext.data.HttpProxy",
|
|
"Ext.data.AjaxProxy"
|
|
],
|
|
"Ext.fx.animation.Fade": [
|
|
"Ext.fx.animation.FadeIn"
|
|
],
|
|
"Ext.layout.Default": [],
|
|
"Ext.util.paintmonitor.CssAnimation": [],
|
|
"Ext.data.writer.Writer": [
|
|
"Ext.data.DataWriter",
|
|
"Ext.data.Writer"
|
|
],
|
|
"Ext.event.recognizer.Recognizer": [],
|
|
"Ext.form.FieldSet": [],
|
|
"Ext.scroll.Indicator": [
|
|
"Ext.util.Indicator"
|
|
],
|
|
"Ext.XTemplateParser": [],
|
|
"Ext.behavior.Scrollable": [],
|
|
"Ext.chart.series.sprite.CandleStick": [],
|
|
"Ext.data.JsonP": [
|
|
"Ext.util.JSONP"
|
|
],
|
|
"Ext.device.connection.PhoneGap": [],
|
|
"Ext.event.publisher.Dom": [],
|
|
"Ext.fx.layout.card.Fade": [],
|
|
"Ext.app.Controller": [],
|
|
"Ext.fx.State": [],
|
|
"Ext.layout.wrapper.BoxDock": [],
|
|
"Ext.chart.series.sprite.Pie3DPart": [],
|
|
"Ext.viewport.Default": [],
|
|
"Ext.layout.HBox": [],
|
|
"Ext.ux.auth.model.Session": [],
|
|
"Ext.scroll.indicator.Default": [],
|
|
"Ext.data.ModelManager": [
|
|
"Ext.ModelMgr",
|
|
"Ext.ModelManager"
|
|
],
|
|
"Ext.data.Validations": [
|
|
"Ext.data.validations"
|
|
],
|
|
"Ext.util.translatable.Abstract": [],
|
|
"Ext.scroll.indicator.Abstract": [],
|
|
"Ext.Button": [],
|
|
"Ext.field.Radio": [
|
|
"Ext.form.Radio"
|
|
],
|
|
"Ext.util.HashMap": [],
|
|
"Ext.field.Input": [],
|
|
"Ext.device.Camera": [],
|
|
"Ext.mixin.Filterable": [],
|
|
"Ext.draw.TextMeasurer": [],
|
|
"Ext.dataview.element.Container": [],
|
|
"Ext.chart.series.sprite.PieSlice": [],
|
|
"Ext.data.Connection": [],
|
|
"Ext.direct.ExceptionEvent": [],
|
|
"Ext.Panel": [
|
|
"Ext.lib.Panel"
|
|
],
|
|
"Ext.data.association.HasOne": [
|
|
"Ext.data.HasOneAssociation"
|
|
],
|
|
"Ext.device.geolocation.Abstract": [],
|
|
"Ext.ActionSheet": [],
|
|
"Ext.layout.Box": [],
|
|
"Ext.bb.CrossCut": [],
|
|
"Ext.Video": [],
|
|
"Ext.ux.auth.Session": [],
|
|
"Ext.chart.series.Line": [],
|
|
"Ext.fx.layout.card.Cube": [],
|
|
"Ext.event.recognizer.HorizontalSwipe": [],
|
|
"Ext.data.writer.Json": [
|
|
"Ext.data.JsonWriter"
|
|
],
|
|
"Ext.layout.Fit": [],
|
|
"Ext.fx.animation.Slide": [
|
|
"Ext.fx.animation.SlideIn"
|
|
],
|
|
"Ext.device.Purchases.Purchase": [],
|
|
"Ext.table.Row": [],
|
|
"Ext.log.formatter.Formatter": [],
|
|
"Ext.Container": [
|
|
"Ext.lib.Container"
|
|
],
|
|
"Ext.fx.animation.Pop": [
|
|
"Ext.fx.animation.PopIn"
|
|
],
|
|
"Ext.draw.sprite.Circle": [],
|
|
"Ext.fx.layout.card.Reveal": [],
|
|
"Ext.fx.layout.card.Cover": [],
|
|
"Ext.log.Base": [],
|
|
"Ext.data.reader.Xml": [
|
|
"Ext.data.XmlReader"
|
|
],
|
|
"Ext.event.publisher.ElementPaint": [],
|
|
"Ext.chart.axis.Category": [],
|
|
"Ext.data.reader.Json": [
|
|
"Ext.data.JsonReader"
|
|
],
|
|
"Ext.Decorator": [],
|
|
"Ext.data.TreeStore": [],
|
|
"Ext.device.Purchases": [],
|
|
"Ext.device.orientation.HTML5": [],
|
|
"Ext.draw.gradient.Gradient": [],
|
|
"Ext.event.recognizer.DoubleTap": [],
|
|
"Ext.log.Logger": [],
|
|
"Ext.picker.Slot": [
|
|
"Ext.Picker.Slot"
|
|
],
|
|
"Ext.device.notification.Simulator": [],
|
|
"Ext.field.Field": [
|
|
"Ext.form.Field"
|
|
],
|
|
"Ext.log.filter.Priority": [],
|
|
"Ext.util.sizemonitor.Abstract": [],
|
|
"Ext.chart.series.sprite.Polar": [],
|
|
"Ext.util.paintmonitor.OverflowChange": [],
|
|
"Ext.util.LineSegment": [],
|
|
"Ext.SegmentedButton": [],
|
|
"Ext.Sortable": [],
|
|
"Ext.fx.easing.Linear": [],
|
|
"Ext.chart.series.sprite.Aggregative": [],
|
|
"Ext.dom.CompositeElement": [
|
|
"Ext.CompositeElement"
|
|
],
|
|
"Ext.data.identifier.Uuid": [],
|
|
"Ext.data.proxy.Client": [
|
|
"Ext.proxy.ClientProxy"
|
|
],
|
|
"Ext.fx.easing.Bounce": [],
|
|
"Ext.data.Types": [],
|
|
"Ext.chart.series.sprite.Cartesian": [],
|
|
"Ext.app.Action": [],
|
|
"Ext.util.Translatable": [],
|
|
"Ext.device.camera.PhoneGap": [],
|
|
"Ext.draw.sprite.Path": [],
|
|
"Ext.LoadMask": [],
|
|
"Ext.data.association.Association": [
|
|
"Ext.data.Association"
|
|
],
|
|
"Ext.chart.axis.sprite.Axis": [],
|
|
"Ext.behavior.Draggable": [],
|
|
"Ext.chart.grid.RadialGrid": [],
|
|
"Ext.util.TranslatableGroup": [],
|
|
"Ext.fx.Animation": [],
|
|
"Ext.draw.sprite.Ellipse": [],
|
|
"Ext.util.Inflector": [],
|
|
"Ext.Map": [],
|
|
"Ext.XTemplate": [],
|
|
"Ext.data.NodeStore": [],
|
|
"Ext.draw.sprite.AttributeParser": [],
|
|
"Ext.form.Panel": [
|
|
"Ext.form.FormPanel"
|
|
],
|
|
"Ext.chart.series.Series": [],
|
|
"Ext.data.Request": [],
|
|
"Ext.draw.sprite.Text": [],
|
|
"Ext.layout.Float": [],
|
|
"Ext.dataview.component.DataItem": [],
|
|
"Ext.chart.CartesianChart": [
|
|
"Ext.chart.Chart"
|
|
],
|
|
"Ext.data.proxy.WebStorage": [
|
|
"Ext.data.WebStorageProxy"
|
|
],
|
|
"Ext.log.writer.Writer": [],
|
|
"Ext.device.Communicator": [],
|
|
"Ext.fx.animation.Flip": [],
|
|
"Ext.util.Point": [],
|
|
"Ext.chart.series.StackedCartesian": [],
|
|
"Ext.fx.layout.card.Slide": [],
|
|
"Ext.Anim": [],
|
|
"Ext.data.DirectStore": [],
|
|
"Ext.dataview.NestedList": [
|
|
"Ext.NestedList"
|
|
],
|
|
"Ext.app.Route": [],
|
|
"Ext.device.connection.Simulator": [],
|
|
"Ext.chart.PolarChart": [],
|
|
"Ext.event.publisher.ComponentSize": [],
|
|
"Ext.slider.Toggle": [],
|
|
"Ext.data.identifier.Sequential": [],
|
|
"Ext.Template": [],
|
|
"Ext.AbstractComponent": [],
|
|
"Ext.device.Push": [],
|
|
"Ext.fx.easing.BoundMomentum": [],
|
|
"Ext.viewport.Viewport": [],
|
|
"Ext.chart.series.Polar": [],
|
|
"Ext.event.recognizer.VerticalSwipe": [],
|
|
"Ext.event.Event": [
|
|
"Ext.EventObject"
|
|
],
|
|
"Ext.behavior.Behavior": [],
|
|
"Ext.chart.grid.VerticalGrid": [],
|
|
"Ext.chart.label.Label": [],
|
|
"Ext.draw.sprite.EllipticalArc": [],
|
|
"Ext.fx.easing.EaseOut": [],
|
|
"Ext.Toolbar": [],
|
|
"Ext.event.recognizer.LongPress": [],
|
|
"Ext.device.notification.Sencha": [],
|
|
"Ext.chart.series.sprite.Line": [],
|
|
"Ext.data.ArrayStore": [],
|
|
"Ext.data.proxy.SQL": [],
|
|
"Ext.mixin.Sortable": [],
|
|
"Ext.fx.layout.card.Flip": [],
|
|
"Ext.chart.interactions.CrossZoom": [],
|
|
"Ext.event.publisher.ComponentPaint": [],
|
|
"Ext.event.recognizer.Rotate": [],
|
|
"Ext.util.TranslatableList": [],
|
|
"Ext.carousel.Item": [],
|
|
"Ext.event.recognizer.Swipe": [],
|
|
"Ext.util.translatable.ScrollPosition": [],
|
|
"Ext.device.camera.Simulator": [],
|
|
"Ext.chart.series.sprite.Area": [],
|
|
"Ext.event.recognizer.Touch": [],
|
|
"Ext.plugin.ListPaging": [],
|
|
"Ext.draw.sprite.Sector": [],
|
|
"Ext.chart.axis.segmenter.Names": [],
|
|
"Ext.mixin.Observable": [
|
|
"Ext.util.Observable"
|
|
],
|
|
"Ext.carousel.Infinite": [],
|
|
"Ext.draw.Matrix": [],
|
|
"Ext.Mask": [],
|
|
"Ext.event.publisher.Publisher": [],
|
|
"Ext.layout.wrapper.Dock": [],
|
|
"Ext.app.History": [],
|
|
"Ext.data.proxy.Direct": [
|
|
"Ext.data.DirectProxy"
|
|
],
|
|
"Ext.chart.axis.layout.Continuous": [],
|
|
"Ext.table.Cell": [],
|
|
"Ext.fx.layout.card.ScrollCover": [],
|
|
"Ext.device.orientation.Sencha": [],
|
|
"Ext.util.Droppable": [],
|
|
"Ext.draw.sprite.Composite": [],
|
|
"Ext.chart.series.Pie": [],
|
|
"Ext.device.Purchases.Product": [],
|
|
"Ext.device.Orientation": [],
|
|
"Ext.direct.Provider": [],
|
|
"Ext.draw.sprite.Arc": [],
|
|
"Ext.chart.axis.segmenter.Time": [],
|
|
"Ext.util.Draggable": [],
|
|
"Ext.device.contacts.Sencha": [],
|
|
"Ext.chart.grid.HorizontalGrid": [],
|
|
"Ext.mixin.Traversable": [],
|
|
"Ext.util.AbstractMixedCollection": [],
|
|
"Ext.data.JsonStore": [],
|
|
"Ext.draw.SegmentTree": [],
|
|
"Ext.direct.RemotingEvent": [],
|
|
"Ext.plugin.PullRefresh": [],
|
|
"Ext.log.writer.Console": [],
|
|
"Ext.field.Spinner": [
|
|
"Ext.form.Spinner"
|
|
],
|
|
"Ext.chart.axis.segmenter.Numeric": [],
|
|
"Ext.data.proxy.LocalStorage": [
|
|
"Ext.data.LocalStorageProxy"
|
|
],
|
|
"Ext.fx.animation.Wipe": [
|
|
"Ext.fx.animation.WipeIn"
|
|
],
|
|
"Ext.fx.layout.Card": [],
|
|
"Ext.TaskQueue": [],
|
|
"Ext.Label": [],
|
|
"Ext.util.translatable.CssTransform": [],
|
|
"Ext.viewport.Ios": [],
|
|
"Ext.Spacer": [],
|
|
"Ext.mixin.Selectable": [],
|
|
"Ext.draw.sprite.Image": [],
|
|
"Ext.data.proxy.Rest": [
|
|
"Ext.data.RestProxy"
|
|
],
|
|
"Ext.Img": [],
|
|
"Ext.chart.series.sprite.Bar": [],
|
|
"Ext.log.writer.DocumentTitle": [],
|
|
"Ext.data.Error": [],
|
|
"Ext.util.Sorter": [],
|
|
"Ext.draw.gradient.Radial": [],
|
|
"Ext.layout.Abstract": [],
|
|
"Ext.device.notification.Abstract": [],
|
|
"Ext.log.filter.Filter": [],
|
|
"Ext.device.camera.Sencha": [],
|
|
"Ext.draw.sprite.Sprite": [],
|
|
"Ext.draw.Color": [],
|
|
"Ext.chart.series.Bar": [],
|
|
"Ext.field.Slider": [
|
|
"Ext.form.Slider"
|
|
],
|
|
"Ext.field.Search": [
|
|
"Ext.form.Search"
|
|
],
|
|
"Ext.chart.series.Scatter": [],
|
|
"Ext.device.Device": [],
|
|
"Ext.event.Dispatcher": [],
|
|
"Ext.data.Store": [],
|
|
"Ext.draw.modifier.Highlight": [],
|
|
"Ext.behavior.Translatable": [],
|
|
"Ext.direct.Manager": [
|
|
"Ext.Direct"
|
|
],
|
|
"Ext.data.proxy.Proxy": [
|
|
"Ext.data.DataProxy",
|
|
"Ext.data.Proxy"
|
|
],
|
|
"Ext.draw.modifier.Modifier": [],
|
|
"Ext.navigation.View": [
|
|
"Ext.NavigationView"
|
|
],
|
|
"Ext.draw.modifier.Target": [],
|
|
"Ext.draw.sprite.AttributeDefinition": [],
|
|
"Ext.device.Notification": [],
|
|
"Ext.draw.Component": [],
|
|
"Ext.layout.VBox": [],
|
|
"Ext.slider.Thumb": [],
|
|
"Ext.MessageBox": [],
|
|
"Ext.ux.Faker": [],
|
|
"Ext.dataview.IndexBar": [
|
|
"Ext.IndexBar"
|
|
],
|
|
"Ext.dataview.element.List": [],
|
|
"Ext.layout.FlexBox": [],
|
|
"Ext.field.Url": [
|
|
"Ext.form.Url"
|
|
],
|
|
"Ext.draw.Solver": [],
|
|
"Ext.data.proxy.Memory": [
|
|
"Ext.data.MemoryProxy"
|
|
],
|
|
"Ext.chart.axis.Time": [],
|
|
"Ext.layout.Card": [],
|
|
"Ext.ComponentQuery": [],
|
|
"Ext.chart.series.Pie3D": [],
|
|
"Ext.device.camera.Abstract": [],
|
|
"Ext.device.device.Sencha": [],
|
|
"Ext.scroll.View": [
|
|
"Ext.util.ScrollView"
|
|
],
|
|
"Ext.draw.sprite.Rect": [],
|
|
"Ext.util.Region": [],
|
|
"Ext.field.Select": [
|
|
"Ext.form.Select"
|
|
],
|
|
"Ext.draw.Draw": [],
|
|
"Ext.ItemCollection": [],
|
|
"Ext.log.formatter.Default": [],
|
|
"Ext.navigation.Bar": [],
|
|
"Ext.chart.axis.layout.CombineDuplicate": [],
|
|
"Ext.device.Geolocation": [],
|
|
"Ext.chart.SpaceFillingChart": [],
|
|
"Ext.data.proxy.SessionStorage": [
|
|
"Ext.data.SessionStorageProxy"
|
|
],
|
|
"Ext.fx.easing.EaseIn": [],
|
|
"Ext.draw.sprite.AnimationParser": [],
|
|
"Ext.field.Password": [
|
|
"Ext.form.Password"
|
|
],
|
|
"Ext.device.connection.Abstract": [],
|
|
"Ext.direct.Event": [],
|
|
"Ext.direct.RemotingMethod": [],
|
|
"Ext.Evented": [
|
|
"Ext.EventedBase"
|
|
],
|
|
"Ext.carousel.Indicator": [
|
|
"Ext.Carousel.Indicator"
|
|
],
|
|
"Ext.util.Collection": [],
|
|
"Ext.chart.interactions.ItemInfo": [],
|
|
"Ext.chart.MarkerHolder": [],
|
|
"Ext.carousel.Carousel": [
|
|
"Ext.Carousel"
|
|
],
|
|
"Ext.Audio": [],
|
|
"Ext.device.Contacts": [],
|
|
"Ext.table.Table": [],
|
|
"Ext.draw.engine.SvgContext.Gradient": [],
|
|
"Ext.chart.axis.layout.Layout": [],
|
|
"Ext.data.Errors": [],
|
|
"Ext.field.Text": [
|
|
"Ext.form.Text"
|
|
],
|
|
"Ext.field.TextAreaInput": [],
|
|
"Ext.field.DatePicker": [
|
|
"Ext.form.DatePicker"
|
|
],
|
|
"Ext.draw.engine.Svg": [],
|
|
"Ext.event.recognizer.Tap": [],
|
|
"Ext.device.orientation.Abstract": [],
|
|
"Ext.AbstractManager": [],
|
|
"Ext.chart.series.Radar": [],
|
|
"Ext.chart.interactions.Abstract": [],
|
|
"Ext.scroll.indicator.CssTransform": [],
|
|
"Ext.util.PaintMonitor": [],
|
|
"Ext.direct.PollingProvider": [],
|
|
"Ext.device.notification.PhoneGap": [],
|
|
"Ext.data.writer.Xml": [
|
|
"Ext.data.XmlWriter"
|
|
],
|
|
"Ext.event.recognizer.SingleTouch": [],
|
|
"Ext.draw.sprite.Instancing": [],
|
|
"Ext.event.publisher.ComponentDelegation": [],
|
|
"Ext.chart.axis.Numeric": [],
|
|
"Ext.field.Toggle": [
|
|
"Ext.form.Toggle"
|
|
],
|
|
"Ext.fx.layout.card.ScrollReveal": [],
|
|
"Ext.data.Operation": [],
|
|
"Ext.fx.animation.Abstract": [],
|
|
"Ext.chart.interactions.Rotate": [],
|
|
"Ext.draw.engine.SvgContext": [],
|
|
"Ext.scroll.Scroller": [],
|
|
"Ext.util.SizeMonitor": [],
|
|
"Ext.event.ListenerStack": [],
|
|
"Ext.util.MixedCollection": []
|
|
});Ext.ClassManager.addNameAliasMappings({
|
|
"Ext.app.Profile": [],
|
|
"Ext.event.recognizer.MultiTouch": [],
|
|
"Ext.fx.Runner": [],
|
|
"Ext.chart.grid.CircularGrid": [
|
|
"grid.circular"
|
|
],
|
|
"Ext.mixin.Templatable": [],
|
|
"Ext.event.recognizer.Pinch": [],
|
|
"Ext.util.Format": [],
|
|
"Ext.direct.JsonProvider": [
|
|
"direct.jsonprovider"
|
|
],
|
|
"Ext.data.identifier.Simple": [
|
|
"data.identifier.simple"
|
|
],
|
|
"Ext.dataview.DataView": [
|
|
"widget.dataview"
|
|
],
|
|
"Ext.field.Hidden": [
|
|
"widget.hiddenfield"
|
|
],
|
|
"Ext.field.Number": [
|
|
"widget.numberfield"
|
|
],
|
|
"Ext.chart.series.CandleStick": [
|
|
"series.candlestick"
|
|
],
|
|
"Ext.device.Connection": [],
|
|
"Ext.data.Model": [],
|
|
"Ext.data.reader.Reader": [],
|
|
"Ext.Sheet": [
|
|
"widget.sheet"
|
|
],
|
|
"Ext.tab.Tab": [
|
|
"widget.tab"
|
|
],
|
|
"Ext.chart.series.sprite.StackedCartesian": [],
|
|
"Ext.util.Grouper": [],
|
|
"Ext.util.translatable.CssPosition": [],
|
|
"Ext.util.paintmonitor.Abstract": [],
|
|
"Ext.direct.RemotingProvider": [
|
|
"direct.remotingprovider"
|
|
],
|
|
"Ext.data.NodeInterface": [],
|
|
"Ext.chart.interactions.PanZoom": [
|
|
"interaction.panzoom"
|
|
],
|
|
"Ext.util.PositionMap": [],
|
|
"Ext.chart.series.ItemPublisher": [],
|
|
"Ext.util.Sortable": [],
|
|
"Ext.chart.series.sprite.AbstractRadial": [],
|
|
"Ext.fx.runner.Css": [],
|
|
"Ext.fx.runner.CssTransition": [],
|
|
"Ext.draw.Group": [],
|
|
"Ext.XTemplateCompiler": [],
|
|
"Ext.util.Wrapper": [],
|
|
"Ext.app.Router": [],
|
|
"Ext.direct.Transaction": [
|
|
"direct.transaction"
|
|
],
|
|
"Ext.util.Offset": [],
|
|
"Ext.device.device.Abstract": [],
|
|
"Ext.mixin.Mixin": [],
|
|
"Ext.fx.animation.FadeOut": [
|
|
"animation.fadeOut"
|
|
],
|
|
"Ext.util.Geolocation": [],
|
|
"Ext.ComponentManager": [],
|
|
"Ext.util.sizemonitor.OverflowChange": [],
|
|
"Ext.event.publisher.ElementSize": [],
|
|
"Ext.tab.Bar": [
|
|
"widget.tabbar"
|
|
],
|
|
"Ext.event.Dom": [],
|
|
"Ext.app.Application": [],
|
|
"Ext.dataview.List": [
|
|
"widget.list"
|
|
],
|
|
"Ext.util.translatable.Dom": [],
|
|
"Ext.fx.layout.card.Scroll": [
|
|
"fx.layout.card.scroll"
|
|
],
|
|
"Ext.draw.LimitedCache": [],
|
|
"Ext.device.geolocation.Sencha": [],
|
|
"Ext.dataview.ListItemHeader": [
|
|
"widget.listitemheader"
|
|
],
|
|
"Ext.event.publisher.TouchGesture": [],
|
|
"Ext.data.SortTypes": [],
|
|
"Ext.device.contacts.Abstract": [],
|
|
"Ext.device.push.Sencha": [],
|
|
"Ext.fx.animation.WipeOut": [],
|
|
"Ext.slider.Slider": [
|
|
"widget.slider"
|
|
],
|
|
"Ext.Component": [
|
|
"widget.component"
|
|
],
|
|
"Ext.device.communicator.Default": [],
|
|
"Ext.fx.runner.CssAnimation": [],
|
|
"Ext.chart.axis.Axis": [
|
|
"widget.axis"
|
|
],
|
|
"Ext.fx.animation.Cube": [
|
|
"animation.cube"
|
|
],
|
|
"Ext.chart.Markers": [],
|
|
"Ext.chart.series.sprite.Radar": [
|
|
"sprite.radar"
|
|
],
|
|
"Ext.device.device.Simulator": [],
|
|
"Ext.Ajax": [],
|
|
"Ext.dataview.component.ListItem": [
|
|
"widget.listitem"
|
|
],
|
|
"Ext.util.Filter": [],
|
|
"Ext.layout.wrapper.Inner": [],
|
|
"Ext.draw.Animator": [],
|
|
"Ext.device.geolocation.Simulator": [],
|
|
"Ext.data.association.BelongsTo": [
|
|
"association.belongsto"
|
|
],
|
|
"Ext.draw.Surface": [
|
|
"widget.surface"
|
|
],
|
|
"Ext.scroll.indicator.ScrollPosition": [],
|
|
"Ext.field.Email": [
|
|
"widget.emailfield"
|
|
],
|
|
"Ext.fx.layout.card.Abstract": [],
|
|
"Ext.event.Controller": [],
|
|
"Ext.dataview.component.Container": [],
|
|
"Ext.log.writer.Remote": [],
|
|
"Ext.fx.layout.card.Style": [],
|
|
"Ext.device.purchases.Sencha": [],
|
|
"Ext.chart.axis.segmenter.Segmenter": [],
|
|
"Ext.viewport.Android": [],
|
|
"Ext.log.formatter.Identity": [],
|
|
"Ext.chart.interactions.ItemHighlight": [
|
|
"interaction.itemhighlight"
|
|
],
|
|
"Ext.picker.Picker": [
|
|
"widget.picker"
|
|
],
|
|
"Ext.data.Batch": [],
|
|
"Ext.draw.modifier.Animation": [
|
|
"modifier.animation"
|
|
],
|
|
"Ext.chart.AbstractChart": [],
|
|
"Ext.tab.Panel": [
|
|
"widget.tabpanel"
|
|
],
|
|
"Ext.draw.Path": [],
|
|
"Ext.scroll.indicator.Throttled": [],
|
|
"Ext.fx.animation.SlideOut": [
|
|
"animation.slideOut"
|
|
],
|
|
"Ext.device.connection.Sencha": [],
|
|
"Ext.fx.layout.card.Pop": [
|
|
"fx.layout.card.pop"
|
|
],
|
|
"Ext.chart.axis.layout.Discrete": [
|
|
"axisLayout.discrete"
|
|
],
|
|
"Ext.data.Field": [
|
|
"data.field"
|
|
],
|
|
"Ext.chart.series.Gauge": [
|
|
"series.gauge"
|
|
],
|
|
"Ext.data.StoreManager": [],
|
|
"Ext.fx.animation.PopOut": [
|
|
"animation.popOut"
|
|
],
|
|
"Ext.chart.label.Callout": [],
|
|
"Ext.device.push.Abstract": [],
|
|
"Ext.util.DelayedTask": [],
|
|
"Ext.fx.easing.Momentum": [],
|
|
"Ext.fx.easing.Abstract": [],
|
|
"Ext.Title": [
|
|
"widget.title"
|
|
],
|
|
"Ext.event.recognizer.Drag": [],
|
|
"Ext.field.TextArea": [
|
|
"widget.textareafield"
|
|
],
|
|
"Ext.fx.Easing": [],
|
|
"Ext.chart.series.sprite.Scatter": [
|
|
"sprite.scatterSeries"
|
|
],
|
|
"Ext.data.reader.Array": [
|
|
"reader.array"
|
|
],
|
|
"Ext.picker.Date": [
|
|
"widget.datepicker"
|
|
],
|
|
"Ext.data.proxy.JsonP": [
|
|
"proxy.jsonp",
|
|
"proxy.scripttag"
|
|
],
|
|
"Ext.device.communicator.Android": [],
|
|
"Ext.chart.series.Area": [
|
|
"series.area"
|
|
],
|
|
"Ext.device.device.PhoneGap": [],
|
|
"Ext.field.Checkbox": [
|
|
"widget.checkboxfield"
|
|
],
|
|
"Ext.chart.Legend": [
|
|
"widget.legend"
|
|
],
|
|
"Ext.Media": [
|
|
"widget.media"
|
|
],
|
|
"Ext.TitleBar": [
|
|
"widget.titlebar"
|
|
],
|
|
"Ext.chart.interactions.RotatePie3D": [
|
|
"interaction.rotatePie3d"
|
|
],
|
|
"Ext.draw.gradient.Linear": [],
|
|
"Ext.util.TapRepeater": [],
|
|
"Ext.event.Touch": [],
|
|
"Ext.mixin.Bindable": [],
|
|
"Ext.data.proxy.Server": [
|
|
"proxy.server"
|
|
],
|
|
"Ext.chart.series.Cartesian": [],
|
|
"Ext.util.sizemonitor.Scroll": [],
|
|
"Ext.data.ResultSet": [],
|
|
"Ext.data.association.HasMany": [
|
|
"association.hasmany"
|
|
],
|
|
"Ext.draw.TimingFunctions": [],
|
|
"Ext.draw.engine.Canvas": [],
|
|
"Ext.data.proxy.Ajax": [
|
|
"proxy.ajax"
|
|
],
|
|
"Ext.fx.animation.Fade": [
|
|
"animation.fade",
|
|
"animation.fadeIn"
|
|
],
|
|
"Ext.layout.Default": [
|
|
"layout.default",
|
|
"layout.auto"
|
|
],
|
|
"Ext.util.paintmonitor.CssAnimation": [],
|
|
"Ext.data.writer.Writer": [
|
|
"writer.base"
|
|
],
|
|
"Ext.event.recognizer.Recognizer": [],
|
|
"Ext.form.FieldSet": [
|
|
"widget.fieldset"
|
|
],
|
|
"Ext.scroll.Indicator": [],
|
|
"Ext.XTemplateParser": [],
|
|
"Ext.behavior.Scrollable": [],
|
|
"Ext.chart.series.sprite.CandleStick": [
|
|
"sprite.candlestickSeries"
|
|
],
|
|
"Ext.data.JsonP": [],
|
|
"Ext.device.connection.PhoneGap": [],
|
|
"Ext.event.publisher.Dom": [],
|
|
"Ext.fx.layout.card.Fade": [
|
|
"fx.layout.card.fade"
|
|
],
|
|
"Ext.app.Controller": [],
|
|
"Ext.fx.State": [],
|
|
"Ext.layout.wrapper.BoxDock": [],
|
|
"Ext.chart.series.sprite.Pie3DPart": [
|
|
"sprite.pie3dPart"
|
|
],
|
|
"Ext.viewport.Default": [
|
|
"widget.viewport"
|
|
],
|
|
"Ext.layout.HBox": [
|
|
"layout.hbox"
|
|
],
|
|
"Ext.ux.auth.model.Session": [],
|
|
"Ext.scroll.indicator.Default": [],
|
|
"Ext.data.ModelManager": [],
|
|
"Ext.data.Validations": [],
|
|
"Ext.util.translatable.Abstract": [],
|
|
"Ext.scroll.indicator.Abstract": [],
|
|
"Ext.Button": [
|
|
"widget.button"
|
|
],
|
|
"Ext.field.Radio": [
|
|
"widget.radiofield"
|
|
],
|
|
"Ext.util.HashMap": [],
|
|
"Ext.field.Input": [
|
|
"widget.input"
|
|
],
|
|
"Ext.device.Camera": [],
|
|
"Ext.mixin.Filterable": [],
|
|
"Ext.draw.TextMeasurer": [],
|
|
"Ext.dataview.element.Container": [],
|
|
"Ext.chart.series.sprite.PieSlice": [
|
|
"sprite.pieslice"
|
|
],
|
|
"Ext.data.Connection": [],
|
|
"Ext.direct.ExceptionEvent": [
|
|
"direct.exception"
|
|
],
|
|
"Ext.Panel": [
|
|
"widget.panel"
|
|
],
|
|
"Ext.data.association.HasOne": [
|
|
"association.hasone"
|
|
],
|
|
"Ext.device.geolocation.Abstract": [],
|
|
"Ext.ActionSheet": [
|
|
"widget.actionsheet"
|
|
],
|
|
"Ext.layout.Box": [
|
|
"layout.tablebox"
|
|
],
|
|
"Ext.bb.CrossCut": [
|
|
"widget.crosscut"
|
|
],
|
|
"Ext.Video": [
|
|
"widget.video"
|
|
],
|
|
"Ext.ux.auth.Session": [],
|
|
"Ext.chart.series.Line": [
|
|
"series.line"
|
|
],
|
|
"Ext.fx.layout.card.Cube": [
|
|
"fx.layout.card.cube"
|
|
],
|
|
"Ext.event.recognizer.HorizontalSwipe": [],
|
|
"Ext.data.writer.Json": [
|
|
"writer.json"
|
|
],
|
|
"Ext.layout.Fit": [
|
|
"layout.fit"
|
|
],
|
|
"Ext.fx.animation.Slide": [
|
|
"animation.slide",
|
|
"animation.slideIn"
|
|
],
|
|
"Ext.device.Purchases.Purchase": [],
|
|
"Ext.table.Row": [
|
|
"widget.tablerow"
|
|
],
|
|
"Ext.log.formatter.Formatter": [],
|
|
"Ext.Container": [
|
|
"widget.container"
|
|
],
|
|
"Ext.fx.animation.Pop": [
|
|
"animation.pop",
|
|
"animation.popIn"
|
|
],
|
|
"Ext.draw.sprite.Circle": [
|
|
"sprite.circle"
|
|
],
|
|
"Ext.fx.layout.card.Reveal": [
|
|
"fx.layout.card.reveal"
|
|
],
|
|
"Ext.fx.layout.card.Cover": [
|
|
"fx.layout.card.cover"
|
|
],
|
|
"Ext.log.Base": [],
|
|
"Ext.data.reader.Xml": [
|
|
"reader.xml"
|
|
],
|
|
"Ext.event.publisher.ElementPaint": [],
|
|
"Ext.chart.axis.Category": [
|
|
"axis.category"
|
|
],
|
|
"Ext.data.reader.Json": [
|
|
"reader.json"
|
|
],
|
|
"Ext.Decorator": [],
|
|
"Ext.data.TreeStore": [
|
|
"store.tree"
|
|
],
|
|
"Ext.device.Purchases": [],
|
|
"Ext.device.orientation.HTML5": [],
|
|
"Ext.draw.gradient.Gradient": [],
|
|
"Ext.event.recognizer.DoubleTap": [],
|
|
"Ext.log.Logger": [],
|
|
"Ext.picker.Slot": [
|
|
"widget.pickerslot"
|
|
],
|
|
"Ext.device.notification.Simulator": [],
|
|
"Ext.field.Field": [
|
|
"widget.field"
|
|
],
|
|
"Ext.log.filter.Priority": [],
|
|
"Ext.util.sizemonitor.Abstract": [],
|
|
"Ext.chart.series.sprite.Polar": [],
|
|
"Ext.util.paintmonitor.OverflowChange": [],
|
|
"Ext.util.LineSegment": [],
|
|
"Ext.SegmentedButton": [
|
|
"widget.segmentedbutton"
|
|
],
|
|
"Ext.Sortable": [],
|
|
"Ext.fx.easing.Linear": [
|
|
"easing.linear"
|
|
],
|
|
"Ext.chart.series.sprite.Aggregative": [],
|
|
"Ext.dom.CompositeElement": [],
|
|
"Ext.data.identifier.Uuid": [
|
|
"data.identifier.uuid"
|
|
],
|
|
"Ext.data.proxy.Client": [],
|
|
"Ext.fx.easing.Bounce": [],
|
|
"Ext.data.Types": [],
|
|
"Ext.chart.series.sprite.Cartesian": [],
|
|
"Ext.app.Action": [],
|
|
"Ext.util.Translatable": [],
|
|
"Ext.device.camera.PhoneGap": [],
|
|
"Ext.draw.sprite.Path": [
|
|
"sprite.path"
|
|
],
|
|
"Ext.LoadMask": [
|
|
"widget.loadmask"
|
|
],
|
|
"Ext.data.association.Association": [],
|
|
"Ext.chart.axis.sprite.Axis": [],
|
|
"Ext.behavior.Draggable": [],
|
|
"Ext.chart.grid.RadialGrid": [
|
|
"grid.radial"
|
|
],
|
|
"Ext.util.TranslatableGroup": [],
|
|
"Ext.fx.Animation": [],
|
|
"Ext.draw.sprite.Ellipse": [
|
|
"sprite.ellipse"
|
|
],
|
|
"Ext.util.Inflector": [],
|
|
"Ext.Map": [
|
|
"widget.map"
|
|
],
|
|
"Ext.XTemplate": [],
|
|
"Ext.data.NodeStore": [
|
|
"store.node"
|
|
],
|
|
"Ext.draw.sprite.AttributeParser": [],
|
|
"Ext.form.Panel": [
|
|
"widget.formpanel"
|
|
],
|
|
"Ext.chart.series.Series": [],
|
|
"Ext.data.Request": [],
|
|
"Ext.draw.sprite.Text": [
|
|
"sprite.text"
|
|
],
|
|
"Ext.layout.Float": [
|
|
"layout.float"
|
|
],
|
|
"Ext.dataview.component.DataItem": [
|
|
"widget.dataitem"
|
|
],
|
|
"Ext.chart.CartesianChart": [
|
|
"widget.chart",
|
|
"Ext.chart.Chart"
|
|
],
|
|
"Ext.data.proxy.WebStorage": [],
|
|
"Ext.log.writer.Writer": [],
|
|
"Ext.device.Communicator": [],
|
|
"Ext.fx.animation.Flip": [
|
|
"animation.flip"
|
|
],
|
|
"Ext.util.Point": [],
|
|
"Ext.chart.series.StackedCartesian": [],
|
|
"Ext.fx.layout.card.Slide": [
|
|
"fx.layout.card.slide"
|
|
],
|
|
"Ext.Anim": [],
|
|
"Ext.data.DirectStore": [
|
|
"store.direct"
|
|
],
|
|
"Ext.dataview.NestedList": [
|
|
"widget.nestedlist"
|
|
],
|
|
"Ext.app.Route": [],
|
|
"Ext.device.connection.Simulator": [],
|
|
"Ext.chart.PolarChart": [
|
|
"widget.polar"
|
|
],
|
|
"Ext.event.publisher.ComponentSize": [],
|
|
"Ext.slider.Toggle": [],
|
|
"Ext.data.identifier.Sequential": [
|
|
"data.identifier.sequential"
|
|
],
|
|
"Ext.Template": [],
|
|
"Ext.AbstractComponent": [],
|
|
"Ext.device.Push": [],
|
|
"Ext.fx.easing.BoundMomentum": [],
|
|
"Ext.viewport.Viewport": [],
|
|
"Ext.chart.series.Polar": [],
|
|
"Ext.event.recognizer.VerticalSwipe": [],
|
|
"Ext.event.Event": [],
|
|
"Ext.behavior.Behavior": [],
|
|
"Ext.chart.grid.VerticalGrid": [
|
|
"grid.vertical"
|
|
],
|
|
"Ext.chart.label.Label": [],
|
|
"Ext.draw.sprite.EllipticalArc": [
|
|
"sprite.ellipticalArc"
|
|
],
|
|
"Ext.fx.easing.EaseOut": [
|
|
"easing.ease-out"
|
|
],
|
|
"Ext.Toolbar": [
|
|
"widget.toolbar"
|
|
],
|
|
"Ext.event.recognizer.LongPress": [],
|
|
"Ext.device.notification.Sencha": [],
|
|
"Ext.chart.series.sprite.Line": [
|
|
"sprite.lineSeries"
|
|
],
|
|
"Ext.data.ArrayStore": [
|
|
"store.array"
|
|
],
|
|
"Ext.data.proxy.SQL": [
|
|
"proxy.sql"
|
|
],
|
|
"Ext.mixin.Sortable": [],
|
|
"Ext.fx.layout.card.Flip": [
|
|
"fx.layout.card.flip"
|
|
],
|
|
"Ext.chart.interactions.CrossZoom": [
|
|
"interaction.crosszoom"
|
|
],
|
|
"Ext.event.publisher.ComponentPaint": [],
|
|
"Ext.event.recognizer.Rotate": [],
|
|
"Ext.util.TranslatableList": [],
|
|
"Ext.carousel.Item": [],
|
|
"Ext.event.recognizer.Swipe": [],
|
|
"Ext.util.translatable.ScrollPosition": [],
|
|
"Ext.device.camera.Simulator": [],
|
|
"Ext.chart.series.sprite.Area": [
|
|
"sprite.areaSeries"
|
|
],
|
|
"Ext.event.recognizer.Touch": [],
|
|
"Ext.plugin.ListPaging": [
|
|
"plugin.listpaging"
|
|
],
|
|
"Ext.draw.sprite.Sector": [
|
|
"sprite.sector"
|
|
],
|
|
"Ext.chart.axis.segmenter.Names": [
|
|
"segmenter.names"
|
|
],
|
|
"Ext.mixin.Observable": [],
|
|
"Ext.carousel.Infinite": [],
|
|
"Ext.draw.Matrix": [],
|
|
"Ext.Mask": [
|
|
"widget.mask"
|
|
],
|
|
"Ext.event.publisher.Publisher": [],
|
|
"Ext.layout.wrapper.Dock": [],
|
|
"Ext.app.History": [],
|
|
"Ext.data.proxy.Direct": [
|
|
"proxy.direct"
|
|
],
|
|
"Ext.chart.axis.layout.Continuous": [
|
|
"axisLayout.continuous"
|
|
],
|
|
"Ext.table.Cell": [
|
|
"widget.tablecell"
|
|
],
|
|
"Ext.fx.layout.card.ScrollCover": [
|
|
"fx.layout.card.scrollcover"
|
|
],
|
|
"Ext.device.orientation.Sencha": [],
|
|
"Ext.util.Droppable": [],
|
|
"Ext.draw.sprite.Composite": [
|
|
"sprite.composite"
|
|
],
|
|
"Ext.chart.series.Pie": [
|
|
"series.pie"
|
|
],
|
|
"Ext.device.Purchases.Product": [],
|
|
"Ext.device.Orientation": [],
|
|
"Ext.direct.Provider": [
|
|
"direct.provider"
|
|
],
|
|
"Ext.draw.sprite.Arc": [
|
|
"sprite.arc"
|
|
],
|
|
"Ext.chart.axis.segmenter.Time": [
|
|
"segmenter.time"
|
|
],
|
|
"Ext.util.Draggable": [],
|
|
"Ext.device.contacts.Sencha": [],
|
|
"Ext.chart.grid.HorizontalGrid": [
|
|
"grid.horizontal"
|
|
],
|
|
"Ext.mixin.Traversable": [],
|
|
"Ext.util.AbstractMixedCollection": [],
|
|
"Ext.data.JsonStore": [
|
|
"store.json"
|
|
],
|
|
"Ext.draw.SegmentTree": [],
|
|
"Ext.direct.RemotingEvent": [
|
|
"direct.rpc"
|
|
],
|
|
"Ext.plugin.PullRefresh": [
|
|
"plugin.pullrefresh"
|
|
],
|
|
"Ext.log.writer.Console": [],
|
|
"Ext.field.Spinner": [
|
|
"widget.spinnerfield"
|
|
],
|
|
"Ext.chart.axis.segmenter.Numeric": [
|
|
"segmenter.numeric"
|
|
],
|
|
"Ext.data.proxy.LocalStorage": [
|
|
"proxy.localstorage"
|
|
],
|
|
"Ext.fx.animation.Wipe": [],
|
|
"Ext.fx.layout.Card": [],
|
|
"Ext.TaskQueue": [],
|
|
"Ext.Label": [
|
|
"widget.label"
|
|
],
|
|
"Ext.util.translatable.CssTransform": [],
|
|
"Ext.viewport.Ios": [],
|
|
"Ext.Spacer": [
|
|
"widget.spacer"
|
|
],
|
|
"Ext.mixin.Selectable": [],
|
|
"Ext.draw.sprite.Image": [
|
|
"sprite.image"
|
|
],
|
|
"Ext.data.proxy.Rest": [
|
|
"proxy.rest"
|
|
],
|
|
"Ext.Img": [
|
|
"widget.img",
|
|
"widget.image"
|
|
],
|
|
"Ext.chart.series.sprite.Bar": [
|
|
"sprite.barSeries"
|
|
],
|
|
"Ext.log.writer.DocumentTitle": [],
|
|
"Ext.data.Error": [],
|
|
"Ext.util.Sorter": [],
|
|
"Ext.draw.gradient.Radial": [],
|
|
"Ext.layout.Abstract": [],
|
|
"Ext.device.notification.Abstract": [],
|
|
"Ext.log.filter.Filter": [],
|
|
"Ext.device.camera.Sencha": [],
|
|
"Ext.draw.sprite.Sprite": [
|
|
"sprite.sprite"
|
|
],
|
|
"Ext.draw.Color": [],
|
|
"Ext.chart.series.Bar": [
|
|
"series.bar"
|
|
],
|
|
"Ext.field.Slider": [
|
|
"widget.sliderfield"
|
|
],
|
|
"Ext.field.Search": [
|
|
"widget.searchfield"
|
|
],
|
|
"Ext.chart.series.Scatter": [
|
|
"series.scatter"
|
|
],
|
|
"Ext.device.Device": [],
|
|
"Ext.event.Dispatcher": [],
|
|
"Ext.data.Store": [
|
|
"store.store"
|
|
],
|
|
"Ext.draw.modifier.Highlight": [
|
|
"modifier.highlight"
|
|
],
|
|
"Ext.behavior.Translatable": [],
|
|
"Ext.direct.Manager": [],
|
|
"Ext.data.proxy.Proxy": [
|
|
"proxy.proxy"
|
|
],
|
|
"Ext.draw.modifier.Modifier": [],
|
|
"Ext.navigation.View": [
|
|
"widget.navigationview"
|
|
],
|
|
"Ext.draw.modifier.Target": [
|
|
"modifier.target"
|
|
],
|
|
"Ext.draw.sprite.AttributeDefinition": [],
|
|
"Ext.device.Notification": [],
|
|
"Ext.draw.Component": [
|
|
"widget.draw"
|
|
],
|
|
"Ext.layout.VBox": [
|
|
"layout.vbox"
|
|
],
|
|
"Ext.slider.Thumb": [
|
|
"widget.thumb"
|
|
],
|
|
"Ext.MessageBox": [],
|
|
"Ext.ux.Faker": [],
|
|
"Ext.dataview.IndexBar": [],
|
|
"Ext.dataview.element.List": [],
|
|
"Ext.layout.FlexBox": [
|
|
"layout.box"
|
|
],
|
|
"Ext.field.Url": [
|
|
"widget.urlfield"
|
|
],
|
|
"Ext.draw.Solver": [],
|
|
"Ext.data.proxy.Memory": [
|
|
"proxy.memory"
|
|
],
|
|
"Ext.chart.axis.Time": [
|
|
"axis.time"
|
|
],
|
|
"Ext.layout.Card": [
|
|
"layout.card"
|
|
],
|
|
"Ext.ComponentQuery": [],
|
|
"Ext.chart.series.Pie3D": [
|
|
"series.pie3d"
|
|
],
|
|
"Ext.device.camera.Abstract": [],
|
|
"Ext.device.device.Sencha": [],
|
|
"Ext.scroll.View": [],
|
|
"Ext.draw.sprite.Rect": [
|
|
"sprite.rect"
|
|
],
|
|
"Ext.util.Region": [],
|
|
"Ext.field.Select": [
|
|
"widget.selectfield"
|
|
],
|
|
"Ext.draw.Draw": [],
|
|
"Ext.ItemCollection": [],
|
|
"Ext.log.formatter.Default": [],
|
|
"Ext.navigation.Bar": [],
|
|
"Ext.chart.axis.layout.CombineDuplicate": [
|
|
"axisLayout.combineDuplicate"
|
|
],
|
|
"Ext.device.Geolocation": [],
|
|
"Ext.chart.SpaceFillingChart": [
|
|
"widget.spacefilling"
|
|
],
|
|
"Ext.data.proxy.SessionStorage": [
|
|
"proxy.sessionstorage"
|
|
],
|
|
"Ext.fx.easing.EaseIn": [
|
|
"easing.ease-in"
|
|
],
|
|
"Ext.draw.sprite.AnimationParser": [],
|
|
"Ext.field.Password": [
|
|
"widget.passwordfield"
|
|
],
|
|
"Ext.device.connection.Abstract": [],
|
|
"Ext.direct.Event": [
|
|
"direct.event"
|
|
],
|
|
"Ext.direct.RemotingMethod": [],
|
|
"Ext.Evented": [],
|
|
"Ext.carousel.Indicator": [
|
|
"widget.carouselindicator"
|
|
],
|
|
"Ext.util.Collection": [],
|
|
"Ext.chart.interactions.ItemInfo": [
|
|
"interaction.iteminfo"
|
|
],
|
|
"Ext.chart.MarkerHolder": [],
|
|
"Ext.carousel.Carousel": [
|
|
"widget.carousel"
|
|
],
|
|
"Ext.Audio": [
|
|
"widget.audio"
|
|
],
|
|
"Ext.device.Contacts": [],
|
|
"Ext.table.Table": [
|
|
"widget.table"
|
|
],
|
|
"Ext.draw.engine.SvgContext.Gradient": [],
|
|
"Ext.chart.axis.layout.Layout": [],
|
|
"Ext.data.Errors": [],
|
|
"Ext.field.Text": [
|
|
"widget.textfield"
|
|
],
|
|
"Ext.field.TextAreaInput": [
|
|
"widget.textareainput"
|
|
],
|
|
"Ext.field.DatePicker": [
|
|
"widget.datepickerfield"
|
|
],
|
|
"Ext.draw.engine.Svg": [],
|
|
"Ext.event.recognizer.Tap": [],
|
|
"Ext.device.orientation.Abstract": [],
|
|
"Ext.AbstractManager": [],
|
|
"Ext.chart.series.Radar": [
|
|
"series.radar"
|
|
],
|
|
"Ext.chart.interactions.Abstract": [
|
|
"widget.interaction"
|
|
],
|
|
"Ext.scroll.indicator.CssTransform": [],
|
|
"Ext.util.PaintMonitor": [],
|
|
"Ext.direct.PollingProvider": [
|
|
"direct.pollingprovider"
|
|
],
|
|
"Ext.device.notification.PhoneGap": [],
|
|
"Ext.data.writer.Xml": [
|
|
"writer.xml"
|
|
],
|
|
"Ext.event.recognizer.SingleTouch": [],
|
|
"Ext.draw.sprite.Instancing": [
|
|
"sprite.instancing"
|
|
],
|
|
"Ext.event.publisher.ComponentDelegation": [],
|
|
"Ext.chart.axis.Numeric": [
|
|
"axis.numeric"
|
|
],
|
|
"Ext.field.Toggle": [
|
|
"widget.togglefield"
|
|
],
|
|
"Ext.fx.layout.card.ScrollReveal": [
|
|
"fx.layout.card.scrollreveal"
|
|
],
|
|
"Ext.data.Operation": [],
|
|
"Ext.fx.animation.Abstract": [],
|
|
"Ext.chart.interactions.Rotate": [
|
|
"interaction.rotate"
|
|
],
|
|
"Ext.draw.engine.SvgContext": [],
|
|
"Ext.scroll.Scroller": [],
|
|
"Ext.util.SizeMonitor": [],
|
|
"Ext.event.ListenerStack": [],
|
|
"Ext.util.MixedCollection": []
|
|
}); |