extjs-gpl
Version:
GPL licensed version of Sencha Ext JS
1,474 lines (1,320 loc) • 73.4 kB
JavaScript
// @tag class
/**
* @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("For he's a jolly good fellow...")
* }
* });
*
* 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("For he's a jolly good fellow...");
* // 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.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
*/
Ext.ClassManager = (function(Class, alias, arraySlice, arrayFrom, global) {
// @define Ext.ClassManager
// @require Ext.Inventory
// @require Ext.Class
// @require Ext.Function
// @require Ext.Array
var makeCtor = Ext.Class.makeCtor,
//<if nonBrowser>
isNonBrowser = typeof window === 'undefined',
//</if>
nameLookupStack = [],
namespaceCache = {
Ext: {
name: 'Ext',
value: Ext // specially added for sandbox (Ext === global.Ext6)
}
/*
'Ext.grid': {
name: 'grid',
parent: namespaceCache['Ext']
},
'Ext.grid.Panel': {
name: 'Panel',
parent: namespaceCache['Ext.grid']
},
...
Also,
'MyApp': {
name: 'MyApp',
value: MyApp
}
*/
},
Manager = Ext.apply(new Ext.Inventory(), {
/**
* @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: {},
classState: {
/*
* 'Ext.foo.Bar': <state enum>
*
* 10 = Ext.define called
* 20 = Ext.define/override called
* 30 = Manager.existCache[<name>] == true for define
* 40 = Manager.existCache[<name>] == true for define/override
* 50 = Manager.isCreated(<name>) == true for define
* 60 = Manager.isCreated(<name>) == true for define/override
*
*/
},
/**
* @private
*/
existCache: {},
/** @private */
instantiators: [],
/**
* Checks if a class has already been created.
*
* @param {String} className
* @return {Boolean} exist
*/
isCreated: function(className) {
//<debug>
if (typeof className !== 'string' || className.length < 1) {
throw new Error("[Ext.ClassManager] Invalid classname, must be a string and must not be empty");
}
//</debug>
if (Manager.classes[className] || Manager.existCache[className]) {
return true;
}
if (!Manager.lookupName(className, false)) {
return false;
}
Manager.triggerCreated(className);
return true;
},
/**
* @private
*/
createdListeners: [],
/**
* @private
*/
nameCreatedListeners: {},
/**
* @private
*/
existsListeners: [],
/**
* @private
*/
nameExistsListeners: {},
/**
* @private
*/
overrideMap: {},
/**
* @private
*/
triggerCreated: function (className, state) {
Manager.existCache[className] = state || 1;
Manager.classState[className] += 40;
Manager.notify(className, Manager.createdListeners, Manager.nameCreatedListeners);
},
/**
* @private
*/
onCreated: function(fn, scope, className) {
Manager.addListener(fn, scope, className, Manager.createdListeners, Manager.nameCreatedListeners);
},
/**
* @private
*/
notify: function (className, listeners, nameListeners) {
var alternateNames = Manager.getAlternatesByName(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);
}
while (names) {
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];
}
}
names = alternateNames; // for 2nd pass (if needed)
alternateNames = null; // no 3rd pass
}
},
/**
* @private
*/
addListener: function(fn, scope, className, listeners, nameListeners) {
if (Ext.isArray(className)) {
fn = Ext.Function.createBarrier(className.length, fn, scope);
for (i = 0; i < className.length; i++) {
this.addListener(fn, null, className[i], listeners, nameListeners);
}
return;
}
var i,
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
*/
$namespaceCache: namespaceCache,
/**
* See `{@link Ext#addRootNamespaces Ext.addRootNamespaces}`.
* @since 6.0.0
* @private
*/
addRootNamespaces: function (namespaces) {
for (var name in namespaces) {
namespaceCache[name] = {
name: name,
value: namespaces[name]
};
}
},
/**
* Clears the namespace lookup cache. After application launch, this cache can
* often contain several hundred entries that are unlikely to be needed again.
* These will be rebuilt as needed, so it is harmless to clear this cache even
* if its results will be used again.
* @since 6.0.0
* @private
*/
clearNamespaceCache: function () {
nameLookupStack.length = 0;
for (var name in namespaceCache) {
if (!namespaceCache[name].value) {
delete namespaceCache[name];
}
}
},
/**
* Return the namespace cache entry for the given a class name or namespace (e.g.,
* "Ext.grid.Panel").
*
* @param {String} namespace The namespace or class name to lookup.
* @return {Object} The cache entry.
* @return {String} return.name The leaf name ("Panel" for "Ext.grid.Panel").
* @return {Object} return.parent The entry of the parent namespace (i.e., "Ext.grid").
* @return {Object} return.value The namespace object. This is only set for
* top-level namespace entries to support renaming them for sandboxing ("Ext6" vs
* "Ext").
* @since 6.0.0
* @private
*/
getNamespaceEntry: function (namespace) {
if (typeof namespace !== 'string') {
return namespace; // assume we've been given an entry object
}
var entry = namespaceCache[namespace],
i;
if (!entry) {
i = namespace.lastIndexOf('.');
if (i < 0) {
entry = {
name: namespace
};
} else {
entry = {
name: namespace.substring(i + 1),
parent: Manager.getNamespaceEntry(namespace.substring(0, i))
};
}
namespaceCache[namespace] = entry;
}
return entry;
},
/**
* Return the value of the given "dot path" name. This supports remapping (for use
* in sandbox builds) as well as auto-creating of namespaces.
*
* @param {String} namespace The name of the namespace or class.
* @param {Boolean} [autoCreate] Pass `true` to create objects for undefined names.
* @return {Object} The object that is the namespace or class name.
* @since 6.0.0
* @private
*/
lookupName: function (namespace, autoCreate) {
var entry = Manager.getNamespaceEntry(namespace),
scope = Ext.global,
i = 0,
e, parent;
// Put entries on the stack in reverse order: [ 'Panel', 'grid', 'Ext' ]
for (e = entry; e; e = e.parent) {
// since we process only what we add to the array, and that always
// starts at index=0, we don't need to clean up the array (that would
// just encourage the GC to do something pointless).
nameLookupStack[i++] = e;
}
while (scope && i-- > 0) {
// We'll process entries in top-down order ('Ext', 'grid' then 'Panel').
e = nameLookupStack[i];
parent = scope;
scope = e.value || scope[e.name];
if (!scope && autoCreate) {
parent[e.name] = scope = {};
}
}
return scope;
},
/**
* 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 (namespace, value) {
var entry = Manager.getNamespaceEntry(namespace),
scope = Ext.global;
if (entry.parent) {
scope = Manager.lookupName(entry.parent, true);
}
scope[entry.name] = value;
return value;
},
/**
* Changes the mapping of an `xtype` to map to the specified component class.
* @param {String/Ext.Class} cls The class or class name to which `xtype` is mapped.
* @param {String} xtype The `xtype` to map or redefine as `cls`.
* @since 6.0.1
* @private
*/
setXType: function (cls, xtype) {
var className = cls.$className,
C = className ? cls : Manager.get(className = cls),
proto = C.prototype,
xtypes = proto.xtypes,
xtypesChain = proto.xtypesChain,
xtypesMap = proto.xtypesMap;
if (!proto.hasOwnProperty('xtypes')) {
proto.xtypes = xtypes = [];
proto.xtypesChain = xtypesChain = xtypesChain ? xtypesChain.slice(0) : [];
proto.xtypesMap = xtypesMap = Ext.apply({}, xtypesMap);
}
Manager.addAlias(className, 'widget.' + xtype, true);
xtypes.push(xtype);
xtypesChain.push(xtype);
xtypesMap[xtype] = true;
//TODO consider updating derived class xtypesChain / xtypesMap
},
/**
* Sets a name reference to a class.
*
* @param {String} name
* @param {Object} value
* @return {Ext.ClassManager} this
*/
set: function (name, value) {
var targetName = Manager.getName(value);
Manager.classes[name] = Manager.setNamespace(name, value);
if (targetName && targetName !== name) {
Manager.addAlternate(targetName, name);
}
return Manager;
},
/**
* Retrieve a class by its name.
*
* @param {String} name
* @return {Ext.Class} class
*/
get: function(name) {
return Manager.classes[name] || Manager.lookupName(name, false);
},
/**
* Adds a batch of class name to alias mappings.
* @param {Object} aliases The set of mappings of the form.
* className : [values...]
*/
addNameAliasMappings: function(aliases) {
Manager.addAlias(aliases);
},
/**
*
* @param {Object} alternates The set of mappings of the form
* className : [values...]
*/
addNameAlternateMappings: function (alternates) {
Manager.addAlternate(alternates);
},
/**
* Get a reference to the class by its alias.
*
* @param {String} alias
* @return {Ext.Class} class
*/
getByAlias: function(alias) {
return Manager.get(Manager.getNameByAlias(alias));
},
/**
* Get a component class name from a config object.
* @param {Object} config The config object.
* @param {String} [aliasPrefix] A prefix to use when getting
* a class name by alias.
* @return {Ext.Class} The class.
*
* @private
*/
getByConfig: function(config, aliasPrefix) {
var xclass = config.xclass,
name;
if (xclass) {
name = xclass;
} else {
name = config.xtype;
if (name) {
aliasPrefix = 'widget.';
} else {
name = config.type;
}
name = Manager.getNameByAlias(aliasPrefix + name);
}
return Manager.get(name);
},
/**
* Get the name of the class by its reference or its instance. This is
* usually invoked by the shorthand {@link Ext#getClassName}.
*
* Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action"
*
* @param {Ext.Class/Object} object
* @return {String} className
*/
getName: function(object) {
return object && object.$className || '';
},
/**
* Get the class of the provided object; returns null if it's not an instance
* of any class created with Ext.define. This is usually invoked by the
* shorthand {@link Ext#getClass}.
*
* 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 Use {@link Ext#define} instead, as that also supports creating overrides.
* @private
*/
create: function(className, data, createdFn) {
//<debug>
if (className != null && typeof className !== 'string') {
throw new Error("[Ext.define] Invalid class name '" + className + "' specified, must be a non-empty string");
}
//</debug>
var ctor = makeCtor(className);
if (typeof data === 'function') {
data = data(ctor);
}
//<debug>
if (className) {
if (Manager.classes[className]) {
Ext.log.warn("[Ext.define] Duplicate class name '" + className + "' specified, must be a non-empty string");
}
ctor.name = className;
}
//</debug>
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) {
//<debug>
Ext.classSystemMonitor && Ext.classSystemMonitor(className, 'Ext.ClassManager#classCreated', arguments);
//</debug>
if (className) {
me.set(className, cls);
}
delete cls._classHooks;
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,
mixins = data.mixins,
mixinsIsArray,
compat = 1, // default if 'compatibility' is not specified
dependenciesLoaded,
classReady = function () {
var cls, dependencies, i, key, temp;
if (!dependenciesLoaded) {
dependencies = requires ? requires.slice(0) : [];
if (mixins) {
if (!(mixinsIsArray = mixins instanceof Array)) {
for (key in mixins) {
if (Ext.isString(cls = mixins[key])) {
dependencies.push(cls);
}
}
} else {
for (i = 0, temp = mixins.length; i < temp; ++i) {
if (Ext.isString(cls = mixins[i])) {
dependencies.push(cls);
}
}
}
}
dependenciesLoaded = true;
if (dependencies.length) {
// 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.require(dependencies, classReady);
return;
}
// else we have no dependencies, so proceed
}
// transform mixin class names into class references, This
// loop can handle both the array and object forms of
// mixin definitions
if (mixinsIsArray) {
for (i = 0, temp = mixins.length; i < temp; ++i) {
if (Ext.isString(cls = mixins[i])) {
mixins[i] = Ext.ClassManager.get(cls);
}
}
} else if (mixins) {
for (key in mixins) {
if (Ext.isString(cls = mixins[key])) {
mixins[key] = Ext.ClassManager.get(cls);
}
}
}
// The target class and the required classes for this override are
// ready, so we can apply the override now:
cls = overriddenClassName.$isClass ? overriddenClassName : me.get(overriddenClassName);
// We don't want to apply these:
delete data.override;
delete data.compatibility;
delete data.requires;
delete data.uses;
Ext.override(cls, data);
// This pushes the overriding file itself into Ext.Loader.history
// Hence if the target class never exists, the overriding file will
// never be included in the build.
Ext.Loader.history.push(className);
if (uses) {
// This "hides" from the Cmd auto-dependency scanner since
// the reference is circular (Loader requires us).
Ext['Loader'].addUsedClasses(uses); // get these classes too!
}
if (createdFn) {
createdFn.call(cls, cls); // last but not least!
}
};
if (className) {
Manager.overrideMap[className] = true;
}
// If specified, parse strings as versions, but otherwise treat as a
// boolean (maybe "compatibility: Ext.isIE8" or something).
//
if ('compatibility' in data) {
compat = data.compatibility;
if (!compat) {
// Cast '', null, undefined, 0 to false.
compat = false;
} else if (typeof compat === 'number') {
// By virtue of the condition above we must be a nonzero number.
compat = true;
} else if (typeof compat !== 'boolean') {
compat = Ext.checkVersion(compat);
}
}
if (compat) {
// override the target class right after it's created
if (overriddenClassName.$isClass) {
classReady();
} else {
me.onCreated(classReady, me, overriddenClassName);
}
}
me.triggerCreated(className, 2);
return me;
},
/**
* Instantiate a class by its alias. This 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);
//<debug>
if (!className) {
throw new Error("[Ext.createByAlias] Unrecognized alias: " + alias);
}
//</debug>
args[0] = className;
return Ext.create.apply(Ext, args);
},
//<deprecated since=5.0>
/**
* Instantiate a class by either full name, alias or alternate name
* @param {String} name
* @param {Mixed} args Additional arguments after the name will be passed to the class' constructor.
* @return {Object} instance
* @deprecated 5.0 Use Ext.create() instead.
*/
instantiate: function() {
//<debug>
Ext.log.warn('Ext.ClassManager.instantiate() is deprecated. Use Ext.create() instead.');
//</debug>
return Ext.create.apply(Ext, arguments);
},
//</deprecated>
/**
* @private
* @param name
* @param args
*/
dynInstantiate: function(name, args) {
args = arrayFrom(args, true);
args.unshift(name);
return Ext.create.apply(Ext, 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(',') + ')');
//<debug>
instantiator.name = "Ext.create" + length;
//</debug>
}
return instantiator;
},
/**
* @private
*/
postprocessors: {},
/**
* @private
*/
defaultPostprocessors: [],
/**
* Register a post-processor function.
*
* @private
* @param {String} name
* @param {Function} postprocessor
*/
registerPostprocessor: function(name, fn, properties, position, relativeTo) {
if (!position) {
position = 'last';
}
if (!properties) {
properties = [name];
}
this.postprocessors[name] = {
name: name,
properties: properties || false,
fn: fn
};
this.setDefaultPostprocessorPosition(name, position, relativeTo);
return this;
},
/**
* Set the default post processors array stack which are applied to every class.
*
* @private
* @param {String/Array} 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;
}
});
/**
* @cfg xtype
* @member Ext.Class
* @inheritdoc Ext.Component#cfg-xtype
*/
/**
* @cfg {String} override
* @member Ext.Class
* Overrides members of the specified `target` class.
*
* **NOTE:** the overridden class must have been defined using
* {@link Ext#define Ext.define} in order to use the `override` config.
*
* Methods defined on the overriding class will not automatically call the methods of
* the same name in the ancestor class chain. To call the parent's method of the
* same name you must call {@link Ext.Base#callParent callParent}. To skip the
* method of the overridden class and call its parent you will instead call
* {@link Ext.Base#callSuper callSuper}.
*
* See {@link Ext#define Ext.define} for additional usage examples.
*/
//<feature classSystem.alias>
/**
* @cfg {String/String[]} alias
* @member Ext.Class
* List of short aliases for class names. An alias consists of a namespace and a name
* concatenated by a period as <namespace>.<name>
*
* - **namespace** - The namespace describes what kind of alias this is and must be
* all lowercase.
* - **name** - The name of the alias which allows the lazy-instantiation via the
* alias. The name shouldn't contain any periods.
*
* A list of namespaces and the usages are:
*
* - **feature** - {@link Ext.grid.Panel Grid} features
* - **plugin** - Plugins
* - **store** - {@link Ext.data.Store}
* - **widget** - Components
*
* 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'}
* ]
* });
*/
Manager.registerPostprocessor('alias', function(name, cls, data) {
//<debug>
Ext.classSystemMonitor && Ext.classSystemMonitor(name, 'Ext.ClassManager#aliasPostProcessor', arguments);
//</debug>
var aliases = Ext.Array.from(data.alias),
i, ln;
for (i = 0,ln = aliases.length; i < ln; i++) {
alias = aliases[i];
this.addAlias(cls, alias);
}
}, ['xtype', 'alias']);
//</feature>
//<feature classSystem.singleton>
/**
* @cfg {Boolean} singleton
* @member Ext.Class
* When set to true, the class will be instantiated as singleton. For example:
*
* Ext.define('Logger', {
* singleton: true,
* log: function(msg) {
* console.log(msg);
* }
* });
*
* Logger.log('Hello');
*/
Manager.registerPostprocessor('singleton', function(name, cls, data, fn) {
//<debug>
Ext.classSystemMonitor && Ext.classSystemMonitor(name, 'Ext.ClassManager#singletonPostProcessor', arguments);
//</debug>
if (data.singleton) {
fn.call(this, name, new cls(), data);
}
else {
return true;
}
return false;
});
//</feature>
//<feature classSystem.alternateClassName>
/**
* @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) {
//<debug>
Ext.classSystemMonitor && Ext.classSystemMonitor(name, 'Ext.ClassManager#alternateClassNamePostprocessor', arguments);
//</debug>
var alternates = data.alternateClassName,
i, ln, alternate;
if (!(alternates instanceof Array)) {
alternates = [alternates];
}
for (i = 0, ln = alternates.length; i < ln; i++) {
alternate = alternates[i];
//<debug>
if (typeof alternate !== 'string') {
throw new Error("[Ext.define] Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string");
}
//</debug>
this.set(alternate, cls);
}
});
//</feature>
/**
* @cfg {Object} debugHooks
* A collection of diagnostic methods to decorate the real methods of the class. These
* methods are applied as an `override` if this class has debug enabled as defined by
* `Ext.isDebugEnabled`.
*
* These will be automatically removed by the Sencha Cmd compiler for production builds.
*
* Example usage:
*
* Ext.define('Foo.bar.Class', {
* foo: function (a, b, c) {
* ...
* },
*
* bar: function (a, b) {
* ...
* return 42;
* },
*
* debugHooks: {
* foo: function (a, b, c) {
* // check arguments...
* return this.callParent(arguments);
* }
* }
* });
*
* If you specify a `$enabled` property in the `debugHooks` object that will be used
* as the default enabled state for the hooks. If the `{@link Ext#manifest}` contains
* a `debug` object of if `{@link Ext#debugConfig}` is specified, the `$enabled` flag
* will override its "*" value.
*/
Manager.registerPostprocessor('debugHooks', function(name, Class, data) {
//<debug>
Ext.classSystemMonitor && Ext.classSystemMonitor(Class, 'Ext.Class#debugHooks', arguments);
if (Ext.isDebugEnabled(Class.$className, data.debugHooks.$enabled)) {
delete data.debugHooks.$enabled;
Ext.override(Class, data.debugHooks);
}
//</debug>
// may already have an instance here in the case of singleton
var target = Class.isInstance ? Class.self : Class;
delete target.prototype.debugHooks;
});
/**
* @cfg {Object} deprecated
* The object given has properties that describe the versions at which the deprecations
* apply.
*
* The purpose of the `deprecated` declaration is to enable development mode to give
* suitable error messages when deprecated methods or properties are used. Methods can
* always be injected to provide this feedback, but properties can only be handled on
* some browsers (those that support `Object.defineProperty`).
*
* In some cases, deprecated methods can be restored to their previous behavior or
* added back if they have been removed.
*
* The structure of a `deprecated` declaration is this:
*
* Ext.define('Foo.bar.Class', {
* ...
*
* deprecated: {
* // Optional package name - default is the framework (ext or touch)
* name: 'foobar',
*
* '5.0': {
* methods: {
* // Throws: '"removedMethod" is deprecated.'
* removedMethod: null,
*
* // Throws: '"oldMethod" is deprecated. Please use "newMethod" instead.'
* oldMethod: 'newMethod',
*
* // When this block is enabled, this method is applied as an
* // override. Otherwise you get same as "removeMethod".
* method: function () {
* // Do what v5 "method" did. If "method" exists in newer
* // versions callParent can call it. If 5.1 has "method"
* // then it would be next in line, otherwise 5.2 and last
* // would be the current class.
* },
*
* moreHelpful: {
* message: 'Something helpful to do instead.',
* fn: function () {
* // The v5 "moreHelpful" method to use when enabled.
* }
* }
* },
* properties: {
* // Throws: '"removedProp" is deprecated.'
* removedProp: null,
*
* // Throws: '"oldProp" is deprecated. Please use "newProp" instead.'
* oldProp: 'newProp',
*
* helpful: {
* message: 'Something helpful message about what to do.'
* }
* ...
* },
* statics: {
* methods: {
* ...
* },
* properties: {
* ...
* },
* }
* },
*
* '5.1': {
* ...
* },
*
* '5.2': {
* ...
* }
* }
* });
*
* The primary content of `deprecated` are the version number keys. These indicate
* a version number where methods or properties were deprecated. These versions are
* compared to the version reported by `Ext.getCompatVersion` to determine the action
* to take for each "block".
*
* When the compatibility version is set to a value less than a version number key,
* that block is said to be "enabled". For example, if a method was deprecated in
* version 5.0 but the desired compatibility level is 4.2 then the block is used to
* patch methods and (to some degree) restore pre-5.0 compatibility.
*
* When multiple active blocks have the same method name, each method is applied as
* an override in reverse order of version. In the above example, if a method appears
* in the "5.0", "5.1" and "5.2" blocks then the "5.2" method is applied as an override
* first, followed by the "5.1" method and finally the "5.0" method. This means that
* the `callParent` from the "5.0" method calls the "5.1" method which calls the
* "5.2" method which can (if applicable) call the current version.
*/
Manager.registerPostprocessor('deprecated', function(name, Class, data) {
//<debug>
Ext.classSystemMonitor && Ext.classSystemMonitor(Class, 'Ext.Class#deprecated', arguments);
//</debug>
// may already have an instance here in the case of singleton
var target = Class.isInstance ? Class.self : Class;
target.addDeprecations(data.deprecated);
delete target.prototype.deprecated;
});
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:
*
* // xtype
* var window = Ext.create({
* xtype: 'window',
* width: 600,
* height: 800,
* ...
* });
*
* // 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: function () {
var name = arguments[0],
nameType = typeof name,
args = arraySlice.call(arguments, 1),
cls;
if (nameType === 'function') {
cls = name;
} else {
if (nameType !== 'string' && args.length === 0) {
args = [name];
if (!(name = name.xclass)) {
name = args[0].xtype;
if (name) {
name = 'widget.' + name;
}
}
}
//<debug>
if (typeof name !== 'string' || name.length < 1) {
throw new Error("[Ext.create] Invalid class name or alias '" + name +
"' specified, must be a non-empty string");
}
//</debug>
name = Manager.resolveName(name);
cls = Manager.get(name);
}
// Still not existing at this point, try to load it via synchronous mode as the last resort
if (!cls) {
//<debug>
//<if nonBrowser>
!isNonBrowser &&
//</if>
Ext.log.warn("[Ext.Loader] Synchronously loading '" + name + "'; consider adding " +