/*
|
This file is part of Ext JS 4.2
|
|
Copyright (c) 2011-2013 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: 2013-05-16 14:36:50 (f9be68accb407158ba2b1be2c226a6ce1f649314)
|
*/
|
// @tag foundation,core
|
// @define Ext
|
|
/**
|
* @class Ext
|
* @singleton
|
*/
|
var Ext = Ext || {};
|
Ext._startTime = new Date().getTime();
|
(function() {
|
var global = this,
|
objectPrototype = Object.prototype,
|
toString = objectPrototype.toString,
|
enumerables = true,
|
enumerablesTest = {toString: 1},
|
emptyFn = function () {},
|
// This is the "$previous" method of a hook function on an instance. When called, it
|
// calls through the class prototype by the name of the called method.
|
callOverrideParent = function () {
|
var method = callOverrideParent.caller.caller; // skip callParent (our caller)
|
return method.$owner.prototype[method.$name].apply(this, arguments);
|
},
|
i,
|
nonWhitespaceRe = /\S/,
|
ExtApp,
|
iterableRe = /\[object\s*(?:Array|Arguments|\w*Collection|\w*List|HTML\s+document\.all\s+class)\]/;
|
|
Function.prototype.$extIsFunction = true;
|
|
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-'
|
}, Ext.buildSettings || {});
|
|
Ext.apply(Ext, {
|
|
/**
|
* @property {String} [name='Ext']
|
* <p>The name of the property in the global namespace (The <code>window</code> in browser environments) which refers to the current instance of Ext.</p>
|
* <p>This is usually <code>"Ext"</code>, but if a sandboxed build of ExtJS is being used, this will be an alternative name.</p>
|
* <p>If code is being generated for use by <code>eval</code> or to create a <code>new Function</code>, and the global instance
|
* of Ext must be referenced, this is the name that should be built into the code.</p>
|
*/
|
name: Ext.sandboxName || 'Ext',
|
|
/**
|
* @property {Function}
|
* A reusable empty function
|
*/
|
emptyFn: emptyFn,
|
|
/**
|
* A reusable identity function. The function will always return the first argument, unchanged.
|
*/
|
identityFn: function(o) {
|
return o;
|
},
|
|
/**
|
* A zero length string which will pass a truth test. Useful for passing to methods
|
* which use a truth test to reject <i>falsy</i> values where a string value must be cleared.
|
*/
|
emptyString: new String(),
|
|
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.
|
* @markdown
|
*/
|
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 <tt>overrides</tt> parameter, or a generated one if not provided.
|
* @deprecated 4.0.0 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);
|
};
|
}
|
|
|
// 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;
|
};
|
}()),
|
|
/**
|
* Overrides members of the specified `target` with the given values.
|
*
|
* If the `target` is a class declared using {@link Ext#define Ext.define}, the
|
* `override` method of that class is called (see {@link Ext.Base#override}) given
|
* the `overrides`.
|
*
|
* If the `target` is a function, it is assumed to be a constructor and the contents
|
* of `overrides` are applied to its `prototype` using {@link Ext#apply Ext.apply}.
|
*
|
* If the `target` is an instance of a class declared using {@link Ext#define Ext.define},
|
* the `overrides` are applied to only that instance. In this case, methods are
|
* specially processed to allow them to use {@link Ext.Base#callParent}.
|
*
|
* var panel = new Ext.Panel({ ... });
|
*
|
* Ext.override(panel, {
|
* initComponent: function () {
|
* // extra processing...
|
*
|
* this.callParent();
|
* }
|
* });
|
*
|
* If the `target` is none of these, the `overrides` are applied to the `target`
|
* using {@link Ext#apply Ext.apply}.
|
*
|
* Please refer to {@link Ext#define Ext.define} and {@link Ext.Base#override} for
|
* further details.
|
*
|
* @param {Object} target The target to override.
|
* @param {Object} overrides The properties to add or replace on `target`.
|
* @method override
|
*/
|
override: function (target, overrides) {
|
if (target.$isClass) {
|
target.override(overrides);
|
} else if (typeof target == 'function') {
|
Ext.apply(target.prototype, overrides);
|
} else {
|
var owner = target.self,
|
name, value;
|
|
if (owner && owner.$isClass) { // if (instance of Ext.define'd class)
|
for (name in overrides) {
|
if (overrides.hasOwnProperty(name)) {
|
value = overrides[name];
|
|
if (typeof value == 'function') {
|
|
value.$name = name;
|
value.$owner = owner;
|
value.$previous = target.hasOwnProperty(name)
|
? target[name] // already hooked, so call previous hook
|
: callOverrideParent; // calls by name on prototype
|
}
|
|
target[name] = value;
|
}
|
}
|
} else {
|
Ext.apply(target, overrides);
|
}
|
}
|
|
return target;
|
}
|
});
|
|
// 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 (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
|
* @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}
|
* @markdown
|
*/
|
typeOf: function(value) {
|
var type,
|
typeToString;
|
|
if (value === null) {
|
return 'null';
|
}
|
|
type = typeof value;
|
|
if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
|
return type;
|
}
|
|
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 (nonWhitespaceRe).test(value.nodeValue) ? 'textnode' : 'whitespace';
|
}
|
else {
|
return 'element';
|
}
|
}
|
|
return 'object';
|
}
|
|
},
|
|
/**
|
* Coerces the first value if possible so that it is comparable to the second value.
|
*
|
* Coercion only works between the basic atomic data types String, Boolean, Number, Date, null and undefined.
|
*
|
* Numbers and numeric strings are coerced to Dates using the value as the millisecond era value.
|
*
|
* Strings are coerced to Dates by parsing using the {@link Ext.Date#defaultFormat defaultFormat}.
|
*
|
* For example
|
*
|
* Ext.coerce('false', true);
|
*
|
* returns the boolean value `false` because the second parameter is of type `Boolean`.
|
*
|
* @param {Mixed} from The value to coerce
|
* @param {Mixed} to The value it must be compared against
|
* @return The coerced value.
|
*/
|
coerce: function(from, to) {
|
var fromType = Ext.typeOf(from),
|
toType = Ext.typeOf(to),
|
isString = typeof from === 'string';
|
|
if (fromType !== toType) {
|
switch (toType) {
|
case 'string':
|
return String(from);
|
case 'number':
|
return Number(from);
|
case 'boolean':
|
return isString && (!from || from === 'false') ? false : Boolean(from);
|
case 'null':
|
return isString && (!from || from === 'null') ? null : from;
|
case 'undefined':
|
return isString && (!from || from === 'undefined') ? undefined : from;
|
case 'date':
|
return isString && isNaN(from) ? Ext.Date.parse(from, Ext.Date.defaultFormat) : Date(Number(from));
|
}
|
}
|
return from;
|
},
|
|
/**
|
* 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 (optional) true to allow empty strings (defaults to false)
|
* @return {Boolean}
|
* @markdown
|
*/
|
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: function(value) {
|
return !!(value && value.$extIsFunction);
|
},
|
|
/**
|
* 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, that is, if elements of it are addressable using array
|
* notation with numeric indices, `false` otherwise.
|
*
|
* Arrays and function `arguments` objects are iterable. Also HTML collections such as `NodeList` and `HTMLCollection'
|
* are iterable.
|
*
|
* @param {Object} value The value to test
|
* @return {Boolean}
|
*/
|
isIterable: function(value) {
|
// To be iterable, the object must have a numeric length property and must not be a string or function.
|
if (!value || typeof value.length !== 'number' || typeof value === 'string' || value.$extIsFunction) {
|
return false;
|
}
|
|
// Certain "standard" collections in IE (such as document.images) do not offer the correct
|
// Javascript Object interface; specifically, they lack the propertyIsEnumerable method.
|
// And the item property while it does exist is not typeof "function"
|
if (!value.propertyIsEnumerable) {
|
return !!value.item;
|
}
|
|
// If it is a regular, interrogatable JS object (not an IE ActiveX object), then...
|
// If it has its own property called "length", but not enumerable, it's iterable
|
if (value.hasOwnProperty('length') && !value.propertyIsEnumerable('length')) {
|
return true;
|
}
|
|
// Test against whitelist which includes known iterable collection types
|
return iterableRe.test(toString.call(value));
|
}
|
});
|
|
Ext.apply(Ext, {
|
|
/**
|
* Clone simple variables including array, {}-like objects, DOM nodes and Date without keeping the old reference.
|
* A reference for the object itself is returned if it's not a direct decendant of Object. For model cloning,
|
* see {@link Ext.data.Model#copy Model.copy}.
|
*
|
* @param {Object} item The variable to clone
|
* @return {Object} clone
|
*/
|
clone: function(item) {
|
var type,
|
i,
|
j,
|
k,
|
clone,
|
key;
|
|
if (item === null || item === undefined) {
|
return item;
|
}
|
|
// DOM nodes
|
// TODO proxy this to Ext.Element.clone to handle automatic id attribute changing
|
// recursively
|
if (item.nodeType && item.cloneNode) {
|
return item.cloneNode(true);
|
}
|
|
type = toString.call(item);
|
|
// Date
|
if (type === '[object Date]') {
|
return new Date(item.getTime());
|
}
|
|
|
// Array
|
if (type === '[object Array]') {
|
i = item.length;
|
|
clone = [];
|
|
while (i--) {
|
clone[i] = Ext.clone(item[i]);
|
}
|
}
|
// Object
|
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];
|
if (item.hasOwnProperty(k)) {
|
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,
|
i;
|
|
if (uniqueGlobalNamespace === undefined) {
|
i = 0;
|
|
do {
|
uniqueGlobalNamespace = 'ExtBox' + (++i);
|
} while (Ext.global[uniqueGlobalNamespace] !== undefined);
|
|
Ext.global[uniqueGlobalNamespace] = Ext;
|
this.uniqueGlobalNamespace = uniqueGlobalNamespace;
|
}
|
|
return uniqueGlobalNamespace;
|
},
|
|
/**
|
* @private
|
*/
|
functionFactoryCache: {},
|
|
cacheableFunctionFactory: function() {
|
var me = this,
|
args = Array.prototype.slice.call(arguments),
|
cache = me.functionFactoryCache,
|
idx, fn, ln;
|
|
if (Ext.isSandboxed) {
|
ln = args.length;
|
if (ln > 0) {
|
ln--;
|
args[ln] = 'var Ext=window.' + Ext.name + ';' + args[ln];
|
}
|
}
|
idx = args.join('');
|
fn = cache[idx];
|
if (!fn) {
|
fn = Function.prototype.constructor.apply(Function.prototype, args);
|
|
cache[idx] = fn;
|
}
|
return fn;
|
},
|
|
functionFactory: function() {
|
var me = this,
|
args = Array.prototype.slice.call(arguments),
|
ln;
|
|
if (Ext.isSandboxed) {
|
ln = args.length;
|
if (ln > 0) {
|
ln--;
|
args[ln] = 'var Ext=window.' + Ext.name + ';' + args[ln];
|
}
|
}
|
|
return Function.prototype.constructor.apply(Function.prototype, args);
|
},
|
|
/**
|
* @private
|
* @property
|
*/
|
Logger: {
|
verbose: emptyFn,
|
log: emptyFn,
|
info: emptyFn,
|
warn: emptyFn,
|
error: function(message) {
|
throw new Error(message);
|
},
|
deprecate: emptyFn
|
}
|
});
|
|
/**
|
* Old alias to {@link Ext#typeOf}
|
* @deprecated 4.0.0 Use {@link Ext#typeOf} instead
|
* @method
|
* @inheritdoc Ext#typeOf
|
*/
|
Ext.type = Ext.typeOf;
|
|
// When using Cmd optimizations, the namespace Ext.app may already be defined
|
// by this point since it's done up front by the tool. Check if app already
|
// exists before overwriting it.
|
ExtApp = Ext.app;
|
if (!ExtApp) {
|
ExtApp = Ext.app = {};
|
}
|
Ext.apply(ExtApp, {
|
namespaces: {},
|
|
/**
|
* @private
|
*/
|
collectNamespaces: function(paths) {
|
var namespaces = Ext.app.namespaces,
|
path;
|
|
for (path in paths) {
|
if (paths.hasOwnProperty(path)) {
|
namespaces[path] = true;
|
}
|
}
|
},
|
|
/**
|
* Adds namespace(s) to known list.
|
*
|
* @param {String/String[]} namespace
|
*/
|
addNamespaces: function(ns) {
|
var namespaces = Ext.app.namespaces,
|
i, l;
|
|
if (!Ext.isArray(ns)) {
|
ns = [ns];
|
}
|
|
for (i = 0, l = ns.length; i < l; i++) {
|
namespaces[ns[i]] = true;
|
}
|
},
|
|
/**
|
* @private Clear all namespaces from known list.
|
*/
|
clearNamespaces: function() {
|
Ext.app.namespaces = {};
|
},
|
|
/**
|
* Get namespace prefix for a class name.
|
*
|
* @param {String} className
|
*
|
* @return {String} Namespace prefix if it's known, otherwise undefined
|
*/
|
getNamespace: function(className) {
|
var namespaces = Ext.app.namespaces,
|
deepestPrefix = '',
|
prefix;
|
|
for (prefix in namespaces) {
|
if (namespaces.hasOwnProperty(prefix) &&
|
prefix.length > deepestPrefix.length &&
|
(prefix + '.' === className.substring(0, prefix.length + 1))) {
|
deepestPrefix = prefix;
|
}
|
}
|
|
return deepestPrefix === '' ? undefined : deepestPrefix;
|
}
|
});
|
}());
|
|
/*
|
* This method evaluates the given code free of any local variable. In some browsers this
|
* will be at global scope, in others it will be in a function.
|
* @parma {String} code The code to evaluate.
|
* @private
|
* @method
|
*/
|
Ext.globalEval = Ext.global.execScript
|
? function(code) {
|
execScript(code);
|
}
|
: function($$code) {
|
// IMPORTANT: because we use eval we cannot place this in the above function or it
|
// will break the compressor's ability to rename local variables...
|
(function(){
|
// This var should not be replaced by the compressor. We need to do this so
|
// that Ext refers to the global Ext, if we're sandboxing it may
|
// refer to the local instance inside the closure
|
var Ext = this.Ext;
|
eval($$code);
|
}());
|
};
|
|
// @tag foundation,core
|
// @require ../Ext.js
|
// @define Ext.Version
|
|
/**
|
* @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
|
// also fix Ext-more.js
|
var version = '4.2.1.883', Version;
|
Ext.Version = Version = Ext.extend(Object, {
|
|
/**
|
* @param {String/Number} version The version number in the following standard format:
|
*
|
* major[.minor[.patch[.build[release]]]]
|
*
|
* Examples:
|
*
|
* 1.0
|
* 1.2.3beta
|
* 1.2.3.4RC
|
*
|
* @return {Ext.Version} this
|
*/
|
constructor: function(version) {
|
var 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 = parseInt(parts.shift() || 0, 10);
|
this.minor = parseInt(parts.shift() || 0, 10);
|
this.patch = parseInt(parts.shift() || 0, 10);
|
this.build = parseInt(parts.shift() || 0, 10);
|
|
return this;
|
},
|
|
/**
|
* 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;
|
}
|
});
|
|
/**
|
* @class Ext
|
*/
|
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
|
// @require ../version/Version.js
|
// @define Ext.String
|
|
/**
|
* @class Ext.String
|
*
|
* A collection of useful static methods to deal with strings.
|
* @singleton
|
*/
|
|
Ext.String = (function() {
|
var 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,
|
basicTrimRe = /^\s+|\s+$/g,
|
whitespaceRe = /\s+/,
|
varReplace = /(^[^a-z]*|[^\w])/gi,
|
charToEntity,
|
entityToChar,
|
charToEntityRegex,
|
entityToCharRegex,
|
htmlEncodeReplaceFn = function(match, capture) {
|
return charToEntity[capture];
|
},
|
htmlDecodeReplaceFn = function(match, capture) {
|
return (capture in entityToChar) ? entityToChar[capture] : String.fromCharCode(parseInt(capture.substr(2), 10));
|
},
|
boundsCheck = function(s, other){
|
if (s === null || s === undefined || other === null || other === undefined) {
|
return false;
|
}
|
|
return other.length <= s.length;
|
};
|
|
return {
|
|
/**
|
* Inserts a substring into a string.
|
* @param {String} s The original string.
|
* @param {String} value The substring to insert.
|
* @param {Number} index The index to insert the substring. Negative indexes will insert from the end of
|
* the string. Example:
|
*
|
* Ext.String.insert("abcdefg", "h", -1); // abcdefhg
|
*
|
* @return {String} The value with the inserted substring
|
*/
|
insert: function(s, value, index) {
|
if (!s) {
|
return value;
|
}
|
|
if (!value) {
|
return s;
|
}
|
|
var len = s.length;
|
|
if (!index && index !== 0) {
|
index = len;
|
}
|
|
if (index < 0) {
|
index *= -1;
|
if (index >= len) {
|
// negative overflow, insert at start
|
index = 0;
|
} else {
|
index = len - index;
|
}
|
}
|
|
if (index === 0) {
|
s = value + s;
|
} else if (index >= s.length) {
|
s += value;
|
} else {
|
s = s.substr(0, index) + value + s.substr(index);
|
}
|
return s;
|
},
|
|
/**
|
* Checks if a string starts with a substring
|
* @param {String} s The original string
|
* @param {String} start The substring to check
|
* @param {Boolean} [ignoreCase=false] True to ignore the case in the comparison
|
*/
|
startsWith: function(s, start, ignoreCase){
|
var result = boundsCheck(s, start);
|
|
if (result) {
|
if (ignoreCase) {
|
s = s.toLowerCase();
|
start = start.toLowerCase();
|
}
|
result = s.lastIndexOf(start, 0) === 0;
|
}
|
return result;
|
},
|
|
/**
|
* Checks if a string ends with a substring
|
* @param {String} s The original string
|
* @param {String} start The substring to check
|
* @param {Boolean} [ignoreCase=false] True to ignore the case in the comparison
|
*/
|
endsWith: function(s, end, ignoreCase){
|
var result = boundsCheck(s, end);
|
|
if (result) {
|
if (ignoreCase) {
|
s = s.toLowerCase();
|
end = end.toLowerCase();
|
}
|
result = s.indexOf(end, s.length - end.length) !== -1;
|
}
|
return result;
|
},
|
|
/**
|
* Converts a string of characters into a legal, parse-able JavaScript `var` name as long as the passed
|
* string contains at least one alphabetic character. Non alphanumeric characters, and *leading* non alphabetic
|
* characters will be removed.
|
* @param {String} s A string to be converted into a `var` name.
|
* @return {String} A legal JavaScript `var` name.
|
*/
|
createVarName: function(s) {
|
return s.replace(varReplace, '');
|
},
|
|
/**
|
* 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(value) {
|
return (!value) ? value : String(value).replace(charToEntityRegex, htmlEncodeReplaceFn);
|
},
|
|
/**
|
* Convert certain characters (&, <, >, ', and ") from their HTML character equivalents.
|
* @param {String} value The string to decode.
|
* @return {String} The decoded text.
|
* @method
|
*/
|
htmlDecode: function(value) {
|
return (!value) ? value : String(value).replace(entityToCharRegex, htmlDecodeReplaceFn);
|
},
|
|
/**
|
* Adds a set of character entity definitions to the set used by
|
* {@link Ext.String#htmlEncode} and {@link Ext.String#htmlDecode}.
|
*
|
* This object should be keyed by the entity name sequence,
|
* with the value being the textual representation of the entity.
|
*
|
* Ext.String.addCharacterEntities({
|
* '&Uuml;':'Ü',
|
* '&ccedil;':'ç',
|
* '&ntilde;':'ñ',
|
* '&egrave;':'è'
|
* });
|
* var s = Ext.String.htmlEncode("A string with entities: èÜçñ");
|
*
|
* __Note:__ the values of the character entities defined on this object are expected
|
* to be single character values. As such, the actual values represented by the
|
* characters are sensitive to the character encoding of the JavaScript source
|
* file when defined in string literal form. Script tags referencing server
|
* resources with character entities must ensure that the 'charset' attribute
|
* of the script node is consistent with the actual character encoding of the
|
* server resource.
|
*
|
* The set of character entities may be reset back to the default state by using
|
* the {@link Ext.String#resetCharacterEntities} method
|
*
|
* @param {Object} entities The set of character entities to add to the current
|
* definitions.
|
*/
|
addCharacterEntities: function(newEntities) {
|
var charKeys = [],
|
entityKeys = [],
|
key, echar;
|
for (key in newEntities) {
|
echar = newEntities[key];
|
entityToChar[key] = echar;
|
charToEntity[echar] = key;
|
charKeys.push(echar);
|
entityKeys.push(key);
|
}
|
charToEntityRegex = new RegExp('(' + charKeys.join('|') + ')', 'g');
|
entityToCharRegex = new RegExp('(' + entityKeys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
|
},
|
|
/**
|
* Resets the set of character entity definitions used by
|
* {@link Ext.String#htmlEncode} and {@link Ext.String#htmlDecode} back to the
|
* default state.
|
*/
|
resetCharacterEntities: function() {
|
charToEntity = {};
|
entityToChar = {};
|
// add the default set
|
this.addCharacterEntities({
|
'&' : '&',
|
'>' : '>',
|
'<' : '<',
|
'"' : '"',
|
''' : "'"
|
});
|
},
|
|
/**
|
* 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:
|
*
|
* var s = ' foo bar ';
|
* alert('-' + s + '-'); //alerts "- foo bar -"
|
* alert('-' + Ext.String.trim(s) + '-'); //alerts "-foo bar-"
|
*
|
* @param {String} string The string to trim.
|
* @return {String} The trimmed string.
|
*/
|
trim: function(string) {
|
return string.replace(trimRegex, "");
|
},
|
|
/**
|
* Capitalize the given string
|
* @param {String} string
|
* @return {String}
|
*/
|
capitalize: function(string) {
|
return string.charAt(0).toUpperCase() + string.substr(1);
|
},
|
|
/**
|
* Uncapitalize the given string.
|
* @param {String} string
|
* @return {String}
|
*/
|
uncapitalize: function(string) {
|
return string.charAt(0).toLowerCase() + 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=false] `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(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(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');
|
* // s now contains the string: '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.
|
* @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);
|
* // s now contains the string: '<div class="my-class">Some text</div>'
|
*
|
* @param {String} string The tokenized string to be formatted.
|
* @param {Mixed...} values The values to replace tokens `{0}`, `{1}`, etc in order.
|
* @return {String} The formatted string.
|
*/
|
format: function(format) {
|
var args = Ext.Array.toArray(arguments, 1);
|
return format.replace(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) {
|
if (count < 1) {
|
count = 0;
|
}
|
for (var buf = [], i = count; i--; ) {
|
buf.push(pattern);
|
}
|
return buf.join(sep || '');
|
},
|
|
/**
|
* Splits a string of space separated words into an array, trimming as needed. If the
|
* words are already an array, it is returned.
|
*
|
* @param {String/Array} words
|
*/
|
splitWords: function (words) {
|
if (words && typeof words == 'string') {
|
return words.replace(basicTrimRe, '').split(whitespaceRe);
|
}
|
return words || [];
|
}
|
};
|
}());
|
|
// initialize the default encode / decode entities
|
Ext.String.resetCharacterEntities();
|
|
/**
|
* Old alias to {@link Ext.String#htmlEncode}
|
* @deprecated Use {@link Ext.String#htmlEncode} instead
|
* @method
|
* @member Ext
|
* @inheritdoc 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
|
* @inheritdoc 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
|
* @inheritdoc Ext.String#urlAppend
|
*/
|
Ext.urlAppend = Ext.String.urlAppend;
|
|
// @tag foundation,core
|
// @require String.js
|
// @define Ext.Number
|
|
/**
|
* @class Ext.Number
|
*
|
* A collection of useful static methods to deal with numbers
|
* @singleton
|
*/
|
|
Ext.Number = new function() {
|
|
var me = this,
|
isToFixedBroken = (0.9).toFixed() !== '1',
|
math = Math;
|
|
Ext.apply(this, {
|
/**
|
* 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) {
|
var x = parseFloat(number);
|
|
// Watch out for NaN in Chrome 18
|
// V8bug: http://code.google.com/p/v8/issues/detail?id=2056
|
|
// Operators are faster than Math.min/max. See http://jsperf.com/number-constrain
|
// ... and (x < Nan) || (x < undefined) == false
|
// ... same for (x > NaN) || (x > undefined)
|
// so if min or max are undefined or NaN, we never return them... sadly, this
|
// is not true of null (but even Math.max(-1,null)==0 and isNaN(null)==false)
|
return (x < min) ? min : ((x > max) ? max : x);
|
},
|
|
/**
|
* Snaps the passed number between stopping points based upon a passed increment value.
|
*
|
* The difference between this and {@link #snapInRange} is that {@link #snapInRange} uses the minValue
|
* when calculating snap points:
|
*
|
* r = Ext.Number.snap(56, 2, 55, 65); // Returns 56 - snap points are zero based
|
*
|
* r = Ext.Number.snapInRange(56, 2, 55, 65); // Returns 57 - snap points are based from minValue
|
*
|
* @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 m;
|
|
// If no value passed, or minValue was passed and value is less than minValue (anything < undefined is false)
|
// Then use the minValue (or zero if the value was undefined)
|
if (value === undefined || value < minValue) {
|
return minValue || 0;
|
}
|
|
if (increment) {
|
m = value % increment;
|
if (m !== 0) {
|
value -= m;
|
if (m * 2 >= increment) {
|
value += increment;
|
} else if (m * 2 < -increment) {
|
value -= increment;
|
}
|
}
|
}
|
return me.constrain(value, minValue, maxValue);
|
},
|
|
/**
|
* Snaps the passed number between stopping points based upon a passed increment value.
|
*
|
* The difference between this and {@link #snap} is that {@link #snap} does not use the minValue
|
* when calculating snap points:
|
*
|
* r = Ext.Number.snap(56, 2, 55, 65); // Returns 56 - snap points are zero based
|
*
|
* r = Ext.Number.snapInRange(56, 2, 55, 65); // Returns 57 - snap points are based from minValue
|
*
|
* @param {Number} value The unsnapped value.
|
* @param {Number} increment The increment by which the value must move.
|
* @param {Number} [minValue=0] The minimum value to which the returned value must be constrained.
|
* @param {Number} [maxValue=Infinity] The maximum value to which the returned value must be constrained.
|
* @return {Number} The value of the nearest snap target.
|
*/
|
snapInRange : function(value, increment, minValue, maxValue) {
|
var tween;
|
|
// default minValue to zero
|
minValue = (minValue || 0);
|
|
// If value is undefined, or less than minValue, use minValue
|
if (value === undefined || value < minValue) {
|
return minValue;
|
}
|
|
// Calculate how many snap points from the minValue the passed value is.
|
if (increment && (tween = ((value - minValue) % increment))) {
|
value -= tween;
|
tween *= 2;
|
if (tween >= increment) {
|
value += increment;
|
}
|
}
|
|
// If constraining within a maximum, ensure the maximum is on a snap point
|
if (maxValue !== undefined) {
|
if (value > (maxValue = me.snapInRange(maxValue, increment, minValue))) {
|
value = maxValue;
|
}
|
}
|
|
return value;
|
},
|
|
/**
|
* 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: isToFixedBroken ? function(value, precision) {
|
precision = precision || 0;
|
var pow = math.pow(10, precision);
|
return (math.round(value * pow) / pow).toFixed(precision);
|
} : function(value, 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;
|
},
|
|
/**
|
* Returns a random integer between the specified range (inclusive)
|
* @param {Number} from Lowest value to return.
|
* @param {Number} to Highst value to return.
|
* @return {Number} A random integer within the specified range.
|
*/
|
randomInt: function (from, to) {
|
return math.floor(math.random() * (to - from + 1) + from);
|
},
|
|
/**
|
* Corrects floating point numbers that overflow to a non-precise
|
* value because of their floating nature, for example `0.1 + 0.2`
|
* @param {Number} The number
|
* @return {Number} The correctly rounded number
|
*/
|
correctFloat: function(n) {
|
// This is to correct the type of errors where 2 floats end with
|
// a long string of decimals, eg 0.1 + 0.2. When they overflow in this
|
// manner, they usually go to 15-16 decimals, so we cut it off at 14.
|
return parseFloat(n.toPrecision(14));
|
}
|
});
|
|
/**
|
* @deprecated 4.0.0 Please use {@link Ext.Number#from} instead.
|
* @member Ext
|
* @method num
|
* @inheritdoc Ext.Number#from
|
*/
|
Ext.num = function() {
|
return me.from.apply(this, arguments);
|
};
|
};
|
|
// @tag foundation,core
|
// @require Number.js
|
// @define Ext.Array
|
|
/**
|
* @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,
|
erase,
|
replace,
|
splice;
|
|
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),
|
remove,
|
tailOldPos,
|
tailNewPos,
|
tailCount,
|
lengthAfterRemove,
|
i;
|
|
// we try to use Array.push when we can for efficiency...
|
if (pos === length) {
|
if (add) {
|
array.push.apply(array, insert);
|
}
|
} else {
|
remove = Math.min(removeCount, length - pos);
|
tailOldPos = pos + remove;
|
tailNewPos = tailOldPos + add - remove;
|
tailCount = length - tailOldPos;
|
lengthAfterRemove = length - remove;
|
|
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) {
|
// Inserting at index zero with no removing: use unshift
|
if (index === 0 && !removeCount) {
|
array.unshift.apply(array, insert);
|
}
|
// Inserting/replacing in middle of array
|
else if (index < array.length) {
|
array.splice.apply(array, [index, removeCount].concat(insert));
|
}
|
// Appending to array
|
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));
|
}
|
|
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 (Optional) Reverse the iteration order (loop from the end to the beginning)
|
* Defaults false
|
* @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) {
|
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 arrayPrototype.indexOf.call(array, 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 arrayPrototype.indexOf.call(array, 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']
|
*
|
* {@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 (Optional) a zero-based index that specifies the start of extraction. Defaults to 0
|
* @param {Number} end (Optional) a 1-based index that specifies the end of extraction. Defaults to the last
|
* index of the iterable value
|
* @return {Array} 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 {Mixed} fn.item Current item.
|
* @param {Number} fn.index Index of the item.
|
* @param {Array} fn.array The whole array that's being iterated.
|
* @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 {Mixed} fn.item Current item.
|
* @param {Number} fn.index Index of the item.
|
* @param {Array} fn.array The whole array that's being iterated.
|
* @param {Object} scope Callback function scope
|
* @return {Boolean} True if no false value is returned by the callback function.
|
*/
|
every: supportsEvery ? function(array, fn, scope) {
|
return array.every(fn, scope);
|
} : function(array, 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 {Mixed} fn.item Current item.
|
* @param {Number} fn.index Index of the item.
|
* @param {Array} fn.array The whole array that's being iterated.
|
* @param {Object} scope Callback function scope
|
* @return {Boolean} True if the callback function returns a truthy value.
|
*/
|
some: supportsSome ? function(array, fn, scope) {
|
return array.some(fn, scope);
|
} : function(array, fn, scope) {
|
var i = 0,
|
ln = array.length;
|
|
for (; i < ln; ++i) {
|
if (fn.call(scope, array[i], i, array)) {
|
return true;
|
}
|
}
|
|
return false;
|
},
|
|
/**
|
* Shallow compares the contents of 2 arrays using strict equality.
|
* @param {Array} array1
|
* @param {Array} array2
|
* @return {Boolean} `true` if the arrays are equal.
|
*/
|
equals: function(array1, array2) {
|
var len1 = array1.length,
|
len2 = array2.length,
|
i;
|
|
// Short circuit if the same array is passed twice
|
if (array1 === array2) {
|
return true;
|
}
|
|
if (len1 !== len2) {
|
return false;
|
}
|
|
for (i = 0; i < len1; ++i) {
|
if (array1[i] !== array2[i]) {
|
return false;
|
}
|
}
|
|
return true;
|
},
|
|
/**
|
* 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 {Mixed} fn.item Current item.
|
* @param {Number} fn.index Index of the item.
|
* @param {Array} fn.array The whole array that's being iterated.
|
* @param {Object} scope Callback function scope
|
* @return {Array} results
|
*/
|
filter: supportsFilter ? function(array, fn, scope) {
|
return array.filter(fn, scope);
|
} : function(array, 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;
|
},
|
|
/**
|
* Returns the first item in the array which elicits a true return value from the
|
* passed selection function.
|
* @param {Array} array The array to search
|
* @param {Function} fn The selection function to execute for each item.
|
* @param {Mixed} fn.item The array item.
|
* @param {String} fn.index The index of the array item.
|
* @param {Object} scope (optional) The scope (<code>this</code> reference) in which the
|
* function is executed. Defaults to the array
|
* @return {Object} The first item in the array which returned true from the selection
|
* function, or null if none was found.
|
*/
|
findBy : function(array, fn, scope) {
|
var i = 0,
|
len = array.length;
|
|
for (; i < len; i++) {
|
if (fn.call(scope || array, array[i], i)) {
|
return array[i];
|
}
|
}
|
return null;
|
},
|
|
/**
|
* 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 (Optional) True to clone the given array and return a new reference if necessary,
|
* defaults to false
|
* @return {Array} array
|
*/
|
from: function(value, newReference) {
|
if (value === undefined || value === null) {
|
return [];
|
}
|
|
if (Ext.isArray(value)) {
|
return (newReference) ? slice.call(value) : value;
|
}
|
|
var type = typeof value;
|
// Both strings and functions will have a length property. In phantomJS, NodeList
|
// instances report typeof=='function' but don't have an apply method...
|
if (value && value.length !== undefined && type !== 'string' && (type !== 'function' || !value.apply)) {
|
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 intersection = [],
|
arrays = slice.call(arguments),
|
arraysLength,
|
array,
|
arrayLength,
|
minArray,
|
minArrayIndex,
|
minArrayCandidate,
|
minArrayLength,
|
element,
|
elementCandidate,
|
elementCount,
|
i, j, k;
|
|
if (!arrays.length) {
|
return intersection;
|
}
|
|
// Find the smallest array
|
arraysLength = arrays.length;
|
for (i = minArrayIndex = 0; i < arraysLength; i++) {
|
minArrayCandidate = arrays[i];
|
if (!minArray || minArrayCandidate.length < minArray.length) {
|
minArray = minArrayCandidate;
|
minArrayIndex = i;
|
}
|
}
|
|
minArray = ExtArray.unique(minArray);
|
erase(arrays, minArrayIndex, 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.
|
minArrayLength = minArray.length;
|
arraysLength = arrays.length;
|
for (i = 0; i < minArrayLength; i++) {
|
element = minArray[i];
|
elementCount = 0;
|
|
for (j = 0; j < arraysLength; j++) {
|
array = arrays[j];
|
arrayLength = array.length;
|
for (k = 0; k < arrayLength; k++) {
|
elementCandidate = array[k];
|
if (element === elementCandidate) {
|
elementCount++;
|
break;
|
}
|
}
|
}
|
|
if (elementCount === arraysLength) {
|
intersection.push(element);
|
}
|
}
|
|
return intersection;
|
},
|
|
/**
|
* 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.
|
* @method slice
|
*/
|
// Note: IE6 will return [] on slice.call(x, undefined).
|
slice: ([1,2].slice(1, undefined).length ?
|
function (array, begin, end) {
|
return slice.call(array, begin, end);
|
} :
|
// at least IE6 uses arguments.length for variadic signature
|
function (array, begin, end) {
|
// After tested for IE 6, the one below is of the best performance
|
// see http://jsperf.com/slice-fix
|
if (typeof begin === 'undefined') {
|
return slice.call(array);
|
}
|
if (typeof end === 'undefined') {
|
return slice.call(array, begin);
|
}
|
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.
|
* @param {Mixed} sortFn.a An item to compare.
|
* @param {Mixed} sortFn.b Another item to compare.
|
* @return {Array} The sorted array.
|
*/
|
sort: supportsSort ? function(array, sortFn) {
|
if (sortFn) {
|
return array.sort(sortFn);
|
} else {
|
return array.sort();
|
}
|
} : function(array, sortFn) {
|
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 comparision which determines minimization.
|
* If omitted the "<" operator will be used. Note: gt = 1; eq = 0; lt = -1
|
* @param {Mixed} comparisonFn.min Current minimum value.
|
* @param {Mixed} comparisonFn.item The value to compare with the current minimum.
|
* @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 comparision which determines maximization.
|
* If omitted the ">" operator will be used. Note: gt = 1; eq = 0; lt = -1
|
* @param {Mixed} comparisonFn.max Current maximum value.
|
* @param {Mixed} comparisonFn.item The value to compare with the current maximum.
|
* @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;
|
},
|
|
/**
|
* Creates a map (object) keyed by the elements of the given array. The values in
|
* the map are the index+1 of the array element. For example:
|
*
|
* var map = Ext.Array.toMap(['a','b','c']);
|
*
|
* // map = { a: 1, b: 2, c: 3 };
|
*
|
* Or a key property can be specified:
|
*
|
* var map = Ext.Array.toMap([
|
* { name: 'a' },
|
* { name: 'b' },
|
* { name: 'c' }
|
* ], 'name');
|
*
|
* // map = { a: 1, b: 2, c: 3 };
|
*
|
* Lastly, a key extractor can be provided:
|
*
|
* var map = Ext.Array.toMap([
|
* { name: 'a' },
|
* { name: 'b' },
|
* { name: 'c' }
|
* ], function (obj) { return obj.name.toUpperCase(); });
|
*
|
* // map = { A: 1, B: 2, C: 3 };
|
*
|
* @param {Array} array The Array to create the map from.
|
* @param {String/Function} [getKey] Name of the object property to use
|
* as a key or a function to extract the key.
|
* @param {Object} [scope] Value of this inside callback.
|
* @return {Object} The resulting map.
|
*/
|
toMap: function(array, getKey, scope) {
|
var map = {},
|
i = array.length;
|
|
if (!getKey) {
|
while (i--) {
|
map[array[i]] = i+1;
|
}
|
} else if (typeof getKey == 'string') {
|
while (i--) {
|
map[array[i][getKey]] = i+1;
|
}
|
} else {
|
while (i--) {
|
map[getKey.call(scope, array[i])] = i+1;
|
}
|
}
|
|
return map;
|
},
|
|
/**
|
* Creates a map (object) keyed by a property of elements of the given array. The values in
|
* the map are the array element. For example:
|
*
|
* var map = Ext.Array.toMap(['a','b','c']);
|
*
|
* // map = { a: 'a', b: 'b', c: 'c' };
|
*
|
* Or a key property can be specified:
|
*
|
* var map = Ext.Array.toMap([
|
* { name: 'a' },
|
* { name: 'b' },
|
* { name: 'c' }
|
* ], 'name');
|
*
|
* // map = { a: {name: 'a'}, b: {name: 'b'}, c: {name: 'c'} };
|
*
|
* Lastly, a key extractor can be provided:
|
*
|
* var map = Ext.Array.toMap([
|
* { name: 'a' },
|
* { name: 'b' },
|
* { name: 'c' }
|
* ], function (obj) { return obj.name.toUpperCase(); });
|
*
|
* // map = { A: {name: 'a'}, B: {name: 'b'}, C: {name: 'c'} };
|
*
|
* @param {Array} array The Array to create the map from.
|
* @param {String/Function} [getKey] Name of the object property to use
|
* as a key or a function to extract the key.
|
* @param {Object} [scope] Value of this inside callback.
|
* @return {Object} The resulting map.
|
*/
|
toValueMap: function(array, getKey, scope) {
|
var map = {},
|
i = array.length;
|
|
if (!getKey) {
|
while (i--) {
|
map[array[i]] = array[i];
|
}
|
} else if (typeof getKey == 'string') {
|
while (i--) {
|
map[array[i][getKey]] = array[i];
|
}
|
} else {
|
while (i--) {
|
map[getKey.call(scope, array[i])] = array[i];
|
}
|
}
|
|
return map;
|
},
|
|
|
/**
|
* 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 in which to insert.
|
* @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).
|
* @param {Object...} elements The elements to add to the array. If you don't specify
|
* any elements, splice simply removes elements from the array.
|
* @return {Array} An array containing the removed items.
|
* @method
|
*/
|
splice: splice,
|
|
/**
|
* Pushes new items onto the end of an Array.
|
*
|
* Passed parameters may be single items, or arrays of items. If an Array is found in the argument list, all its
|
* elements are pushed into the end of the target Array.
|
*
|
* @param {Array} target The Array onto which to push new items
|
* @param {Object...} elements The elements to add to the array. Each parameter may
|
* be an Array, in which case all the elements of that Array will be pushed into the end of the
|
* destination Array.
|
* @return {Array} An array containing all the new items push onto the end.
|
*
|
*/
|
push: function(array) {
|
var len = arguments.length,
|
i = 1,
|
newItem;
|
|
if (array === undefined) {
|
array = [];
|
} else if (!Ext.isArray(array)) {
|
array = [array];
|
}
|
for (; i < len; i++) {
|
newItem = arguments[i];
|
Array.prototype.push[Ext.isIterable(newItem) ? 'apply' : 'call'](array, newItem);
|
}
|
return array;
|
}
|
};
|
|
/**
|
* @method
|
* @member Ext
|
* @inheritdoc Ext.Array#each
|
*/
|
Ext.each = ExtArray.each;
|
|
/**
|
* @method
|
* @member Ext.Array
|
* @inheritdoc Ext.Array#merge
|
*/
|
ExtArray.union = ExtArray.merge;
|
|
/**
|
* Old alias to {@link Ext.Array#min}
|
* @deprecated 4.0.0 Use {@link Ext.Array#min} instead
|
* @method
|
* @member Ext
|
* @inheritdoc Ext.Array#min
|
*/
|
Ext.min = ExtArray.min;
|
|
/**
|
* Old alias to {@link Ext.Array#max}
|
* @deprecated 4.0.0 Use {@link Ext.Array#max} instead
|
* @method
|
* @member Ext
|
* @inheritdoc Ext.Array#max
|
*/
|
Ext.max = ExtArray.max;
|
|
/**
|
* Old alias to {@link Ext.Array#sum}
|
* @deprecated 4.0.0 Use {@link Ext.Array#sum} instead
|
* @method
|
* @member Ext
|
* @inheritdoc Ext.Array#sum
|
*/
|
Ext.sum = ExtArray.sum;
|
|
/**
|
* Old alias to {@link Ext.Array#mean}
|
* @deprecated 4.0.0 Use {@link Ext.Array#mean} instead
|
* @method
|
* @member Ext
|
* @inheritdoc Ext.Array#mean
|
*/
|
Ext.mean = ExtArray.mean;
|
|
/**
|
* Old alias to {@link Ext.Array#flatten}
|
* @deprecated 4.0.0 Use {@link Ext.Array#flatten} instead
|
* @method
|
* @member Ext
|
* @inheritdoc Ext.Array#flatten
|
*/
|
Ext.flatten = ExtArray.flatten;
|
|
/**
|
* Old alias to {@link Ext.Array#clean}
|
* @deprecated 4.0.0 Use {@link Ext.Array#clean} instead
|
* @method
|
* @member Ext
|
* @inheritdoc Ext.Array#clean
|
*/
|
Ext.clean = ExtArray.clean;
|
|
/**
|
* Old alias to {@link Ext.Array#unique}
|
* @deprecated 4.0.0 Use {@link Ext.Array#unique} instead
|
* @method
|
* @member Ext
|
* @inheritdoc Ext.Array#unique
|
*/
|
Ext.unique = ExtArray.unique;
|
|
/**
|
* Old alias to {@link Ext.Array#pluck Ext.Array.pluck}
|
* @deprecated 4.0.0 Use {@link Ext.Array#pluck Ext.Array.pluck} instead
|
* @method
|
* @member Ext
|
* @inheritdoc Ext.Array#pluck
|
*/
|
Ext.pluck = ExtArray.pluck;
|
|
/**
|
* @method
|
* @member Ext
|
* @inheritdoc Ext.Array#toArray
|
*/
|
Ext.toArray = function() {
|
return ExtArray.toArray.apply(ExtArray, arguments);
|
};
|
}());
|
|
// @tag foundation,core
|
// @require Array.js
|
// @define Ext.Function
|
|
/**
|
* @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
|
* @returns {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 default global environment object (usually 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 || Ext.global, 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)) {
|
if (Ext.isIterable(args)) {
|
args = Ext.Array.clone(args);
|
} else {
|
args = args !== undefined ? [args] : [];
|
}
|
}
|
|
return function() {
|
var fnArgs = [].concat(args);
|
fnArgs.push.apply(fnArgs, arguments);
|
return fn.apply(scope || this, fnArgs);
|
};
|
},
|
|
/**
|
* 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] 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] 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 {
|
returnValue = Ext.isDefined(returnValue) ? returnValue : null;
|
return function() {
|
var me = this,
|
args = arguments;
|
|
newFn.target = me;
|
newFn.method = origFn;
|
return (newFn.apply(scope || me || Ext.global, args) !== false) ? origFn.apply(me || Ext.global, args) : returnValue;
|
};
|
}
|
},
|
|
/**
|
* 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 millseconds 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(Ext.supports.TimeoutActualLateness ? function () {
|
fn();
|
} : 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 default global environment object (usually 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.
|
* @returns {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 = Ext.Date.now();
|
};
|
|
return function() {
|
elapsed = Ext.Date.now() - lastCallTime;
|
lastArgs = arguments;
|
|
clearTimeout(timer);
|
if (!lastCallTime || (elapsed >= interval)) {
|
execute();
|
} else {
|
timer = setTimeout(execute, interval - elapsed);
|
}
|
};
|
},
|
|
|
/**
|
* Adds behavior to an existing method that is executed before the
|
* original behavior of the function. For example:
|
*
|
* var soup = {
|
* contents: [],
|
* add: function(ingredient) {
|
* this.contents.push(ingredient);
|
* }
|
* };
|
* Ext.Function.interceptBefore(soup, "add", function(ingredient){
|
* if (!this.contents.length && ingredient !== "water") {
|
* // Always add water to start with
|
* this.contents.push("water");
|
* }
|
* });
|
* soup.add("onions");
|
* soup.add("salt");
|
* soup.contents; // will contain: water, onions, salt
|
*
|
* @param {Object} object The target object
|
* @param {String} methodName Name of the method to override
|
* @param {Function} fn Function with the new behavior. It will
|
* be called with the same arguments as the original method. The
|
* return value of this function will be the return value of the
|
* new method.
|
* @param {Object} [scope] The scope to execute the interceptor function. Defaults to the object.
|
* @return {Function} The new function just created.
|
*/
|
interceptBefore: function(object, methodName, fn, scope) {
|
var method = object[methodName] || Ext.emptyFn;
|
|
return (object[methodName] = function() {
|
var ret = fn.apply(scope || this, arguments);
|
method.apply(this, arguments);
|
|
return ret;
|
});
|
},
|
|
/**
|
* Adds behavior to an existing method that is executed after the
|
* original behavior of the function. For example:
|
*
|
* var soup = {
|
* contents: [],
|
* add: function(ingredient) {
|
* this.contents.push(ingredient);
|
* }
|
* };
|
* Ext.Function.interceptAfter(soup, "add", function(ingredient){
|
* // Always add a bit of extra salt
|
* this.contents.push("salt");
|
* });
|
* soup.add("water");
|
* soup.add("onions");
|
* soup.contents; // will contain: water, salt, onions, salt
|
*
|
* @param {Object} object The target object
|
* @param {String} methodName Name of the method to override
|
* @param {Function} fn Function with the new behavior. It will
|
* be called with the same arguments as the original method. The
|
* return value of this function will be the return value of the
|
* new method.
|
* @param {Object} [scope] The scope to execute the interceptor function. Defaults to the object.
|
* @return {Function} The new function just created.
|
*/
|
interceptAfter: function(object, methodName, fn, scope) {
|
var method = object[methodName] || Ext.emptyFn;
|
|
return (object[methodName] = function() {
|
method.apply(this, arguments);
|
return fn.apply(scope || this, arguments);
|
});
|
}
|
};
|
|
/**
|
* @method
|
* @member Ext
|
* @inheritdoc Ext.Function#defer
|
*/
|
Ext.defer = Ext.Function.alias(Ext.Function, 'defer');
|
|
/**
|
* @method
|
* @member Ext
|
* @inheritdoc Ext.Function#pass
|
*/
|
Ext.pass = Ext.Function.alias(Ext.Function, 'pass');
|
|
/**
|
* @method
|
* @member Ext
|
* @inheritdoc Ext.Function#bind
|
*/
|
Ext.bind = Ext.Function.alias(Ext.Function, 'bind');
|
|
// @tag foundation,core
|
// @require Function.js
|
// @define Ext.Object
|
|
/**
|
* @class Ext.Object
|
*
|
* A collection of useful static methods to deal with objects.
|
*
|
* @singleton
|
*/
|
|
(function() {
|
|
// The "constructor" for chain:
|
var TemplateClass = function(){},
|
ExtObject = Ext.Object = {
|
|
/**
|
* Returns a new object with the given object as the prototype chain. This method is
|
* designed to mimic the ECMA standard `Object.create` method and is assigned to that
|
* function when it is available.
|
*
|
* **NOTE** This method does not support the property definitions capability of the
|
* `Object.create` method. Only the first argument is supported.
|
*
|
* @param {Object} object The prototype chain for the new object.
|
*/
|
chain: Object.create || function (object) {
|
TemplateClass.prototype = object;
|
var result = new TemplateClass();
|
TemplateClass.prototype = null;
|
return result;
|
},
|
|
/**
|
* Converts a `name` - `value` pair to an array of objects with support for nested structures. Useful to construct
|
* query strings. For example:
|
*
|
* var objects = Ext.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']);
|
*
|
* // objects then equals:
|
* [
|
* { name: 'hobbies', value: 'reading' },
|
* { name: 'hobbies', value: 'cooking' },
|
* { name: 'hobbies', value: 'swimming' },
|
* ];
|
*
|
* var objects = Ext.Object.toQueryObjects('dateOfBirth', {
|
* day: 3,
|
* month: 8,
|
* year: 1987,
|
* extra: {
|
* hour: 4
|
* minute: 30
|
* }
|
* }, true); // Recursive
|
*
|
* // 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/Array} value
|
* @param {Boolean} [recursive=false] True to traverse object recursively
|
* @return {Array}
|
*/
|
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(/^([^\[]+)/);
|
|
|
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;
|
},
|
|
/**
|
* Iterates through an object and invokes the given callback function for each iteration.
|
* The iteration can be stopped 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 {Object} 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);
|
*
|
* // extjs and 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} destination The object into which all subsequent objects are merged.
|
* @param {Object...} object Any number of objects to merge into the destination.
|
* @return {Object} merged The destination object with all passed objects merged in.
|
*/
|
merge: function(destination) {
|
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 = destination[key];
|
if (sourceKey && sourceKey.constructor === Object) {
|
mergeFn(sourceKey, value);
|
}
|
else {
|
destination[key] = cloneFn(value);
|
}
|
}
|
else {
|
destination[key] = value;
|
}
|
}
|
}
|
|
return destination;
|
},
|
|
/**
|
* @private
|
* @param destination
|
*/
|
mergeIf: function(destination) {
|
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 destination)) {
|
value = object[key];
|
|
if (value && value.constructor === Object) {
|
destination[key] = cloneFn(value);
|
}
|
else {
|
destination[key] = value;
|
}
|
}
|
}
|
}
|
|
return destination;
|
},
|
|
/**
|
* 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(person, '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: (typeof Object.keys == 'function')
|
? function(object){
|
if (!object) {
|
return [];
|
}
|
return Object.keys(object);
|
}
|
: 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;
|
},
|
|
/**
|
* Checks if there are any properties on this object.
|
* @param {Object} object
|
* @return {Boolean} `true` if there no properties on the object.
|
*/
|
isEmpty: function(object){
|
for (var key in object) {
|
if (object.hasOwnProperty(key)) {
|
return false;
|
}
|
}
|
return true;
|
},
|
|
/**
|
* Shallow compares the contents of 2 objects using strict equality. Objects are
|
* considered equal if they both have the same set of properties and the
|
* value for those properties equals the other in the corresponding object.
|
*
|
* // Returns true
|
* Ext.Object.equals({
|
* foo: 1,
|
* bar: 2
|
* }, {
|
* foo: 1,
|
* bar: 2
|
* });
|
*
|
* @param {Object} object1
|
* @param {Object} object2
|
* @return {Boolean} `true` if the objects are equal.
|
*/
|
equals: (function() {
|
var check = function(o1, o2) {
|
var key;
|
|
for (key in o1) {
|
if (o1.hasOwnProperty(key)) {
|
if (o1[key] !== o2[key]) {
|
return false;
|
}
|
}
|
}
|
return true;
|
};
|
|
return function(object1, object2) {
|
|
// Short circuit if the same object is passed twice
|
if (object1 === object2) {
|
return true;
|
} if (object1 && object2) {
|
// Do the second check because we could have extra keys in
|
// object2 that don't exist in object1.
|
return check(object1, object2) && check(object2, object1);
|
} else if (!object1 && !object2) {
|
return object1 === object2;
|
} else {
|
return false;
|
}
|
};
|
})(),
|
|
/**
|
* @private
|
*/
|
classify: function(object) {
|
var prototype = object,
|
objectProperties = [],
|
propertyClassesMap = {},
|
objectClass = function() {
|
var i = 0,
|
ln = objectProperties.length,
|
property;
|
|
for (; i < ln; i++) {
|
property = objectProperties[i];
|
this[property] = new propertyClassesMap[property]();
|
}
|
},
|
key, value;
|
|
for (key in object) {
|
if (object.hasOwnProperty(key)) {
|
value = object[key];
|
|
if (value && value.constructor === Object) {
|
objectProperties.push(key);
|
propertyClassesMap[key] = ExtObject.classify(value);
|
}
|
}
|
}
|
|
objectClass.prototype = prototype;
|
|
return objectClass;
|
}
|
};
|
|
/**
|
* A convenient alias method for {@link Ext.Object#merge}.
|
*
|
* @member Ext
|
* @method merge
|
* @inheritdoc Ext.Object#merge
|
*/
|
Ext.merge = Ext.Object.merge;
|
|
/**
|
* @private
|
* @member Ext
|
*/
|
Ext.mergeIf = Ext.Object.mergeIf;
|
|
/**
|
*
|
* @member Ext
|
* @method urlEncode
|
* @inheritdoc Ext.Object#toQueryString
|
* @deprecated 4.0.0 Use {@link 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);
|
};
|
|
/**
|
* Alias for {@link Ext.Object#fromQueryString}.
|
*
|
* @member Ext
|
* @method urlDecode
|
* @inheritdoc Ext.Object#fromQueryString
|
* @deprecated 4.0.0 Use {@link Ext.Object#fromQueryString} instead
|
*/
|
Ext.urlDecode = function() {
|
return ExtObject.fromQueryString.apply(ExtObject, arguments);
|
};
|
|
}());
|
|
// @tag foundation,core
|
// @require Object.js
|
// @define Ext.Date
|
|
/**
|
* @class Ext.Date
|
* A set of useful static methods to deal with date
|
* Note that if Ext.Date is required and loaded, it will copy all methods / properties to
|
* this object for convenience
|
*
|
* The date parsing and formatting syntax contains a subset of
|
* [PHP's `date()` function](http://www.php.net/date), and the formats that are
|
* supported will provide results equivalent to their PHP versions.
|
*
|
* The following is a list of all currently supported formats:
|
* <pre class="">
|
Format Description Example returned values
|
------ ----------------------------------------------------------------------- -----------------------
|
d Day of the month, 2 digits with leading zeros 01 to 31
|
D A short textual representation of the day of the week Mon to Sun
|
j Day of the month without leading zeros 1 to 31
|
l A full textual representation of the day of the week Sunday to Saturday
|
N ISO-8601 numeric representation of the day of the week 1 (for Monday) through 7 (for Sunday)
|
S English ordinal suffix for the day of the month, 2 characters st, nd, rd or th. Works well with j
|
w Numeric representation of the day of the week 0 (for Sunday) to 6 (for Saturday)
|
z The day of the year (starting from 0) 0 to 364 (365 in leap years)
|
W ISO-8601 week number of year, weeks starting on Monday 01 to 53
|
F A full textual representation of a month, such as January or March January to December
|
m Numeric representation of a month, with leading zeros 01 to 12
|
M A short textual representation of a month Jan to Dec
|
n Numeric representation of a month, without leading zeros 1 to 12
|
t Number of days in the given month 28 to 31
|
L Whether it's a leap year 1 if it is a leap year, 0 otherwise.
|
o ISO-8601 year number (identical to (Y), but if the ISO week number (W) Examples: 1998 or 2004
|
belongs to the previous or next year, that year is used instead)
|
Y A full numeric representation of a year, 4 digits Examples: 1999 or 2003
|
y A two digit representation of a year Examples: 99 or 03
|
a Lowercase Ante meridiem and Post meridiem am or pm
|
A Uppercase Ante meridiem and Post meridiem AM or PM
|
g 12-hour format of an hour without leading zeros 1 to 12
|
G 24-hour format of an hour without leading zeros 0 to 23
|
h 12-hour format of an hour with leading zeros 01 to 12
|
H 24-hour format of an hour with leading zeros 00 to 23
|
i Minutes, with leading zeros 00 to 59
|
s Seconds, with leading zeros 00 to 59
|
u Decimal fraction of a second Examples:
|
(minimum 1 digit, arbitrary number of digits allowed) 001 (i.e. 0.001s) or
|
100 (i.e. 0.100s) or
|
999 (i.e. 0.999s) or
|
999876543210 (i.e. 0.999876543210s)
|
O Difference to Greenwich time (GMT) in hours and minutes Example: +1030
|
P Difference to Greenwich time (GMT) with colon between hours and minutes Example: -08:00
|
T Timezone abbreviation of the machine running the code Examples: EST, MDT, PDT ...
|
Z Timezone offset in seconds (negative if west of UTC, positive if east) -43200 to 50400
|
c ISO 8601 date
|
Notes: Examples:
|
1) If unspecified, the month / day defaults to the current month / day, 1991 or
|
the time defaults to midnight, while the timezone defaults to the 1992-10 or
|
browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
|
and minutes. The "T" delimiter, seconds, milliseconds and timezone 1994-08-19T16:20+01:00 or
|
are optional. 1995-07-18T17:21:28-02:00 or
|
2) The decimal fraction of a second, if specified, must contain at 1996-06-17T18:22:29.98765+03:00 or
|
least 1 digit (there is no limit to the maximum number 1997-05-16T19:23:30,12345-0400 or
|
of digits allowed), and may be delimited by either a '.' or a ',' 1998-04-15T20:24:31.2468Z or
|
Refer to the examples on the right for the various levels of 1999-03-14T20:24:32Z or
|
date-time granularity which are supported, or see 2000-02-13T21:25:33
|
http://www.w3.org/TR/NOTE-datetime for more info. 2001-01-12 22:26:34
|
U Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) 1193432466 or -2138434463
|
MS Microsoft AJAX serialized dates \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
|
\/Date(1238606590509+0800)\/
|
time A javascript millisecond timestamp 1350024476440
|
timestamp A UNIX timestamp (same as U) 1350024866
|
</pre>
|
*
|
* Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
|
*
|
* // Sample date:
|
* // 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
|
*
|
* var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
|
* console.log(Ext.Date.format(dt, 'Y-m-d')); // 2007-01-10
|
* console.log(Ext.Date.format(dt, 'F j, Y, g:i a')); // January 10, 2007, 3:05 pm
|
* console.log(Ext.Date.format(dt, 'l, \\t\\he jS \\of F Y h:i:s A')); // Wednesday, the 10th of January 2007 03:05:01 PM
|
*
|
* Here are some standard date/time patterns that you might find helpful. They
|
* are not part of the source of Ext.Date, but to use them you can simply copy this
|
* block of code into any script that is included after Ext.Date and they will also become
|
* globally available on the Date object. Feel free to add or remove patterns as needed in your code.
|
*
|
* Ext.Date.patterns = {
|
* ISO8601Long:"Y-m-d H:i:s",
|
* ISO8601Short:"Y-m-d",
|
* ShortDate: "n/j/Y",
|
* LongDate: "l, F d, Y",
|
* FullDateTime: "l, F d, Y g:i:s A",
|
* MonthDay: "F d",
|
* ShortTime: "g:i A",
|
* LongTime: "g:i:s A",
|
* SortableDateTime: "Y-m-d\\TH:i:s",
|
* UniversalSortableDateTime: "Y-m-d H:i:sO",
|
* YearMonth: "F, Y"
|
* };
|
*
|
* Example usage:
|
*
|
* var dt = new Date();
|
* console.log(Ext.Date.format(dt, Ext.Date.patterns.ShortDate));
|
*
|
* Developer-written, custom formats may be used by supplying both a formatting and a parsing function
|
* which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.
|
* @singleton
|
*/
|
|
/*
|
* Most of the date-formatting functions below are the excellent work of Baron Schwartz.
|
* (see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/)
|
* They generate precompiled functions from format patterns instead of parsing and
|
* processing each pattern every time a date is formatted. These functions are available
|
* on every Date object.
|
*/
|
|
Ext.Date = new function() {
|
var utilDate = this,
|
stripEscapeRe = /(\\.)/g,
|
hourInfoRe = /([gGhHisucUOPZ]|MS)/,
|
dateInfoRe = /([djzmnYycU]|MS)/,
|
slashRe = /\\/gi,
|
numberTokenRe = /\{(\d+)\}/g,
|
MSFormatRe = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/'),
|
code = [
|
// date calculations (note: the code below creates a dependency on Ext.Number.from())
|
"var me = this, dt, y, m, d, h, i, s, ms, o, O, z, zz, u, v, W, year, jan4, week1monday, daysInMonth, dayMatched,",
|
"def = me.defaults,",
|
"from = Ext.Number.from,",
|
"results = String(input).match(me.parseRegexes[{0}]);", // either null, or an array of matched strings
|
|
"if(results){",
|
"{1}",
|
|
"if(u != null){", // i.e. unix time is defined
|
"v = new Date(u * 1000);", // give top priority to UNIX time
|
"}else{",
|
// create Date object representing midnight of the current day;
|
// this will provide us with our date defaults
|
// (note: clearTime() handles Daylight Saving Time automatically)
|
"dt = me.clearTime(new Date);",
|
|
"y = from(y, from(def.y, dt.getFullYear()));",
|
"m = from(m, from(def.m - 1, dt.getMonth()));",
|
"dayMatched = d !== undefined;",
|
"d = from(d, from(def.d, dt.getDate()));",
|
|
// Attempt to validate the day. Since it defaults to today, it may go out
|
// of range, for example parsing m/Y where the value is 02/2000 on the 31st of May.
|
// It will attempt to parse 2000/02/31, which will overflow to March and end up
|
// returning 03/2000. We only do this when we default the day. If an invalid day value
|
// was set to be parsed by the user, continue on and either let it overflow or return null
|
// depending on the strict value. This will be in line with the normal Date behaviour.
|
|
"if (!dayMatched) {",
|
"dt.setDate(1);",
|
"dt.setMonth(m);",
|
"dt.setFullYear(y);",
|
|
"daysInMonth = me.getDaysInMonth(dt);",
|
"if (d > daysInMonth) {",
|
"d = daysInMonth;",
|
"}",
|
"}",
|
|
"h = from(h, from(def.h, dt.getHours()));",
|
"i = from(i, from(def.i, dt.getMinutes()));",
|
"s = from(s, from(def.s, dt.getSeconds()));",
|
"ms = from(ms, from(def.ms, dt.getMilliseconds()));",
|
|
"if(z >= 0 && y >= 0){",
|
// both the year and zero-based day of year are defined and >= 0.
|
// these 2 values alone provide sufficient info to create a full date object
|
|
// create Date object representing January 1st for the given year
|
// handle years < 100 appropriately
|
"v = me.add(new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms), me.YEAR, y < 100 ? y - 100 : 0);",
|
|
// then add day of year, checking for Date "rollover" if necessary
|
"v = !strict? v : (strict === true && (z <= 364 || (me.isLeapYear(v) && z <= 365))? me.add(v, me.DAY, z) : null);",
|
"}else if(strict === true && !me.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
|
"v = null;", // invalid date, so return null
|
"}else{",
|
"if (W) {", // support ISO-8601
|
// http://en.wikipedia.org/wiki/ISO_week_date
|
//
|
// Mutually equivalent definitions for week 01 are:
|
// a. the week starting with the Monday which is nearest in time to 1 January
|
// b. the week with 4 January in it
|
// ... there are many others ...
|
//
|
// We'll use letter b above to determine the first week of the year.
|
//
|
// So, first get a Date object for January 4th of whatever calendar year is desired.
|
//
|
// Then, the first Monday of the year can easily be determined by (operating on this Date):
|
// 1. Getting the day of the week.
|
// 2. Subtracting that by one.
|
// 3. Multiplying that by 86400000 (one day in ms).
|
// 4. Subtracting this number of days (in ms) from the January 4 date (represented in ms).
|
//
|
// Example #1 ...
|
//
|
// January 2012
|
// Su Mo Tu We Th Fr Sa
|
// 1 2 3 4 5 6 7
|
// 8 9 10 11 12 13 14
|
// 15 16 17 18 19 20 21
|
// 22 23 24 25 26 27 28
|
// 29 30 31
|
//
|
// 1. January 4th is a Wednesday.
|
// 2. Its day number is 3.
|
// 3. Simply substract 2 days from Wednesday.
|
// 4. The first week of the year begins on Monday, January 2. Simple!
|
//
|
// Example #2 ...
|
// January 1992
|
// Su Mo Tu We Th Fr Sa
|
// 1 2 3 4
|
// 5 6 7 8 9 10 11
|
// 12 13 14 15 16 17 18
|
// 19 20 21 22 23 24 25
|
// 26 27 28 29 30 31
|
//
|
// 1. January 4th is a Saturday.
|
// 2. Its day number is 6.
|
// 3. Simply subtract 5 days from Saturday.
|
// 4. The first week of the year begins on Monday, December 30. Simple!
|
//
|
// v = Ext.Date.clearTime(new Date(week1monday.getTime() + ((W - 1) * 604800000)));
|
// (This is essentially doing the same thing as above but for the week rather than the day)
|
"year = y || (new Date()).getFullYear(),",
|
"jan4 = new Date(year, 0, 4, 0, 0, 0),",
|
"week1monday = new Date(jan4.getTime() - ((jan4.getDay() - 1) * 86400000));",
|
"v = Ext.Date.clearTime(new Date(week1monday.getTime() + ((W - 1) * 604800000)));",
|
"} else {",
|
// plain old Date object
|
// handle years < 100 properly
|
"v = me.add(new Date(y < 100 ? 100 : y, m, d, h, i, s, ms), me.YEAR, y < 100 ? y - 100 : 0);",
|
"}",
|
"}",
|
"}",
|
"}",
|
|
"if(v){",
|
// favor UTC offset over GMT offset
|
"if(zz != null){",
|
// reset to UTC, then add offset
|
"v = me.add(v, me.SECOND, -v.getTimezoneOffset() * 60 - zz);",
|
"}else if(o){",
|
// reset to GMT, then add offset
|
"v = me.add(v, me.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
|
"}",
|
"}",
|
|
"return v;"
|
].join('\n');
|
|
// create private copy of Ext JS's `Ext.util.Format.format()` method
|
// - to remove unnecessary dependency
|
// - to resolve namespace conflict with MS-Ajax's implementation
|
function xf(format) {
|
var args = Array.prototype.slice.call(arguments, 1);
|
return format.replace(numberTokenRe, function(m, i) {
|
return args[i];
|
});
|
}
|
|
Ext.apply(utilDate, {
|
/**
|
* Returns the current timestamp.
|
* @return {Number} Milliseconds since UNIX epoch.
|
* @method
|
*/
|
now: Date.now || function() {
|
return +new Date();
|
},
|
|
/**
|
* @private
|
* Private for now
|
*/
|
toString: function(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');
|
},
|
|
/**
|
* Returns the number of milliseconds between two dates.
|
* @param {Date} dateA The first date.
|
* @param {Date} [dateB=new Date()] (optional) The second date.
|
* @return {Number} The difference in milliseconds
|
*/
|
getElapsed: function(dateA, dateB) {
|
return Math.abs(dateA - (dateB || utilDate.now()));
|
},
|
|
/**
|
* Global flag which determines if strict date parsing should be used.
|
* Strict date parsing will not roll-over invalid dates, which is the
|
* default behavior of JavaScript Date objects.
|
* (see {@link #parse} for more information)
|
* @type Boolean
|
*/
|
useStrict: false,
|
|
// private
|
formatCodeToRegex: function(character, currentGroup) {
|
// Note: currentGroup - position in regex result array (see notes for Ext.Date.parseCodes below)
|
var p = utilDate.parseCodes[character];
|
|
if (p) {
|
p = typeof p == 'function'? p() : p;
|
utilDate.parseCodes[character] = p; // reassign function result to prevent repeated execution
|
}
|
|
return p ? Ext.applyIf({
|
c: p.c ? xf(p.c, currentGroup || "{0}") : p.c
|
}, p) : {
|
g: 0,
|
c: null,
|
s: Ext.String.escapeRegex(character) // treat unrecognized characters as literals
|
};
|
},
|
|
/**
|
* An object hash in which each property is a date parsing function. The property name is the
|
* format string which that function parses.
|
*
|
* This object is automatically populated with date parsing functions as
|
* date formats are requested for Ext standard formatting strings.
|
*
|
* Custom parsing functions may be inserted into this object, keyed by a name which from then on
|
* may be used as a format string to {@link #parse}.
|
*
|
* Example:
|
*
|
* Ext.Date.parseFunctions['x-date-format'] = myDateParser;
|
*
|
* A parsing function should return a Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
|
* <li><code>date</code> : String<div class="sub-desc">The date string to parse.</div></li>
|
* <li><code>strict</code> : Boolean<div class="sub-desc">True to validate date strings while parsing
|
* (i.e. prevent JavaScript Date "rollover") (The default must be `false`).
|
* Invalid date strings should return `null` when parsed.</div></li>
|
* </ul></div>
|
*
|
* To enable Dates to also be _formatted_ according to that format, a corresponding
|
* formatting function must be placed into the {@link #formatFunctions} property.
|
* @property parseFunctions
|
* @type Object
|
*/
|
parseFunctions: {
|
"MS": function(input, strict) {
|
// note: the timezone offset is ignored since the MS Ajax server sends
|
// a UTC milliseconds-since-Unix-epoch value (negative values are allowed)
|
var r = (input || '').match(MSFormatRe);
|
return r ? new Date(((r[1] || '') + r[2]) * 1) : null;
|
},
|
"time": function(input, strict) {
|
var num = parseInt(input, 10);
|
if (num || num === 0) {
|
return new Date(num);
|
}
|
return null;
|
},
|
"timestamp": function(input, strict) {
|
var num = parseInt(input, 10);
|
if (num || num === 0) {
|
return new Date(num * 1000);
|
}
|
return null;
|
}
|
},
|
parseRegexes: [],
|
|
/**
|
* An object hash in which each property is a date formatting function. The property name is the
|
* format string which corresponds to the produced formatted date string.
|
*
|
* This object is automatically populated with date formatting functions as
|
* date formats are requested for Ext standard formatting strings.
|
*
|
* Custom formatting functions may be inserted into this object, keyed by a name which from then on
|
* may be used as a format string to {@link #format}.
|
*
|
* Example:
|
*
|
* Ext.Date.formatFunctions['x-date-format'] = myDateFormatter;
|
*
|
* A formatting function should return a string representation of the passed Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
|
* <li><code>date</code> : Date<div class="sub-desc">The Date to format.</div></li>
|
* </ul></div>
|
*
|
* To enable date strings to also be _parsed_ according to that format, a corresponding
|
* parsing function must be placed into the {@link #parseFunctions} property.
|
* @property formatFunctions
|
* @type Object
|
*/
|
formatFunctions: {
|
"MS": function() {
|
// UTC milliseconds since Unix epoch (MS-AJAX serialized date format (MRSF))
|
return '\\/Date(' + this.getTime() + ')\\/';
|
},
|
"time": function(){
|
return this.getTime().toString();
|
},
|
"timestamp": function(){
|
return utilDate.format(this, 'U');
|
}
|
},
|
|
y2kYear : 50,
|
|
/**
|
* Date interval constant
|
* @type String
|
*/
|
MILLI : "ms",
|
|
/**
|
* Date interval constant
|
* @type String
|
*/
|
SECOND : "s",
|
|
/**
|
* Date interval constant
|
* @type String
|
*/
|
MINUTE : "mi",
|
|
/** Date interval constant
|
* @type String
|
*/
|
HOUR : "h",
|
|
/**
|
* Date interval constant
|
* @type String
|
*/
|
DAY : "d",
|
|
/**
|
* Date interval constant
|
* @type String
|
*/
|
MONTH : "mo",
|
|
/**
|
* Date interval constant
|
* @type String
|
*/
|
YEAR : "y",
|
|
/**
|
* An object hash containing default date values used during date parsing.
|
*
|
* The following properties are available:<div class="mdetail-params"><ul>
|
* <li><code>y</code> : Number<div class="sub-desc">The default year value. (defaults to undefined)</div></li>
|
* <li><code>m</code> : Number<div class="sub-desc">The default 1-based month value. (defaults to undefined)</div></li>
|
* <li><code>d</code> : Number<div class="sub-desc">The default day value. (defaults to undefined)</div></li>
|
* <li><code>h</code> : Number<div class="sub-desc">The default hour value. (defaults to undefined)</div></li>
|
* <li><code>i</code> : Number<div class="sub-desc">The default minute value. (defaults to undefined)</div></li>
|
* <li><code>s</code> : Number<div class="sub-desc">The default second value. (defaults to undefined)</div></li>
|
* <li><code>ms</code> : Number<div class="sub-desc">The default millisecond value. (defaults to undefined)</div></li>
|
* </ul></div>
|
*
|
* Override these properties to customize the default date values used by the {@link #parse} method.
|
*
|
* __Note:__ In countries which experience Daylight Saving Time (i.e. DST), the `h`, `i`, `s`
|
* and `ms` properties may coincide with the exact time in which DST takes effect.
|
* It is the responsibility of the developer to account for this.
|
*
|
* Example Usage:
|
*
|
* // set default day value to the first day of the month
|
* Ext.Date.defaults.d = 1;
|
*
|
* // parse a February date string containing only year and month values.
|
* // setting the default day value to 1 prevents weird date rollover issues
|
* // when attempting to parse the following date string on, for example, March 31st 2009.
|
* Ext.Date.parse('2009-02', 'Y-m'); // returns a Date object representing February 1st 2009
|
*
|
* @property defaults
|
* @type Object
|
*/
|
defaults: {},
|
|
//<locale type="array">
|
/**
|
* @property {String[]} dayNames
|
* An array of textual day names.
|
* Override these values for international dates.
|
*
|
* Example:
|
*
|
* Ext.Date.dayNames = [
|
* 'SundayInYourLang',
|
* 'MondayInYourLang'
|
* // ...
|
* ];
|
*/
|
dayNames : [
|
"Sunday",
|
"Monday",
|
"Tuesday",
|
"Wednesday",
|
"Thursday",
|
"Friday",
|
"Saturday"
|
],
|
//</locale>
|
|
//<locale type="array">
|
/**
|
* @property {String[]} monthNames
|
* An array of textual month names.
|
* Override these values for international dates.
|
*
|
* Example:
|
*
|
* Ext.Date.monthNames = [
|
* 'JanInYourLang',
|
* 'FebInYourLang'
|
* // ...
|
* ];
|
*/
|
monthNames : [
|
"January",
|
"February",
|
"March",
|
"April",
|
"May",
|
"June",
|
"July",
|
"August",
|
"September",
|
"October",
|
"November",
|
"December"
|
],
|
//</locale>
|
|
//<locale type="object">
|
/**
|
* @property {Object} monthNumbers
|
* An object hash of zero-based JavaScript month numbers (with short month names as keys. **Note:** keys are case-sensitive).
|
* Override these values for international dates.
|
*
|
* Example:
|
*
|
* Ext.Date.monthNumbers = {
|
* 'LongJanNameInYourLang': 0,
|
* 'ShortJanNameInYourLang':0,
|
* 'LongFebNameInYourLang':1,
|
* 'ShortFebNameInYourLang':1
|
* // ...
|
* };
|
*/
|
monthNumbers : {
|
January: 0,
|
Jan: 0,
|
February: 1,
|
Feb: 1,
|
March: 2,
|
Mar: 2,
|
April: 3,
|
Apr: 3,
|
May: 4,
|
June: 5,
|
Jun: 5,
|
July: 6,
|
Jul: 6,
|
August: 7,
|
Aug: 7,
|
September: 8,
|
Sep: 8,
|
October: 9,
|
Oct: 9,
|
November: 10,
|
Nov: 10,
|
December: 11,
|
Dec: 11
|
},
|
//</locale>
|
|
//<locale>
|
/**
|
* @property {String} defaultFormat
|
* The date format string that the {@link Ext.util.Format#dateRenderer}
|
* and {@link Ext.util.Format#date} functions use. See {@link Ext.Date} for details.
|
*
|
* This may be overridden in a locale file.
|
*/
|
defaultFormat : "m/d/Y",
|
//</locale>
|
//<locale type="function">
|
/**
|
* Get the short month name for the given month number.
|
* Override this function for international dates.
|
* @param {Number} month A zero-based JavaScript month number.
|
* @return {String} The short month name.
|
*/
|
getShortMonthName : function(month) {
|
return Ext.Date.monthNames[month].substring(0, 3);
|
},
|
//</locale>
|
|
//<locale type="function">
|
/**
|
* Get the short day name for the given day number.
|
* Override this function for international dates.
|
* @param {Number} day A zero-based JavaScript day number.
|
* @return {String} The short day name.
|
*/
|
getShortDayName : function(day) {
|
return Ext.Date.dayNames[day].substring(0, 3);
|
},
|
//</locale>
|
|
//<locale type="function">
|
/**
|
* Get the zero-based JavaScript month number for the given short/full month name.
|
* Override this function for international dates.
|
* @param {String} name The short/full month name.
|
* @return {Number} The zero-based JavaScript month number.
|
*/
|
getMonthNumber : function(name) {
|
// handle camel casing for English month names (since the keys for the Ext.Date.monthNumbers hash are case sensitive)
|
return Ext.Date.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
|
},
|
//</locale>
|
|
/**
|
* Checks if the specified format contains hour information
|
* @param {String} format The format to check
|
* @return {Boolean} True if the format contains hour information
|
* @method
|
*/
|
formatContainsHourInfo : function(format){
|
return hourInfoRe.test(format.replace(stripEscapeRe, ''));
|
},
|
|
/**
|
* Checks if the specified format contains information about
|
* anything other than the time.
|
* @param {String} format The format to check
|
* @return {Boolean} True if the format contains information about
|
* date/day information.
|
* @method
|
*/
|
formatContainsDateInfo : function(format){
|
return dateInfoRe.test(format.replace(stripEscapeRe, ''));
|
},
|
|
/**
|
* Removes all escaping for a date format string. In date formats,
|
* using a '\' can be used to escape special characters.
|
* @param {String} format The format to unescape
|
* @return {String} The unescaped format
|
* @method
|
*/
|
unescapeFormat: function(format) {
|
// Escape the format, since \ can be used to escape special
|
// characters in a date format. For example, in a Spanish
|
// locale the format may be: 'd \\de F \\de Y'
|
return format.replace(slashRe, '');
|
},
|
|
/**
|
* The base format-code to formatting-function hashmap used by the {@link #format} method.
|
* Formatting functions are strings (or functions which return strings) which
|
* will return the appropriate value when evaluated in the context of the Date object
|
* from which the {@link #format} method is called.
|
* Add to / override these mappings for custom date formatting.
|
*
|
* __Note:__ Ext.Date.format() treats characters as literals if an appropriate mapping cannot be found.
|
*
|
* Example:
|
*
|
* Ext.Date.formatCodes.x = "Ext.util.Format.leftPad(this.getDate(), 2, '0')";
|
* console.log(Ext.Date.format(new Date(), 'X'); // returns the current day of the month
|
* @type Object
|
*/
|
formatCodes : {
|
d: "Ext.String.leftPad(this.getDate(), 2, '0')",
|
D: "Ext.Date.getShortDayName(this.getDay())", // get localized short day name
|
j: "this.getDate()",
|
l: "Ext.Date.dayNames[this.getDay()]",
|
N: "(this.getDay() ? this.getDay() : 7)",
|
S: "Ext.Date.getSuffix(this)",
|
w: "this.getDay()",
|
z: "Ext.Date.getDayOfYear(this)",
|
W: "Ext.String.leftPad(Ext.Date.getWeekOfYear(this), 2, '0')",
|
F: "Ext.Date.monthNames[this.getMonth()]",
|
m: "Ext.String.leftPad(this.getMonth() + 1, 2, '0')",
|
M: "Ext.Date.getShortMonthName(this.getMonth())", // get localized short month name
|
n: "(this.getMonth() + 1)",
|
t: "Ext.Date.getDaysInMonth(this)",
|
L: "(Ext.Date.isLeapYear(this) ? 1 : 0)",
|
o: "(this.getFullYear() + (Ext.Date.getWeekOfYear(this) == 1 && this.getMonth() > 0 ? +1 : (Ext.Date.getWeekOfYear(this) >= 52 && this.getMonth() < 11 ? -1 : 0)))",
|
Y: "Ext.String.leftPad(this.getFullYear(), 4, '0')",
|
y: "('' + this.getFullYear()).substring(2, 4)",
|
a: "(this.getHours() < 12 ? 'am' : 'pm')",
|
A: "(this.getHours() < 12 ? 'AM' : 'PM')",
|
g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
|
G: "this.getHours()",
|
h: "Ext.String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
|
H: "Ext.String.leftPad(this.getHours(), 2, '0')",
|
i: "Ext.String.leftPad(this.getMinutes(), 2, '0')",
|
s: "Ext.String.leftPad(this.getSeconds(), 2, '0')",
|
u: "Ext.String.leftPad(this.getMilliseconds(), 3, '0')",
|
O: "Ext.Date.getGMTOffset(this)",
|
P: "Ext.Date.getGMTOffset(this, true)",
|
T: "Ext.Date.getTimezone(this)",
|
Z: "(this.getTimezoneOffset() * -60)",
|
|
c: function() { // ISO-8601 -- GMT format
|
var c, code, i, l, e;
|
for (c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
|
e = c.charAt(i);
|
code.push(e == "T" ? "'T'" : utilDate.getFormatCode(e)); // treat T as a character literal
|
}
|
return code.join(" + ");
|
},
|
/*
|
c: function() { // ISO-8601 -- UTC format
|
return [
|
"this.getUTCFullYear()", "'-'",
|
"Ext.util.Format.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
|
"Ext.util.Format.leftPad(this.getUTCDate(), 2, '0')",
|
"'T'",
|
"Ext.util.Format.leftPad(this.getUTCHours(), 2, '0')", "':'",
|
"Ext.util.Format.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
|
"Ext.util.Format.leftPad(this.getUTCSeconds(), 2, '0')",
|
"'Z'"
|
].join(" + ");
|
},
|
*/
|
|
U: "Math.round(this.getTime() / 1000)"
|
},
|
|
/**
|
* Checks if the passed Date parameters will cause a JavaScript Date "rollover".
|
* @param {Number} year 4-digit year
|
* @param {Number} month 1-based month-of-year
|
* @param {Number} day Day of month
|
* @param {Number} hour (optional) Hour
|
* @param {Number} minute (optional) Minute
|
* @param {Number} second (optional) Second
|
* @param {Number} millisecond (optional) Millisecond
|
* @return {Boolean} `true` if the passed parameters do not cause a Date "rollover", `false` otherwise.
|
*/
|
isValid : function(y, m, d, h, i, s, ms) {
|
// setup defaults
|
h = h || 0;
|
i = i || 0;
|
s = s || 0;
|
ms = ms || 0;
|
|
// Special handling for year < 100
|
var dt = utilDate.add(new Date(y < 100 ? 100 : y, m - 1, d, h, i, s, ms), utilDate.YEAR, y < 100 ? y - 100 : 0);
|
|
return y == dt.getFullYear() &&
|
m == dt.getMonth() + 1 &&
|
d == dt.getDate() &&
|
h == dt.getHours() &&
|
i == dt.getMinutes() &&
|
s == dt.getSeconds() &&
|
ms == dt.getMilliseconds();
|
},
|
|
/**
|
* Parses the passed string using the specified date format.
|
* Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
|
* The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
|
* which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
|
* the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
|
* Keep in mind that the input date string must precisely match the specified format string
|
* in order for the parse operation to be successful (failed parse operations return a null value).
|
*
|
* Example:
|
*
|
* //dt = Fri May 25 2007 (current date)
|
* var dt = new Date();
|
*
|
* //dt = Thu May 25 2006 (today's month/day in 2006)
|
* dt = Ext.Date.parse("2006", "Y");
|
*
|
* //dt = Sun Jan 15 2006 (all date parts specified)
|
* dt = Ext.Date.parse("2006-01-15", "Y-m-d");
|
*
|
* //dt = Sun Jan 15 2006 15:20:01
|
* dt = Ext.Date.parse("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
|
*
|
* // attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
|
* dt = Ext.Date.parse("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // returns null
|
*
|
* @param {String} input The raw date string.
|
* @param {String} format The expected date string format.
|
* @param {Boolean} [strict=false] (optional) `true` to validate date strings while parsing (i.e. prevents JavaScript Date "rollover").
|
* Invalid date strings will return `null` when parsed.
|
* @return {Date} The parsed Date.
|
*/
|
parse : function(input, format, strict) {
|
var p = utilDate.parseFunctions;
|
if (p[format] == null) {
|
utilDate.createParser(format);
|
}
|
return p[format].call(utilDate, input, Ext.isDefined(strict) ? strict : utilDate.useStrict);
|
},
|
|
// Backwards compat
|
parseDate: function(input, format, strict){
|
return utilDate.parse(input, format, strict);
|
},
|
|
|
// private
|
getFormatCode : function(character) {
|
var f = utilDate.formatCodes[character];
|
|
if (f) {
|
f = typeof f == 'function'? f() : f;
|
utilDate.formatCodes[character] = f; // reassign function result to prevent repeated execution
|
}
|
|
// note: unknown characters are treated as literals
|
return f || ("'" + Ext.String.escape(character) + "'");
|
},
|
|
// private
|
createFormat : function(format) {
|
var code = [],
|
special = false,
|
ch = '',
|
i;
|
|
for (i = 0; i < format.length; ++i) {
|
ch = format.charAt(i);
|
if (!special && ch == "\\") {
|
special = true;
|
} else if (special) {
|
special = false;
|
code.push("'" + Ext.String.escape(ch) + "'");
|
} else {
|
code.push(utilDate.getFormatCode(ch));
|
}
|
}
|
utilDate.formatFunctions[format] = Ext.functionFactory("return " + code.join('+'));
|
},
|
|
// private
|
createParser : function(format) {
|
var regexNum = utilDate.parseRegexes.length,
|
currentGroup = 1,
|
calc = [],
|
regex = [],
|
special = false,
|
ch = "",
|
i = 0,
|
len = format.length,
|
atEnd = [],
|
obj;
|
|
for (; i < len; ++i) {
|
ch = format.charAt(i);
|
if (!special && ch == "\\") {
|
special = true;
|
} else if (special) {
|
special = false;
|
regex.push(Ext.String.escape(ch));
|
} else {
|
obj = utilDate.formatCodeToRegex(ch, currentGroup);
|
currentGroup += obj.g;
|
regex.push(obj.s);
|
if (obj.g && obj.c) {
|
if (obj.calcAtEnd) {
|
atEnd.push(obj.c);
|
} else {
|
calc.push(obj.c);
|
}
|
}
|
}
|
}
|
|
calc = calc.concat(atEnd);
|
|
utilDate.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", 'i');
|
utilDate.parseFunctions[format] = Ext.functionFactory("input", "strict", xf(code, regexNum, calc.join('')));
|
},
|
|
// private
|
parseCodes : {
|
/*
|
* Notes:
|
* g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
|
* c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
|
* s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
|
*/
|
d: {
|
g:1,
|
c:"d = parseInt(results[{0}], 10);\n",
|
s:"(3[0-1]|[1-2][0-9]|0[1-9])" // day of month with leading zeroes (01 - 31)
|
},
|
j: {
|
g:1,
|
c:"d = parseInt(results[{0}], 10);\n",
|
s:"(3[0-1]|[1-2][0-9]|[1-9])" // day of month without leading zeroes (1 - 31)
|
},
|
D: function() {
|
for (var a = [], i = 0; i < 7; a.push(utilDate.getShortDayName(i)), ++i); // get localised short day names
|
return {
|
g:0,
|
c:null,
|
s:"(?:" + a.join("|") +")"
|
};
|
},
|
l: function() {
|
return {
|
g:0,
|
c:null,
|
s:"(?:" + utilDate.dayNames.join("|") + ")"
|
};
|
},
|
N: {
|
g:0,
|
c:null,
|
s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
|
},
|
//<locale type="object" property="parseCodes">
|
S: {
|
g:0,
|
c:null,
|
s:"(?:st|nd|rd|th)"
|
},
|
//</locale>
|
w: {
|
g:0,
|
c:null,
|
s:"[0-6]" // JavaScript day number (0 (sunday) - 6 (saturday))
|
},
|
z: {
|
g:1,
|
c:"z = parseInt(results[{0}], 10);\n",
|
s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
|
},
|
W: {
|
g:1,
|
c:"W = parseInt(results[{0}], 10);\n",
|
s:"(\\d{2})" // ISO-8601 week number (with leading zero)
|
},
|
F: function() {
|
return {
|
g:1,
|
c:"m = parseInt(me.getMonthNumber(results[{0}]), 10);\n", // get localised month number
|
s:"(" + utilDate.monthNames.join("|") + ")"
|
};
|
},
|
M: function() {
|
for (var a = [], i = 0; i < 12; a.push(utilDate.getShortMonthName(i)), ++i); // get localised short month names
|
return Ext.applyIf({
|
s:"(" + a.join("|") + ")"
|
}, utilDate.formatCodeToRegex("F"));
|
},
|
m: {
|
g:1,
|
c:"m = parseInt(results[{0}], 10) - 1;\n",
|
s:"(1[0-2]|0[1-9])" // month number with leading zeros (01 - 12)
|
},
|
n: {
|
g:1,
|
c:"m = parseInt(results[{0}], 10) - 1;\n",
|
s:"(1[0-2]|[1-9])" // month number without leading zeros (1 - 12)
|
},
|
t: {
|
g:0,
|
c:null,
|
s:"(?:\\d{2})" // no. of days in the month (28 - 31)
|
},
|
L: {
|
g:0,
|
c:null,
|
s:"(?:1|0)"
|
},
|
o: {
|
g: 1,
|
c: "y = parseInt(results[{0}], 10);\n",
|
s: "(\\d{4})" // ISO-8601 year number (with leading zero)
|
|
},
|
Y: {
|
g:1,
|
c:"y = parseInt(results[{0}], 10);\n",
|
s:"(\\d{4})" // 4-digit year
|
},
|
y: {
|
g:1,
|
c:"var ty = parseInt(results[{0}], 10);\n"
|
+ "y = ty > me.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
|
s:"(\\d{1,2})"
|
},
|
/*
|
* In the am/pm parsing routines, we allow both upper and lower case
|
* even though it doesn't exactly match the spec. It gives much more flexibility
|
* in being able to specify case insensitive regexes.
|
*/
|
//<locale type="object" property="parseCodes">
|
a: {
|
g:1,
|
c:"if (/(am)/i.test(results[{0}])) {\n"
|
+ "if (!h || h == 12) { h = 0; }\n"
|
+ "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
|
s:"(am|pm|AM|PM)",
|
calcAtEnd: true
|
},
|
//</locale>
|
//<locale type="object" property="parseCodes">
|
A: {
|
g:1,
|
c:"if (/(am)/i.test(results[{0}])) {\n"
|
+ "if (!h || h == 12) { h = 0; }\n"
|
+ "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
|
s:"(AM|PM|am|pm)",
|
calcAtEnd: true
|
},
|
//</locale>
|
g: {
|
g:1,
|
c:"h = parseInt(results[{0}], 10);\n",
|
s:"(1[0-2]|[0-9])" // 12-hr format of an hour without leading zeroes (1 - 12)
|
},
|
G: {
|
g:1,
|
c:"h = parseInt(results[{0}], 10);\n",
|
s:"(2[0-3]|1[0-9]|[0-9])" // 24-hr format of an hour without leading zeroes (0 - 23)
|
},
|
h: {
|
g:1,
|
c:"h = parseInt(results[{0}], 10);\n",
|
s:"(1[0-2]|0[1-9])" // 12-hr format of an hour with leading zeroes (01 - 12)
|
},
|
H: {
|
g:1,
|
c:"h = parseInt(results[{0}], 10);\n",
|
s:"(2[0-3]|[0-1][0-9])" // 24-hr format of an hour with leading zeroes (00 - 23)
|
},
|
i: {
|
g:1,
|
c:"i = parseInt(results[{0}], 10);\n",
|
s:"([0-5][0-9])" // minutes with leading zeros (00 - 59)
|
},
|
s: {
|
g:1,
|
c:"s = parseInt(results[{0}], 10);\n",
|
s:"([0-5][0-9])" // seconds with leading zeros (00 - 59)
|
},
|
u: {
|
g:1,
|
c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
|
s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
|
},
|
O: {
|
g:1,
|
c:[
|
"o = results[{0}];",
|
"var sn = o.substring(0,1),", // get + / - sign
|
"hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
|
"mn = o.substring(3,5) % 60;", // get minutes
|
"o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + Ext.String.leftPad(hr, 2, '0') + Ext.String.leftPad(mn, 2, '0')) : null;\n" // -12hrs <= GMT offset <= 14hrs
|
].join("\n"),
|
s: "([+-]\\d{4})" // GMT offset in hrs and mins
|
},
|
P: {
|
g:1,
|
c:[
|
"o = results[{0}];",
|
"var sn = o.substring(0,1),", // get + / - sign
|
"hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
|
"mn = o.substring(4,6) % 60;", // get minutes
|
"o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + Ext.String.leftPad(hr, 2, '0') + Ext.String.leftPad(mn, 2, '0')) : null;\n" // -12hrs <= GMT offset <= 14hrs
|
].join("\n"),
|
s: "([+-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
|
},
|
T: {
|
g:0,
|
c:null,
|
s:"[A-Z]{1,5}" // timezone abbrev. may be between 1 - 5 chars
|
},
|
Z: {
|
g:1,
|
c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
|
+ "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
|
s:"([+-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
|
},
|
c: function() {
|
var calc = [],
|
arr = [
|
utilDate.formatCodeToRegex("Y", 1), // year
|
utilDate.formatCodeToRegex("m", 2), // month
|
utilDate.formatCodeToRegex("d", 3), // day
|
utilDate.formatCodeToRegex("H", 4), // hour
|
utilDate.formatCodeToRegex("i", 5), // minute
|
utilDate.formatCodeToRegex("s", 6), // second
|
{c:"ms = results[7] || '0'; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n"}, // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
|
{c:[ // allow either "Z" (i.e. UTC) or "-0530" or "+08:00" (i.e. UTC offset) timezone delimiters. assumes local timezone if no timezone is specified
|
"if(results[8]) {", // timezone specified
|
"if(results[8] == 'Z'){",
|
"zz = 0;", // UTC
|
"}else if (results[8].indexOf(':') > -1){",
|
utilDate.formatCodeToRegex("P", 8).c, // timezone offset with colon separator
|
"}else{",
|
utilDate.formatCodeToRegex("O", 8).c, // timezone offset without colon separator
|
"}",
|
"}"
|
].join('\n')}
|
],
|
i,
|
l;
|
|
for (i = 0, l = arr.length; i < l; ++i) {
|
calc.push(arr[i].c);
|
}
|
|
return {
|
g:1,
|
c:calc.join(""),
|
s:[
|
arr[0].s, // year (required)
|
"(?:", "-", arr[1].s, // month (optional)
|
"(?:", "-", arr[2].s, // day (optional)
|
"(?:",
|
"(?:T| )?", // time delimiter -- either a "T" or a single blank space
|
arr[3].s, ":", arr[4].s, // hour AND minute, delimited by a single colon (optional). MUST be preceded by either a "T" or a single blank space
|
"(?::", arr[5].s, ")?", // seconds (optional)
|
"(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
|
"(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
|
")?",
|
")?",
|
")?"
|
].join("")
|
};
|
},
|
U: {
|
g:1,
|
c:"u = parseInt(results[{0}], 10);\n",
|
s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
|
}
|
},
|
|
//Old Ext.Date prototype methods.
|
// private
|
dateFormat: function(date, format) {
|
return utilDate.format(date, format);
|
},
|
|
/**
|
* Compares if two dates are equal by comparing their values.
|
* @param {Date} date1
|
* @param {Date} date2
|
* @return {Boolean} `true` if the date values are equal
|
*/
|
isEqual: function(date1, date2) {
|
// check we have 2 date objects
|
if (date1 && date2) {
|
return (date1.getTime() === date2.getTime());
|
}
|
// one or both isn't a date, only equal if both are falsey
|
return !(date1 || date2);
|
},
|
|
/**
|
* Formats a date given the supplied format string.
|
* @param {Date} date The date to format
|
* @param {String} format The format string
|
* @return {String} The formatted date or an empty string if date parameter is not a JavaScript Date object
|
*/
|
format: function(date, format) {
|
var formatFunctions = utilDate.formatFunctions;
|
|
if (!Ext.isDate(date)) {
|
return '';
|
}
|
|
if (formatFunctions[format] == null) {
|
utilDate.createFormat(format);
|
}
|
|
return formatFunctions[format].call(date) + '';
|
},
|
|
/**
|
* Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
|
*
|
* __Note:__ The date string returned by the JavaScript Date object's `toString()` method varies
|
* between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
|
* For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
|
* getTimezone() first tries to get the timezone abbreviation from between a pair of parentheses
|
* (which may or may not be present), failing which it proceeds to get the timezone abbreviation
|
* from the GMT offset portion of the date string.
|
* @param {Date} date The date
|
* @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
|
*/
|
getTimezone : function(date) {
|
// the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
|
//
|
// Opera : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
|
// Safari : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone (same as FF)
|
// FF : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
|
// IE : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
|
// IE : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
|
//
|
// this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
|
// step 1: (?:\((.*)\) -- find timezone in parentheses
|
// step 2: ([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?) -- if nothing was found in step 1, find timezone from timezone offset portion of date string
|
// step 3: remove all non uppercase characters found in step 1 and 2
|
return date.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,5})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
|
},
|
|
/**
|
* Get the offset from GMT of the current date (equivalent to the format specifier 'O').
|
* @param {Date} date The date
|
* @param {Boolean} [colon=false] (optional) true to separate the hours and minutes with a colon.
|
* @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
|
*/
|
getGMTOffset : function(date, colon) {
|
var offset = date.getTimezoneOffset();
|
return (offset > 0 ? "-" : "+")
|
+ Ext.String.leftPad(Math.floor(Math.abs(offset) / 60), 2, "0")
|
+ (colon ? ":" : "")
|
+ Ext.String.leftPad(Math.abs(offset % 60), 2, "0");
|
},
|
|
/**
|
* Get the numeric day number of the year, adjusted for leap year.
|
* @param {Date} date The date
|
* @return {Number} 0 to 364 (365 in leap years).
|
*/
|
getDayOfYear: function(date) {
|
var num = 0,
|
d = Ext.Date.clone(date),
|
m = date.getMonth(),
|
i;
|
|
for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
|
num += utilDate.getDaysInMonth(d);
|
}
|
return num + date.getDate() - 1;
|
},
|
|
/**
|
* Get the numeric ISO-8601 week number of the year.
|
* (equivalent to the format specifier 'W', but without a leading zero).
|
* @param {Date} date The date
|
* @return {Number} 1 to 53
|
* @method
|
*/
|
getWeekOfYear : (function() {
|
// adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
|
var ms1d = 864e5, // milliseconds in a day
|
ms7d = 7 * ms1d; // milliseconds in a week
|
|
return function(date) { // return a closure so constants get calculated only once
|
var DC3 = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate() + 3) / ms1d, // an Absolute Day Number
|
AWN = Math.floor(DC3 / 7), // an Absolute Week Number
|
Wyr = new Date(AWN * ms7d).getUTCFullYear();
|
|
return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
|
};
|
}()),
|
|
/**
|
* Checks if the current date falls within a leap year.
|
* @param {Date} date The date
|
* @return {Boolean} True if the current date falls within a leap year, false otherwise.
|
*/
|
isLeapYear : function(date) {
|
var year = date.getFullYear();
|
return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
|
},
|
|
/**
|
* Get the first day of the current month, adjusted for leap year. The returned value
|
* is the numeric day index within the week (0-6) which can be used in conjunction with
|
* the {@link #monthNames} array to retrieve the textual day name.
|
*
|
* Example:
|
*
|
* var dt = new Date('1/10/2007'),
|
* firstDay = Ext.Date.getFirstDayOfMonth(dt);
|
* console.log(Ext.Date.dayNames[firstDay]); // output: 'Monday'
|
*
|
* @param {Date} date The date
|
* @return {Number} The day number (0-6).
|
*/
|
getFirstDayOfMonth : function(date) {
|
var day = (date.getDay() - (date.getDate() - 1)) % 7;
|
return (day < 0) ? (day + 7) : day;
|
},
|
|
/**
|
* Get the last day of the current month, adjusted for leap year. The returned value
|
* is the numeric day index within the week (0-6) which can be used in conjunction with
|
* the {@link #monthNames} array to retrieve the textual day name.
|
*
|
* Example:
|
*
|
* var dt = new Date('1/10/2007'),
|
* lastDay = Ext.Date.getLastDayOfMonth(dt);
|
* console.log(Ext.Date.dayNames[lastDay]); // output: 'Wednesday'
|
*
|
* @param {Date} date The date
|
* @return {Number} The day number (0-6).
|
*/
|
getLastDayOfMonth : function(date) {
|
return utilDate.getLastDateOfMonth(date).getDay();
|
},
|
|
|
/**
|
* Get the date of the first day of the month in which this date resides.
|
* @param {Date} date The date
|
* @return {Date}
|
*/
|
getFirstDateOfMonth : function(date) {
|
return new Date(date.getFullYear(), date.getMonth(), 1);
|
},
|
|
/**
|
* Get the date of the last day of the month in which this date resides.
|
* @param {Date} date The date
|
* @return {Date}
|
*/
|
getLastDateOfMonth : function(date) {
|
return new Date(date.getFullYear(), date.getMonth(), utilDate.getDaysInMonth(date));
|
},
|
|
/**
|
* Get the number of days in the current month, adjusted for leap year.
|
* @param {Date} date The date
|
* @return {Number} The number of days in the month.
|
* @method
|
*/
|
getDaysInMonth: (function() {
|
var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
return function(date) { // return a closure for efficiency
|
var m = date.getMonth();
|
|
return m == 1 && utilDate.isLeapYear(date) ? 29 : daysInMonth[m];
|
};
|
}()),
|
|
//<locale type="function">
|
/**
|
* Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
|
* @param {Date} date The date
|
* @return {String} 'st, 'nd', 'rd' or 'th'.
|
*/
|
getSuffix : function(date) {
|
switch (date.getDate()) {
|
case 1:
|
case 21:
|
case 31:
|
return "st";
|
case 2:
|
case 22:
|
return "nd";
|
case 3:
|
case 23:
|
return "rd";
|
default:
|
return "th";
|
}
|
},
|
//</locale>
|
|
/**
|
* Creates and returns a new Date instance with the exact same date value as the called instance.
|
* Dates are copied and passed by reference, so if a copied date variable is modified later, the original
|
* variable will also be changed. When the intention is to create a new variable that will not
|
* modify the original instance, you should create a clone.
|
*
|
* Example of correctly cloning a date:
|
*
|
* //wrong way:
|
* var orig = new Date('10/1/2006');
|
* var copy = orig;
|
* copy.setDate(5);
|
* console.log(orig); // returns 'Thu Oct 05 2006'!
|
*
|
* //correct way:
|
* var orig = new Date('10/1/2006'),
|
* copy = Ext.Date.clone(orig);
|
* copy.setDate(5);
|
* console.log(orig); // returns 'Thu Oct 01 2006'
|
*
|
* @param {Date} date The date.
|
* @return {Date} The new Date instance.
|
*/
|
clone : function(date) {
|
return new Date(date.getTime());
|
},
|
|
/**
|
* Checks if the current date is affected by Daylight Saving Time (DST).
|
* @param {Date} date The date
|
* @return {Boolean} `true` if the current date is affected by DST.
|
*/
|
isDST : function(date) {
|
// adapted from http://sencha.com/forum/showthread.php?p=247172#post247172
|
// courtesy of @geoffrey.mcgill
|
return new Date(date.getFullYear(), 0, 1).getTimezoneOffset() != date.getTimezoneOffset();
|
},
|
|
/**
|
* Attempts to clear all time information from this Date by setting the time to midnight of the same day,
|
* automatically adjusting for Daylight Saving Time (DST) where applicable.
|
*
|
* __Note:__ DST timezone information for the browser's host operating system is assumed to be up-to-date.
|
* @param {Date} date The date
|
* @param {Boolean} [clone=false] `true` to create a clone of this date, clear the time and return it.
|
* @return {Date} this or the clone.
|
*/
|
clearTime : function(date, clone) {
|
if (clone) {
|
return Ext.Date.clearTime(Ext.Date.clone(date));
|
}
|
|
// get current date before clearing time
|
var d = date.getDate(),
|
hr,
|
c;
|
|
// clear time
|
date.setHours(0);
|
date.setMinutes(0);
|
date.setSeconds(0);
|
date.setMilliseconds(0);
|
|
if (date.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
|
// note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
|
// refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
|
|
// increment hour until cloned date == current date
|
for (hr = 1, c = utilDate.add(date, Ext.Date.HOUR, hr); c.getDate() != d; hr++, c = utilDate.add(date, Ext.Date.HOUR, hr));
|
|
date.setDate(d);
|
date.setHours(c.getHours());
|
}
|
|
return date;
|
},
|
|
/**
|
* Provides a convenient method for performing basic date arithmetic. This method
|
* does not modify the Date instance being called - it creates and returns
|
* a new Date instance containing the resulting date value.
|
*
|
* Examples:
|
*
|
* // Basic usage:
|
* var dt = Ext.Date.add(new Date('10/29/2006'), Ext.Date.DAY, 5);
|
* console.log(dt); // returns 'Fri Nov 03 2006 00:00:00'
|
*
|
* // Negative values will be subtracted:
|
* var dt2 = Ext.Date.add(new Date('10/1/2006'), Ext.Date.DAY, -5);
|
* console.log(dt2); // returns 'Tue Sep 26 2006 00:00:00'
|
*
|
* // Decimal values can be used:
|
* var dt3 = Ext.Date.add(new Date('10/1/2006'), Ext.Date.DAY, 1.25);
|
* console.log(dt3); // returns 'Mon Oct 02 2006 06:00:00'
|
*
|
* @param {Date} date The date to modify
|
* @param {String} interval A valid date interval enum value.
|
* @param {Number} value The amount to add to the current date.
|
* @return {Date} The new Date instance.
|
*/
|
add : function(date, interval, value) {
|
var d = Ext.Date.clone(date),
|
Date = Ext.Date,
|
day, decimalValue, base = 0;
|
if (!interval || value === 0) {
|
return d;
|
}
|
|
decimalValue = value - parseInt(value, 10);
|
value = parseInt(value, 10);
|
|
if (value) {
|
switch(interval.toLowerCase()) {
|
// See EXTJSIV-7418. We use setTime() here to deal with issues related to
|
// the switchover that occurs when changing to daylight savings and vice
|
// versa. setTime() handles this correctly where setHour/Minute/Second/Millisecond
|
// do not. Let's assume the DST change occurs at 2am and we're incrementing using add
|
// for 15 minutes at time. When entering DST, we should see:
|
// 01:30am
|
// 01:45am
|
// 03:00am // skip 2am because the hour does not exist
|
// ...
|
// Similarly, leaving DST, we should see:
|
// 01:30am
|
// 01:45am
|
// 01:00am // repeat 1am because that's the change over
|
// 01:30am
|
// 01:45am
|
// 02:00am
|
// ....
|
//
|
case Ext.Date.MILLI:
|
d.setTime(d.getTime() + value);
|
break;
|
case Ext.Date.SECOND:
|
d.setTime(d.getTime() + value * 1000);
|
break;
|
case Ext.Date.MINUTE:
|
d.setTime(d.getTime() + value * 60 * 1000);
|
break;
|
case Ext.Date.HOUR:
|
d.setTime(d.getTime() + value * 60 * 60 * 1000);
|
break;
|
case Ext.Date.DAY:
|
d.setDate(d.getDate() + value);
|
break;
|
case Ext.Date.MONTH:
|
day = date.getDate();
|
if (day > 28) {
|
day = Math.min(day, Ext.Date.getLastDateOfMonth(Ext.Date.add(Ext.Date.getFirstDateOfMonth(date), Ext.Date.MONTH, value)).getDate());
|
}
|
d.setDate(day);
|
d.setMonth(date.getMonth() + value);
|
break;
|
case Ext.Date.YEAR:
|
day = date.getDate();
|
if (day > 28) {
|
day = Math.min(day, Ext.Date.getLastDateOfMonth(Ext.Date.add(Ext.Date.getFirstDateOfMonth(date), Ext.Date.YEAR, value)).getDate());
|
}
|
d.setDate(day);
|
d.setFullYear(date.getFullYear() + value);
|
break;
|
}
|
}
|
|
if (decimalValue) {
|
switch (interval.toLowerCase()) {
|
case Ext.Date.MILLI: base = 1; break;
|
case Ext.Date.SECOND: base = 1000; break;
|
case Ext.Date.MINUTE: base = 1000*60; break;
|
case Ext.Date.HOUR: base = 1000*60*60; break;
|
case Ext.Date.DAY: base = 1000*60*60*24; break;
|
|
case Ext.Date.MONTH:
|
day = utilDate.getDaysInMonth(d);
|
base = 1000*60*60*24*day;
|
break;
|
|
case Ext.Date.YEAR:
|
day = (utilDate.isLeapYear(d) ? 366 : 365);
|
base = 1000*60*60*24*day;
|
break;
|
}
|
if (base) {
|
d.setTime(d.getTime() + base * decimalValue);
|
}
|
}
|
|
return d;
|
},
|
|
/**
|
* Provides a convenient method for performing basic date arithmetic. This method
|
* does not modify the Date instance being called - it creates and returns
|
* a new Date instance containing the resulting date value.
|
*
|
* Examples:
|
*
|
* // Basic usage:
|
* var dt = Ext.Date.subtract(new Date('10/29/2006'), Ext.Date.DAY, 5);
|
* console.log(dt); // returns 'Tue Oct 24 2006 00:00:00'
|
*
|
* // Negative values will be added:
|
* var dt2 = Ext.Date.subtract(new Date('10/1/2006'), Ext.Date.DAY, -5);
|
* console.log(dt2); // returns 'Fri Oct 6 2006 00:00:00'
|
*
|
* // Decimal values can be used:
|
* var dt3 = Ext.Date.subtract(new Date('10/1/2006'), Ext.Date.DAY, 1.25);
|
* console.log(dt3); // returns 'Fri Sep 29 2006 06:00:00'
|
*
|
* @param {Date} date The date to modify
|
* @param {String} interval A valid date interval enum value.
|
* @param {Number} value The amount to subtract from the current date.
|
* @return {Date} The new Date instance.
|
*/
|
subtract: function(date, interval, value){
|
return utilDate.add(date, interval, -value);
|
},
|
|
/**
|
* Checks if a date falls on or between the given start and end dates.
|
* @param {Date} date The date to check
|
* @param {Date} start Start date
|
* @param {Date} end End date
|
* @return {Boolean} `true` if this date falls on or between the given start and end dates.
|
*/
|
between : function(date, start, end) {
|
var t = date.getTime();
|
return start.getTime() <= t && t <= end.getTime();
|
},
|
|
//Maintains compatibility with old static and prototype window.Date methods.
|
compat: function() {
|
var nativeDate = window.Date,
|
p,
|
statics = ['useStrict', 'formatCodeToRegex', 'parseFunctions', 'parseRegexes', 'formatFunctions', 'y2kYear', 'MILLI', 'SECOND', 'MINUTE', 'HOUR', 'DAY', 'MONTH', 'YEAR', 'defaults', 'dayNames', 'monthNames', 'monthNumbers', 'getShortMonthName', 'getShortDayName', 'getMonthNumber', 'formatCodes', 'isValid', 'parseDate', 'getFormatCode', 'createFormat', 'createParser', 'parseCodes'],
|
proto = ['dateFormat', 'format', 'getTimezone', 'getGMTOffset', 'getDayOfYear', 'getWeekOfYear', 'isLeapYear', 'getFirstDayOfMonth', 'getLastDayOfMonth', 'getDaysInMonth', 'getSuffix', 'clone', 'isDST', 'clearTime', 'add', 'between'],
|
sLen = statics.length,
|
pLen = proto.length,
|
stat, prot, s;
|
|
//Append statics
|
for (s = 0; s < sLen; s++) {
|
stat = statics[s];
|
nativeDate[stat] = utilDate[stat];
|
}
|
|
//Append to prototype
|
for (p = 0; p < pLen; p++) {
|
prot = proto[p];
|
nativeDate.prototype[prot] = function() {
|
var args = Array.prototype.slice.call(arguments);
|
args.unshift(this);
|
return utilDate[prot].apply(utilDate, args);
|
};
|
}
|
}
|
});
|
};
|
|
// @tag foundation,core
|
// @require ../lang/Date.js
|
// @define Ext.Base
|
|
/**
|
* @author Jacky Nguyen <jacky@sencha.com>
|
* @docauthor Jacky Nguyen <jacky@sencha.com>
|
* @class Ext.Base
|
*
|
* 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.
|
*/
|
(function(flexSetter) {
|
|
var noArgs = [],
|
Base = function(){},
|
hookFunctionFactory = function(hookFunction, underriddenFunction, methodName, owningClass) {
|
var result = function() {
|
var result = this.callParent(arguments);
|
hookFunction.apply(this, arguments);
|
return result;
|
};
|
result.$name = methodName;
|
result.$owner = owningClass;
|
if (underriddenFunction) {
|
result.$previous = underriddenFunction.$previous;
|
underriddenFunction.$previous = result;
|
}
|
return result;
|
};
|
|
// 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
|
* @param config
|
*/
|
extend: function(parent) {
|
var parentPrototype = parent.prototype,
|
basePrototype, prototype, i, ln, name, statics;
|
|
prototype = this.prototype = Ext.Object.chain(parentPrototype);
|
prototype.self = this;
|
|
this.superclass = prototype.superclass = parentPrototype;
|
|
if (!parent.$isClass) {
|
basePrototype = Ext.Base.prototype;
|
|
for (i in basePrototype) {
|
if (i in prototype) {
|
prototype[i] = basePrototype[i];
|
}
|
}
|
}
|
|
// 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];
|
}
|
}
|
}
|
|
if (parent.$onExtended) {
|
this.$onExtended = parent.$onExtended.slice();
|
}
|
|
prototype.config = new prototype.configClass();
|
prototype.initConfigList = prototype.initConfigList.slice();
|
prototype.initConfigMap = Ext.clone(prototype.initConfigMap);
|
prototype.configMap = Ext.Object.chain(prototype.configMap);
|
},
|
|
/**
|
* @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
|
* @param config
|
*/
|
addConfig: function(config, fullMerge) {
|
var prototype = this.prototype,
|
configNameCache = Ext.Class.configNameCache,
|
hasConfig = prototype.configMap,
|
initConfigList = prototype.initConfigList,
|
initConfigMap = prototype.initConfigMap,
|
defaultConfig = prototype.config,
|
initializedName, name, value;
|
|
for (name in config) {
|
if (config.hasOwnProperty(name)) {
|
if (!hasConfig[name]) {
|
hasConfig[name] = true;
|
}
|
|
value = config[name];
|
|
initializedName = configNameCache[name].initialized;
|
|
if (!initConfigMap[name] && value !== null && !prototype[initializedName]) {
|
initConfigMap[name] = true;
|
initConfigList.push(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', {
|
* ...
|
* });
|
*
|
* 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;
|
|
for (name in members) {
|
if (members.hasOwnProperty(name)) {
|
member = members[name];
|
if (typeof member == 'function' && !member.$isClass && member !== Ext.emptyFn && member !== Ext.identityFn) {
|
member.$owner = this;
|
member.$name = name;
|
}
|
this[name] = member;
|
}
|
}
|
|
return this;
|
},
|
|
/**
|
* @private
|
* @static
|
* @inheritable
|
* @param {Object} members
|
*/
|
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 = {};
|
}
|
|
for (name in members) {
|
if (members.hasOwnProperty(name)) {
|
member = members[name];
|
this[name] = member;
|
|
if (!hasInheritableStatics[name]) {
|
hasInheritableStatics[name] = true;
|
inheritableStatics.push(name);
|
}
|
}
|
}
|
|
return this;
|
},
|
|
/**
|
* Add methods / properties to the prototype of this class.
|
*
|
* 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,
|
enumerables = Ext.enumerables,
|
names = [],
|
i, ln, name, member;
|
|
for (name in members) {
|
names.push(name);
|
}
|
|
if (enumerables) {
|
names.push.apply(names, enumerables);
|
}
|
|
for (i = 0,ln = names.length; i < ln; i++) {
|
name = names[i];
|
|
if (members.hasOwnProperty(name)) {
|
member = members[name];
|
|
if (typeof member == 'function' && !member.$isClass && member !== Ext.emptyFn && member !== Ext.identityFn) {
|
member.$owner = this;
|
member.$name = name;
|
}
|
|
prototype[name] = member;
|
}
|
}
|
|
return this;
|
},
|
|
/**
|
* @private
|
* @static
|
* @inheritable
|
* @param name
|
* @param member
|
*/
|
addMember: function(name, member) {
|
if (typeof member == 'function' && !member.$isClass && member !== Ext.emptyFn && member !== Ext.identityFn) {
|
member.$owner = this;
|
member.$name = name;
|
}
|
|
this.prototype[name] = member;
|
return this;
|
},
|
|
/**
|
* Adds members to class.
|
* @static
|
* @inheritable
|
* @deprecated 4.1 Use {@link #addMembers} instead.
|
*/
|
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,
|
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 = Ext.Function.clone(toBorrow);
|
|
|
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!");
|
*
|
* this.callParent(arguments);
|
*
|
* alert("Meeeeoooowwww");
|
* }
|
* });
|
*
|
* var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
|
* // alerts "I'm a cat!"
|
* // alerts "Meeeeoooowwww"
|
*
|
* As of 4.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!");
|
*
|
* this.callParent(arguments);
|
*
|
* alert("Meeeeoooowwww");
|
* }
|
* });
|
*
|
* 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
|
* @markdown
|
* @deprecated 4.1.0 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 == 'inheritableStatics'){
|
me.addInheritableStatics(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 && member !== Ext.identityFn) {
|
if (typeof member.$owner != 'undefined') {
|
member = cloneFunction(member);
|
}
|
|
|
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;
|
},
|
|
// Documented downwards
|
callParent: function(args) {
|
var method;
|
|
// This code is intentionally inlined for the least number of debugger stepping
|
return (method = this.callParent.caller) && (method.$previous ||
|
((method = method.$owner ? method : method.caller) &&
|
method.$owner.superclass.self[method.$name])).apply(this, args || noArgs);
|
},
|
|
// Documented downwards
|
callSuper: function(args) {
|
var method;
|
|
// This code is intentionally inlined for the least number of debugger stepping
|
return (method = this.callSuper.caller) &&
|
((method = method.$owner ? method : method.caller) &&
|
method.$owner.superclass.self[method.$name]).apply(this, args || noArgs);
|
},
|
|
/**
|
* Used internally by the mixins pre-processor
|
* @private
|
* @static
|
* @inheritable
|
*/
|
mixin: function(name, mixinClass) {
|
var me = this,
|
mixin = mixinClass.prototype,
|
prototype = me.prototype,
|
key, statics, i, ln, staticName,
|
mixinValue, hookKey, hookFunction;
|
|
if (typeof mixin.onClassMixedIn != 'undefined') {
|
mixin.onClassMixedIn.call(mixinClass, me);
|
}
|
|
if (!prototype.hasOwnProperty('mixins')) {
|
if ('mixins' in prototype) {
|
prototype.mixins = Ext.Object.chain(prototype.mixins);
|
}
|
else {
|
prototype.mixins = {};
|
}
|
}
|
|
for (key in mixin) {
|
mixinValue = mixin[key];
|
if (key === 'mixins') {
|
Ext.merge(prototype.mixins, mixinValue);
|
}
|
else if (key === 'xhooks') {
|
for (hookKey in mixinValue) {
|
hookFunction = mixinValue[hookKey];
|
|
// Mixed in xhook methods cannot call a parent.
|
hookFunction.$previous = Ext.emptyFn;
|
|
if (prototype.hasOwnProperty(hookKey)) {
|
|
// Pass the hook function, and the existing function which it is to underride.
|
// The existing function has its $previous pointer replaced by a closure
|
// which calls the hookFunction and then the existing function's original $previous
|
hookFunctionFactory(hookFunction, prototype[hookKey], hookKey, me);
|
} else {
|
// There's no original function, so generate an implementation which calls
|
// the hook function. It will not get any $previous pointer.
|
prototype[hookKey] = hookFunctionFactory(hookFunction, null, hookKey, me);
|
}
|
}
|
}
|
else if (!(key === 'mixinId' || key === 'config') && (prototype[key] === undefined)) {
|
prototype[key] = mixinValue;
|
}
|
}
|
|
// Mixin statics inheritance
|
statics = mixin.$inheritableStatics;
|
|
if (statics) {
|
for (i = 0, ln = statics.length; i < ln; i++) {
|
staticName = statics[i];
|
|
if (!me.hasOwnProperty(staticName)) {
|
me[staticName] = mixinClass[staticName];
|
}
|
}
|
}
|
|
if ('config' in mixin) {
|
me.addConfig(mixin.config, false);
|
}
|
|
prototype.mixins[name] = mixin;
|
return me;
|
},
|
|
/**
|
* 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({
|
/** @private */
|
isInstance: true,
|
|
/** @private */
|
$className: 'Ext.Base',
|
|
/** @private */
|
configClass: Ext.emptyFn,
|
|
/** @private */
|
initConfigList: [],
|
|
/** @private */
|
configMap: {},
|
|
/** @private */
|
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]));
|
|
|
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) {
|
// 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.callSuper.caller) &&
|
((method = method.$owner ? method : method.caller) &&
|
method.$owner.superclass[method.$name]);
|
|
|
return superMethod.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;
|
},
|
|
/**
|
* 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} config
|
* @return {Ext.Base} this
|
*/
|
initConfig: function(config) {
|
var instanceConfig = config,
|
configNameCache = Ext.Class.configNameCache,
|
defaultConfig = new this.configClass(),
|
defaultConfigList = this.initConfigList,
|
hasConfig = this.configMap,
|
nameMap, i, ln, name, initializedName;
|
|
this.initConfig = Ext.emptyFn;
|
|
this.initialConfig = instanceConfig || {};
|
|
this.config = config = (instanceConfig) ? Ext.merge(defaultConfig, config) : defaultConfig;
|
|
if (instanceConfig) {
|
defaultConfigList = defaultConfigList.slice();
|
|
for (name in instanceConfig) {
|
if (hasConfig[name]) {
|
if (instanceConfig[name] !== null) {
|
defaultConfigList.push(name);
|
this[configNameCache[name].initialized] = false;
|
}
|
}
|
}
|
}
|
|
for (i = 0,ln = defaultConfigList.length; i < ln; i++) {
|
name = defaultConfigList[i];
|
nameMap = configNameCache[name];
|
initializedName = nameMap.initialized;
|
|
if (!this[initializedName]) {
|
this[initializedName] = true;
|
this[nameMap.set].call(this, config[name]);
|
}
|
}
|
|
return this;
|
},
|
|
/**
|
* @private
|
* @param config
|
*/
|
hasConfig: function(name) {
|
return Boolean(this.configMap[name]);
|
},
|
|
/**
|
* @private
|
*/
|
setConfig: function(config, applyIfNotSet) {
|
if (!config) {
|
return this;
|
}
|
|
var configNameCache = Ext.Class.configNameCache,
|
currentConfig = this.config,
|
hasConfig = this.configMap,
|
initialConfig = this.initialConfig,
|
name, value;
|
|
applyIfNotSet = Boolean(applyIfNotSet);
|
|
for (name in config) {
|
if (applyIfNotSet && initialConfig.hasOwnProperty(name)) {
|
continue;
|
}
|
|
value = config[name];
|
currentConfig[name] = value;
|
|
if (hasConfig[name]) {
|
this[configNameCache[name].set](value);
|
}
|
}
|
|
return this;
|
},
|
|
/**
|
* @private
|
* @param name
|
*/
|
getConfig: function(name) {
|
var configNameCache = Ext.Class.configNameCache;
|
|
return this[configNameCache[name].get]();
|
},
|
|
/**
|
* Returns the initial configuration passed to constructor when instantiating
|
* this class.
|
* @param {String} [name] Name of the config option to return.
|
* @return {Object/Mixed} The full config object or a single config value
|
* when `name` parameter specified.
|
*/
|
getInitialConfig: function(name) {
|
var config = this.config;
|
|
if (!name) {
|
return config;
|
}
|
else {
|
return config[name];
|
}
|
},
|
|
/**
|
* @private
|
* @param names
|
* @param callback
|
* @param scope
|
*/
|
onConfigUpdate: function(names, callback, scope) {
|
var self = this.self,
|
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;
|
|
this[updaterName] = newUpdater;
|
}
|
},
|
|
/**
|
* @private
|
*/
|
destroy: function() {
|
this.destroy = Ext.emptyFn;
|
}
|
});
|
|
/**
|
* Call the original method that was previously overridden with {@link Ext.Base#override}
|
*
|
* Ext.define('My.Cat', {
|
* constructor: function() {
|
* alert("I'm a cat!");
|
* }
|
* });
|
*
|
* My.Cat.override({
|
* constructor: function() {
|
* alert("I'm going to be a cat!");
|
*
|
* this.callOverridden();
|
*
|
* alert("Meeeeoooowwww");
|
* }
|
* });
|
*
|
* 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 as of 4.1. Use {@link #callParent} instead.
|
*/
|
Base.prototype.callOverridden = Base.prototype.callParent;
|
|
Ext.Base = Base;
|
|
}(Ext.Function.flexSetter));
|
|
// @tag foundation,core
|
// @require Base.js
|
// @define Ext.Class
|
|
/**
|
* @author Jacky Nguyen <jacky@sencha.com>
|
* @docauthor Jacky Nguyen <jacky@sencha.com>
|
* @class Ext.Class
|
*
|
* 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 depency 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;
|
|
// Creates a constructor that has nothing extra in its scope chain.
|
function makeCtor (className) {
|
function constructor () {
|
// Opera has some problems returning from a constructor when Dragonfly isn't running. The || null seems to
|
// be sufficient to stop it misbehaving. Known to be required against 10.53, 11.51 and 11.61.
|
return this.constructor.apply(this, arguments) || null;
|
}
|
return constructor;
|
}
|
|
/**
|
* @method constructor
|
* Create 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, data);
|
|
ExtClass.process(Class, data, onCreated);
|
|
return Class;
|
};
|
|
Ext.apply(ExtClass, {
|
/**
|
* @private
|
*/
|
onBeforeCreated: function(Class, data, hooks) {
|
|
Class.addMembers(data);
|
|
hooks.onCreated.call(Class, Class);
|
|
},
|
|
/**
|
* @private
|
*/
|
create: function(Class, data) {
|
var name, i;
|
|
if (!Class) {
|
Class = makeCtor(
|
);
|
}
|
|
for (i = 0; i < baseStaticMemberLength; i++) {
|
name = baseStaticMembers[i];
|
Class[name] = Base[name];
|
}
|
|
return Class;
|
},
|
|
/**
|
* @private
|
*/
|
process: function(Class, data, onCreated) {
|
var preprocessorStack = data.preprocessors || ExtClass.defaultPreprocessors,
|
registeredPreprocessors = this.preprocessors,
|
hooks = {
|
onBeforeCreated: this.onBeforeCreated
|
},
|
preprocessors = [],
|
preprocessor, preprocessorsProperties,
|
i, ln, j, subLn, preprocessorProperty;
|
|
delete data.preprocessors;
|
|
for (i = 0,ln = preprocessorStack.length; i < ln; i++) {
|
preprocessor = preprocessorStack[i];
|
|
if (typeof preprocessor == 'string') {
|
preprocessor = registeredPreprocessors[preprocessor];
|
preprocessorsProperties = preprocessor.properties;
|
|
if (preprocessorsProperties === true) {
|
preprocessors.push(preprocessor.fn);
|
}
|
else if (preprocessorsProperties) {
|
for (j = 0,subLn = preprocessorsProperties.length; j < subLn; j++) {
|
preprocessorProperty = preprocessorsProperties[j];
|
|
if (data.hasOwnProperty(preprocessorProperty)) {
|
preprocessors.push(preprocessor.fn);
|
break;
|
}
|
}
|
}
|
}
|
else {
|
preprocessors.push(preprocessor);
|
}
|
}
|
|
hooks.onCreated = onCreated ? onCreated : Ext.emptyFn;
|
hooks.preprocessors = preprocessors;
|
|
this.doProcess(Class, data, hooks);
|
},
|
|
doProcess: function(Class, data, hooks) {
|
var me = this,
|
preprocessors = hooks.preprocessors,
|
preprocessor = preprocessors.shift(),
|
doProcess = me.doProcess;
|
|
for ( ; preprocessor ; preprocessor = preprocessors.shift()) {
|
// Returning false signifies an asynchronous preprocessor - it will call doProcess when we can continue
|
if (preprocessor.call(me, Class, data, hooks, doProcess) === false) {
|
return;
|
}
|
}
|
hooks.onBeforeCreated.apply(me, arguments);
|
},
|
|
/** @private */
|
preprocessors: {},
|
|
/**
|
* Register a new pre-processor to be used during the class creation process
|
*
|
* @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 aynchronous.
|
* @return {Ext.Class} this
|
* @private
|
* @static
|
*/
|
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
|
*
|
* @param {String} name
|
* @return {Function} preprocessor
|
* @private
|
* @static
|
*/
|
getPreprocessor: function(name) {
|
return this.preprocessors[name];
|
},
|
|
/**
|
* @private
|
*/
|
getPreprocessors: function() {
|
return this.preprocessors;
|
},
|
|
/**
|
* @private
|
*/
|
defaultPreprocessors: [],
|
|
/**
|
* Retrieve the array stack of default pre-processors
|
* @return {Function[]} defaultPreprocessors
|
* @private
|
* @static
|
*/
|
getDefaultPreprocessors: function() {
|
return this.defaultPreprocessors;
|
},
|
|
/**
|
* Set the default array stack of default pre-processors
|
*
|
* @private
|
* @param {Array} preprocessors
|
* @return {Ext.Class} this
|
* @static
|
*/
|
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);
|
* }
|
* }).setDefaultPreprocessorPosition('debug', 'last');
|
*
|
* @private
|
* @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
|
* @static
|
*/
|
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;
|
},
|
|
configNameCache: {},
|
|
getConfigNameMap: function(name) {
|
var cache = this.configNameCache,
|
map = cache[name],
|
capitalizedName;
|
|
if (!map) {
|
capitalizedName = name.charAt(0).toUpperCase() + name.substr(1);
|
|
map = cache[name] = {
|
internal: name,
|
initialized: '_is' + capitalizedName + 'Initialized',
|
apply: 'apply' + capitalizedName,
|
update: 'update' + capitalizedName,
|
'set': 'set' + capitalizedName,
|
'get': 'get' + capitalizedName,
|
doSet : 'doSet' + capitalizedName,
|
changeEvent: name.toLowerCase() + 'change'
|
};
|
}
|
|
return map;
|
}
|
});
|
|
/**
|
* @cfg {String} extend
|
* The parent class that this class extends. For example:
|
*
|
* Ext.define('Person', {
|
* say: function(text) { alert(text); }
|
* });
|
*
|
* Ext.define('Developer', {
|
* extend: 'Person',
|
* say: function(text) { this.callParent(["print "+text]); }
|
* });
|
*/
|
ExtClass.registerPreprocessor('extend', function(Class, data, hooks) {
|
|
var Base = Ext.Base,
|
basePrototype = Base.prototype,
|
extend = data.extend,
|
Parent, parentPrototype, i;
|
|
delete data.extend;
|
|
if (extend && extend !== Object) {
|
Parent = extend;
|
}
|
else {
|
Parent = Base;
|
}
|
|
parentPrototype = Parent.prototype;
|
|
if (!Parent.$isClass) {
|
for (i in basePrototype) {
|
if (!parentPrototype[i]) {
|
parentPrototype[i] = basePrototype[i];
|
}
|
}
|
}
|
|
Class.extend(Parent);
|
|
Class.triggerExtended.apply(Class, arguments);
|
|
if (data.onClassExtended) {
|
Class.onExtended(data.onClassExtended, Class);
|
delete data.onClassExtended;
|
}
|
|
}, true);
|
|
/**
|
* @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;
|
});
|
|
/**
|
* @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;
|
});
|
|
/**
|
* @cfg {Object} config
|
* List of configuration options with their default values, for which automatically
|
* accessor methods are generated. For example:
|
*
|
* Ext.define('SmartPhone', {
|
* config: {
|
* hasTouchScreen: false,
|
* operatingSystem: 'Other',
|
* price: 500
|
* },
|
* constructor: function(cfg) {
|
* this.initConfig(cfg);
|
* }
|
* });
|
*
|
* var iPhone = new SmartPhone({
|
* hasTouchScreen: true,
|
* operatingSystem: 'iOS'
|
* });
|
*
|
* iPhone.getPrice(); // 500;
|
* iPhone.getOperatingSystem(); // 'iOS'
|
* iPhone.getHasTouchScreen(); // true;
|
*
|
* NOTE for when configs are reference types, the getter and setter methods do not make copies.
|
*
|
* For example, when a config value is set, the reference is stored on the instance. All instances that set
|
* the same reference type will share it.
|
*
|
* In the case of the getter, the value with either come from the prototype if the setter was never called or from
|
* the instance as the last value passed to the setter.
|
*
|
* For some config properties, the value passed to the setter is transformed prior to being stored on the instance.
|
*/
|
ExtClass.registerPreprocessor('config', function(Class, data) {
|
|
var config = data.config,
|
prototype = Class.prototype;
|
|
delete data.config;
|
|
Ext.Object.each(config, function(name, value) {
|
var nameMap = ExtClass.getConfigNameMap(name),
|
internalName = nameMap.internal,
|
initializedName = nameMap.initialized,
|
applyName = nameMap.apply,
|
updateName = nameMap.update,
|
setName = nameMap.set,
|
getName = nameMap.get,
|
hasOwnSetter = (setName in prototype) || data.hasOwnProperty(setName),
|
hasOwnApplier = (applyName in prototype) || data.hasOwnProperty(applyName),
|
hasOwnUpdater = (updateName in prototype) || data.hasOwnProperty(updateName),
|
optimizedGetter, customGetter;
|
|
if (value === null || (!hasOwnSetter && !hasOwnApplier && !hasOwnUpdater)) {
|
prototype[internalName] = value;
|
prototype[initializedName] = true;
|
}
|
else {
|
prototype[initializedName] = false;
|
}
|
|
if (!hasOwnSetter) {
|
data[setName] = function(value) {
|
var oldValue = this[internalName],
|
applier = this[applyName],
|
updater = this[updateName];
|
|
if (!this[initializedName]) {
|
this[initializedName] = true;
|
}
|
|
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;
|
};
|
}
|
|
if (!(getName in prototype) || data.hasOwnProperty(getName)) {
|
customGetter = data[getName] || false;
|
|
if (customGetter) {
|
optimizedGetter = function() {
|
return customGetter.apply(this, arguments);
|
};
|
}
|
else {
|
optimizedGetter = function() {
|
return this[internalName];
|
};
|
}
|
|
data[getName] = function() {
|
var currentGetter;
|
|
if (!this[initializedName]) {
|
this[initializedName] = true;
|
this[setName](this.config[name]);
|
}
|
|
currentGetter = this[getName];
|
|
if ('$previous' in currentGetter) {
|
currentGetter.$previous = optimizedGetter;
|
}
|
else {
|
this[getName] = optimizedGetter;
|
}
|
|
return optimizedGetter.apply(this, arguments);
|
};
|
}
|
});
|
|
Class.addConfig(config, true);
|
});
|
|
/**
|
* @cfg {String[]/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', {
|
* mixins: ['CanSing']
|
* })
|
*
|
* In this case the Musician class will get a `sing` method from CanSing mixin.
|
*
|
* But what if the Musician already has a `sing` method? Or you want to mix
|
* in two classes, both of which define `sing`? In such a cases it's good
|
* to define mixins as an object, where you assign a name to each mixin:
|
*
|
* Ext.define('Musician', {
|
* mixins: {
|
* canSing: 'CanSing'
|
* },
|
*
|
* sing: function() {
|
* // delegate singing operation to mixin
|
* this.mixins.canSing.sing.call(this);
|
* }
|
* })
|
*
|
* In this case the `sing` method of Musician will overwrite the
|
* mixed in `sing` method. But you can access the original mixed in method
|
* through special `mixins` property.
|
*/
|
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 (var mixinName in mixins) {
|
if (mixins.hasOwnProperty(mixinName)) {
|
Class.mixin(mixinName, mixins[mixinName]);
|
}
|
}
|
}
|
});
|
});
|
|
// 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'
|
,'statics'
|
,'inheritableStatics'
|
,'mixins'
|
,'config'
|
];
|
|
if (Class) {
|
cls = new ExtClass(Class, members);
|
// The 'constructor' is given as 'Class' but also needs to be on prototype
|
cls.prototype.constructor = Class;
|
} 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;
|
};
|
}());
|
|
// @tag foundation,core
|
// @require Class.js
|
// @define Ext.ClassManager
|
|
/**
|
* @author Jacky Nguyen <jacky@sencha.com>
|
* @docauthor Jacky Nguyen <jacky@sencha.com>
|
* @class Ext.ClassManager
|
*
|
* 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#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.
|
*
|
* Ext.define('Person', {
|
* name: 'Unknown',
|
*
|
* constructor: function(name) {
|
* if (name) {
|
* this.name = name;
|
* }
|
* },
|
*
|
* 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]);
|
* },
|
*
|
* 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);
|
* },
|
*
|
* 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.hasTouchScreen(); // 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) {
|
|
// Creates a constructor that has nothing extra in its scope chain.
|
function makeCtor () {
|
function constructor () {
|
// Opera has some problems returning from a constructor when Dragonfly isn't running. The || null seems to
|
// be sufficient to stop it misbehaving. Known to be required against 10.53, 11.51 and 11.61.
|
return this.constructor.apply(this, arguments) || null;
|
}
|
return constructor;
|
}
|
|
var Manager = Ext.ClassManager = {
|
|
/**
|
* @property {Object} classes
|
* 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;
|
|
|
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) {
|
|
var cache = this.namespaceParseCache,
|
parts,
|
rewrites,
|
root,
|
name,
|
rewrite, from, to, i, ln;
|
|
if (this.enableNamespaceParseCache) {
|
if (cache.hasOwnProperty(namespace)) {
|
return cache[namespace];
|
}
|
}
|
|
parts = [];
|
rewrites = this.namespaceRewrites;
|
root = global;
|
name = namespace;
|
|
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 {Object} 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,
|
root,
|
parts,
|
part, i, ln;
|
|
if (classes[name]) {
|
return classes[name];
|
}
|
|
root = global;
|
parts = this.parseNamespace(name);
|
|
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) {
|
|
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][i];
|
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;
|
*
|
* {@link Ext.ClassManager#getName} is usually invoked by the shorthand {@link Ext#getClassName}.
|
*
|
* Ext.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.
|
*
|
* {@link Ext.ClassManager#getClass} is usually invoked by the shorthand {@link Ext#getClass}.
|
*
|
* var component = new Ext.Component();
|
*
|
* Ext.getClass(component); // returns Ext.Component
|
*
|
* @param {Object} object
|
* @return {Ext.Class} class
|
*/
|
getClass: function(object) {
|
return object && object.self || null;
|
},
|
|
/**
|
* Defines a class.
|
* @deprecated 4.1.0 Use {@link Ext#define} instead, as that also supports creating overrides.
|
*/
|
create: function(className, data, createdFn) {
|
|
var ctor = makeCtor();
|
if (typeof data == 'function') {
|
data = data(ctor);
|
}
|
|
|
data.$className = className;
|
|
return new Class(ctor, data, function() {
|
var postprocessorStack = data.postprocessors || Manager.defaultPostprocessors,
|
registeredPostprocessors = Manager.postprocessors,
|
postprocessors = [],
|
postprocessor, 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);
|
}
|
}
|
|
data.postprocessors = postprocessors;
|
data.createdFn = createdFn;
|
Manager.processCreate(className, this, data);
|
});
|
},
|
|
processCreate: function(className, cls, clsData){
|
var me = this,
|
postprocessor = clsData.postprocessors.shift(),
|
createdFn = clsData.createdFn;
|
|
if (!postprocessor) {
|
|
if (className) {
|
me.set(className, cls);
|
}
|
|
if (createdFn) {
|
createdFn.call(cls, cls);
|
}
|
|
if (className) {
|
me.triggerCreated(className);
|
}
|
return;
|
}
|
|
if (postprocessor.call(me, className, cls, clsData, me.processCreate) !== false) {
|
me.processCreate(className, cls, clsData);
|
}
|
},
|
|
createOverride: function (className, data, createdFn) {
|
var me = this,
|
overriddenClassName = data.override,
|
requires = data.requires,
|
uses = data.uses,
|
classReady = function () {
|
var cls, temp;
|
|
if (requires) {
|
temp = requires;
|
requires = null; // do the real thing next time (which may be now)
|
|
// Since the override is going to be used (its target class is now
|
// created), we need to fetch the required classes for the override
|
// and call us back once they are loaded:
|
Ext.Loader.require(temp, classReady);
|
} else {
|
// The target class and the required classes for this override are
|
// ready, so we can apply the override now:
|
cls = me.get(overriddenClassName);
|
|
// We don't want to apply these:
|
delete data.override;
|
delete data.requires;
|
delete data.uses;
|
|
Ext.override(cls, data);
|
|
// This pushes 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.
|
me.triggerCreated(className);
|
|
if (uses) {
|
Ext.Loader.addUsedClasses(uses); // get these classes too!
|
}
|
|
if (createdFn) {
|
createdFn.call(cls); // last but not least!
|
}
|
}
|
};
|
|
me.existCache[className] = true;
|
|
// Override the target class right after it's created
|
me.onCreated(classReady, me, overriddenClassName);
|
|
return me;
|
},
|
|
/**
|
* Instantiate a class by its alias.
|
*
|
* {@link Ext.ClassManager#instantiateByAlias} is usually invoked by the shorthand {@link 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.createByAlias('widget.window', { width: 600, height: 800, ... });
|
*
|
* @param {String} alias
|
* @param {Object...} 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];
|
|
|
|
Ext.syncRequire(className);
|
}
|
|
args[0] = className;
|
|
return this.instantiate.apply(this, args);
|
},
|
|
/**
|
* @private
|
*/
|
instantiate: function() {
|
var name = arguments[0],
|
nameType = typeof name,
|
args = arraySlice.call(arguments, 1),
|
alias = name,
|
possibleName, cls;
|
|
if (nameType != 'function') {
|
if (nameType != 'string' && args.length === 0) {
|
args = [name];
|
name = name.xclass;
|
}
|
|
|
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) {
|
|
Ext.syncRequire(name);
|
|
cls = this.get(name);
|
}
|
|
|
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,
|
i,
|
args;
|
|
instantiator = instantiators[length];
|
|
if (!instantiator) {
|
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(',') + ')');
|
}
|
|
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} postprocessors 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 {String[]} classNames
|
*/
|
getNamesByExpression: function(expression) {
|
var nameToAliasesMap = this.maps.nameToAliases,
|
names = [],
|
name, alias, aliases, possibleName, regex, i, ln;
|
|
|
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;
|
}
|
};
|
|
/**
|
* @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 defining widgets by xtype
|
* Ext.widget('panel', {
|
* items: [
|
* {xtype: 'coolpanel', html: 'Foo'},
|
* {xtype: 'coolpanel', html: 'Bar'}
|
* ]
|
* });
|
*
|
* Besides "widget" for xtype there are alias namespaces like "feature" for ftype and "plugin" for ptype.
|
*/
|
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']);
|
|
/**
|
* @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) {
|
|
if (data.singleton) {
|
fn.call(this, name, new cls(), data);
|
}
|
else {
|
return true;
|
}
|
return false;
|
});
|
|
/**
|
* @cfg {String/String[]} alternateClassName
|
* @member Ext.Class
|
* Defines alternate names for this class. For 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];
|
|
|
this.set(alternate, cls);
|
}
|
});
|
|
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 window = Ext.create('widget.window', {
|
* width: 600,
|
* height: 800,
|
* ...
|
* });
|
*
|
* // alternate name
|
* var window = Ext.create('Ext.Window', {
|
* width: 600,
|
* height: 800,
|
* ...
|
* });
|
*
|
* // full class name
|
* var window = Ext.create('Ext.window.Window', {
|
* width: 600,
|
* height: 800,
|
* ...
|
* });
|
*
|
* // single object with xclass property:
|
* var window = Ext.create({
|
* xclass: 'Ext.window.Window', // any valid value for 'name' (above)
|
* width: 600,
|
* height: 800,
|
* ...
|
* });
|
*
|
* @param {String} [name] The class name or alias. Can be specified as `xclass`
|
* property if only one object parameter is specified.
|
* @param {Object...} [args] Additional arguments after the name will be passed to
|
* the class' constructor.
|
* @return {Object} instance
|
* @member Ext
|
* @method create
|
*/
|
create: alias(Manager, 'instantiate'),
|
|
/**
|
* Convenient shorthand to create a widget by its xtype or a config object.
|
* See also {@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')
|
* title: 'Panel'
|
* });
|
*
|
* var grid = Ext.widget({
|
* xtype: 'grid',
|
* ...
|
* });
|
*
|
* If a {@link Ext.Component component} instance is passed, it is simply returned.
|
*
|
* @member Ext
|
* @param {String} [name] The xtype of the widget to create.
|
* @param {Object} [config] The configuration object for the widget constructor.
|
* @return {Object} The widget instance
|
*/
|
widget: function(name, config) {
|
// forms:
|
// 1: (xtype)
|
// 2: (xtype, config)
|
// 3: (config)
|
// 4: (xtype, component)
|
// 5: (component)
|
//
|
var xtype = name,
|
alias, className, T, load;
|
|
if (typeof xtype != 'string') { // if (form 3 or 5)
|
// first arg is config or component
|
config = name; // arguments[0]
|
xtype = config.xtype;
|
} else {
|
config = config || {};
|
}
|
|
if (config.isComponent) {
|
return config;
|
}
|
|
alias = 'widget.' + xtype;
|
className = Manager.getNameByAlias(alias);
|
|
// this is needed to support demand loading of the class
|
if (!className) {
|
load = true;
|
}
|
|
T = Manager.get(className);
|
if (load || !T) {
|
return Manager.instantiateByAlias(alias, config);
|
}
|
return new T(config);
|
},
|
|
/**
|
* @inheritdoc 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) {
|
* alert(s + this.someProperty);
|
* }
|
*
|
* ...
|
* });
|
*
|
* var obj = new My.awesome.Class();
|
*
|
* obj.someMethod('Say '); // alerts 'Say something'
|
*
|
* To create an anonymous class, pass `null` for the `className`:
|
*
|
* Ext.define(null, {
|
* constructor: function () {
|
* // ...
|
* }
|
* });
|
*
|
* In some cases, it is helpful to create a nested scope to contain some private
|
* properties. The best way to do this is to pass a function instead of an object
|
* as the second parameter. This function will be called to produce the class
|
* body:
|
*
|
* Ext.define('MyApp.foo.Bar', function () {
|
* var id = 0;
|
*
|
* return {
|
* nextId: function () {
|
* return ++id;
|
* }
|
* };
|
* });
|
*
|
* _Note_ that when using override, the above syntax will not override successfully, because
|
* the passed function would need to be executed first to determine whether or not the result
|
* is an override or defining a new object. As such, an alternative syntax that immediately
|
* invokes the function can be used:
|
*
|
* Ext.define('MyApp.override.BaseOverride', function () {
|
* var counter = 0;
|
*
|
* return {
|
* override: 'Ext.Component',
|
* logId: function () {
|
* console.log(++counter, this.id);
|
* }
|
* };
|
* }());
|
*
|
*
|
* When using this form of `Ext.define`, the function is passed a reference to its
|
* class. This can be used as an efficient way to access any static properties you
|
* may have:
|
*
|
* Ext.define('MyApp.foo.Bar', function (Bar) {
|
* return {
|
* statics: {
|
* staticMethod: function () {
|
* // ...
|
* }
|
* },
|
*
|
* method: function () {
|
* return Bar.staticMethod();
|
* }
|
* };
|
* });
|
*
|
* To define 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 of overrides 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
|
* Pass `null` to create an anonymous class.
|
* @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 is created, the execution scope of which
|
* (`this`) will be the newly created class itself.
|
* @return {Ext.Base}
|
* @member Ext
|
*/
|
define: function (className, data, createdFn) {
|
|
if (data.override) {
|
return Manager.createOverride.apply(Manager, arguments);
|
}
|
|
return Manager.create.apply(Manager, arguments);
|
},
|
|
/**
|
* Undefines a class defined using the #define method. Typically used
|
* for unit testing where setting up and tearing down a class multiple
|
* times is required. For example:
|
*
|
* // define a class
|
* Ext.define('Foo', {
|
* ...
|
* });
|
*
|
* // run test
|
*
|
* // undefine the class
|
* Ext.undefine('Foo');
|
* @param {String} className The class name to undefine in string dot-namespaced format.
|
* @private
|
*/
|
undefine: function(className) {
|
|
var classes = Manager.classes,
|
maps = Manager.maps,
|
aliasToName = maps.aliasToName,
|
nameToAliases = maps.nameToAliases,
|
alternateToName = maps.alternateToName,
|
nameToAlternates = maps.nameToAlternates,
|
aliases = nameToAliases[className],
|
alternates = nameToAlternates[className],
|
parts, partCount, namespace, i;
|
|
delete Manager.namespaceParseCache[className];
|
delete nameToAliases[className];
|
delete nameToAlternates[className];
|
delete classes[className];
|
|
if (aliases) {
|
for (i = aliases.length; i--;) {
|
delete aliasToName[aliases[i]];
|
}
|
}
|
|
if (alternates) {
|
for (i = alternates.length; i--; ) {
|
delete alternateToName[alternates[i]];
|
}
|
}
|
|
parts = Manager.parseNamespace(className);
|
partCount = parts.length - 1;
|
namespace = parts[0];
|
|
for (i = 1; i < partCount; i++) {
|
namespace = namespace[parts[i]];
|
if (!namespace) {
|
return;
|
}
|
}
|
|
// Old IE blows up on attempt to delete window property
|
try {
|
delete namespace[parts[partCount]];
|
}
|
catch (e) {
|
namespace[parts[partCount]] = undefined;
|
}
|
},
|
|
/**
|
* @inheritdoc Ext.ClassManager#getName
|
* @member Ext
|
* @method getClassName
|
*/
|
getClassName: alias(Manager, 'getName'),
|
|
/**
|
* Returns the displayName property or className or object. When all else fails, returns "Anonymous".
|
* @param {Object} object
|
* @return {String}
|
*/
|
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';
|
},
|
|
/**
|
* @inheritdoc 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.ns('Company.data');
|
*
|
* Company.Widget = function() { ... };
|
*
|
* Company.data.CustomStore = function(config) { ... };
|
*
|
* @param {String...} namespaces
|
* @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 Use {@link Ext#widget} instead.
|
* @method createWidget
|
* @member Ext
|
*/
|
Ext.createWidget = Ext.widget;
|
|
/**
|
* Convenient alias for {@link Ext#namespace Ext.namespace}.
|
* @inheritdoc Ext#namespace
|
* @member Ext
|
* @method ns
|
*/
|
Ext.ns = Ext.namespace;
|
|
Class.registerPreprocessor('className', function(cls, data) {
|
if (data.$className) {
|
cls.$className = data.$className;
|
}
|
|
}, 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];
|
|
|
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];
|
|
|
Ext.Array.include(aliases, widgetPrefix + xtype);
|
}
|
|
data.alias = aliases;
|
|
}, ['xtype', 'alias']);
|
|
}(Ext.Class, Ext.Function.alias, Array.prototype.slice, Ext.Array.from, Ext.global));
|
|
// simple mechanism for automated means of injecting large amounts of dependency info
|
// at the appropriate time in the load cycle
|
if (Ext._alternatesMetadata) {
|
Ext.ClassManager.addNameAlternateMappings(Ext._alternatesMetadata);
|
Ext._alternatesMetadata = null;
|
}
|
|
if (Ext._aliasMetadata) {
|
Ext.ClassManager.addNameAliasMappings(Ext._aliasMetadata);
|
Ext._aliasMetadata = null;
|
}
|
|
// @tag foundation,core
|
// @require ClassManager.js
|
// @define Ext.Loader
|
|
/**
|
* @author Jacky Nguyen <jacky@sencha.com>
|
* @docauthor Jacky Nguyen <jacky@sencha.com>
|
* @class Ext.Loader
|
*
|
* 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:
|
*
|
* # 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.widget('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(...);
|
*
|
* 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
|
*/
|
|
Ext.Loader = new function() {
|
var Loader = this,
|
Manager = Ext.ClassManager,
|
Class = Ext.Class,
|
flexSetter = Ext.Function.flexSetter,
|
alias = Ext.Function.alias,
|
pass = Ext.Function.pass,
|
defer = Ext.Function.defer,
|
arrayErase = Ext.Array.erase,
|
dependencyProperties = ['extend', 'mixins', 'requires'],
|
isInHistory = {},
|
history = [],
|
slashDotSlashRe = /\/\.\//g,
|
dotRe = /\./g,
|
setPathCount = 0;
|
|
Ext.apply(Loader, {
|
|
/**
|
* @private
|
*/
|
isInHistory: isInHistory,
|
|
/**
|
* An array of class names to keep track of the dependency loading order.
|
* This is not guaranteed to be the same everytime due to the asynchronous
|
* nature of the Loader.
|
*
|
* @property {Array} history
|
*/
|
history: history,
|
|
/**
|
* Configuration
|
* @private
|
*/
|
config: {
|
/**
|
* @cfg {Boolean} enabled
|
* Whether or not to enable the dynamic dependency loading feature.
|
*/
|
enabled: false,
|
|
/**
|
* @cfg {Boolean} scriptChainDelay
|
* millisecond delay between asynchronous script injection (prevents stack overflow on some user agents)
|
* 'false' disables delay but potentially increases stack load.
|
*/
|
scriptChainDelay : false,
|
|
/**
|
* @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 {Boolean} garbageCollect
|
* True to prepare an asynchronous script tag for garbage collection (effective only
|
* if {@link #preserveScripts preserveScripts} is false)
|
*/
|
garbageCollect : false,
|
|
/**
|
* @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, <code>Other.awesome.Class</code>
|
* will simply be loaded from <code>./Other/awesome/Class.js</code>
|
*/
|
paths: {
|
'Ext': '.'
|
},
|
|
/**
|
* @cfg {Boolean} preserveScripts
|
* False to remove and optionally {@link #garbageCollect garbage-collect} asynchronously loaded scripts,
|
* True to retain script element for browser debugger compatibility and improved load performance.
|
*/
|
preserveScripts : true,
|
|
/**
|
* @cfg {String} scriptCharset
|
* Optional charset to specify encoding of dynamic script content.
|
*/
|
scriptCharset : undefined
|
},
|
|
/**
|
* 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(Loader.config, name);
|
|
if ('paths' in name) {
|
Ext.app.collectNamespaces(name.paths);
|
}
|
}
|
else {
|
Loader.config[name] = (Ext.isObject(value)) ? Ext.merge(Loader.config[name], value) : value;
|
|
if (name === 'paths') {
|
Ext.app.collectNamespaces(value);
|
}
|
}
|
|
return Loader;
|
},
|
|
/**
|
* 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}
|
*/
|
getConfig: function(name) {
|
if (name) {
|
return Loader.config[name];
|
}
|
|
return Loader.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) {
|
Loader.config.paths[name] = path;
|
Ext.app.namespaces[name] = true;
|
setPathCount++;
|
|
return Loader;
|
}),
|
|
/**
|
* 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 = Loader.config.paths,
|
prefix = Loader.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(slashDotSlashRe, '/') + className.replace(dotRe, "/") + '.js';
|
},
|
|
/**
|
* @private
|
* @param {String} className
|
*/
|
getPrefix: function(className) {
|
var paths = Loader.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;
|
},
|
|
/**
|
* @private
|
* @param {String} className
|
*/
|
isAClassNameWithAKnownPrefix: function(className) {
|
var prefix = Loader.getPrefix(className);
|
|
// we can only say it's really a class if className is not equal to any known namespace
|
return prefix !== '' && prefix !== className;
|
},
|
|
/**
|
* Loads all classes by the given names and all their direct dependencies; optionally executes
|
* the given callback function when finishes, within the optional scope.
|
*
|
* {@link Ext#require} is alias for {@link Ext.Loader#require}.
|
*
|
* @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.
|
*
|
* {@link Ext#syncRequire} is alias for {@link Ext.Loader#syncRequire}.
|
*
|
* @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.*');
|
*
|
* {@link Ext#exclude} is alias for {@link Ext.Loader#exclude}.
|
*
|
* @param {Array} excludes
|
* @return {Object} object contains `require` method for chaining
|
*/
|
exclude: function(excludes) {
|
return {
|
require: function(expressions, fn, scope) {
|
return Loader.require(expressions, fn, scope, excludes);
|
},
|
|
syncRequire: function(expressions, fn, scope) {
|
return Loader.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 (<code>this</code>) 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);
|
}
|
});
|
|
var queue = [],
|
isClassFileLoaded = {},
|
isFileLoaded = {},
|
classNameToFilePathMap = {},
|
scriptElements = {},
|
readyListeners = [],
|
usedClasses = [],
|
requiresMap = {},
|
comparePriority = function(listenerA, listenerB) {
|
return listenerB.priority - listenerA.priority;
|
};
|
|
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: queue,
|
|
/**
|
* Maintain the list of files that have already been handled so that they never get double-loaded
|
* @private
|
*/
|
isClassFileLoaded: isClassFileLoaded,
|
|
/**
|
* @private
|
*/
|
isFileLoaded: isFileLoaded,
|
|
/**
|
* Maintain the list of listeners to execute when all required scripts are fully loaded
|
* @private
|
*/
|
readyListeners: readyListeners,
|
|
/**
|
* Contains classes referenced in `uses` properties.
|
* @private
|
*/
|
optionalRequires: usedClasses,
|
|
/**
|
* Map of fully qualified class names to an array of dependent classes.
|
* @private
|
*/
|
requiresMap: requiresMap,
|
|
/**
|
* @private
|
*/
|
numPendingFiles: 0,
|
|
/**
|
* @private
|
*/
|
numLoadedFiles: 0,
|
|
/** @private */
|
hasFileLoadError: false,
|
|
/**
|
* @private
|
*/
|
classNameToFilePathMap: classNameToFilePathMap,
|
|
/**
|
* The number of scripts loading via loadScript.
|
* @private
|
*/
|
scriptsLoading: 0,
|
|
/**
|
* @private
|
*/
|
syncModeEnabled: false,
|
|
scriptElements: 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 ln = queue.length,
|
i, item, j, requires;
|
|
// When the queue of loading classes reaches zero, trigger readiness
|
|
if (!ln && !Loader.scriptsLoading) {
|
return Loader.triggerReady();
|
}
|
|
for (i = 0; i < ln; i++) {
|
item = queue[i];
|
|
if (item) {
|
requires = item.requires;
|
|
// Don't bother checking when the number of files loaded
|
// is still less than the array length
|
if (requires.length > Loader.numLoadedFiles) {
|
continue;
|
}
|
|
// Remove any required classes that are loaded
|
for (j = 0; j < requires.length; ) {
|
if (Manager.isCreated(requires[j])) {
|
// Take out from the queue
|
arrayErase(requires, j, 1);
|
}
|
else {
|
j++;
|
}
|
}
|
|
// If we've ended up with no required classes, call the callback
|
if (item.requires.length === 0) {
|
arrayErase(queue, i, 1);
|
item.callback.call(item.scope);
|
Loader.refreshQueue();
|
break;
|
}
|
}
|
}
|
|
return Loader;
|
},
|
|
/**
|
* Inject a script element to document's head, call onLoad and onError accordingly
|
* @private
|
*/
|
injectScriptElement: function(url, onLoad, onError, scope, charset) {
|
var script = document.createElement('script'),
|
dispatched = false,
|
config = Loader.config,
|
onLoadFn = function() {
|
|
if(!dispatched) {
|
dispatched = true;
|
script.onload = script.onreadystatechange = script.onerror = null;
|
if (typeof config.scriptChainDelay == 'number') {
|
//free the stack (and defer the next script)
|
defer(onLoad, config.scriptChainDelay, scope);
|
} else {
|
onLoad.call(scope);
|
}
|
Loader.cleanupScriptElement(script, config.preserveScripts === false, config.garbageCollect);
|
}
|
|
},
|
onErrorFn = function(arg) {
|
defer(onError, 1, scope); //free the stack
|
Loader.cleanupScriptElement(script, config.preserveScripts === false, config.garbageCollect);
|
};
|
|
script.type = 'text/javascript';
|
script.onerror = onErrorFn;
|
charset = charset || config.scriptCharset;
|
if (charset) {
|
script.charset = charset;
|
}
|
|
/*
|
* IE9 Standards mode (and others) SHOULD follow the load event only
|
* (Note: IE9 supports both onload AND readystatechange events)
|
*/
|
if ('addEventListener' in script ) {
|
script.onload = onLoadFn;
|
} else if ('readyState' in script) { // for <IE9 Compatability
|
script.onreadystatechange = function() {
|
if ( this.readyState == 'loaded' || this.readyState == 'complete' ) {
|
onLoadFn();
|
}
|
};
|
} else {
|
script.onload = onLoadFn;
|
}
|
|
script.src = url;
|
(Loader.documentHead || document.getElementsByTagName('head')[0]).appendChild(script);
|
|
return script;
|
},
|
|
/**
|
* @private
|
*/
|
removeScriptElement: function(url) {
|
if (scriptElements[url]) {
|
Loader.cleanupScriptElement(scriptElements[url], true, !!Loader.getConfig('garbageCollect'));
|
delete scriptElements[url];
|
}
|
|
return Loader;
|
},
|
|
/**
|
* @private
|
*/
|
cleanupScriptElement: function(script, remove, collect) {
|
var prop;
|
script.onload = script.onreadystatechange = script.onerror = null;
|
if (remove) {
|
Ext.removeNode(script); // Remove, since its useless now
|
if (collect) {
|
for (prop in script) {
|
try {
|
if (prop != 'src') {
|
// If we set the src property to null IE
|
// will try and request a script at './null'
|
script[prop] = null;
|
}
|
delete script[prop]; // and prepare for GC
|
} catch (cleanEx) {
|
//ignore
|
}
|
}
|
}
|
}
|
|
return Loader;
|
},
|
|
/**
|
* Loads the specified script URL and calls the supplied callbacks. If this method
|
* is called before {@link Ext#isReady}, the script's load will delay the transition
|
* to ready. This can be used to load arbitrary scripts that may contain further
|
* {@link Ext#require Ext.require} calls.
|
*
|
* @param {Object/String} options The options object or simply the URL to load.
|
* @param {String} options.url The URL from which to load the script.
|
* @param {Function} [options.onLoad] The callback to call on successful load.
|
* @param {Function} [options.onError] The callback to call on failure to load.
|
* @param {Object} [options.scope] The scope (`this`) for the supplied callbacks.
|
*/
|
loadScript: function (options) {
|
var config = Loader.getConfig(),
|
isString = typeof options == 'string',
|
url = isString ? options : options.url,
|
onError = !isString && options.onError,
|
onLoad = !isString && options.onLoad,
|
scope = !isString && options.scope,
|
onScriptError = function() {
|
Loader.numPendingFiles--;
|
Loader.scriptsLoading--;
|
|
if (onError) {
|
onError.call(scope, "Failed loading '" + url + "', please verify that the file exists");
|
}
|
|
if (Loader.numPendingFiles + Loader.scriptsLoading === 0) {
|
Loader.refreshQueue();
|
}
|
},
|
onScriptLoad = function () {
|
Loader.numPendingFiles--;
|
Loader.scriptsLoading--;
|
|
if (onLoad) {
|
onLoad.call(scope);
|
}
|
|
if (Loader.numPendingFiles + Loader.scriptsLoading === 0) {
|
Loader.refreshQueue();
|
}
|
},
|
src;
|
|
Loader.isLoading = true;
|
Loader.numPendingFiles++;
|
Loader.scriptsLoading++;
|
|
src = config.disableCaching ?
|
(url + '?' + config.disableCachingParam + '=' + Ext.Date.now()) : url;
|
|
scriptElements[url] = Loader.injectScriptElement(src, onScriptLoad, onScriptError);
|
},
|
|
/**
|
* Load a script file, supports both asynchronous and synchronous approaches
|
* @private
|
*/
|
loadScriptFile: function(url, onLoad, onError, scope, synchronous) {
|
if (isFileLoaded[url]) {
|
return Loader;
|
}
|
|
var config = Loader.getConfig(),
|
noCacheUrl = url + (config.disableCaching ? ('?' + config.disableCachingParam + '=' + Ext.Date.now()) : ''),
|
isCrossOriginRestricted = false,
|
xhr, status, onScriptError,
|
debugSourceURL = "";
|
|
scope = scope || Loader;
|
|
Loader.isLoading = true;
|
|
if (!synchronous) {
|
onScriptError = function() {
|
};
|
|
scriptElements[url] = Loader.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) {
|
isCrossOriginRestricted = true;
|
}
|
|
status = (xhr.status === 1223) ? 204 :
|
(xhr.status === 0 && ((self.location || {}).protocol == 'file:' || (self.location || {}).protocol == 'ionp:')) ? 200 : xhr.status;
|
|
isCrossOriginRestricted = isCrossOriginRestricted || (status === 0);
|
|
if (isCrossOriginRestricted
|
) {
|
}
|
else if ((status >= 200 && status < 300) || (status === 304)
|
) {
|
// Debugger friendly, file names are still shown even though they're eval'ed code
|
// Breakpoints work on both Firebug and Chrome's Web Inspector
|
if (!Ext.isIE) {
|
debugSourceURL = "\n//@ sourceURL=" + url;
|
}
|
|
Ext.globalEval(xhr.responseText + debugSourceURL);
|
|
onLoad.call(scope);
|
}
|
else {
|
}
|
|
// Prevent potential IE memory leak
|
xhr = null;
|
}
|
},
|
|
// documented above
|
syncRequire: function() {
|
var syncModeEnabled = Loader.syncModeEnabled;
|
|
if (!syncModeEnabled) {
|
Loader.syncModeEnabled = true;
|
}
|
|
Loader.require.apply(Loader, arguments);
|
|
if (!syncModeEnabled) {
|
Loader.syncModeEnabled = false;
|
}
|
|
Loader.refreshQueue();
|
},
|
|
// documented above
|
require: function(expressions, fn, scope, excludes) {
|
var excluded = {},
|
included = {},
|
excludedClassNames = [],
|
possibleClassNames = [],
|
classNames = [],
|
references = [],
|
callback,
|
syncModeEnabled,
|
filePath, expression, exclude, className,
|
possibleClassName, i, j, ln, subLn;
|
|
if (excludes) {
|
// Convert possible single string to an array.
|
excludes = (typeof excludes === 'string') ? [ excludes ] : 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;
|
}
|
}
|
}
|
}
|
|
// Convert possible single string to an array.
|
expressions = (typeof expressions === 'string') ? [ expressions ] : (expressions ? expressions : []);
|
|
if (fn) {
|
if (fn.length > 0) {
|
callback = function() {
|
var classes = [],
|
i, ln;
|
|
for (i = 0,ln = references.length; i < ln; i++) {
|
classes.push(Manager.get(references[i]));
|
}
|
|
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 (!Loader.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 Loader;
|
}
|
|
syncModeEnabled = Loader.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 = Loader.getPath(className);
|
|
// If we are synchronously loading a file that has already been asychronously 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)) {
|
if (!isClassFileLoaded[className]) {
|
Loader.numPendingFiles--;
|
Loader.removeScriptElement(filePath);
|
delete isClassFileLoaded[className];
|
}
|
}
|
|
if (!isClassFileLoaded.hasOwnProperty(className)) {
|
isClassFileLoaded[className] = false;
|
classNameToFilePathMap[className] = filePath;
|
|
Loader.numPendingFiles++;
|
Loader.loadScriptFile(
|
filePath,
|
pass(Loader.onFileLoaded, [className, filePath], Loader),
|
pass(Loader.onFileLoadError, [className, filePath], Loader),
|
Loader,
|
syncModeEnabled
|
);
|
}
|
}
|
|
if (syncModeEnabled) {
|
callback.call(scope);
|
|
if (ln === 1) {
|
return Manager.get(className);
|
}
|
}
|
|
return Loader;
|
},
|
|
/**
|
* @private
|
* @param {String} className
|
* @param {String} filePath
|
*/
|
onFileLoaded: function(className, filePath) {
|
var loaded = isClassFileLoaded[className];
|
Loader.numLoadedFiles++;
|
|
isClassFileLoaded[className] = true;
|
isFileLoaded[filePath] = true;
|
|
// In FF, when we sync load something that has had a script tag inserted, the load event may
|
// sometimes fire even if we clean it up and set it to null, so check if we're already loaded here.
|
if (!loaded) {
|
Loader.numPendingFiles--;
|
}
|
|
if (Loader.numPendingFiles === 0) {
|
Loader.refreshQueue();
|
}
|
|
},
|
|
/**
|
* @private
|
*/
|
onFileLoadError: function(className, filePath, errorMessage, isSynchronous) {
|
Loader.numPendingFiles--;
|
Loader.hasFileLoadError = true;
|
|
},
|
|
/**
|
* @private
|
* Ensure that any classes referenced in the `uses` property are loaded.
|
*/
|
addUsedClasses: function (classes) {
|
var cls, i, ln;
|
|
if (classes) {
|
classes = (typeof classes == 'string') ? [classes] : classes;
|
for (i = 0, ln = classes.length; i < ln; i++) {
|
cls = classes[i];
|
if (typeof cls == 'string' && !Ext.Array.contains(usedClasses, cls)) {
|
usedClasses.push(cls);
|
}
|
}
|
}
|
|
return Loader;
|
},
|
|
/**
|
* @private
|
*/
|
triggerReady: function() {
|
var listener,
|
refClasses = usedClasses;
|
|
if (Loader.isLoading) {
|
Loader.isLoading = false;
|
|
if (refClasses.length !== 0) {
|
// Clone then empty the array to eliminate potential recursive loop issue
|
refClasses = refClasses.slice();
|
usedClasses.length = 0;
|
// this may immediately call us back if all 'uses' classes
|
// have been loaded
|
Loader.require(refClasses, Loader.triggerReady, Loader);
|
return Loader;
|
}
|
}
|
|
Ext.Array.sort(readyListeners, comparePriority);
|
|
// this method can be called with Loader.isLoading either true or false
|
// (can be called with false when all 'uses' classes are already loaded)
|
// this may bypass the above if condition
|
while (readyListeners.length && !Loader.isLoading) {
|
// calls to refreshQueue may re-enter triggerReady
|
// so we cannot necessarily iterate the readyListeners array
|
listener = readyListeners.shift();
|
listener.fn.call(listener.scope);
|
}
|
|
return Loader;
|
},
|
|
// Documented above already
|
onReady: function(fn, scope, withDomReady, options) {
|
var oldFn;
|
|
if (withDomReady !== false && Ext.onDocumentReady) {
|
oldFn = fn;
|
|
fn = function() {
|
Ext.onDocumentReady(oldFn, scope, options);
|
};
|
}
|
|
if (!Loader.isLoading) {
|
fn.call(scope);
|
}
|
else {
|
readyListeners.push({
|
fn: fn,
|
scope: scope,
|
priority: (options && options.priority) || 0
|
});
|
}
|
},
|
|
/**
|
* @private
|
* @param {String} className
|
*/
|
historyPush: function(className) {
|
if (className && isClassFileLoaded.hasOwnProperty(className) && !isInHistory[className]) {
|
isInHistory[className] = true;
|
history.push(className);
|
}
|
return Loader;
|
}
|
});
|
|
/**
|
* Turns on or off the "cache buster" applied to dynamically loaded scripts. Normally
|
* dynamically loaded scripts have an extra query parameter appended to avoid stale
|
* cached scripts. This method can be used to disable this mechanism, and is primarily
|
* useful for testing. This is done using a cookie.
|
* @param {Boolean} disable True to disable the cache buster.
|
* @param {String} [path="/"] An optional path to scope the cookie.
|
* @private
|
*/
|
Ext.disableCacheBuster = function (disable, path) {
|
var date = new Date();
|
date.setTime(date.getTime() + (disable ? 10*365 : -1) * 24*60*60*1000);
|
date = date.toGMTString();
|
document.cookie = 'ext-cache=1; expires=' + date + '; path='+(path || '/');
|
};
|
|
|
/**
|
* @member Ext
|
* @method require
|
* @inheritdoc Ext.Loader#require
|
*/
|
Ext.require = alias(Loader, 'require');
|
|
/**
|
* @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');
|
|
/**
|
* @member Ext
|
* @method onReady
|
* @ignore
|
*/
|
Ext.onReady = function(fn, scope, options) {
|
Loader.onReady(fn, scope, true, options);
|
};
|
|
/**
|
* @cfg {String[]} requires
|
* @member Ext.Class
|
* List of classes that have to be loaded before instantiating this class.
|
* For example:
|
*
|
* Ext.define('Mother', {
|
* requires: ['Child'],
|
* giveBirth: function() {
|
* // we can be sure that child class is available.
|
* return new Child();
|
* }
|
* });
|
*/
|
Class.registerPreprocessor('loader', function(cls, data, hooks, continueFn) {
|
|
var me = this,
|
dependencies = [],
|
dependency,
|
className = Manager.getName(cls),
|
i, j, ln, subLn, value, propertyName, propertyValue,
|
requiredMap, requiredDep;
|
|
/*
|
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.util.Observable';
|
}
|
}
|
which will later be transformed into:
|
{
|
extend: Ext.MyClass,
|
requires: [Ext.some.OtherClass],
|
mixins: {
|
observable: Ext.util.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;
|
}
|
|
|
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');
|
|
/**
|
* @cfg {String[]} uses
|
* @member Ext.Class
|
* List of optional classes to load together with this class. These aren't neccessarily loaded before
|
* this class is created, but are guaranteed to be available before Ext.onReady listeners are
|
* invoked. For example:
|
*
|
* Ext.define('Mother', {
|
* uses: ['Child'],
|
* giveBirth: function() {
|
* // This code might, or might not work:
|
* // return new Child();
|
*
|
* // Instead use Ext.create() to load the class at the spot if not loaded already:
|
* return Ext.create('Child');
|
* }
|
* });
|
*/
|
Manager.registerPostprocessor('uses', function(name, cls, data) {
|
|
var uses = data.uses;
|
if (uses) {
|
Loader.addUsedClasses(uses);
|
}
|
});
|
|
Manager.onCreated(Loader.historyPush);
|
};
|
|
// simple mechanism for automated means of injecting large amounts of dependency info
|
// at the appropriate time in the load cycle
|
if (Ext._classPathMetadata) {
|
Ext.Loader.addClassPathMappings(Ext._classPathMetadata);
|
Ext._classPathMetadata = null;
|
}
|
|
// initalize the default path of the framework
|
(function() {
|
var scripts = document.getElementsByTagName('script'),
|
currentScript = scripts[scripts.length - 1],
|
src = currentScript.src,
|
path = src.substring(0, src.lastIndexOf('/') + 1),
|
Loader = Ext.Loader;
|
|
|
Loader.setConfig({
|
enabled: true,
|
disableCaching:
|
true,
|
paths: {
|
'Ext': path + 'src'
|
}
|
});
|
})();
|
|
// allows a tools like dynatrace to deterministically detect onReady state by invoking
|
// a callback (intended for external consumption)
|
Ext._endTime = new Date().getTime();
|
if (Ext._beforereadyhandler){
|
Ext._beforereadyhandler();
|
}
|
|
// @tag foundation,core
|
// @require ../class/Loader.js
|
// @define Ext.Error
|
|
/**
|
* @author Brian Moeskau <brian@sencha.com>
|
* @docauthor Brian Moeskau <brian@sencha.com>
|
*
|
* A wrapper class for the native JavaScript Error object that adds a few useful capabilities for handling
|
* errors in an Ext application. When you use Ext.Error to {@link #raise} an error from within any class that
|
* uses the Ext 4 class system, the Error class can automatically add the source class and method from which
|
* the error was raised. It also includes logic to automatically log the error to the console, if available,
|
* with additional metadata about the error. In all cases, the error will always be thrown at the end so that
|
* execution will halt.
|
*
|
* Ext.Error also offers a global error {@link #handle handling} method that can be overridden in order to
|
* handle application-wide errors in a single spot. You can optionally {@link #ignore} errors altogether,
|
* although in a real application it's usually a better idea to override the handling function and perform
|
* logging or some other method of reporting the errors in a way that is meaningful to the application.
|
*
|
* At its simplest you can simply raise an error as a simple string from within any code:
|
*
|
* Example usage:
|
*
|
* Ext.Error.raise('Something bad happened!');
|
*
|
* If raised from plain JavaScript code, the error will be logged to the console (if available) and the message
|
* displayed. In most cases however you'll be raising errors from within a class, and it may often be useful to add
|
* additional metadata about the error being raised. The {@link #raise} method can also take a config object.
|
* In this form the `msg` attribute becomes the error description, and any other data added to the config gets
|
* added to the error object and, if the console is available, logged to the console for inspection.
|
*
|
* Example usage:
|
*
|
* Ext.define('Ext.Foo', {
|
* doSomething: function(option){
|
* if (someCondition === false) {
|
* Ext.Error.raise({
|
* msg: 'You cannot do that!',
|
* option: option, // whatever was passed into the method
|
* 'error code': 100 // other arbitrary info
|
* });
|
* }
|
* }
|
* });
|
*
|
* If a console is available (that supports the `console.dir` function) you'll see console output like:
|
*
|
* An error was raised with the following data:
|
* option: Object { foo: "bar"}
|
* foo: "bar"
|
* error code: 100
|
* msg: "You cannot do that!"
|
* sourceClass: "Ext.Foo"
|
* sourceMethod: "doSomething"
|
*
|
* uncaught exception: You cannot do that!
|
*
|
* As you can see, the error will report exactly where it was raised and will include as much information as the
|
* raising code can usefully provide.
|
*
|
* If you want to handle all application errors globally you can simply override the static {@link #handle} method
|
* and provide whatever handling logic you need. If the method returns true then the error is considered handled
|
* and will not be thrown to the browser. If anything but true is returned then the error will be thrown normally.
|
*
|
* Example usage:
|
*
|
* Ext.Error.handle = function(err) {
|
* if (err.someProperty == 'NotReallyAnError') {
|
* // maybe log something to the application here if applicable
|
* return true;
|
* }
|
* // any non-true return value (including none) will cause the error to be thrown
|
* }
|
*
|
*/
|
Ext.Error = Ext.extend(Error, {
|
statics: {
|
/**
|
* @property {Boolean} ignore
|
* Static flag that can be used to globally disable error reporting to the browser if set to true
|
* (defaults to false). Note that if you ignore Ext errors it's likely that some other code may fail
|
* and throw a native JavaScript error thereafter, so use with caution. In most cases it will probably
|
* be preferable to supply a custom error {@link #handle handling} function instead.
|
*
|
* Example usage:
|
*
|
* Ext.Error.ignore = true;
|
*
|
* @static
|
*/
|
ignore: false,
|
|
/**
|
* @property {Boolean} notify
|
* Static flag that can be used to globally control error notification to the user. Unlike
|
* Ex.Error.ignore, this does not effect exceptions. They are still thrown. This value can be
|
* set to false to disable the alert notification (default is true for IE6 and IE7).
|
*
|
* Only the first error will generate an alert. Internally this flag is set to false when the
|
* first error occurs prior to displaying the alert.
|
*
|
* This flag is not used in a release build.
|
*
|
* Example usage:
|
*
|
* Ext.Error.notify = false;
|
*
|
* @static
|
*/
|
//notify: Ext.isIE6 || Ext.isIE7,
|
|
/**
|
* Raise an error that can include additional data and supports automatic console logging if available.
|
* You can pass a string error message or an object with the `msg` attribute which will be used as the
|
* error message. The object can contain any other name-value attributes (or objects) to be logged
|
* along with the error.
|
*
|
* Note that after displaying the error message a JavaScript error will ultimately be thrown so that
|
* execution will halt.
|
*
|
* Example usage:
|
*
|
* Ext.Error.raise('A simple string error message');
|
*
|
* // or...
|
*
|
* Ext.define('Ext.Foo', {
|
* doSomething: function(option){
|
* if (someCondition === false) {
|
* Ext.Error.raise({
|
* msg: 'You cannot do that!',
|
* option: option, // whatever was passed into the method
|
* 'error code': 100 // other arbitrary info
|
* });
|
* }
|
* }
|
* });
|
*
|
* @param {String/Object} err The error message string, or an object containing the attribute "msg" that will be
|
* used as the error message. Any other data included in the object will also be logged to the browser console,
|
* if available.
|
* @static
|
*/
|
raise: function(err){
|
err = err || {};
|
if (Ext.isString(err)) {
|
err = { msg: err };
|
}
|
|
var method = this.raise.caller,
|
msg;
|
|
if (method) {
|
if (method.$name) {
|
err.sourceMethod = method.$name;
|
}
|
if (method.$owner) {
|
err.sourceClass = method.$owner.$className;
|
}
|
}
|
|
if (Ext.Error.handle(err) !== true) {
|
msg = Ext.Error.prototype.toString.call(err);
|
|
Ext.log({
|
msg: msg,
|
level: 'error',
|
dump: err,
|
stack: true
|
});
|
|
throw new Ext.Error(err);
|
}
|
},
|
|
/**
|
* Globally handle any Ext errors that may be raised, optionally providing custom logic to
|
* handle different errors individually. Return true from the function to bypass throwing the
|
* error to the browser, otherwise the error will be thrown and execution will halt.
|
*
|
* Example usage:
|
*
|
* Ext.Error.handle = function(err) {
|
* if (err.someProperty == 'NotReallyAnError') {
|
* // maybe log something to the application here if applicable
|
* return true;
|
* }
|
* // any non-true return value (including none) will cause the error to be thrown
|
* }
|
*
|
* @param {Ext.Error} err The Ext.Error object being raised. It will contain any attributes that were originally
|
* raised with it, plus properties about the method and class from which the error originated (if raised from a
|
* class that uses the Ext 4 class system).
|
* @static
|
*/
|
handle: function(){
|
return Ext.Error.ignore;
|
}
|
},
|
|
// This is the standard property that is the name of the constructor.
|
name: 'Ext.Error',
|
|
/**
|
* Creates new Error object.
|
* @param {String/Object} config The error message string, or an object containing the
|
* attribute "msg" that will be used as the error message. Any other data included in
|
* the object will be applied to the error instance and logged to the browser console, if available.
|
*/
|
constructor: function(config){
|
if (Ext.isString(config)) {
|
config = { msg: config };
|
}
|
|
var me = this;
|
|
Ext.apply(me, config);
|
|
me.message = me.message || me.msg; // 'message' is standard ('msg' is non-standard)
|
// note: the above does not work in old WebKit (me.message is readonly) (Safari 4)
|
},
|
|
/**
|
* Provides a custom string representation of the error object. This is an override of the base JavaScript
|
* `Object.toString` method, which is useful so that when logged to the browser console, an error object will
|
* be displayed with a useful message instead of `[object Object]`, the default `toString` result.
|
*
|
* The default implementation will include the error message along with the raising class and method, if available,
|
* but this can be overridden with a custom implementation either at the prototype level (for all errors) or on
|
* a particular error instance, if you want to provide a custom description that will show up in the console.
|
* @return {String} The error message. If raised from within the Ext 4 class system, the error message will also
|
* include the raising class and method names, if available.
|
*/
|
toString: function(){
|
var me = this,
|
className = me.sourceClass ? me.sourceClass : '',
|
methodName = me.sourceMethod ? '.' + me.sourceMethod + '(): ' : '',
|
msg = me.msg || '(No description provided)';
|
|
return className + methodName + msg;
|
}
|
});
|
|
/*
|
* Create a function that will throw an error if called (in debug mode) with a message that
|
* indicates the method has been removed.
|
* @param {String} suggestion Optional text to include in the message (a workaround perhaps).
|
* @return {Function} The generated function.
|
* @private
|
*/
|
Ext.deprecated = function (suggestion) {
|
return Ext.emptyFn;
|
};
|
|
/*
|
* This mechanism is used to notify the user of the first error encountered on the page. This
|
* was previously internal to Ext.Error.raise and is a desirable feature since errors often
|
* slip silently under the radar. It cannot live in Ext.Error.raise since there are times
|
* where exceptions are handled in a try/catch.
|
*/
|