3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * @author Jacky Nguyen <jacky@sencha.com>
17 * @docauthor Jacky Nguyen <jacky@sencha.com>
18 * @class Ext.ClassManager
20 * Ext.ClassManager manages all classes and handles mapping from string class name to
21 * actual class objects throughout the whole framework. It is not generally accessed directly, rather through
22 * these convenient shorthands:
24 * - {@link Ext#define Ext.define}
25 * - {@link Ext#create Ext.create}
26 * - {@link Ext#widget Ext.widget}
27 * - {@link Ext#getClass Ext.getClass}
28 * - {@link Ext#getClassName Ext.getClassName}
32 (function(Class, alias) {
34 var slice = Array.prototype.slice;
36 var Manager = Ext.ClassManager = {
39 * @property {Object} classes
40 * All classes which were defined through the ClassManager. Keys are the
41 * name of the classes and the values are references to the classes.
69 enableNamespaceParseCache: true,
72 namespaceParseCache: {},
79 instantiationCounts: {},
83 * Checks if a class has already been created.
85 * @param {String} className
86 * @return {Boolean} exist
88 isCreated: function(className) {
89 var i, ln, part, root, parts;
92 if (typeof className !== 'string' || className.length < 1) {
94 sourceClass: "Ext.ClassManager",
95 sourceMethod: "exist",
96 msg: "Invalid classname, must be a string and must not be empty"
101 if (this.classes.hasOwnProperty(className) || this.existCache.hasOwnProperty(className)) {
106 parts = this.parseNamespace(className);
108 for (i = 0, ln = parts.length; i < ln; i++) {
111 if (typeof part !== 'string') {
114 if (!root || !root[part]) {
122 Ext.Loader.historyPush(className);
124 this.existCache[className] = true;
130 * Supports namespace rewriting
133 parseNamespace: function(namespace) {
135 if (typeof namespace !== 'string') {
137 sourceClass: "Ext.ClassManager",
138 sourceMethod: "parseNamespace",
139 msg: "Invalid namespace, must be a string"
144 var cache = this.namespaceParseCache;
146 if (this.enableNamespaceParseCache) {
147 if (cache.hasOwnProperty(namespace)) {
148 return cache[namespace];
153 rewrites = this.namespaceRewrites,
154 rewrite, from, to, i, ln, root = Ext.global;
156 for (i = 0, ln = rewrites.length; i < ln; i++) {
157 rewrite = rewrites[i];
161 if (namespace === from || namespace.substring(0, from.length) === from) {
162 namespace = namespace.substring(from.length);
164 if (typeof to !== 'string') {
167 parts = parts.concat(to.split('.'));
176 parts = parts.concat(namespace.split('.'));
178 if (this.enableNamespaceParseCache) {
179 cache[namespace] = parts;
186 * Creates a namespace and assign the `value` to the created object
188 * Ext.ClassManager.setNamespace('MyCompany.pkg.Example', someObject);
190 * alert(MyCompany.pkg.Example === someObject); // alerts true
192 * @param {String} name
193 * @param {Mixed} value
195 setNamespace: function(name, value) {
196 var root = Ext.global,
197 parts = this.parseNamespace(name),
201 for (i = 0, ln = parts.length; i < ln; i++) {
204 if (typeof part !== 'string') {
221 * The new Ext.ns, supports namespace rewriting
224 createNamespaces: function() {
225 var root = Ext.global,
226 parts, part, i, j, ln, subLn;
228 for (i = 0, ln = arguments.length; i < ln; i++) {
229 parts = this.parseNamespace(arguments[i]);
231 for (j = 0, subLn = parts.length; j < subLn; j++) {
234 if (typeof part !== 'string') {
250 * Sets a name reference to a class.
252 * @param {String} name
253 * @param {Object} value
254 * @return {Ext.ClassManager} this
256 set: function(name, value) {
257 var targetName = this.getName(value);
259 this.classes[name] = this.setNamespace(name, value);
261 if (targetName && targetName !== name) {
262 this.maps.alternateToName[name] = targetName;
269 * Retrieve a class by its name.
271 * @param {String} name
272 * @return {Class} class
274 get: function(name) {
275 if (this.classes.hasOwnProperty(name)) {
276 return this.classes[name];
279 var root = Ext.global,
280 parts = this.parseNamespace(name),
283 for (i = 0, ln = parts.length; i < ln; i++) {
286 if (typeof part !== 'string') {
289 if (!root || !root[part]) {
301 * Register the alias for a class.
303 * @param {Class/String} cls a reference to a class or a className
304 * @param {String} alias Alias to use when referring to this class
306 setAlias: function(cls, alias) {
307 var aliasToNameMap = this.maps.aliasToName,
308 nameToAliasesMap = this.maps.nameToAliases,
311 if (typeof cls === 'string') {
314 className = this.getName(cls);
317 if (alias && aliasToNameMap[alias] !== className) {
319 if (aliasToNameMap.hasOwnProperty(alias) && Ext.isDefined(Ext.global.console)) {
320 Ext.global.console.log("[Ext.ClassManager] Overriding existing alias: '" + alias + "' " +
321 "of: '" + aliasToNameMap[alias] + "' with: '" + className + "'. Be sure it's intentional.");
325 aliasToNameMap[alias] = className;
328 if (!nameToAliasesMap[className]) {
329 nameToAliasesMap[className] = [];
333 Ext.Array.include(nameToAliasesMap[className], alias);
340 * Get a reference to the class by its alias.
342 * @param {String} alias
343 * @return {Class} class
345 getByAlias: function(alias) {
346 return this.get(this.getNameByAlias(alias));
350 * Get the name of a class by its alias.
352 * @param {String} alias
353 * @return {String} className
355 getNameByAlias: function(alias) {
356 return this.maps.aliasToName[alias] || '';
360 * Get the name of a class by its alternate name.
362 * @param {String} alternate
363 * @return {String} className
365 getNameByAlternate: function(alternate) {
366 return this.maps.alternateToName[alternate] || '';
370 * Get the aliases of a class by the class name
372 * @param {String} name
373 * @return {Array} aliases
375 getAliasesByName: function(name) {
376 return this.maps.nameToAliases[name] || [];
380 * Get the name of the class by its reference or its instance.
382 * Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action"
384 * {@link Ext#getClassName Ext.getClassName} is alias for {@link Ext.ClassManager#getName Ext.ClassManager.getName}.
386 * @param {Class/Object} object
387 * @return {String} className
389 getName: function(object) {
390 return object && object.$className || '';
394 * Get the class of the provided object; returns null if it's not an instance
395 * of any class created with Ext.define.
397 * var component = new Ext.Component();
399 * Ext.ClassManager.getClass(component); // returns Ext.Component
401 * {@link Ext#getClass Ext.getClass} is alias for {@link Ext.ClassManager#getClass Ext.ClassManager.getClass}.
403 * @param {Object} object
404 * @return {Class} class
406 getClass: function(object) {
407 return object && object.self || null;
413 * Ext.ClassManager.create('My.awesome.Class', {
414 * someProperty: 'something',
415 * someMethod: function() { ... }
420 * alert(this === My.awesome.Class); // alerts true
422 * var myInstance = new this();
425 * {@link Ext#define Ext.define} is alias for {@link Ext.ClassManager#create Ext.ClassManager.create}.
427 * @param {String} className The class name to create in string dot-namespaced format, for example:
428 * 'My.very.awesome.Class', 'FeedViewer.plugin.CoolPager'
429 * It is highly recommended to follow this simple convention:
431 * - The root and the class name are 'CamelCased'
432 * - Everything else is lower-cased
434 * @param {Object} data The key - value pairs of properties to apply to this class. Property names can be of any valid
435 * strings, except those in the reserved list below:
437 * - {@link Ext.Base#self self}
438 * - {@link Ext.Class#alias alias}
439 * - {@link Ext.Class#alternateClassName alternateClassName}
440 * - {@link Ext.Class#config config}
441 * - {@link Ext.Class#extend extend}
442 * - {@link Ext.Class#inheritableStatics inheritableStatics}
443 * - {@link Ext.Class#mixins mixins}
444 * - {@link Ext.Class#requires requires}
445 * - {@link Ext.Class#singleton singleton}
446 * - {@link Ext.Class#statics statics}
447 * - {@link Ext.Class#uses uses}
449 * @param {Function} createdFn Optional callback to execute after the class is created, the execution scope of which
450 * (`this`) will be the newly created class itself.
453 create: function(className, data, createdFn) {
457 if (typeof className !== 'string') {
460 sourceMethod: "define",
461 msg: "Invalid class name '" + className + "' specified, must be a non-empty string"
466 data.$className = className;
468 return new Class(data, function() {
469 var postprocessorStack = data.postprocessors || manager.defaultPostprocessors,
470 registeredPostprocessors = manager.postprocessors,
473 postprocessor, postprocessors, process, i, ln;
475 delete data.postprocessors;
477 for (i = 0, ln = postprocessorStack.length; i < ln; i++) {
478 postprocessor = postprocessorStack[i];
480 if (typeof postprocessor === 'string') {
481 postprocessor = registeredPostprocessors[postprocessor];
483 if (!postprocessor.always) {
484 if (data[postprocessor.name] !== undefined) {
485 postprocessors.push(postprocessor.fn);
489 postprocessors.push(postprocessor.fn);
493 postprocessors.push(postprocessor);
497 process = function(clsName, cls, clsData) {
498 postprocessor = postprocessors[index++];
500 if (!postprocessor) {
501 manager.set(className, cls);
503 Ext.Loader.historyPush(className);
506 createdFn.call(cls, cls);
512 if (postprocessor.call(this, clsName, cls, clsData, process) !== false) {
513 process.apply(this, arguments);
517 process.call(manager, className, this, data);
522 * Instantiate a class by its alias.
524 * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
525 * attempt to load the class via synchronous loading.
527 * var window = Ext.ClassManager.instantiateByAlias('widget.window', { width: 600, height: 800, ... });
529 * {@link Ext#createByAlias Ext.createByAlias} is alias for {@link Ext.ClassManager#instantiateByAlias Ext.ClassManager.instantiateByAlias}.
531 * @param {String} alias
532 * @param {Mixed} args,... Additional arguments after the alias will be passed to the
534 * @return {Object} instance
536 instantiateByAlias: function() {
537 var alias = arguments[0],
538 args = slice.call(arguments),
539 className = this.getNameByAlias(alias);
542 className = this.maps.aliasToName[alias];
548 sourceMethod: "createByAlias",
549 msg: "Cannot create an instance of unrecognized alias: " + alias
555 if (Ext.global.console) {
556 Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + className + "'; consider adding " +
557 "Ext.require('" + alias + "') above Ext.onReady");
561 Ext.syncRequire(className);
566 return this.instantiate.apply(this, args);
570 * Instantiate a class by either full name, alias or alternate name.
572 * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
573 * attempt to load the class via synchronous loading.
575 * For example, all these three lines return the same result:
578 * var window = Ext.ClassManager.instantiate('widget.window', { width: 600, height: 800, ... });
581 * var window = Ext.ClassManager.instantiate('Ext.Window', { width: 600, height: 800, ... });
584 * var window = Ext.ClassManager.instantiate('Ext.window.Window', { width: 600, height: 800, ... });
586 * {@link Ext#create Ext.create} is alias for {@link Ext.ClassManager#instantiate Ext.ClassManager.instantiate}.
588 * @param {String} name
589 * @param {Mixed} args,... Additional arguments after the name will be passed to the class' constructor.
590 * @return {Object} instance
592 instantiate: function() {
593 var name = arguments[0],
594 args = slice.call(arguments, 1),
598 if (typeof name !== 'function') {
600 if ((typeof name !== 'string' || name.length < 1)) {
603 sourceMethod: "create",
604 msg: "Invalid class name or alias '" + name + "' specified, must be a non-empty string"
609 cls = this.get(name);
615 // No record of this class name, it's possibly an alias, so look it up
617 possibleName = this.getNameByAlias(name);
622 cls = this.get(name);
626 // Still no record of this class name, it's possibly an alternate name, so look it up
628 possibleName = this.getNameByAlternate(name);
633 cls = this.get(name);
637 // Still not existing at this point, try to load it via synchronous mode as the last resort
640 if (Ext.global.console) {
641 Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + name + "'; consider adding " +
642 "Ext.require('" + ((possibleName) ? alias : name) + "') above Ext.onReady");
646 Ext.syncRequire(name);
648 cls = this.get(name);
655 sourceMethod: "create",
656 msg: "Cannot create an instance of unrecognized class name / alias: " + alias
660 if (typeof cls !== 'function') {
663 sourceMethod: "create",
664 msg: "'" + name + "' is a singleton and cannot be instantiated"
670 if (!this.instantiationCounts[name]) {
671 this.instantiationCounts[name] = 0;
674 this.instantiationCounts[name]++;
677 return this.getInstantiator(args.length)(cls, args);
685 dynInstantiate: function(name, args) {
686 args = Ext.Array.from(args, true);
689 return this.instantiate.apply(this, args);
696 getInstantiator: function(length) {
697 if (!this.instantiators[length]) {
701 for (i = 0; i < length; i++) {
702 args.push('a['+i+']');
705 this.instantiators[length] = new Function('c', 'a', 'return new c('+args.join(',')+')');
708 return this.instantiators[length];
719 defaultPostprocessors: [],
722 * Register a post-processor function.
724 * @param {String} name
725 * @param {Function} postprocessor
727 registerPostprocessor: function(name, fn, always) {
728 this.postprocessors[name] = {
730 always: always || false,
738 * Set the default post processors array stack which are applied to every class.
740 * @param {String/Array} The name of a registered post processor or an array of registered names.
741 * @return {Ext.ClassManager} this
743 setDefaultPostprocessors: function(postprocessors) {
744 this.defaultPostprocessors = Ext.Array.from(postprocessors);
750 * Insert this post-processor at a specific position in the stack, optionally relative to
751 * any existing post-processor
753 * @param {String} name The post-processor name. Note that it needs to be registered with
754 * {@link Ext.ClassManager#registerPostprocessor} before this
755 * @param {String} offset The insertion position. Four possible values are:
756 * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
757 * @param {String} relativeName
758 * @return {Ext.ClassManager} this
760 setDefaultPostprocessorPosition: function(name, offset, relativeName) {
761 var defaultPostprocessors = this.defaultPostprocessors,
764 if (typeof offset === 'string') {
765 if (offset === 'first') {
766 defaultPostprocessors.unshift(name);
770 else if (offset === 'last') {
771 defaultPostprocessors.push(name);
776 offset = (offset === 'after') ? 1 : -1;
779 index = Ext.Array.indexOf(defaultPostprocessors, relativeName);
782 Ext.Array.splice(defaultPostprocessors, Math.max(0, index + offset), 0, name);
789 * Converts a string expression to an array of matching class names. An expression can either refers to class aliases
790 * or class names. Expressions support wildcards:
792 * // returns ['Ext.window.Window']
793 * var window = Ext.ClassManager.getNamesByExpression('widget.window');
795 * // returns ['widget.panel', 'widget.window', ...]
796 * var allWidgets = Ext.ClassManager.getNamesByExpression('widget.*');
798 * // returns ['Ext.data.Store', 'Ext.data.ArrayProxy', ...]
799 * var allData = Ext.ClassManager.getNamesByExpression('Ext.data.*');
801 * @param {String} expression
802 * @return {Array} classNames
805 getNamesByExpression: function(expression) {
806 var nameToAliasesMap = this.maps.nameToAliases,
808 name, alias, aliases, possibleName, regex, i, ln;
811 if (typeof expression !== 'string' || expression.length < 1) {
813 sourceClass: "Ext.ClassManager",
814 sourceMethod: "getNamesByExpression",
815 msg: "Expression " + expression + " is invalid, must be a non-empty string"
820 if (expression.indexOf('*') !== -1) {
821 expression = expression.replace(/\*/g, '(.*?)');
822 regex = new RegExp('^' + expression + '$');
824 for (name in nameToAliasesMap) {
825 if (nameToAliasesMap.hasOwnProperty(name)) {
826 aliases = nameToAliasesMap[name];
828 if (name.search(regex) !== -1) {
832 for (i = 0, ln = aliases.length; i < ln; i++) {
835 if (alias.search(regex) !== -1) {
845 possibleName = this.getNameByAlias(expression);
848 names.push(possibleName);
850 possibleName = this.getNameByAlternate(expression);
853 names.push(possibleName);
855 names.push(expression);
865 * @cfg {[String]} alias
867 * List of short aliases for class names. Most useful for defining xtypes for widgets:
869 * Ext.define('MyApp.CoolPanel', {
870 * extend: 'Ext.panel.Panel',
871 * alias: ['widget.coolpanel'],
875 * // Using Ext.create
876 * Ext.widget('widget.coolpanel');
877 * // Using the shorthand for widgets and in xtypes
878 * Ext.widget('panel', {
880 * {xtype: 'coolpanel', html: 'Foo'},
881 * {xtype: 'coolpanel', html: 'Bar'}
885 Manager.registerPostprocessor('alias', function(name, cls, data) {
886 var aliases = data.alias,
887 widgetPrefix = 'widget.',
890 if (!(aliases instanceof Array)) {
894 for (i = 0, ln = aliases.length; i < ln; i++) {
898 if (typeof alias !== 'string') {
901 sourceMethod: "define",
902 msg: "Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string"
907 this.setAlias(cls, alias);
910 // This is ugly, will change to make use of parseNamespace for alias later on
911 for (i = 0, ln = aliases.length; i < ln; i++) {
914 if (alias.substring(0, widgetPrefix.length) === widgetPrefix) {
915 // Only the first alias with 'widget.' prefix will be used for xtype
916 cls.xtype = cls.$xtype = alias.substring(widgetPrefix.length);
923 * @cfg {Boolean} singleton
925 * When set to true, the class will be instanciated as singleton. For example:
927 * Ext.define('Logger', {
929 * log: function(msg) {
934 * Logger.log('Hello');
936 Manager.registerPostprocessor('singleton', function(name, cls, data, fn) {
937 fn.call(this, name, new cls(), data);
942 * @cfg {String/[String]} alternateClassName
944 * Defines alternate names for this class. For example:
946 * Ext.define('Developer', {
947 * alternateClassName: ['Coder', 'Hacker'],
948 * code: function(msg) {
949 * alert('Typing... ' + msg);
953 * var joe = Ext.create('Developer');
954 * joe.code('stackoverflow');
956 * var rms = Ext.create('Hacker');
957 * rms.code('hack hack');
959 Manager.registerPostprocessor('alternateClassName', function(name, cls, data) {
960 var alternates = data.alternateClassName,
963 if (!(alternates instanceof Array)) {
964 alternates = [alternates];
967 for (i = 0, ln = alternates.length; i < ln; i++) {
968 alternate = alternates[i];
971 if (typeof alternate !== 'string') {
974 sourceMethod: "define",
975 msg: "Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string"
980 this.set(alternate, cls);
984 Manager.setDefaultPostprocessors(['alias', 'singleton', 'alternateClassName']);
990 * @alias Ext.ClassManager#instantiate
992 create: alias(Manager, 'instantiate'),
996 * API to be stablized
998 * @param {Mixed} item
999 * @param {String} namespace
1001 factory: function(item, namespace) {
1002 if (item instanceof Array) {
1005 for (i = 0, ln = item.length; i < ln; i++) {
1006 item[i] = Ext.factory(item[i], namespace);
1012 var isString = (typeof item === 'string');
1014 if (isString || (item instanceof Object && item.constructor === Object)) {
1015 var name, config = {};
1021 name = item.className;
1023 delete config.className;
1026 if (namespace !== undefined && name.indexOf(namespace) === -1) {
1027 name = namespace + '.' + Ext.String.capitalize(name);
1030 return Ext.create(name, config);
1033 if (typeof item === 'function') {
1034 return Ext.create(item);
1041 * Convenient shorthand to create a widget by its xtype, also see {@link Ext.ClassManager#instantiateByAlias}
1043 * var button = Ext.widget('button'); // Equivalent to Ext.create('widget.button')
1044 * var panel = Ext.widget('panel'); // Equivalent to Ext.create('widget.panel')
1048 * @param {String} name xtype of the widget to create.
1049 * @return {Object} widget instance
1051 widget: function(name) {
1052 var args = slice.call(arguments);
1053 args[0] = 'widget.' + name;
1055 return Manager.instantiateByAlias.apply(Manager, args);
1061 * @alias Ext.ClassManager#instantiateByAlias
1063 createByAlias: alias(Manager, 'instantiateByAlias'),
1068 * @alias Ext.ClassManager#create
1070 define: alias(Manager, 'create'),
1075 * @alias Ext.ClassManager#getName
1077 getClassName: alias(Manager, 'getName'),
1081 * @param {Mixed} object
1083 getDisplayName: function(object) {
1084 if (object.displayName) {
1085 return object.displayName;
1088 if (object.$name && object.$class) {
1089 return Ext.getClassName(object.$class) + '#' + object.$name;
1092 if (object.$className) {
1093 return object.$className;
1102 * @alias Ext.ClassManager#getClass
1104 getClass: alias(Manager, 'getClass'),
1107 * Creates namespaces to be used for scoping variables and classes so that they are not global.
1108 * Specifying the last node of a namespace implicitly creates all other nodes. Usage:
1110 * Ext.namespace('Company', 'Company.data');
1112 * // equivalent and preferable to the above syntax
1113 * Ext.namespace('Company.data');
1115 * Company.Widget = function() { ... };
1117 * Company.data.CustomStore = function(config) { ... };
1121 * @param {String} namespace1
1122 * @param {String} namespace2
1123 * @param {String} etc
1124 * @return {Object} The namespace object. (If multiple arguments are passed, this will be the last namespace created)
1126 namespace: alias(Manager, 'createNamespaces')
1130 * Old name for {@link Ext#widget}.
1131 * @deprecated 4.0.0 Use {@link Ext#widget} instead.
1136 Ext.createWidget = Ext.widget;
1139 * Convenient alias for {@link Ext#namespace Ext.namespace}
1142 * @alias Ext#namespace
1144 Ext.ns = Ext.namespace;
1146 Class.registerPreprocessor('className', function(cls, data) {
1147 if (data.$className) {
1148 cls.$className = data.$className;
1150 cls.displayName = cls.$className;
1155 Class.setDefaultPreprocessorPosition('className', 'first');
1157 })(Ext.Class, Ext.Function.alias);