//@tag foundation,core //@define Ext.ClassManager //@require Ext.Class /** * @class Ext.ClassManager * * @author Jacky Nguyen <jacky@sencha.com> * @aside guide class_system * @aside video class-system * * Ext.ClassManager manages all classes and handles mapping from string class name to * actual class objects throughout the whole framework. It is not generally accessed directly, rather through * these convenient shorthands: * * - {@link Ext#define Ext.define} * - {@link Ext.ClassManager#create Ext.create} * - {@link Ext#widget Ext.widget} * - {@link Ext#getClass Ext.getClass} * - {@link Ext#getClassName Ext.getClassName} * * ## Basic syntax: * * Ext.define(className, properties); * * in which `properties` is an object represent a collection of properties that apply to the class. See * {@link Ext.ClassManager#create} for more detailed instructions. * * @example * Ext.define('Person', { * name: 'Unknown', * * constructor: function(name) { * if (name) { * this.name = name; * } * * return this; * }, * * eat: function(foodType) { * alert("I'm eating: " + foodType); * * return this; * } * }); * * var aaron = new Person("Aaron"); * aaron.eat("Sandwich"); // alert("I'm eating: Sandwich"); * * Ext.Class has a powerful set of extensible {@link Ext.Class#registerPreprocessor pre-processors} which takes care of * everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc. * * ## Inheritance: * * Ext.define('Developer', { * extend: 'Person', * * constructor: function(name, isGeek) { * this.isGeek = isGeek; * * // Apply a method from the parent class' prototype * this.callParent([name]); * * return this; * * }, * * code: function(language) { * alert("I'm coding in: " + language); * * this.eat("Bugs"); * * return this; * } * }); * * var jacky = new Developer("Jacky", true); * jacky.code("JavaScript"); // alert("I'm coding in: JavaScript"); * // alert("I'm eating: Bugs"); * * See {@link Ext.Base#callParent} for more details on calling superclass' methods * * ## Mixins: * * Ext.define('CanPlayGuitar', { * playGuitar: function() { * alert("F#...G...D...A"); * } * }); * * Ext.define('CanComposeSongs', { * composeSongs: function() { } * }); * * Ext.define('CanSing', { * sing: function() { * alert("I'm on the highway to hell..."); * } * }); * * Ext.define('Musician', { * extend: 'Person', * * mixins: { * canPlayGuitar: 'CanPlayGuitar', * canComposeSongs: 'CanComposeSongs', * canSing: 'CanSing' * } * }); * * Ext.define('CoolPerson', { * extend: 'Person', * * mixins: { * canPlayGuitar: 'CanPlayGuitar', * canSing: 'CanSing' * }, * * sing: function() { * alert("Ahem..."); * * this.mixins.canSing.sing.call(this); * * alert("[Playing guitar at the same time...]"); * * this.playGuitar(); * } * }); * * var me = new CoolPerson("Jacky"); * * me.sing(); // alert("Ahem..."); * // alert("I'm on the highway to hell..."); * // alert("[Playing guitar at the same time...]"); * // alert("F#...G...D...A"); * * ## Config: * * Ext.define('SmartPhone', { * config: { * hasTouchScreen: false, * operatingSystem: 'Other', * price: 500 * }, * * isExpensive: false, * * constructor: function(config) { * this.initConfig(config); * * return this; * }, * * applyPrice: function(price) { * this.isExpensive = (price > 500); * * return price; * }, * * applyOperatingSystem: function(operatingSystem) { * if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) { * return 'Other'; * } * * return operatingSystem; * } * }); * * var iPhone = new SmartPhone({ * hasTouchScreen: true, * operatingSystem: 'iOS' * }); * * iPhone.getPrice(); // 500; * iPhone.getOperatingSystem(); // 'iOS' * iPhone.getHasTouchScreen(); // true; * * iPhone.isExpensive; // false; * iPhone.setPrice(600); * iPhone.getPrice(); // 600 * iPhone.isExpensive; // true; * * iPhone.setOperatingSystem('AlienOS'); * iPhone.getOperatingSystem(); // 'Other' * * ## Statics: * * Ext.define('Computer', { * statics: { * factory: function(brand) { * // 'this' in static methods refer to the class itself * return new this(brand); * } * }, * * constructor: function() { } * }); * * var dellComputer = Computer.factory('Dell'); * * Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing * static properties within class methods * * @singleton */ (function(Class, alias, arraySlice, arrayFrom, global) { //<if nonBrowser> var isNonBrowser = typeof window == 'undefined'; //</if> var Manager = Ext.ClassManager = { /** * @property classes * @type Object * All classes which were defined through the ClassManager. Keys are the * name of the classes and the values are references to the classes. * @private */ classes: {}, /** * @private */ existCache: {}, /** * @private */ namespaceRewrites: [{ from: 'Ext.', to: Ext }], /** * @private */ maps: { alternateToName: {}, aliasToName: {}, nameToAliases: {}, nameToAlternates: {} }, /** @private */ enableNamespaceParseCache: true, /** @private */ namespaceParseCache: {}, /** @private */ instantiators: [], /** * Checks if a class has already been created. * * @param {String} className * @return {Boolean} exist */ isCreated: function(className) { var existCache = this.existCache, i, ln, part, root, parts; //<debug error> if (typeof className != 'string' || className.length < 1) { throw new Error("[Ext.ClassManager] Invalid classname, must be a string and must not be empty"); } //</debug> if (this.classes[className] || existCache[className]) { return true; } root = global; parts = this.parseNamespace(className); for (i = 0, ln = parts.length; i < ln; i++) { part = parts[i]; if (typeof part != 'string') { root = part; } else { if (!root || !root[part]) { return false; } root = root[part]; } } existCache[className] = true; this.triggerCreated(className); return true; }, /** * @private */ createdListeners: [], /** * @private */ nameCreatedListeners: {}, /** * @private */ triggerCreated: function(className) { var listeners = this.createdListeners, nameListeners = this.nameCreatedListeners, alternateNames = this.maps.nameToAlternates[className], names = [className], i, ln, j, subLn, listener, name; for (i = 0,ln = listeners.length; i < ln; i++) { listener = listeners[i]; listener.fn.call(listener.scope, className); } if (alternateNames) { names.push.apply(names, alternateNames); } for (i = 0,ln = names.length; i < ln; i++) { name = names[i]; listeners = nameListeners[name]; if (listeners) { for (j = 0,subLn = listeners.length; j < subLn; j++) { listener = listeners[j]; listener.fn.call(listener.scope, name); } delete nameListeners[name]; } } }, /** * @private */ onCreated: function(fn, scope, className) { var listeners = this.createdListeners, nameListeners = this.nameCreatedListeners, listener = { fn: fn, scope: scope }; if (className) { if (this.isCreated(className)) { fn.call(scope, className); return; } if (!nameListeners[className]) { nameListeners[className] = []; } nameListeners[className].push(listener); } else { listeners.push(listener); } }, /** * Supports namespace rewriting. * @private */ parseNamespace: function(namespace) { //<debug error> if (typeof namespace != 'string') { throw new Error("[Ext.ClassManager] Invalid namespace, must be a string"); } //</debug> var cache = this.namespaceParseCache; if (this.enableNamespaceParseCache) { if (cache.hasOwnProperty(namespace)) { return cache[namespace]; } } var parts = [], rewrites = this.namespaceRewrites, root = global, name = namespace, rewrite, from, to, i, ln; for (i = 0, ln = rewrites.length; i < ln; i++) { rewrite = rewrites[i]; from = rewrite.from; to = rewrite.to; if (name === from || name.substring(0, from.length) === from) { name = name.substring(from.length); if (typeof to != 'string') { root = to; } else { parts = parts.concat(to.split('.')); } break; } } parts.push(root); parts = parts.concat(name.split('.')); if (this.enableNamespaceParseCache) { cache[namespace] = parts; } return parts; }, /** * Creates a namespace and assign the `value` to the created object. * * Ext.ClassManager.setNamespace('MyCompany.pkg.Example', someObject); * alert(MyCompany.pkg.Example === someObject); // alerts true * * @param {String} name * @param {Mixed} value */ setNamespace: function(name, value) { var root = global, parts = this.parseNamespace(name), ln = parts.length - 1, leaf = parts[ln], i, part; for (i = 0; i < ln; i++) { part = parts[i]; if (typeof part != 'string') { root = part; } else { if (!root[part]) { root[part] = {}; } root = root[part]; } } root[leaf] = value; return root[leaf]; }, /** * The new Ext.ns, supports namespace rewriting. * @private */ createNamespaces: function() { var root = global, parts, part, i, j, ln, subLn; for (i = 0, ln = arguments.length; i < ln; i++) { parts = this.parseNamespace(arguments[i]); for (j = 0, subLn = parts.length; j < subLn; j++) { part = parts[j]; if (typeof part != 'string') { root = part; } else { if (!root[part]) { root[part] = {}; } root = root[part]; } } } return root; }, /** * Sets a name reference to a class. * * @param {String} name * @param {Object} value * @return {Ext.ClassManager} this */ set: function(name, value) { var me = this, maps = me.maps, nameToAlternates = maps.nameToAlternates, targetName = me.getName(value), alternates; me.classes[name] = me.setNamespace(name, value); if (targetName && targetName !== name) { maps.alternateToName[name] = targetName; alternates = nameToAlternates[targetName] || (nameToAlternates[targetName] = []); alternates.push(name); } return this; }, /** * Retrieve a class by its name. * * @param {String} name * @return {Ext.Class} class */ get: function(name) { var classes = this.classes; if (classes[name]) { return classes[name]; } var root = global, parts = this.parseNamespace(name), part, i, ln; for (i = 0, ln = parts.length; i < ln; i++) { part = parts[i]; if (typeof part != 'string') { root = part; } else { if (!root || !root[part]) { return null; } root = root[part]; } } return root; }, /** * Register the alias for a class. * * @param {Ext.Class/String} cls a reference to a class or a `className`. * @param {String} alias Alias to use when referring to this class. */ setAlias: function(cls, alias) { var aliasToNameMap = this.maps.aliasToName, nameToAliasesMap = this.maps.nameToAliases, className; if (typeof cls == 'string') { className = cls; } else { className = this.getName(cls); } if (alias && aliasToNameMap[alias] !== className) { //<debug info> if (aliasToNameMap[alias]) { Ext.Logger.info("[Ext.ClassManager] Overriding existing alias: '" + alias + "' " + "of: '" + aliasToNameMap[alias] + "' with: '" + className + "'. Be sure it's intentional."); } //</debug> aliasToNameMap[alias] = className; } if (!nameToAliasesMap[className]) { nameToAliasesMap[className] = []; } if (alias) { Ext.Array.include(nameToAliasesMap[className], alias); } return this; }, /** * Adds a batch of class name to alias mappings * @param {Object} aliases The set of mappings of the form * className : [values...] */ addNameAliasMappings: function(aliases){ var aliasToNameMap = this.maps.aliasToName, nameToAliasesMap = this.maps.nameToAliases, className, aliasList, alias, i; for (className in aliases) { aliasList = nameToAliasesMap[className] || (nameToAliasesMap[className] = []); for (i = 0; i < aliases[className].length; i++) { alias = aliases[className][i]; if (!aliasToNameMap[alias]) { aliasToNameMap[alias] = className; aliasList.push(alias); } } } return this; }, /** * * @param {Object} alternates The set of mappings of the form * className : [values...] */ addNameAlternateMappings: function(alternates) { var alternateToName = this.maps.alternateToName, nameToAlternates = this.maps.nameToAlternates, className, aliasList, alternate, i; for (className in alternates) { aliasList = nameToAlternates[className] || (nameToAlternates[className] = []); for (i = 0; i < alternates[className].length; i++) { alternate = alternates[className]; if (!alternateToName[alternate]) { alternateToName[alternate] = className; aliasList.push(alternate); } } } return this; }, /** * Get a reference to the class by its alias. * * @param {String} alias * @return {Ext.Class} class */ getByAlias: function(alias) { return this.get(this.getNameByAlias(alias)); }, /** * Get the name of a class by its alias. * * @param {String} alias * @return {String} className */ getNameByAlias: function(alias) { return this.maps.aliasToName[alias] || ''; }, /** * Get the name of a class by its alternate name. * * @param {String} alternate * @return {String} className */ getNameByAlternate: function(alternate) { return this.maps.alternateToName[alternate] || ''; }, /** * Get the aliases of a class by the class name * * @param {String} name * @return {Array} aliases */ getAliasesByName: function(name) { return this.maps.nameToAliases[name] || []; }, /** * Get the name of the class by its reference or its instance; * usually invoked by the shorthand {@link Ext#getClassName Ext.getClassName} * * Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action" * * @param {Ext.Class/Object} object * @return {String} className */ getName: function(object) { return object && object.$className || ''; }, /** * Get the class of the provided object; returns null if it's not an instance * of any class created with Ext.define. This is usually invoked by the shorthand {@link Ext#getClass Ext.getClass}. * * var component = new Ext.Component(); * * Ext.ClassManager.getClass(component); // returns Ext.Component * * @param {Object} object * @return {Ext.Class} class */ getClass: function(object) { return object && object.self || null; }, /** * @private */ create: function(className, data, createdFn) { //<debug error> if (typeof className != 'string') { throw new Error("[Ext.define] Invalid class name '" + className + "' specified, must be a non-empty string"); } //</debug> data.$className = className; return new Class(data, function() { var postprocessorStack = data.postprocessors || Manager.defaultPostprocessors, registeredPostprocessors = Manager.postprocessors, index = 0, postprocessors = [], postprocessor, process, i, ln, j, subLn, postprocessorProperties, postprocessorProperty; delete data.postprocessors; for (i = 0,ln = postprocessorStack.length; i < ln; i++) { postprocessor = postprocessorStack[i]; if (typeof postprocessor == 'string') { postprocessor = registeredPostprocessors[postprocessor]; postprocessorProperties = postprocessor.properties; if (postprocessorProperties === true) { postprocessors.push(postprocessor.fn); } else if (postprocessorProperties) { for (j = 0,subLn = postprocessorProperties.length; j < subLn; j++) { postprocessorProperty = postprocessorProperties[j]; if (data.hasOwnProperty(postprocessorProperty)) { postprocessors.push(postprocessor.fn); break; } } } } else { postprocessors.push(postprocessor); } } process = function(clsName, cls, clsData) { postprocessor = postprocessors[index++]; if (!postprocessor) { Manager.set(className, cls); if (createdFn) { createdFn.call(cls, cls); } Manager.triggerCreated(className); return; } if (postprocessor.call(this, clsName, cls, clsData, process) !== false) { process.apply(this, arguments); } }; process.call(Manager, className, this, data); }); }, createOverride: function(className, data, createdFn) { var overriddenClassName = data.override, requires = Ext.Array.from(data.requires); delete data.override; delete data.requires; this.existCache[className] = true; Ext.require(requires, function() { // Override the target class right after it's created this.onCreated(function() { var overridenClass = this.get(overriddenClassName); if (overridenClass.singleton) { overridenClass.self.override(data); } else { overridenClass.override(data); } if (createdFn) { createdFn.call(overridenClass, overridenClass); } // This push the overridding file itself into Ext.Loader.history // Hence if the target class never exists, the overriding file will // never be included in the build this.triggerCreated(className); }, this, overriddenClassName); }, this); return this; }, /** * Instantiate a class by its alias; usually invoked by the convenient shorthand {@link Ext#createByAlias Ext.createByAlias} * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will * attempt to load the class via synchronous loading. * * var window = Ext.ClassManager.instantiateByAlias('widget.window', { width: 600, height: 800 }); * * @param {String} alias * @param {Mixed...} args Additional arguments after the alias will be passed to the class constructor. * @return {Object} instance */ instantiateByAlias: function() { var alias = arguments[0], args = arraySlice.call(arguments), className = this.getNameByAlias(alias); if (!className) { className = this.maps.aliasToName[alias]; //<debug error> if (!className) { throw new Error("[Ext.createByAlias] Cannot create an instance of unrecognized alias: " + alias); } //</debug> //<debug warn> Ext.Logger.warn("[Ext.Loader] Synchronously loading '" + className + "'; consider adding " + "Ext.require('" + alias + "') above Ext.onReady"); //</debug> Ext.syncRequire(className); } args[0] = className; return this.instantiate.apply(this, args); }, /** * Instantiate a class by either full name, alias or alternate name; usually invoked by the convenient * shorthand {@link Ext.ClassManager#create Ext.create}. * * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will * attempt to load the class via synchronous loading. * * For example, all these three lines return the same result: * * // alias * var formPanel = Ext.create('widget.formpanel', { width: 600, height: 800 }); * * // alternate name * var formPanel = Ext.create('Ext.form.FormPanel', { width: 600, height: 800 }); * * // full class name * var formPanel = Ext.create('Ext.form.Panel', { width: 600, height: 800 }); * * @param {String} name * @param {Mixed} args Additional arguments after the name will be passed to the class' constructor. * @return {Object} instance */ instantiate: function() { var name = arguments[0], args = arraySlice.call(arguments, 1), alias = name, possibleName, cls; if (typeof name != 'function') { //<debug error> if ((typeof name != 'string' || name.length < 1)) { throw new Error("[Ext.create] Invalid class name or alias '" + name + "' specified, must be a non-empty string"); } //</debug> cls = this.get(name); } else { cls = name; } // No record of this class name, it's possibly an alias, so look it up if (!cls) { possibleName = this.getNameByAlias(name); if (possibleName) { name = possibleName; cls = this.get(name); } } // Still no record of this class name, it's possibly an alternate name, so look it up if (!cls) { possibleName = this.getNameByAlternate(name); if (possibleName) { name = possibleName; cls = this.get(name); } } // Still not existing at this point, try to load it via synchronous mode as the last resort if (!cls) { //<debug warn> //<if nonBrowser> !isNonBrowser && //</if> Ext.Logger.warn("[Ext.Loader] Synchronously loading '" + name + "'; consider adding '" + ((possibleName) ? alias : name) + "' explicitly as a require of the corresponding class"); //</debug> Ext.syncRequire(name); cls = this.get(name); } //<debug error> if (!cls) { throw new Error("[Ext.create] Cannot create an instance of unrecognized class name / alias: " + alias); } if (typeof cls != 'function') { throw new Error("[Ext.create] '" + name + "' is a singleton and cannot be instantiated"); } //</debug> return this.getInstantiator(args.length)(cls, args); }, /** * @private * @param {String} name * @param {Array} args */ dynInstantiate: function(name, args) { args = arrayFrom(args, true); args.unshift(name); return this.instantiate.apply(this, args); }, /** * @private * @param {Number} length */ getInstantiator: function(length) { var instantiators = this.instantiators, instantiator; instantiator = instantiators[length]; if (!instantiator) { var i = length, args = []; for (i = 0; i < length; i++) { args.push('a[' + i + ']'); } instantiator = instantiators[length] = new Function('c', 'a', 'return new c(' + args.join(',') + ')'); //<debug> instantiator.displayName = "Ext.ClassManager.instantiate" + length; //</debug> } return instantiator; }, /** * @private */ postprocessors: {}, /** * @private */ defaultPostprocessors: [], /** * Register a post-processor function. * @private */ 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 {Array} classNames */ getNamesByExpression: function(expression) { var nameToAliasesMap = this.maps.nameToAliases, names = [], name, alias, aliases, possibleName, regex, i, ln; //<debug error> if (typeof expression != 'string' || expression.length < 1) { throw new Error("[Ext.ClassManager.getNamesByExpression] Expression " + expression + " is invalid, must be a non-empty string"); } //</debug> if (expression.indexOf('*') !== -1) { expression = expression.replace(/\*/g, '(.*?)'); regex = new RegExp('^' + expression + '$'); for (name in nameToAliasesMap) { if (nameToAliasesMap.hasOwnProperty(name)) { aliases = nameToAliasesMap[name]; if (name.search(regex) !== -1) { names.push(name); } else { for (i = 0, ln = aliases.length; i < ln; i++) { alias = aliases[i]; if (alias.search(regex) !== -1) { names.push(name); break; } } } } } } else { possibleName = this.getNameByAlias(expression); if (possibleName) { names.push(possibleName); } else { possibleName = this.getNameByAlternate(expression); if (possibleName) { names.push(possibleName); } else { names.push(expression); } } } return names; } }; //<feature classSystem.alias> /** * @cfg {String[]} alias * @member Ext.Class * List of short aliases for class names. Most useful for defining xtypes for widgets: * * Ext.define('MyApp.CoolPanel', { * extend: 'Ext.panel.Panel', * alias: ['widget.coolpanel'], * * config: { * html : 'Yeah!' * } * }); * * // Using Ext.create * Ext.create('widget.coolpanel'); * * // Using the shorthand for widgets and in xtypes * Ext.widget('panel', { * items: [ * {xtype: 'coolpanel', html: 'Foo'}, * {xtype: 'coolpanel', html: 'Bar'} * ] * }); * * For {@link Ext.Component}, you can also use the {@link Ext.Component#xtype} property. */ /** * @cfg {String[]} xtype * @member Ext.Component * List of xtypes for {@link Ext.Component}. XTypes must not contain periods. * * Ext.define('MyApp.CoolPanel', { * extend: 'Ext.panel.Panel', * xtype: 'coolpanel', * * config: { * html : 'Yeah!' * } * }); * * // Using Ext.create * Ext.create('widget.coolpanel'); * * // Using the shorthand for widgets and in xtypes * Ext.widget('panel', { * items: [ * {xtype: 'coolpanel', html: 'Foo'}, * {xtype: 'coolpanel', html: 'Bar'} * ] * }); */ Manager.registerPostprocessor('alias', function(name, cls, data) { var aliases = data.alias, i, ln; for (i = 0,ln = aliases.length; i < ln; i++) { alias = aliases[i]; this.setAlias(cls, alias); } }, ['xtype', 'alias']); //</feature> //<feature classSystem.singleton> /** * @cfg {Boolean} singleton * @member Ext.Class * When set to true, the class will be instantiated as singleton. For example: * * Ext.define('Logger', { * singleton: true, * log: function(msg) { * console.log(msg); * } * }); * * Logger.log('Hello'); */ Manager.registerPostprocessor('singleton', function(name, cls, data, fn) { fn.call(this, name, new cls(), data); return false; }); //</feature> //<feature classSystem.alternateClassName> /** * @cfg {String/String[]} alternateClassName * @member Ext.Class * Defines alternate names for this class. For example: * * @example * Ext.define('Developer', { * alternateClassName: ['Coder', 'Hacker'], * code: function(msg) { * alert('Typing... ' + msg); * } * }); * * var joe = Ext.create('Developer'); * joe.code('stackoverflow'); * * var rms = Ext.create('Hacker'); * rms.code('hack hack'); */ Manager.registerPostprocessor('alternateClassName', function(name, cls, data) { var alternates = data.alternateClassName, i, ln, alternate; if (!(alternates instanceof Array)) { alternates = [alternates]; } for (i = 0, ln = alternates.length; i < ln; i++) { alternate = alternates[i]; //<debug error> if (typeof alternate != 'string') { throw new Error("[Ext.define] Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string"); } //</debug> this.set(alternate, cls); } }); //</feature> Ext.apply(Ext, { /** * Instantiate a class by either full name, alias or alternate name. * * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will * attempt to load the class via synchronous loading. * * For example, all these three lines return the same result: * * // alias * var formPanel = Ext.create('widget.formpanel', { width: 600, height: 800 }); * * // alternate name * var formPanel = Ext.create('Ext.form.FormPanel', { width: 600, height: 800 }); * * // full class name * var formPanel = Ext.create('Ext.form.Panel', { width: 600, height: 800 }); * * @param {String} name * @param {Mixed} args Additional arguments after the name will be passed to the class' constructor. * @return {Object} instance * @member Ext */ create: alias(Manager, 'instantiate'), /** * Convenient shorthand to create a widget by its xtype, also see {@link Ext.ClassManager#instantiateByAlias} * * var button = Ext.widget('button'); // Equivalent to Ext.create('widget.button') * var panel = Ext.widget('panel'); // Equivalent to Ext.create('widget.panel') * * @member Ext * @method widget * @param {String} name * @return {Object} instance */ widget: function(name) { var args = arraySlice.call(arguments); args[0] = 'widget.' + name; return Manager.instantiateByAlias.apply(Manager, args); }, /** * Convenient shorthand, see {@link Ext.ClassManager#instantiateByAlias}. * @member Ext * @method createByAlias * @param {String} alias * @param {Mixed...} args Additional arguments after the alias will be passed to the class constructor. * @return {Object} instance */ createByAlias: alias(Manager, 'instantiateByAlias'), /** * Defines a class or override. A basic class is defined like this: * * Ext.define('My.awesome.Class', { * someProperty: 'something', * * someMethod: function(s) { * console.log(s + this.someProperty); * } * }); * * var obj = new My.awesome.Class(); * * obj.someMethod('Say '); // logs 'Say something' to the console * * To defines an override, include the `override` property. The content of an * override is aggregated with the specified class in order to extend or modify * that class. This can be as simple as setting default property values or it can * extend and/or replace methods. This can also extend the statics of the class. * * One use for an override is to break a large class into manageable pieces. * * // File: /src/app/Panel.js * Ext.define('My.app.Panel', { * extend: 'Ext.panel.Panel', * requires: [ * 'My.app.PanelPart2', * 'My.app.PanelPart3' * ], * * constructor: function (config) { * this.callParent(arguments); // calls Ext.panel.Panel's constructor * // ... * }, * * statics: { * method: function () { * return 'abc'; * } * } * }); * * // File: /src/app/PanelPart2.js * Ext.define('My.app.PanelPart2', { * override: 'My.app.Panel', * * constructor: function (config) { * this.callParent(arguments); // calls My.app.Panel's constructor * // ... * } * }); * * Another use for an override is to provide optional parts of classes that can be * independently required. In this case, the class may even be unaware of the * override altogether. * * Ext.define('My.ux.CoolTip', { * override: 'Ext.tip.ToolTip', * * constructor: function (config) { * this.callParent(arguments); // calls Ext.tip.ToolTip's constructor * // ... * } * }); * * The above override can now be required as normal. * * Ext.define('My.app.App', { * requires: [ * 'My.ux.CoolTip' * ] * }); * * Overrides can also contain statics: * * Ext.define('My.app.BarMod', { * override: 'Ext.foo.Bar', * * statics: { * method: function (x) { * return this.callParent([x * 2]); // call Ext.foo.Bar.method * } * } * }); * * __IMPORTANT:__ An override is only included in a build if the class it overrides is * required. Otherwise, the override, like the target class, is not included. * * @param {String} className The class name to create in string dot-namespaced format, for example: * 'My.very.awesome.Class', 'FeedViewer.plugin.CoolPager' * * It is highly recommended to follow this simple convention: * - The root and the class name are 'CamelCased' * - Everything else is lower-cased * * @param {Object} data The key - value pairs of properties to apply to this class. Property names can be of * any valid strings, except those in the reserved listed below: * * - `mixins` * - `statics` * - `config` * - `alias` * - `xtype` (for {@link Ext.Component}s only) * - `self` * - `singleton` * - `alternateClassName` * - `override` * * @param {Function} [createdFn] Optional callback to execute after the class (or override) * is created. The execution scope (`this`) will be the newly created class itself. * @return {Ext.Base} * * @member Ext * @method define */ define: function (className, data, createdFn) { if ('override' in data) { return Manager.createOverride.apply(Manager, arguments); } return Manager.create.apply(Manager, arguments); }, /** * Convenient shorthand for {@link Ext.ClassManager#getName}. * @member Ext * @method getClassName * @inheritdoc Ext.ClassManager#getName */ getClassName: alias(Manager, 'getName'), /** * Returns the display name for object. This name is looked for in order from the following places: * * - `displayName` field of the object. * - `$name` and `$class` fields of the object. * - '$className` field of the object. * * This method is used by {@link Ext.Logger#log} to display information about objects. * * @param {Mixed} [object] The object who's display name to determine. * @return {String} The determined display name, or "Anonymous" if none found. * @member Ext */ getDisplayName: function(object) { if (object) { if (object.displayName) { return object.displayName; } if (object.$name && object.$class) { return Ext.getClassName(object.$class) + '#' + object.$name; } if (object.$className) { return object.$className; } } return 'Anonymous'; }, /** * Convenient shorthand, see {@link Ext.ClassManager#getClass}. * @member Ext * @method getClass */ getClass: alias(Manager, 'getClass'), /** * Creates namespaces to be used for scoping variables and classes so that they are not global. * Specifying the last node of a namespace implicitly creates all other nodes. Usage: * * Ext.namespace('Company', 'Company.data'); * * // equivalent and preferable to the above syntax * Ext.namespace('Company.data'); * * Company.Widget = function() { * // ... * }; * * Company.data.CustomStore = function(config) { * // ... * }; * * @param {String} namespace1 * @param {String} namespace2 * @param {String} etc * @return {Object} The namespace object. If multiple arguments are passed, this will be the last namespace created. * @member Ext * @method namespace */ namespace: alias(Manager, 'createNamespaces') }); /** * Old name for {@link Ext#widget}. * @deprecated 2.0.0 Please use {@link Ext#widget} instead. * @method createWidget * @member Ext */ Ext.createWidget = Ext.widget; /** * Convenient alias for {@link Ext#namespace Ext.namespace}. * @member Ext * @method ns */ Ext.ns = Ext.namespace; Class.registerPreprocessor('className', function(cls, data) { if (data.$className) { cls.$className = data.$className; //<debug> cls.displayName = cls.$className; //</debug> } }, true, 'first'); Class.registerPreprocessor('alias', function(cls, data) { var prototype = cls.prototype, xtypes = arrayFrom(data.xtype), aliases = arrayFrom(data.alias), widgetPrefix = 'widget.', widgetPrefixLength = widgetPrefix.length, xtypesChain = Array.prototype.slice.call(prototype.xtypesChain || []), xtypesMap = Ext.merge({}, prototype.xtypesMap || {}), i, ln, alias, xtype; for (i = 0,ln = aliases.length; i < ln; i++) { alias = aliases[i]; //<debug error> if (typeof alias != 'string' || alias.length < 1) { throw new Error("[Ext.define] Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string"); } //</debug> if (alias.substring(0, widgetPrefixLength) === widgetPrefix) { xtype = alias.substring(widgetPrefixLength); Ext.Array.include(xtypes, xtype); } } cls.xtype = data.xtype = xtypes[0]; data.xtypes = xtypes; for (i = 0,ln = xtypes.length; i < ln; i++) { xtype = xtypes[i]; if (!xtypesMap[xtype]) { xtypesMap[xtype] = true; xtypesChain.push(xtype); } } data.xtypesChain = xtypesChain; data.xtypesMap = xtypesMap; Ext.Function.interceptAfter(data, 'onClassCreated', function() { var mixins = prototype.mixins, key, mixin; for (key in mixins) { if (mixins.hasOwnProperty(key)) { mixin = mixins[key]; xtypes = mixin.xtypes; if (xtypes) { for (i = 0,ln = xtypes.length; i < ln; i++) { xtype = xtypes[i]; if (!xtypesMap[xtype]) { xtypesMap[xtype] = true; xtypesChain.push(xtype); } } } } } }); for (i = 0,ln = xtypes.length; i < ln; i++) { xtype = xtypes[i]; //<debug error> if (typeof xtype != 'string' || xtype.length < 1) { throw new Error("[Ext.define] Invalid xtype of: '" + xtype + "' for class: '" + name + "'; must be a valid non-empty string"); } //</debug> Ext.Array.include(aliases, widgetPrefix + xtype); } data.alias = aliases; }, ['xtype', 'alias']); })(Ext.Class, Ext.Function.alias, Array.prototype.slice, Ext.Array.from, Ext.global);