Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / core / src / class / ClassManager.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
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.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @author Jacky Nguyen <jacky@sencha.com>
17  * @docauthor Jacky Nguyen <jacky@sencha.com>
18  * @class Ext.ClassManager
19  *
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:
23  *
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}
29  *
30  * @singleton
31  */
32 (function(Class, alias) {
33
34     var slice = Array.prototype.slice;
35
36     var Manager = Ext.ClassManager = {
37
38         /**
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.
42          * @private
43          */
44         classes: {},
45
46         /**
47          * @private
48          */
49         existCache: {},
50
51         /**
52          * @private
53          */
54         namespaceRewrites: [{
55             from: 'Ext.',
56             to: Ext
57         }],
58
59         /**
60          * @private
61          */
62         maps: {
63             alternateToName: {},
64             aliasToName: {},
65             nameToAliases: {}
66         },
67
68         /** @private */
69         enableNamespaceParseCache: true,
70
71         /** @private */
72         namespaceParseCache: {},
73
74         /** @private */
75         instantiators: [],
76
77         //<debug>
78         /** @private */
79         instantiationCounts: {},
80         //</debug>
81
82         /**
83          * Checks if a class has already been created.
84          *
85          * @param {String} className
86          * @return {Boolean} exist
87          */
88         isCreated: function(className) {
89             var i, ln, part, root, parts;
90
91             //<debug error>
92             if (typeof className !== 'string' || className.length < 1) {
93                 Ext.Error.raise({
94                     sourceClass: "Ext.ClassManager",
95                     sourceMethod: "exist",
96                     msg: "Invalid classname, must be a string and must not be empty"
97                 });
98             }
99             //</debug>
100
101             if (this.classes.hasOwnProperty(className) || this.existCache.hasOwnProperty(className)) {
102                 return true;
103             }
104
105             root = Ext.global;
106             parts = this.parseNamespace(className);
107
108             for (i = 0, ln = parts.length; i < ln; i++) {
109                 part = parts[i];
110
111                 if (typeof part !== 'string') {
112                     root = part;
113                 } else {
114                     if (!root || !root[part]) {
115                         return false;
116                     }
117
118                     root = root[part];
119                 }
120             }
121
122             Ext.Loader.historyPush(className);
123
124             this.existCache[className] = true;
125
126             return true;
127         },
128
129         /**
130          * Supports namespace rewriting
131          * @private
132          */
133         parseNamespace: function(namespace) {
134             //<debug error>
135             if (typeof namespace !== 'string') {
136                 Ext.Error.raise({
137                     sourceClass: "Ext.ClassManager",
138                     sourceMethod: "parseNamespace",
139                     msg: "Invalid namespace, must be a string"
140                 });
141             }
142             //</debug>
143
144             var cache = this.namespaceParseCache;
145
146             if (this.enableNamespaceParseCache) {
147                 if (cache.hasOwnProperty(namespace)) {
148                     return cache[namespace];
149                 }
150             }
151
152             var parts = [],
153                 rewrites = this.namespaceRewrites,
154                 rewrite, from, to, i, ln, root = Ext.global;
155
156             for (i = 0, ln = rewrites.length; i < ln; i++) {
157                 rewrite = rewrites[i];
158                 from = rewrite.from;
159                 to = rewrite.to;
160
161                 if (namespace === from || namespace.substring(0, from.length) === from) {
162                     namespace = namespace.substring(from.length);
163
164                     if (typeof to !== 'string') {
165                         root = to;
166                     } else {
167                         parts = parts.concat(to.split('.'));
168                     }
169
170                     break;
171                 }
172             }
173
174             parts.push(root);
175
176             parts = parts.concat(namespace.split('.'));
177
178             if (this.enableNamespaceParseCache) {
179                 cache[namespace] = parts;
180             }
181
182             return parts;
183         },
184
185         /**
186          * Creates a namespace and assign the `value` to the created object
187          *
188          *     Ext.ClassManager.setNamespace('MyCompany.pkg.Example', someObject);
189          *
190          *     alert(MyCompany.pkg.Example === someObject); // alerts true
191          *
192          * @param {String} name
193          * @param {Mixed} value
194          */
195         setNamespace: function(name, value) {
196             var root = Ext.global,
197                 parts = this.parseNamespace(name),
198                 leaf = parts.pop(),
199                 i, ln, part;
200
201             for (i = 0, ln = parts.length; i < ln; i++) {
202                 part = parts[i];
203
204                 if (typeof part !== 'string') {
205                     root = part;
206                 } else {
207                     if (!root[part]) {
208                         root[part] = {};
209                     }
210
211                     root = root[part];
212                 }
213             }
214
215             root[leaf] = value;
216
217             return root[leaf];
218         },
219
220         /**
221          * The new Ext.ns, supports namespace rewriting
222          * @private
223          */
224         createNamespaces: function() {
225             var root = Ext.global,
226                 parts, part, i, j, ln, subLn;
227
228             for (i = 0, ln = arguments.length; i < ln; i++) {
229                 parts = this.parseNamespace(arguments[i]);
230
231                 for (j = 0, subLn = parts.length; j < subLn; j++) {
232                     part = parts[j];
233
234                     if (typeof part !== 'string') {
235                         root = part;
236                     } else {
237                         if (!root[part]) {
238                             root[part] = {};
239                         }
240
241                         root = root[part];
242                     }
243                 }
244             }
245
246             return root;
247         },
248
249         /**
250          * Sets a name reference to a class.
251          *
252          * @param {String} name
253          * @param {Object} value
254          * @return {Ext.ClassManager} this
255          */
256         set: function(name, value) {
257             var targetName = this.getName(value);
258
259             this.classes[name] = this.setNamespace(name, value);
260
261             if (targetName && targetName !== name) {
262                 this.maps.alternateToName[name] = targetName;
263             }
264
265             return this;
266         },
267
268         /**
269          * Retrieve a class by its name.
270          *
271          * @param {String} name
272          * @return {Class} class
273          */
274         get: function(name) {
275             if (this.classes.hasOwnProperty(name)) {
276                 return this.classes[name];
277             }
278
279             var root = Ext.global,
280                 parts = this.parseNamespace(name),
281                 part, i, ln;
282
283             for (i = 0, ln = parts.length; i < ln; i++) {
284                 part = parts[i];
285
286                 if (typeof part !== 'string') {
287                     root = part;
288                 } else {
289                     if (!root || !root[part]) {
290                         return null;
291                     }
292
293                     root = root[part];
294                 }
295             }
296
297             return root;
298         },
299
300         /**
301          * Register the alias for a class.
302          *
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
305          */
306         setAlias: function(cls, alias) {
307             var aliasToNameMap = this.maps.aliasToName,
308                 nameToAliasesMap = this.maps.nameToAliases,
309                 className;
310
311             if (typeof cls === 'string') {
312                 className = cls;
313             } else {
314                 className = this.getName(cls);
315             }
316
317             if (alias && aliasToNameMap[alias] !== className) {
318                 //<debug info>
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.");
322                 }
323                 //</debug>
324
325                 aliasToNameMap[alias] = className;
326             }
327
328             if (!nameToAliasesMap[className]) {
329                 nameToAliasesMap[className] = [];
330             }
331
332             if (alias) {
333                 Ext.Array.include(nameToAliasesMap[className], alias);
334             }
335
336             return this;
337         },
338
339         /**
340          * Get a reference to the class by its alias.
341          *
342          * @param {String} alias
343          * @return {Class} class
344          */
345         getByAlias: function(alias) {
346             return this.get(this.getNameByAlias(alias));
347         },
348
349         /**
350          * Get the name of a class by its alias.
351          *
352          * @param {String} alias
353          * @return {String} className
354          */
355         getNameByAlias: function(alias) {
356             return this.maps.aliasToName[alias] || '';
357         },
358
359         /**
360          * Get the name of a class by its alternate name.
361          *
362          * @param {String} alternate
363          * @return {String} className
364          */
365         getNameByAlternate: function(alternate) {
366             return this.maps.alternateToName[alternate] || '';
367         },
368
369         /**
370          * Get the aliases of a class by the class name
371          *
372          * @param {String} name
373          * @return {Array} aliases
374          */
375         getAliasesByName: function(name) {
376             return this.maps.nameToAliases[name] || [];
377         },
378
379         /**
380          * Get the name of the class by its reference or its instance.
381          *
382          *     Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action"
383          *
384          * {@link Ext#getClassName Ext.getClassName} is alias for {@link Ext.ClassManager#getName Ext.ClassManager.getName}.
385          *
386          * @param {Class/Object} object
387          * @return {String} className
388          */
389         getName: function(object) {
390             return object && object.$className || '';
391         },
392
393         /**
394          * Get the class of the provided object; returns null if it's not an instance
395          * of any class created with Ext.define.
396          *
397          *     var component = new Ext.Component();
398          *
399          *     Ext.ClassManager.getClass(component); // returns Ext.Component
400          *
401          * {@link Ext#getClass Ext.getClass} is alias for {@link Ext.ClassManager#getClass Ext.ClassManager.getClass}.
402          *
403          * @param {Object} object
404          * @return {Class} class
405          */
406         getClass: function(object) {
407             return object && object.self || null;
408         },
409
410         /**
411          * Defines a class.
412          *
413          *     Ext.ClassManager.create('My.awesome.Class', {
414          *         someProperty: 'something',
415          *         someMethod: function() { ... }
416          *         ...
417          *
418          *     }, function() {
419          *         alert('Created!');
420          *         alert(this === My.awesome.Class); // alerts true
421          *
422          *         var myInstance = new this();
423          *     });
424          *
425          * {@link Ext#define Ext.define} is alias for {@link Ext.ClassManager#create Ext.ClassManager.create}.
426          *
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:
430          *
431          * - The root and the class name are 'CamelCased'
432          * - Everything else is lower-cased
433          *
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:
436          *
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}
448          *
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.
451          * @return {Ext.Base}
452          */
453         create: function(className, data, createdFn) {
454             var manager = this;
455
456             //<debug error>
457             if (typeof className !== 'string') {
458                 Ext.Error.raise({
459                     sourceClass: "Ext",
460                     sourceMethod: "define",
461                     msg: "Invalid class name '" + className + "' specified, must be a non-empty string"
462                 });
463             }
464             //</debug>
465
466             data.$className = className;
467
468             return new Class(data, function() {
469                 var postprocessorStack = data.postprocessors || manager.defaultPostprocessors,
470                     registeredPostprocessors = manager.postprocessors,
471                     index = 0,
472                     postprocessors = [],
473                     postprocessor, postprocessors, process, i, ln;
474
475                 delete data.postprocessors;
476
477                 for (i = 0, ln = postprocessorStack.length; i < ln; i++) {
478                     postprocessor = postprocessorStack[i];
479
480                     if (typeof postprocessor === 'string') {
481                         postprocessor = registeredPostprocessors[postprocessor];
482
483                         if (!postprocessor.always) {
484                             if (data[postprocessor.name] !== undefined) {
485                                 postprocessors.push(postprocessor.fn);
486                             }
487                         }
488                         else {
489                             postprocessors.push(postprocessor.fn);
490                         }
491                     }
492                     else {
493                         postprocessors.push(postprocessor);
494                     }
495                 }
496
497                 process = function(clsName, cls, clsData) {
498                     postprocessor = postprocessors[index++];
499
500                     if (!postprocessor) {
501                         manager.set(className, cls);
502
503                         Ext.Loader.historyPush(className);
504
505                         if (createdFn) {
506                             createdFn.call(cls, cls);
507                         }
508
509                         return;
510                     }
511
512                     if (postprocessor.call(this, clsName, cls, clsData, process) !== false) {
513                         process.apply(this, arguments);
514                     }
515                 };
516
517                 process.call(manager, className, this, data);
518             });
519         },
520
521         /**
522          * Instantiate a class by its alias.
523          *
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.
526          *
527          *     var window = Ext.ClassManager.instantiateByAlias('widget.window', { width: 600, height: 800, ... });
528          *
529          * {@link Ext#createByAlias Ext.createByAlias} is alias for {@link Ext.ClassManager#instantiateByAlias Ext.ClassManager.instantiateByAlias}.
530          *
531          * @param {String} alias
532          * @param {Mixed} args,... Additional arguments after the alias will be passed to the
533          * class constructor.
534          * @return {Object} instance
535          */
536         instantiateByAlias: function() {
537             var alias = arguments[0],
538                 args = slice.call(arguments),
539                 className = this.getNameByAlias(alias);
540
541             if (!className) {
542                 className = this.maps.aliasToName[alias];
543
544                 //<debug error>
545                 if (!className) {
546                     Ext.Error.raise({
547                         sourceClass: "Ext",
548                         sourceMethod: "createByAlias",
549                         msg: "Cannot create an instance of unrecognized alias: " + alias
550                     });
551                 }
552                 //</debug>
553
554                 //<debug warn>
555                 if (Ext.global.console) {
556                     Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + className + "'; consider adding " +
557                          "Ext.require('" + alias + "') above Ext.onReady");
558                 }
559                 //</debug>
560
561                 Ext.syncRequire(className);
562             }
563
564             args[0] = className;
565
566             return this.instantiate.apply(this, args);
567         },
568
569         /**
570          * Instantiate a class by either full name, alias or alternate name.
571          *
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.
574          *
575          * For example, all these three lines return the same result:
576          *
577          *     // alias
578          *     var window = Ext.ClassManager.instantiate('widget.window', { width: 600, height: 800, ... });
579          *
580          *     // alternate name
581          *     var window = Ext.ClassManager.instantiate('Ext.Window', { width: 600, height: 800, ... });
582          *
583          *     // full class name
584          *     var window = Ext.ClassManager.instantiate('Ext.window.Window', { width: 600, height: 800, ... });
585          *
586          * {@link Ext#create Ext.create} is alias for {@link Ext.ClassManager#instantiate Ext.ClassManager.instantiate}.
587          *
588          * @param {String} name
589          * @param {Mixed} args,... Additional arguments after the name will be passed to the class' constructor.
590          * @return {Object} instance
591          */
592         instantiate: function() {
593             var name = arguments[0],
594                 args = slice.call(arguments, 1),
595                 alias = name,
596                 possibleName, cls;
597
598             if (typeof name !== 'function') {
599                 //<debug error>
600                 if ((typeof name !== 'string' || name.length < 1)) {
601                     Ext.Error.raise({
602                         sourceClass: "Ext",
603                         sourceMethod: "create",
604                         msg: "Invalid class name or alias '" + name + "' specified, must be a non-empty string"
605                     });
606                 }
607                 //</debug>
608
609                 cls = this.get(name);
610             }
611             else {
612                 cls = name;
613             }
614
615             // No record of this class name, it's possibly an alias, so look it up
616             if (!cls) {
617                 possibleName = this.getNameByAlias(name);
618
619                 if (possibleName) {
620                     name = possibleName;
621
622                     cls = this.get(name);
623                 }
624             }
625
626             // Still no record of this class name, it's possibly an alternate name, so look it up
627             if (!cls) {
628                 possibleName = this.getNameByAlternate(name);
629
630                 if (possibleName) {
631                     name = possibleName;
632
633                     cls = this.get(name);
634                 }
635             }
636
637             // Still not existing at this point, try to load it via synchronous mode as the last resort
638             if (!cls) {
639                 //<debug warn>
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");
643                 }
644                 //</debug>
645
646                 Ext.syncRequire(name);
647
648                 cls = this.get(name);
649             }
650
651             //<debug error>
652             if (!cls) {
653                 Ext.Error.raise({
654                     sourceClass: "Ext",
655                     sourceMethod: "create",
656                     msg: "Cannot create an instance of unrecognized class name / alias: " + alias
657                 });
658             }
659
660             if (typeof cls !== 'function') {
661                 Ext.Error.raise({
662                     sourceClass: "Ext",
663                     sourceMethod: "create",
664                     msg: "'" + name + "' is a singleton and cannot be instantiated"
665                 });
666             }
667             //</debug>
668
669             //<debug>
670             if (!this.instantiationCounts[name]) {
671                 this.instantiationCounts[name] = 0;
672             }
673
674             this.instantiationCounts[name]++;
675             //</debug>
676
677             return this.getInstantiator(args.length)(cls, args);
678         },
679
680         /**
681          * @private
682          * @param name
683          * @param args
684          */
685         dynInstantiate: function(name, args) {
686             args = Ext.Array.from(args, true);
687             args.unshift(name);
688
689             return this.instantiate.apply(this, args);
690         },
691
692         /**
693          * @private
694          * @param length
695          */
696         getInstantiator: function(length) {
697             if (!this.instantiators[length]) {
698                 var i = length,
699                     args = [];
700
701                 for (i = 0; i < length; i++) {
702                     args.push('a['+i+']');
703                 }
704
705                 this.instantiators[length] = new Function('c', 'a', 'return new c('+args.join(',')+')');
706             }
707
708             return this.instantiators[length];
709         },
710
711         /**
712          * @private
713          */
714         postprocessors: {},
715
716         /**
717          * @private
718          */
719         defaultPostprocessors: [],
720
721         /**
722          * Register a post-processor function.
723          *
724          * @param {String} name
725          * @param {Function} postprocessor
726          */
727         registerPostprocessor: function(name, fn, always) {
728             this.postprocessors[name] = {
729                 name: name,
730                 always: always ||  false,
731                 fn: fn
732             };
733
734             return this;
735         },
736
737         /**
738          * Set the default post processors array stack which are applied to every class.
739          *
740          * @param {String/Array} The name of a registered post processor or an array of registered names.
741          * @return {Ext.ClassManager} this
742          */
743         setDefaultPostprocessors: function(postprocessors) {
744             this.defaultPostprocessors = Ext.Array.from(postprocessors);
745
746             return this;
747         },
748
749         /**
750          * Insert this post-processor at a specific position in the stack, optionally relative to
751          * any existing post-processor
752          *
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
759          */
760         setDefaultPostprocessorPosition: function(name, offset, relativeName) {
761             var defaultPostprocessors = this.defaultPostprocessors,
762                 index;
763
764             if (typeof offset === 'string') {
765                 if (offset === 'first') {
766                     defaultPostprocessors.unshift(name);
767
768                     return this;
769                 }
770                 else if (offset === 'last') {
771                     defaultPostprocessors.push(name);
772
773                     return this;
774                 }
775
776                 offset = (offset === 'after') ? 1 : -1;
777             }
778
779             index = Ext.Array.indexOf(defaultPostprocessors, relativeName);
780
781             if (index !== -1) {
782                 Ext.Array.splice(defaultPostprocessors, Math.max(0, index + offset), 0, name);
783             }
784
785             return this;
786         },
787
788         /**
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:
791          *
792          *     // returns ['Ext.window.Window']
793          *     var window = Ext.ClassManager.getNamesByExpression('widget.window');
794          *
795          *     // returns ['widget.panel', 'widget.window', ...]
796          *     var allWidgets = Ext.ClassManager.getNamesByExpression('widget.*');
797          *
798          *     // returns ['Ext.data.Store', 'Ext.data.ArrayProxy', ...]
799          *     var allData = Ext.ClassManager.getNamesByExpression('Ext.data.*');
800          *
801          * @param {String} expression
802          * @return {Array} classNames
803          * @markdown
804          */
805         getNamesByExpression: function(expression) {
806             var nameToAliasesMap = this.maps.nameToAliases,
807                 names = [],
808                 name, alias, aliases, possibleName, regex, i, ln;
809
810             //<debug error>
811             if (typeof expression !== 'string' || expression.length < 1) {
812                 Ext.Error.raise({
813                     sourceClass: "Ext.ClassManager",
814                     sourceMethod: "getNamesByExpression",
815                     msg: "Expression " + expression + " is invalid, must be a non-empty string"
816                 });
817             }
818             //</debug>
819
820             if (expression.indexOf('*') !== -1) {
821                 expression = expression.replace(/\*/g, '(.*?)');
822                 regex = new RegExp('^' + expression + '$');
823
824                 for (name in nameToAliasesMap) {
825                     if (nameToAliasesMap.hasOwnProperty(name)) {
826                         aliases = nameToAliasesMap[name];
827
828                         if (name.search(regex) !== -1) {
829                             names.push(name);
830                         }
831                         else {
832                             for (i = 0, ln = aliases.length; i < ln; i++) {
833                                 alias = aliases[i];
834
835                                 if (alias.search(regex) !== -1) {
836                                     names.push(name);
837                                     break;
838                                 }
839                             }
840                         }
841                     }
842                 }
843
844             } else {
845                 possibleName = this.getNameByAlias(expression);
846
847                 if (possibleName) {
848                     names.push(possibleName);
849                 } else {
850                     possibleName = this.getNameByAlternate(expression);
851
852                     if (possibleName) {
853                         names.push(possibleName);
854                     } else {
855                         names.push(expression);
856                     }
857                 }
858             }
859
860             return names;
861         }
862     };
863
864     /**
865      * @cfg {[String]} alias
866      * @member Ext.Class
867      * List of short aliases for class names.  Most useful for defining xtypes for widgets:
868      *
869      *     Ext.define('MyApp.CoolPanel', {
870      *         extend: 'Ext.panel.Panel',
871      *         alias: ['widget.coolpanel'],
872      *         title: 'Yeah!'
873      *     });
874      *
875      *     // Using Ext.create
876      *     Ext.widget('widget.coolpanel');
877      *     // Using the shorthand for widgets and in xtypes
878      *     Ext.widget('panel', {
879      *         items: [
880      *             {xtype: 'coolpanel', html: 'Foo'},
881      *             {xtype: 'coolpanel', html: 'Bar'}
882      *         ]
883      *     });
884      */
885     Manager.registerPostprocessor('alias', function(name, cls, data) {
886         var aliases = data.alias,
887             widgetPrefix = 'widget.',
888             i, ln, alias;
889
890         if (!(aliases instanceof Array)) {
891             aliases = [aliases];
892         }
893
894         for (i = 0, ln = aliases.length; i < ln; i++) {
895             alias = aliases[i];
896
897             //<debug error>
898             if (typeof alias !== 'string') {
899                 Ext.Error.raise({
900                     sourceClass: "Ext",
901                     sourceMethod: "define",
902                     msg: "Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string"
903                 });
904             }
905             //</debug>
906
907             this.setAlias(cls, alias);
908         }
909
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++) {
912             alias = aliases[i];
913
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);
917                 break;
918             }
919         }
920     });
921
922     /**
923      * @cfg {Boolean} singleton
924      * @member Ext.Class
925      * When set to true, the class will be instanciated as singleton.  For example:
926      *
927      *     Ext.define('Logger', {
928      *         singleton: true,
929      *         log: function(msg) {
930      *             console.log(msg);
931      *         }
932      *     });
933      *
934      *     Logger.log('Hello');
935      */
936     Manager.registerPostprocessor('singleton', function(name, cls, data, fn) {
937         fn.call(this, name, new cls(), data);
938         return false;
939     });
940
941     /**
942      * @cfg {String/[String]} alternateClassName
943      * @member Ext.Class
944      * Defines alternate names for this class.  For example:
945      *
946      *     Ext.define('Developer', {
947      *         alternateClassName: ['Coder', 'Hacker'],
948      *         code: function(msg) {
949      *             alert('Typing... ' + msg);
950      *         }
951      *     });
952      *
953      *     var joe = Ext.create('Developer');
954      *     joe.code('stackoverflow');
955      *
956      *     var rms = Ext.create('Hacker');
957      *     rms.code('hack hack');
958      */
959     Manager.registerPostprocessor('alternateClassName', function(name, cls, data) {
960         var alternates = data.alternateClassName,
961             i, ln, alternate;
962
963         if (!(alternates instanceof Array)) {
964             alternates = [alternates];
965         }
966
967         for (i = 0, ln = alternates.length; i < ln; i++) {
968             alternate = alternates[i];
969
970             //<debug error>
971             if (typeof alternate !== 'string') {
972                 Ext.Error.raise({
973                     sourceClass: "Ext",
974                     sourceMethod: "define",
975                     msg: "Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string"
976                 });
977             }
978             //</debug>
979
980             this.set(alternate, cls);
981         }
982     });
983
984     Manager.setDefaultPostprocessors(['alias', 'singleton', 'alternateClassName']);
985
986     Ext.apply(Ext, {
987         /**
988          * @method
989          * @member Ext
990          * @alias Ext.ClassManager#instantiate
991          */
992         create: alias(Manager, 'instantiate'),
993
994         /**
995          * @private
996          * API to be stablized
997          *
998          * @param {Mixed} item
999          * @param {String} namespace
1000          */
1001         factory: function(item, namespace) {
1002             if (item instanceof Array) {
1003                 var i, ln;
1004
1005                 for (i = 0, ln = item.length; i < ln; i++) {
1006                     item[i] = Ext.factory(item[i], namespace);
1007                 }
1008
1009                 return item;
1010             }
1011
1012             var isString = (typeof item === 'string');
1013
1014             if (isString || (item instanceof Object && item.constructor === Object)) {
1015                 var name, config = {};
1016
1017                 if (isString) {
1018                     name = item;
1019                 }
1020                 else {
1021                     name = item.className;
1022                     config = item;
1023                     delete config.className;
1024                 }
1025
1026                 if (namespace !== undefined && name.indexOf(namespace) === -1) {
1027                     name = namespace + '.' + Ext.String.capitalize(name);
1028                 }
1029
1030                 return Ext.create(name, config);
1031             }
1032
1033             if (typeof item === 'function') {
1034                 return Ext.create(item);
1035             }
1036
1037             return item;
1038         },
1039
1040         /**
1041          * Convenient shorthand to create a widget by its xtype, also see {@link Ext.ClassManager#instantiateByAlias}
1042          *
1043          *     var button = Ext.widget('button'); // Equivalent to Ext.create('widget.button')
1044          *     var panel = Ext.widget('panel'); // Equivalent to Ext.create('widget.panel')
1045          *
1046          * @method
1047          * @member Ext
1048          * @param {String} name  xtype of the widget to create.
1049          * @return {Object} widget instance
1050          */
1051         widget: function(name) {
1052             var args = slice.call(arguments);
1053             args[0] = 'widget.' + name;
1054
1055             return Manager.instantiateByAlias.apply(Manager, args);
1056         },
1057
1058         /**
1059          * @method
1060          * @member Ext
1061          * @alias Ext.ClassManager#instantiateByAlias
1062          */
1063         createByAlias: alias(Manager, 'instantiateByAlias'),
1064
1065         /**
1066          * @method
1067          * @member Ext
1068          * @alias Ext.ClassManager#create
1069          */
1070         define: alias(Manager, 'create'),
1071
1072         /**
1073          * @method
1074          * @member Ext
1075          * @alias Ext.ClassManager#getName
1076          */
1077         getClassName: alias(Manager, 'getName'),
1078
1079         /**
1080          *
1081          * @param {Mixed} object
1082          */
1083         getDisplayName: function(object) {
1084             if (object.displayName) {
1085                 return object.displayName;
1086             }
1087
1088             if (object.$name && object.$class) {
1089                 return Ext.getClassName(object.$class) + '#' + object.$name;
1090             }
1091
1092             if (object.$className) {
1093                 return object.$className;
1094             }
1095
1096             return 'Anonymous';
1097         },
1098
1099         /**
1100          * @method
1101          * @member Ext
1102          * @alias Ext.ClassManager#getClass
1103          */
1104         getClass: alias(Manager, 'getClass'),
1105
1106         /**
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:
1109          *
1110          *     Ext.namespace('Company', 'Company.data');
1111          *
1112          *     // equivalent and preferable to the above syntax
1113          *     Ext.namespace('Company.data');
1114          *
1115          *     Company.Widget = function() { ... };
1116          *
1117          *     Company.data.CustomStore = function(config) { ... };
1118          *
1119          * @method
1120          * @member Ext
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)
1125          */
1126         namespace: alias(Manager, 'createNamespaces')
1127     });
1128
1129     /**
1130      * Old name for {@link Ext#widget}.
1131      * @deprecated 4.0.0 Use {@link Ext#widget} instead.
1132      * @method
1133      * @member Ext
1134      * @alias Ext#widget
1135      */
1136     Ext.createWidget = Ext.widget;
1137
1138     /**
1139      * Convenient alias for {@link Ext#namespace Ext.namespace}
1140      * @method
1141      * @member Ext
1142      * @alias Ext#namespace
1143      */
1144     Ext.ns = Ext.namespace;
1145
1146     Class.registerPreprocessor('className', function(cls, data) {
1147         if (data.$className) {
1148             cls.$className = data.$className;
1149             //<debug>
1150             cls.displayName = cls.$className;
1151             //</debug>
1152         }
1153     }, true);
1154
1155     Class.setDefaultPreprocessorPosition('className', 'first');
1156
1157 })(Ext.Class, Ext.Function.alias);
1158