// @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} namespace * @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 4.1 Use {@link Ext#define} instead. * @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. * * @param {String} name * @param {Function} fn * @param {String/String[]} properties * @param {String} position * @param {String} relativeTo * @private */ 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 {String} 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 " + "Ext.require('" + name + "') above Ext.onReady"); //</debug> Ext.syncRequire(name); cls = Manager.get(name); } //<debug> if (!cls) { throw new Error("[Ext.create] Unrecognized class name / alias: " + name); } if (typeof cls !== 'function') { throw new Error("[Ext.create] Singleton '" + name + "' cannot be instantiated."); } //</debug> return Manager.getInstantiator(args.length)(cls, args); }, /** * Convenient shorthand to create a widget by its xtype or a config object. * * 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; if (typeof xtype !== 'string') { // if (form 3 or 5) // first arg is config or component config = name; // arguments[0] xtype = config.xtype; className = config.xclass; } else { config = config || {}; } if (config.isComponent) { return config; } if (!className) { alias = 'widget.' + xtype; className = Manager.getNameByAlias(alias); } // this is needed to support demand loading of the class if (className) { T = Manager.get(className); } if (!T) { return Ext.create(className || 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, inheritableStatics, or privates: * * Ext.define('My.app.BarMod', { * override: 'Ext.foo.Bar', * * statics: { * method: function (x) { * return this.callParent([x * 2]); // call Ext.foo.Bar.method * } * } * }); * * Starting in version 4.2.2, overrides can declare their `compatibility` based * on the framework version or on versions of other packages. For details on the * syntax and options for these checks, see `Ext.checkVersion`. * * The simplest use case is to test framework version for compatibility: * * Ext.define('App.overrides.grid.Panel', { * override: 'Ext.grid.Panel', * * compatibility: '4.2.2', // only if framework version is 4.2.2 * * //... * }); * * An array is treated as an OR, so if any specs match, the override is * compatible. * * Ext.define('App.overrides.some.Thing', { * override: 'Foo.some.Thing', * * compatibility: [ * '4.2.2', * '[email protected]' * ], * * //... * }); * * To require that all specifications match, an object can be provided: * * Ext.define('App.overrides.some.Thing', { * override: 'Foo.some.Thing', * * compatibility: { * and: [ * '4.2.2', * '[email protected]' * ] * }, * * //... * }); * * Because the object form is just a recursive check, these can be nested: * * Ext.define('App.overrides.some.Thing', { * override: 'Foo.some.Thing', * * compatibility: { * and: [ * '4.2.2', // exactly version 4.2.2 of the framework *AND* * { * // either (or both) of these package specs: * or: [ * '[email protected]', * '[email protected]+' * ] * } * ] * }, * * //... * }); * * 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. In * Sencha Cmd v4, the `compatibility` declaration can likewise be used to remove * incompatible overrides from a build. * * @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: * * - {@link Ext.Class#cfg-alias alias} * - {@link Ext.Class#cfg-alternateClassName alternateClassName} * - {@link Ext.Class#cfg-cachedConfig cachedConfig} * - {@link Ext.Class#cfg-config config} * - {@link Ext.Class#cfg-extend extend} * - {@link Ext.Class#cfg-inheritableStatics inheritableStatics} * - {@link Ext.Class#cfg-mixins mixins} * - {@link Ext.Class#cfg-override override} * - {@link Ext.Class#cfg-platformConfig platformConfig} * - {@link Ext.Class#cfg-privates privates} * - {@link Ext.Class#cfg-requires requires} * - `self` * - {@link Ext.Class#cfg-singleton singleton} * - {@link Ext.Class#cfg-statics statics} * - {@link Ext.Class#cfg-uses uses} * - {@link Ext.Class#cfg-xtype xtype} (for {@link Ext.Component Components} only) * * @param {Function} [createdFn] 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) { //<debug> Ext.classSystemMonitor && Ext.classSystemMonitor(className, 'ClassManager#define', arguments); //</debug> if (data.override) { Manager.classState[className] = 20; return Manager.createOverride.apply(Manager, arguments); } Manager.classState[className] = 10; 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) { //<debug> Ext.classSystemMonitor && Ext.classSystemMonitor(className, 'Ext.ClassManager#undefine', arguments); //</debug> var classes = Manager.classes; delete classes[className]; delete Manager.existCache[className]; delete Manager.classState[className]; Manager.removeName(className); var entry = Manager.getNamespaceEntry(className), scope = entry.parent ? Manager.lookupName(entry.parent, false) : Ext.global; if (scope) { // Old IE blows up on attempt to delete window property try { delete scope[entry.name]; } catch (e) { scope[entry.name] = 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 (last) namespace object created. * @member Ext * @method namespace */ namespace: function () { var root = global, i; for (i = arguments.length; i-- > 0; ) { root = Manager.lookupName(arguments[i], true); } return root; } }); /** * This function registers top-level (root) namespaces. This is needed for "sandbox" * builds. * * Ext.addRootNamespaces({ * MyApp: MyApp, * Common: Common * }); * * In the above example, `MyApp` and `Common` are top-level namespaces that happen * to also be included in the sandbox closure. Something like this: * * (function(Ext) { * * Ext.sandboxName = 'Ext6'; * Ext.isSandboxed = true; * Ext.buildSettings = { baseCSSPrefix: "x6-", scopeResetCSS: true }; * * var MyApp = MyApp || {}; * Ext.addRootNamespaces({ MyApp: MyApp ); * * ... normal app.js goes here ... * * })(this.Ext6 || (this.Ext6 = {})); * * The sandbox wrapper around the normally built `app.js` content has to take care * of introducing top-level namespaces as well as call this method. * * @param {Object} namespaces * @method addRootNamespaces * @member Ext * @since 6.0.0 * @private */ Ext.addRootNamespaces = Manager.addRootNamespaces; /** * Old name for {@link Ext#widget}. * @deprecated 5.0 Use {@link Ext#widget} instead. * @method createWidget * @member Ext * @private */ 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 ('$className' in data) { cls.$className = data.$className; //<debug> cls.displayName = cls.$className; //</debug> } //<debug> Ext.classSystemMonitor && Ext.classSystemMonitor(cls, 'Ext.ClassManager#classNamePreprocessor', arguments); //</debug> }, true, 'first'); Class.registerPreprocessor('alias', function(cls, data) { //<debug> Ext.classSystemMonitor && Ext.classSystemMonitor(cls, 'Ext.ClassManager#aliasPreprocessor', arguments); //</debug> var prototype = cls.prototype, xtypes = arrayFrom(data.xtype), aliases = arrayFrom(data.alias), widgetPrefix = 'widget.', widgetPrefixLength = widgetPrefix.length, xtypesChain = Array.prototype.slice.call(prototype.xtypesChain || []), xtypesMap = Ext.merge({}, prototype.xtypesMap || {}), i, ln, alias, xtype; for (i = 0,ln = aliases.length; i < ln; i++) { alias = aliases[i]; //<debug> if (typeof alias !== 'string' || alias.length < 1) { throw new Error("[Ext.define] Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string"); } //</debug> if (alias.substring(0, widgetPrefixLength) === widgetPrefix) { xtype = alias.substring(widgetPrefixLength); Ext.Array.include(xtypes, xtype); } } cls.xtype = data.xtype = xtypes[0]; data.xtypes = xtypes; for (i = 0,ln = xtypes.length; i < ln; i++) { xtype = xtypes[i]; if (!xtypesMap[xtype]) { xtypesMap[xtype] = true; xtypesChain.push(xtype); } } data.xtypesChain = xtypesChain; data.xtypesMap = xtypesMap; // Class is already extended at this point Ext.Function.interceptAfterOnce(cls, 'onClassCreated', function() { var cls = this, prototype = cls.prototype, mixins = prototype.mixins, key, mixin; //<debug> Ext.classSystemMonitor && Ext.classSystemMonitor(cls, 'Ext.ClassManager#aliasPreprocessor#afterClassCreated', arguments); //</debug> for (key in mixins) { if (mixins.hasOwnProperty(key)) { mixin = mixins[key]; xtypes = mixin.xtypes; if (xtypes) { for (i = 0,ln = xtypes.length; i < ln; i++) { xtype = xtypes[i]; if (!xtypesMap[xtype]) { xtypesMap[xtype] = true; xtypesChain.push(xtype); } } } } } }); for (i = 0,ln = xtypes.length; i < ln; i++) { xtype = xtypes[i]; //<debug> if (typeof xtype !== 'string' || xtype.length < 1) { throw new Error("[Ext.define] Invalid xtype of: '" + xtype + "' for class: '" + name + "'; must be a valid non-empty string"); } //</debug> Ext.Array.include(aliases, widgetPrefix + xtype); } data.alias = aliases; }, ['xtype', 'alias']); // load the cmd-5 style app manifest metadata now, if available... if (Ext.manifest) { var manifest = Ext.manifest, classes = manifest.classes, paths = manifest.paths, aliases = {}, alternates = {}, className, obj, name, path, baseUrl; if (paths) { // if the manifest paths were calculated as relative to the // bootstrap file, then we need to prepend Boot.baseUrl to the // paths before processing if (manifest.bootRelative) { baseUrl = Ext.Boot.baseUrl; for (path in paths) { if (paths.hasOwnProperty(path)) { paths[path] = baseUrl + paths[path]; } } } Manager.setPath(paths); } if (classes) { for (className in classes) { alternates[className] = []; aliases[className] = []; obj = classes[className]; if (obj.alias) { aliases[className] = obj.alias; } if (obj.alternates) { alternates[className] = obj.alternates; } } } Manager.addAlias(aliases); Manager.addAlternate(alternates); } return Manager;}(Ext.Class, Ext.Function.alias, Array.prototype.slice, Ext.Array.from, Ext.global));