]> git.proxmox.com Git - sencha-touch.git/blob - src/src/core/class/ClassManager.js
import Sencha Touch 2.4.2 source
[sencha-touch.git] / src / src / core / class / ClassManager.js
1 //@tag foundation,core
2 //@define Ext.ClassManager
3 //@require Ext.Class
4
5 /**
6 * @class Ext.ClassManager
7 * @author Jacky Nguyen <jacky@sencha.com>
8 *
9 * Ext.ClassManager manages all classes and handles mapping from string class name to
10 * actual class objects throughout the whole framework. It is not generally accessed directly, rather through
11 * these convenient shorthands:
12 *
13 * - {@link Ext#define Ext.define}
14 * - {@link Ext.ClassManager#create Ext.create}
15 * - {@link Ext#widget Ext.widget}
16 * - {@link Ext#getClass Ext.getClass}
17 * - {@link Ext#getClassName Ext.getClassName}
18 *
19 * For more information about Sencha Touch's Class System, please check out the [class system guide](../../../core_concepts/class_system.html).
20 *
21 * ## Basic syntax:
22 *
23 * Ext.define(className, properties);
24 *
25 * in which `properties` is an object represent a collection of properties that apply to the class. See
26 * {@link Ext.ClassManager#create} for more detailed instructions.
27 *
28 * @example
29 * Ext.define('Person', {
30 * name: 'Unknown',
31 *
32 * constructor: function(name) {
33 * if (name) {
34 * this.name = name;
35 * }
36 *
37 * return this;
38 * },
39 *
40 * eat: function(foodType) {
41 * alert("I'm eating: " + foodType);
42 *
43 * return this;
44 * }
45 * });
46 *
47 * var aaron = new Person("Aaron");
48 * aaron.eat("Sandwich"); // alert("I'm eating: Sandwich");
49 *
50 * Ext.Class has a powerful set of extensible {@link Ext.Class#registerPreprocessor pre-processors} which takes care of
51 * everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc.
52 *
53 * ## Inheritance:
54 *
55 * Ext.define('Developer', {
56 * extend: 'Person',
57 *
58 * constructor: function(name, isGeek) {
59 * this.isGeek = isGeek;
60 *
61 * // Apply a method from the parent class' prototype
62 * this.callParent([name]);
63 *
64 * return this;
65 *
66 * },
67 *
68 * code: function(language) {
69 * alert("I'm coding in: " + language);
70 *
71 * this.eat("Bugs");
72 *
73 * return this;
74 * }
75 * });
76 *
77 * var jacky = new Developer("Jacky", true);
78 * jacky.code("JavaScript"); // alert("I'm coding in: JavaScript");
79 * // alert("I'm eating: Bugs");
80 *
81 * See {@link Ext.Base#callParent} for more details on calling superclass' methods
82 *
83 * ## Mixins:
84 *
85 * Ext.define('CanPlayGuitar', {
86 * playGuitar: function() {
87 * alert("F#...G...D...A");
88 * }
89 * });
90 *
91 * Ext.define('CanComposeSongs', {
92 * composeSongs: function() { }
93 * });
94 *
95 * Ext.define('CanSing', {
96 * sing: function() {
97 * alert("I'm on the highway to hell...");
98 * }
99 * });
100 *
101 * Ext.define('Musician', {
102 * extend: 'Person',
103 *
104 * mixins: {
105 * canPlayGuitar: 'CanPlayGuitar',
106 * canComposeSongs: 'CanComposeSongs',
107 * canSing: 'CanSing'
108 * }
109 * });
110 *
111 * Ext.define('CoolPerson', {
112 * extend: 'Person',
113 *
114 * mixins: {
115 * canPlayGuitar: 'CanPlayGuitar',
116 * canSing: 'CanSing'
117 * },
118 *
119 * sing: function() {
120 * alert("Ahem...");
121 *
122 * this.mixins.canSing.sing.call(this);
123 *
124 * alert("[Playing guitar at the same time...]");
125 *
126 * this.playGuitar();
127 * }
128 * });
129 *
130 * var me = new CoolPerson("Jacky");
131 *
132 * me.sing(); // alert("Ahem...");
133 * // alert("I'm on the highway to hell...");
134 * // alert("[Playing guitar at the same time...]");
135 * // alert("F#...G...D...A");
136 *
137 * ## Config:
138 *
139 * Ext.define('SmartPhone', {
140 * config: {
141 * hasTouchScreen: false,
142 * operatingSystem: 'Other',
143 * price: 500
144 * },
145 *
146 * isExpensive: false,
147 *
148 * constructor: function(config) {
149 * this.initConfig(config);
150 *
151 * return this;
152 * },
153 *
154 * applyPrice: function(price) {
155 * this.isExpensive = (price > 500);
156 *
157 * return price;
158 * },
159 *
160 * applyOperatingSystem: function(operatingSystem) {
161 * if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) {
162 * return 'Other';
163 * }
164 *
165 * return operatingSystem;
166 * }
167 * });
168 *
169 * var iPhone = new SmartPhone({
170 * hasTouchScreen: true,
171 * operatingSystem: 'iOS'
172 * });
173 *
174 * iPhone.getPrice(); // 500;
175 * iPhone.getOperatingSystem(); // 'iOS'
176 * iPhone.getHasTouchScreen(); // true;
177 *
178 * iPhone.isExpensive; // false;
179 * iPhone.setPrice(600);
180 * iPhone.getPrice(); // 600
181 * iPhone.isExpensive; // true;
182 *
183 * iPhone.setOperatingSystem('AlienOS');
184 * iPhone.getOperatingSystem(); // 'Other'
185 *
186 * ## Statics:
187 *
188 * Ext.define('Computer', {
189 * statics: {
190 * factory: function(brand) {
191 * // 'this' in static methods refer to the class itself
192 * return new this(brand);
193 * }
194 * },
195 *
196 * constructor: function() { }
197 * });
198 *
199 * var dellComputer = Computer.factory('Dell');
200 *
201 * Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing
202 * static properties within class methods
203 *
204 * @singleton
205 */
206 (function(Class, alias, arraySlice, arrayFrom, global) {
207 //<if nonBrowser>
208 var isNonBrowser = typeof window == 'undefined';
209 //</if>
210 var Manager = Ext.ClassManager = {
211
212 /**
213 * @property classes
214 * @type Object
215 * All classes which were defined through the ClassManager. Keys are the
216 * name of the classes and the values are references to the classes.
217 * @private
218 */
219 classes: {},
220
221 /**
222 * @private
223 */
224 existCache: {},
225
226 /**
227 * @private
228 */
229 namespaceRewrites: [{
230 from: 'Ext.',
231 to: Ext
232 }],
233
234 /**
235 * @private
236 */
237 maps: {
238 alternateToName: {},
239 aliasToName: {},
240 nameToAliases: {},
241 nameToAlternates: {}
242 },
243
244 /** @private */
245 enableNamespaceParseCache: true,
246
247 /** @private */
248 namespaceParseCache: {},
249
250 /** @private */
251 instantiators: [],
252
253 /**
254 * Checks if a class has already been created.
255 *
256 * @param {String} className
257 * @return {Boolean} exist
258 */
259 isCreated: function(className) {
260 var existCache = this.existCache,
261 i, ln, part, root, parts;
262
263 //<debug error>
264 if (typeof className != 'string' || className.length < 1) {
265 throw new Error("[Ext.ClassManager] Invalid classname, must be a string and must not be empty");
266 }
267 //</debug>
268
269 if (this.classes[className] || existCache[className]) {
270 return true;
271 }
272
273 root = global;
274 parts = this.parseNamespace(className);
275
276 for (i = 0, ln = parts.length; i < ln; i++) {
277 part = parts[i];
278
279 if (typeof part != 'string') {
280 root = part;
281 } else {
282 if (!root || !root[part]) {
283 return false;
284 }
285
286 root = root[part];
287 }
288 }
289
290 existCache[className] = true;
291
292 this.triggerCreated(className);
293
294 return true;
295 },
296
297 /**
298 * @private
299 */
300 createdListeners: [],
301
302 /**
303 * @private
304 */
305 nameCreatedListeners: {},
306
307 /**
308 * @private
309 */
310 triggerCreated: function(className) {
311 var listeners = this.createdListeners,
312 nameListeners = this.nameCreatedListeners,
313 alternateNames = this.maps.nameToAlternates[className],
314 names = [className],
315 i, ln, j, subLn, listener, name;
316
317 for (i = 0,ln = listeners.length; i < ln; i++) {
318 listener = listeners[i];
319 listener.fn.call(listener.scope, className);
320 }
321
322 if (alternateNames) {
323 names.push.apply(names, alternateNames);
324 }
325
326 for (i = 0,ln = names.length; i < ln; i++) {
327 name = names[i];
328 listeners = nameListeners[name];
329
330 if (listeners) {
331 for (j = 0,subLn = listeners.length; j < subLn; j++) {
332 listener = listeners[j];
333 listener.fn.call(listener.scope, name);
334 }
335 delete nameListeners[name];
336 }
337 }
338 },
339
340 /**
341 * @private
342 */
343 onCreated: function(fn, scope, className) {
344 var listeners = this.createdListeners,
345 nameListeners = this.nameCreatedListeners,
346 listener = {
347 fn: fn,
348 scope: scope
349 };
350
351 if (className) {
352 if (this.isCreated(className)) {
353 fn.call(scope, className);
354 return;
355 }
356
357 if (!nameListeners[className]) {
358 nameListeners[className] = [];
359 }
360
361 nameListeners[className].push(listener);
362 }
363 else {
364 listeners.push(listener);
365 }
366 },
367
368 /**
369 * Supports namespace rewriting.
370 * @private
371 */
372 parseNamespace: function(namespace) {
373 //<debug error>
374 if (typeof namespace != 'string') {
375 throw new Error("[Ext.ClassManager] Invalid namespace, must be a string");
376 }
377 //</debug>
378
379 var cache = this.namespaceParseCache;
380
381 if (this.enableNamespaceParseCache) {
382 if (cache.hasOwnProperty(namespace)) {
383 return cache[namespace];
384 }
385 }
386
387 var parts = [],
388 rewrites = this.namespaceRewrites,
389 root = global,
390 name = namespace,
391 rewrite, from, to, i, ln;
392
393 for (i = 0, ln = rewrites.length; i < ln; i++) {
394 rewrite = rewrites[i];
395 from = rewrite.from;
396 to = rewrite.to;
397
398 if (name === from || name.substring(0, from.length) === from) {
399 name = name.substring(from.length);
400
401 if (typeof to != 'string') {
402 root = to;
403 } else {
404 parts = parts.concat(to.split('.'));
405 }
406
407 break;
408 }
409 }
410
411 parts.push(root);
412
413 parts = parts.concat(name.split('.'));
414
415 if (this.enableNamespaceParseCache) {
416 cache[namespace] = parts;
417 }
418
419 return parts;
420 },
421
422 /**
423 * Creates a namespace and assign the `value` to the created object.
424 *
425 * Ext.ClassManager.setNamespace('MyCompany.pkg.Example', someObject);
426 * alert(MyCompany.pkg.Example === someObject); // alerts true
427 *
428 * @param {String} name
429 * @param {Mixed} value
430 */
431 setNamespace: function(name, value) {
432 var root = global,
433 parts = this.parseNamespace(name),
434 ln = parts.length - 1,
435 leaf = parts[ln],
436 i, part;
437
438 for (i = 0; i < ln; i++) {
439 part = parts[i];
440
441 if (typeof part != 'string') {
442 root = part;
443 } else {
444 if (!root[part]) {
445 root[part] = {};
446 }
447
448 root = root[part];
449 }
450 }
451
452 root[leaf] = value;
453
454 return root[leaf];
455 },
456
457 /**
458 * The new Ext.ns, supports namespace rewriting.
459 * @private
460 */
461 createNamespaces: function() {
462 var root = global,
463 parts, part, i, j, ln, subLn;
464
465 for (i = 0, ln = arguments.length; i < ln; i++) {
466 parts = this.parseNamespace(arguments[i]);
467
468 for (j = 0, subLn = parts.length; j < subLn; j++) {
469 part = parts[j];
470
471 if (typeof part != 'string') {
472 root = part;
473 } else {
474 if (!root[part]) {
475 root[part] = {};
476 }
477
478 root = root[part];
479 }
480 }
481 }
482
483 return root;
484 },
485
486 /**
487 * Sets a name reference to a class.
488 *
489 * @param {String} name
490 * @param {Object} value
491 * @return {Ext.ClassManager} this
492 */
493 set: function(name, value) {
494 var me = this,
495 maps = me.maps,
496 nameToAlternates = maps.nameToAlternates,
497 targetName = me.getName(value),
498 alternates;
499
500 me.classes[name] = me.setNamespace(name, value);
501
502 if (targetName && targetName !== name) {
503 maps.alternateToName[name] = targetName;
504 alternates = nameToAlternates[targetName] || (nameToAlternates[targetName] = []);
505 alternates.push(name);
506 }
507
508 return this;
509 },
510
511 /**
512 * Retrieve a class by its name.
513 *
514 * @param {String} name
515 * @return {Ext.Class} class
516 */
517 get: function(name) {
518 var classes = this.classes;
519
520 if (classes[name]) {
521 return classes[name];
522 }
523
524 var root = global,
525 parts = this.parseNamespace(name),
526 part, i, ln;
527
528 for (i = 0, ln = parts.length; i < ln; i++) {
529 part = parts[i];
530
531 if (typeof part != 'string') {
532 root = part;
533 } else {
534 if (!root || !root[part]) {
535 return null;
536 }
537
538 root = root[part];
539 }
540 }
541
542 return root;
543 },
544
545 /**
546 * Register the alias for a class.
547 *
548 * @param {Ext.Class/String} cls a reference to a class or a `className`.
549 * @param {String} alias Alias to use when referring to this class.
550 */
551 setAlias: function(cls, alias) {
552 var aliasToNameMap = this.maps.aliasToName,
553 nameToAliasesMap = this.maps.nameToAliases,
554 className;
555
556 if (typeof cls == 'string') {
557 className = cls;
558 } else {
559 className = this.getName(cls);
560 }
561
562 if (alias && aliasToNameMap[alias] !== className) {
563 //<debug info>
564 if (aliasToNameMap[alias]) {
565 Ext.Logger.info("[Ext.ClassManager] Overriding existing alias: '" + alias + "' " +
566 "of: '" + aliasToNameMap[alias] + "' with: '" + className + "'. Be sure it's intentional.");
567 }
568 //</debug>
569
570 aliasToNameMap[alias] = className;
571 }
572
573 if (!nameToAliasesMap[className]) {
574 nameToAliasesMap[className] = [];
575 }
576
577 if (alias) {
578 Ext.Array.include(nameToAliasesMap[className], alias);
579 }
580
581 return this;
582 },
583
584 /**
585 * Adds a batch of class name to alias mappings
586 * @param {Object} aliases The set of mappings of the form
587 * className : [values...]
588 */
589 addNameAliasMappings: function(aliases){
590 var aliasToNameMap = this.maps.aliasToName,
591 nameToAliasesMap = this.maps.nameToAliases,
592 className, aliasList, alias, i;
593
594 for (className in aliases) {
595 aliasList = nameToAliasesMap[className] ||
596 (nameToAliasesMap[className] = []);
597
598 for (i = 0; i < aliases[className].length; i++) {
599 alias = aliases[className][i];
600 if (!aliasToNameMap[alias]) {
601 aliasToNameMap[alias] = className;
602 aliasList.push(alias);
603 }
604 }
605
606 }
607 return this;
608 },
609
610 /**
611 *
612 * @param {Object} alternates The set of mappings of the form
613 * className : [values...]
614 */
615 addNameAlternateMappings: function(alternates) {
616 var alternateToName = this.maps.alternateToName,
617 nameToAlternates = this.maps.nameToAlternates,
618 className, aliasList, alternate, i;
619
620 for (className in alternates) {
621 aliasList = nameToAlternates[className] ||
622 (nameToAlternates[className] = []);
623
624 for (i = 0; i < alternates[className].length; i++) {
625 alternate = alternates[className];
626 if (!alternateToName[alternate]) {
627 alternateToName[alternate] = className;
628 aliasList.push(alternate);
629 }
630 }
631
632 }
633 return this;
634 },
635
636 /**
637 * Get a reference to the class by its alias.
638 *
639 * @param {String} alias
640 * @return {Ext.Class} class
641 */
642 getByAlias: function(alias) {
643 return this.get(this.getNameByAlias(alias));
644 },
645
646 /**
647 * Get the name of a class by its alias.
648 *
649 * @param {String} alias
650 * @return {String} className
651 */
652 getNameByAlias: function(alias) {
653 return this.maps.aliasToName[alias] || '';
654 },
655
656 /**
657 * Get the name of a class by its alternate name.
658 *
659 * @param {String} alternate
660 * @return {String} className
661 */
662 getNameByAlternate: function(alternate) {
663 return this.maps.alternateToName[alternate] || '';
664 },
665
666 /**
667 * Get the aliases of a class by the class name
668 *
669 * @param {String} name
670 * @return {Array} aliases
671 */
672 getAliasesByName: function(name) {
673 return this.maps.nameToAliases[name] || [];
674 },
675
676 /**
677 * Get the name of the class by its reference or its instance;
678 * usually invoked by the shorthand {@link Ext#getClassName Ext.getClassName}
679 *
680 * Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action"
681 *
682 * @param {Ext.Class/Object} object
683 * @return {String} className
684 */
685 getName: function(object) {
686 return object && object.$className || '';
687 },
688
689 /**
690 * Get the class of the provided object; returns null if it's not an instance
691 * of any class created with Ext.define. This is usually invoked by the shorthand {@link Ext#getClass Ext.getClass}.
692 *
693 * var component = new Ext.Component();
694 *
695 * Ext.ClassManager.getClass(component); // returns Ext.Component
696 *
697 * @param {Object} object
698 * @return {Ext.Class} class
699 */
700 getClass: function(object) {
701 return object && object.self || null;
702 },
703
704 /**
705 * @private
706 */
707 create: function(className, data, createdFn) {
708 //<debug error>
709 if (typeof className != 'string') {
710 throw new Error("[Ext.define] Invalid class name '" + className + "' specified, must be a non-empty string");
711 }
712 //</debug>
713
714 data.$className = className;
715
716 return new Class(data, function() {
717 var postprocessorStack = data.postprocessors || Manager.defaultPostprocessors,
718 registeredPostprocessors = Manager.postprocessors,
719 index = 0,
720 postprocessors = [],
721 postprocessor, process, i, ln, j, subLn, postprocessorProperties, postprocessorProperty;
722
723 delete data.postprocessors;
724
725 for (i = 0,ln = postprocessorStack.length; i < ln; i++) {
726 postprocessor = postprocessorStack[i];
727
728 if (typeof postprocessor == 'string') {
729 postprocessor = registeredPostprocessors[postprocessor];
730 postprocessorProperties = postprocessor.properties;
731
732 if (postprocessorProperties === true) {
733 postprocessors.push(postprocessor.fn);
734 }
735 else if (postprocessorProperties) {
736 for (j = 0,subLn = postprocessorProperties.length; j < subLn; j++) {
737 postprocessorProperty = postprocessorProperties[j];
738
739 if (data.hasOwnProperty(postprocessorProperty)) {
740 postprocessors.push(postprocessor.fn);
741 break;
742 }
743 }
744 }
745 }
746 else {
747 postprocessors.push(postprocessor);
748 }
749 }
750
751 process = function(clsName, cls, clsData) {
752 postprocessor = postprocessors[index++];
753
754 if (!postprocessor) {
755 Manager.set(className, cls);
756
757 if (createdFn) {
758 createdFn.call(cls, cls);
759 }
760
761 Manager.triggerCreated(className);
762 return;
763 }
764
765 if (postprocessor.call(this, clsName, cls, clsData, process) !== false) {
766 process.apply(this, arguments);
767 }
768 };
769
770 process.call(Manager, className, this, data);
771 });
772 },
773
774 createOverride: function(className, data, createdFn) {
775 var overriddenClassName = data.override,
776 requires = Ext.Array.from(data.requires);
777
778 delete data.override;
779 delete data.requires;
780
781 this.existCache[className] = true;
782
783 Ext.require(requires, function() {
784 // Override the target class right after it's created
785 this.onCreated(function() {
786 var overridenClass = this.get(overriddenClassName);
787 if (overridenClass.singleton) {
788 overridenClass.self.override(data);
789 }
790 else {
791 overridenClass.override(data);
792 }
793
794 if (createdFn) {
795 createdFn.call(overridenClass, overridenClass);
796 }
797
798 // This push the overridding file itself into Ext.Loader.history
799 // Hence if the target class never exists, the overriding file will
800 // never be included in the build
801 this.triggerCreated(className);
802 }, this, overriddenClassName);
803 }, this);
804
805 return this;
806 },
807
808 /**
809 * Instantiate a class by its alias; usually invoked by the convenient shorthand {@link Ext#createByAlias Ext.createByAlias}
810 * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
811 * attempt to load the class via synchronous loading.
812 *
813 * var window = Ext.ClassManager.instantiateByAlias('widget.window', { width: 600, height: 800 });
814 *
815 * @param {String} alias
816 * @param {Mixed...} args Additional arguments after the alias will be passed to the class constructor.
817 * @return {Object} instance
818 */
819 instantiateByAlias: function() {
820 var alias = arguments[0],
821 args = arraySlice.call(arguments),
822 className = this.getNameByAlias(alias);
823
824 if (!className) {
825 className = this.maps.aliasToName[alias];
826
827 //<debug error>
828 if (!className) {
829 throw new Error("[Ext.createByAlias] Cannot create an instance of unrecognized alias: " + alias);
830 }
831 //</debug>
832
833 //<debug warn>
834 Ext.Logger.warn("[Ext.Loader] Synchronously loading '" + className + "'; consider adding " +
835 "Ext.require('" + alias + "') above Ext.onReady");
836 //</debug>
837
838 Ext.syncRequire(className);
839 }
840
841 args[0] = className;
842
843 return this.instantiate.apply(this, args);
844 },
845
846 /**
847 * Instantiate a class by either full name, alias or alternate name; usually invoked by the convenient
848 * shorthand {@link Ext.ClassManager#create Ext.create}.
849 *
850 * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
851 * attempt to load the class via synchronous loading.
852 *
853 * For example, all these three lines return the same result:
854 *
855 * // alias
856 * var formPanel = Ext.create('widget.formpanel', { width: 600, height: 800 });
857 *
858 * // alternate name
859 * var formPanel = Ext.create('Ext.form.FormPanel', { width: 600, height: 800 });
860 *
861 * // full class name
862 * var formPanel = Ext.create('Ext.form.Panel', { width: 600, height: 800 });
863 *
864 * @param {String} name
865 * @param {Mixed} args Additional arguments after the name will be passed to the class' constructor.
866 * @return {Object} instance
867 */
868 instantiate: function() {
869 var name = arguments[0],
870 args = arraySlice.call(arguments, 1),
871 alias = name,
872 possibleName, cls;
873
874 if (typeof name != 'function') {
875 //<debug error>
876 if ((typeof name != 'string' || name.length < 1)) {
877 throw new Error("[Ext.create] Invalid class name or alias '" + name + "' specified, must be a non-empty string");
878 }
879 //</debug>
880
881 cls = this.get(name);
882 }
883 else {
884 cls = name;
885 }
886
887 // No record of this class name, it's possibly an alias, so look it up
888 if (!cls) {
889 possibleName = this.getNameByAlias(name);
890
891 if (possibleName) {
892 name = possibleName;
893
894 cls = this.get(name);
895 }
896 }
897
898 // Still no record of this class name, it's possibly an alternate name, so look it up
899 if (!cls) {
900 possibleName = this.getNameByAlternate(name);
901
902 if (possibleName) {
903 name = possibleName;
904
905 cls = this.get(name);
906 }
907 }
908
909 // Still not existing at this point, try to load it via synchronous mode as the last resort
910 if (!cls) {
911 //<debug warn>
912 //<if nonBrowser>
913 !isNonBrowser &&
914 //</if>
915 Ext.Logger.warn("[Ext.Loader] Synchronously loading '" + name + "'; consider adding '" +
916 ((possibleName) ? alias : name) + "' explicitly as a require of the corresponding class");
917 //</debug>
918
919 Ext.syncRequire(name);
920
921 cls = this.get(name);
922 }
923
924 //<debug error>
925 if (!cls) {
926 throw new Error("[Ext.create] Cannot create an instance of unrecognized class name / alias: " + alias);
927 }
928
929 if (typeof cls != 'function') {
930 throw new Error("[Ext.create] '" + name + "' is a singleton and cannot be instantiated");
931 }
932 //</debug>
933
934 return this.getInstantiator(args.length)(cls, args);
935 },
936
937 /**
938 * @private
939 * @param {String} name
940 * @param {Array} args
941 */
942 dynInstantiate: function(name, args) {
943 args = arrayFrom(args, true);
944 args.unshift(name);
945
946 return this.instantiate.apply(this, args);
947 },
948
949 /**
950 * @private
951 * @param {Number} length
952 */
953 getInstantiator: function(length) {
954 var instantiators = this.instantiators,
955 instantiator;
956
957 instantiator = instantiators[length];
958
959 if (!instantiator) {
960 var i = length,
961 args = [];
962
963 for (i = 0; i < length; i++) {
964 args.push('a[' + i + ']');
965 }
966
967 instantiator = instantiators[length] = new Function('c', 'a', 'return new c(' + args.join(',') + ')');
968 //<debug>
969 instantiator.displayName = "Ext.ClassManager.instantiate" + length;
970 //</debug>
971 }
972
973 return instantiator;
974 },
975
976 /**
977 * @private
978 */
979 postprocessors: {},
980
981 /**
982 * @private
983 */
984 defaultPostprocessors: [],
985
986 /**
987 * Register a post-processor function.
988 * @private
989 */
990 registerPostprocessor: function(name, fn, properties, position, relativeTo) {
991 if (!position) {
992 position = 'last';
993 }
994
995 if (!properties) {
996 properties = [name];
997 }
998
999 this.postprocessors[name] = {
1000 name: name,
1001 properties: properties || false,
1002 fn: fn
1003 };
1004
1005 this.setDefaultPostprocessorPosition(name, position, relativeTo);
1006
1007 return this;
1008 },
1009
1010 /**
1011 * Set the default post processors array stack which are applied to every class.
1012 *
1013 * @private
1014 * @param {String/Array} postprocessors The name of a registered post processor or an array of registered names.
1015 * @return {Ext.ClassManager} this
1016 */
1017 setDefaultPostprocessors: function(postprocessors) {
1018 this.defaultPostprocessors = arrayFrom(postprocessors);
1019
1020 return this;
1021 },
1022
1023 /**
1024 * Insert this post-processor at a specific position in the stack, optionally relative to
1025 * any existing post-processor
1026 *
1027 * @private
1028 * @param {String} name The post-processor name. Note that it needs to be registered with
1029 * {@link Ext.ClassManager#registerPostprocessor} before this
1030 * @param {String} offset The insertion position. Four possible values are:
1031 * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
1032 * @param {String} relativeName
1033 * @return {Ext.ClassManager} this
1034 */
1035 setDefaultPostprocessorPosition: function(name, offset, relativeName) {
1036 var defaultPostprocessors = this.defaultPostprocessors,
1037 index;
1038
1039 if (typeof offset == 'string') {
1040 if (offset === 'first') {
1041 defaultPostprocessors.unshift(name);
1042
1043 return this;
1044 }
1045 else if (offset === 'last') {
1046 defaultPostprocessors.push(name);
1047
1048 return this;
1049 }
1050
1051 offset = (offset === 'after') ? 1 : -1;
1052 }
1053
1054 index = Ext.Array.indexOf(defaultPostprocessors, relativeName);
1055
1056 if (index !== -1) {
1057 Ext.Array.splice(defaultPostprocessors, Math.max(0, index + offset), 0, name);
1058 }
1059
1060 return this;
1061 },
1062
1063 /**
1064 * Converts a string expression to an array of matching class names. An expression can either refers to class aliases
1065 * or class names. Expressions support wildcards:
1066 *
1067 * // returns ['Ext.window.Window']
1068 * var window = Ext.ClassManager.getNamesByExpression('widget.window');
1069 *
1070 * // returns ['widget.panel', 'widget.window', ...]
1071 * var allWidgets = Ext.ClassManager.getNamesByExpression('widget.*');
1072 *
1073 * // returns ['Ext.data.Store', 'Ext.data.ArrayProxy', ...]
1074 * var allData = Ext.ClassManager.getNamesByExpression('Ext.data.*');
1075 *
1076 * @param {String} expression
1077 * @return {Array} classNames
1078 */
1079 getNamesByExpression: function(expression) {
1080 var nameToAliasesMap = this.maps.nameToAliases,
1081 names = [],
1082 name, alias, aliases, possibleName, regex, i, ln;
1083
1084 //<debug error>
1085 if (typeof expression != 'string' || expression.length < 1) {
1086 throw new Error("[Ext.ClassManager.getNamesByExpression] Expression " + expression + " is invalid, must be a non-empty string");
1087 }
1088 //</debug>
1089
1090 if (expression.indexOf('*') !== -1) {
1091 expression = expression.replace(/\*/g, '(.*?)');
1092 regex = new RegExp('^' + expression + '$');
1093
1094 for (name in nameToAliasesMap) {
1095 if (nameToAliasesMap.hasOwnProperty(name)) {
1096 aliases = nameToAliasesMap[name];
1097
1098 if (name.search(regex) !== -1) {
1099 names.push(name);
1100 }
1101 else {
1102 for (i = 0, ln = aliases.length; i < ln; i++) {
1103 alias = aliases[i];
1104
1105 if (alias.search(regex) !== -1) {
1106 names.push(name);
1107 break;
1108 }
1109 }
1110 }
1111 }
1112 }
1113
1114 } else {
1115 possibleName = this.getNameByAlias(expression);
1116
1117 if (possibleName) {
1118 names.push(possibleName);
1119 } else {
1120 possibleName = this.getNameByAlternate(expression);
1121
1122 if (possibleName) {
1123 names.push(possibleName);
1124 } else {
1125 names.push(expression);
1126 }
1127 }
1128 }
1129
1130 return names;
1131 }
1132 };
1133
1134 //<feature classSystem.alias>
1135 /**
1136 * @cfg {String[]} alias
1137 * @member Ext.Class
1138 * List of short aliases for class names. Most useful for defining xtypes for widgets:
1139 *
1140 * Ext.define('MyApp.CoolPanel', {
1141 * extend: 'Ext.panel.Panel',
1142 * alias: ['widget.coolpanel'],
1143 *
1144 * config: {
1145 * html : 'Yeah!'
1146 * }
1147 * });
1148 *
1149 * // Using Ext.create
1150 * Ext.create('widget.coolpanel');
1151 *
1152 * // Using the shorthand for widgets and in xtypes
1153 * Ext.widget('panel', {
1154 * items: [
1155 * {xtype: 'coolpanel', html: 'Foo'},
1156 * {xtype: 'coolpanel', html: 'Bar'}
1157 * ]
1158 * });
1159 *
1160 * For {@link Ext.Component}, you can also use the {@link Ext.Component#xtype} property.
1161 */
1162 /**
1163 * @cfg {String[]} xtype
1164 * @member Ext.Component
1165 * List of xtypes for {@link Ext.Component}. XTypes must not contain periods.
1166 *
1167 * Ext.define('MyApp.CoolPanel', {
1168 * extend: 'Ext.panel.Panel',
1169 * xtype: 'coolpanel',
1170 *
1171 * config: {
1172 * html : 'Yeah!'
1173 * }
1174 * });
1175 *
1176 * // Using Ext.create
1177 * Ext.create('widget.coolpanel');
1178 *
1179 * // Using the shorthand for widgets and in xtypes
1180 * Ext.widget('panel', {
1181 * items: [
1182 * {xtype: 'coolpanel', html: 'Foo'},
1183 * {xtype: 'coolpanel', html: 'Bar'}
1184 * ]
1185 * });
1186 */
1187 Manager.registerPostprocessor('alias', function(name, cls, data) {
1188 var aliases = data.alias,
1189 i, ln;
1190
1191 for (i = 0,ln = aliases.length; i < ln; i++) {
1192 alias = aliases[i];
1193
1194 this.setAlias(cls, alias);
1195 }
1196
1197 }, ['xtype', 'alias']);
1198 //</feature>
1199
1200 //<feature classSystem.singleton>
1201 /**
1202 * @cfg {Boolean} singleton
1203 * @member Ext.Class
1204 * When set to true, the class will be instantiated as singleton. For example:
1205 *
1206 * Ext.define('Logger', {
1207 * singleton: true,
1208 * log: function(msg) {
1209 * console.log(msg);
1210 * }
1211 * });
1212 *
1213 * Logger.log('Hello');
1214 */
1215 Manager.registerPostprocessor('singleton', function(name, cls, data, fn) {
1216 fn.call(this, name, new cls(), data);
1217 return false;
1218 });
1219 //</feature>
1220
1221 //<feature classSystem.alternateClassName>
1222 /**
1223 * @cfg {String/String[]} alternateClassName
1224 * @member Ext.Class
1225 * Defines alternate names for this class. For example:
1226 *
1227 * @example
1228 * Ext.define('Developer', {
1229 * alternateClassName: ['Coder', 'Hacker'],
1230 * code: function(msg) {
1231 * alert('Typing... ' + msg);
1232 * }
1233 * });
1234 *
1235 * var joe = Ext.create('Developer');
1236 * joe.code('stackoverflow');
1237 *
1238 * var rms = Ext.create('Hacker');
1239 * rms.code('hack hack');
1240 */
1241 Manager.registerPostprocessor('alternateClassName', function(name, cls, data) {
1242 var alternates = data.alternateClassName,
1243 i, ln, alternate;
1244
1245 if (!(alternates instanceof Array)) {
1246 alternates = [alternates];
1247 }
1248
1249 for (i = 0, ln = alternates.length; i < ln; i++) {
1250 alternate = alternates[i];
1251
1252 //<debug error>
1253 if (typeof alternate != 'string') {
1254 throw new Error("[Ext.define] Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string");
1255 }
1256 //</debug>
1257
1258 this.set(alternate, cls);
1259 }
1260 });
1261 //</feature>
1262
1263 Ext.apply(Ext, {
1264 /**
1265 * Instantiate a class by either full name, alias or alternate name.
1266 *
1267 * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
1268 * attempt to load the class via synchronous loading.
1269 *
1270 * For example, all these three lines return the same result:
1271 *
1272 * // alias
1273 * var formPanel = Ext.create('widget.formpanel', { width: 600, height: 800 });
1274 *
1275 * // alternate name
1276 * var formPanel = Ext.create('Ext.form.FormPanel', { width: 600, height: 800 });
1277 *
1278 * // full class name
1279 * var formPanel = Ext.create('Ext.form.Panel', { width: 600, height: 800 });
1280 *
1281 * @param {String} name
1282 * @param {Mixed} args Additional arguments after the name will be passed to the class' constructor.
1283 * @return {Object} instance
1284 * @member Ext
1285 */
1286 create: alias(Manager, 'instantiate'),
1287
1288 /**
1289 * Convenient shorthand to create a widget by its xtype, also see {@link Ext.ClassManager#instantiateByAlias}
1290 *
1291 * var button = Ext.widget('button'); // Equivalent to Ext.create('widget.button')
1292 * var panel = Ext.widget('panel'); // Equivalent to Ext.create('widget.panel')
1293 *
1294 * @member Ext
1295 * @method widget
1296 * @param {String} name
1297 * @return {Object} instance
1298 */
1299 widget: function(name) {
1300 var args = arraySlice.call(arguments);
1301 args[0] = 'widget.' + name;
1302
1303 return Manager.instantiateByAlias.apply(Manager, args);
1304 },
1305
1306 /**
1307 * Convenient shorthand, see {@link Ext.ClassManager#instantiateByAlias}.
1308 * @member Ext
1309 * @method createByAlias
1310 * @param {String} alias
1311 * @param {Mixed...} args Additional arguments after the alias will be passed to the class constructor.
1312 * @return {Object} instance
1313 */
1314 createByAlias: alias(Manager, 'instantiateByAlias'),
1315
1316 /**
1317 * Defines a class or override. A basic class is defined like this:
1318 *
1319 * Ext.define('My.awesome.Class', {
1320 * someProperty: 'something',
1321 *
1322 * someMethod: function(s) {
1323 * console.log(s + this.someProperty);
1324 * }
1325 * });
1326 *
1327 * var obj = new My.awesome.Class();
1328 *
1329 * obj.someMethod('Say '); // logs 'Say something' to the console
1330 *
1331 * To defines an override, include the `override` property. The content of an
1332 * override is aggregated with the specified class in order to extend or modify
1333 * that class. This can be as simple as setting default property values or it can
1334 * extend and/or replace methods. This can also extend the statics of the class.
1335 *
1336 * One use for an override is to break a large class into manageable pieces.
1337 *
1338 * // File: /src/app/Panel.js
1339 * Ext.define('My.app.Panel', {
1340 * extend: 'Ext.panel.Panel',
1341 * requires: [
1342 * 'My.app.PanelPart2',
1343 * 'My.app.PanelPart3'
1344 * ],
1345 *
1346 * constructor: function (config) {
1347 * this.callParent(arguments); // calls Ext.panel.Panel's constructor
1348 * // ...
1349 * },
1350 *
1351 * statics: {
1352 * method: function () {
1353 * return 'abc';
1354 * }
1355 * }
1356 * });
1357 *
1358 * // File: /src/app/PanelPart2.js
1359 * Ext.define('My.app.PanelPart2', {
1360 * override: 'My.app.Panel',
1361 *
1362 * constructor: function (config) {
1363 * this.callParent(arguments); // calls My.app.Panel's constructor
1364 * // ...
1365 * }
1366 * });
1367 *
1368 * Another use for an override is to provide optional parts of classes that can be
1369 * independently required. In this case, the class may even be unaware of the
1370 * override altogether.
1371 *
1372 * Ext.define('My.ux.CoolTip', {
1373 * override: 'Ext.tip.ToolTip',
1374 *
1375 * constructor: function (config) {
1376 * this.callParent(arguments); // calls Ext.tip.ToolTip's constructor
1377 * // ...
1378 * }
1379 * });
1380 *
1381 * The above override can now be required as normal.
1382 *
1383 * Ext.define('My.app.App', {
1384 * requires: [
1385 * 'My.ux.CoolTip'
1386 * ]
1387 * });
1388 *
1389 * Overrides can also contain statics:
1390 *
1391 * Ext.define('My.app.BarMod', {
1392 * override: 'Ext.foo.Bar',
1393 *
1394 * statics: {
1395 * method: function (x) {
1396 * return this.callParent([x * 2]); // call Ext.foo.Bar.method
1397 * }
1398 * }
1399 * });
1400 *
1401 * __IMPORTANT:__ An override is only included in a build if the class it overrides is
1402 * required. Otherwise, the override, like the target class, is not included.
1403 *
1404 * @param {String} className The class name to create in string dot-namespaced format, for example:
1405 * 'My.very.awesome.Class', 'FeedViewer.plugin.CoolPager'
1406 *
1407 * It is highly recommended to follow this simple convention:
1408 * - The root and the class name are 'CamelCased'
1409 * - Everything else is lower-cased
1410 *
1411 * @param {Object} data The key - value pairs of properties to apply to this class. Property names can be of
1412 * any valid strings, except those in the reserved listed below:
1413 *
1414 * - `mixins`
1415 * - `statics`
1416 * - `config`
1417 * - `alias`
1418 * - `xtype` (for {@link Ext.Component}s only)
1419 * - `self`
1420 * - `singleton`
1421 * - `alternateClassName`
1422 * - `override`
1423 *
1424 * @param {Function} [createdFn] Optional callback to execute after the class (or override)
1425 * is created. The execution scope (`this`) will be the newly created class itself.
1426 * @return {Ext.Base}
1427 *
1428 * @member Ext
1429 * @method define
1430 */
1431 define: function (className, data, createdFn) {
1432 if ('override' in data) {
1433 return Manager.createOverride.apply(Manager, arguments);
1434 }
1435
1436 return Manager.create.apply(Manager, arguments);
1437 },
1438
1439 /**
1440 * Convenient shorthand for {@link Ext.ClassManager#getName}.
1441 * @member Ext
1442 * @method getClassName
1443 * @inheritdoc Ext.ClassManager#getName
1444 */
1445 getClassName: alias(Manager, 'getName'),
1446
1447 /**
1448 * Returns the display name for object. This name is looked for in order from the following places:
1449 *
1450 * - `displayName` field of the object.
1451 * - `$name` and `$class` fields of the object.
1452 * - '$className` field of the object.
1453 *
1454 * This method is used by {@link Ext.Logger#log} to display information about objects.
1455 *
1456 * @param {Mixed} [object] The object who's display name to determine.
1457 * @return {String} The determined display name, or "Anonymous" if none found.
1458 * @member Ext
1459 */
1460 getDisplayName: function(object) {
1461 if (object) {
1462 if (object.displayName) {
1463 return object.displayName;
1464 }
1465
1466 if (object.$name && object.$class) {
1467 return Ext.getClassName(object.$class) + '#' + object.$name;
1468 }
1469
1470 if (object.$className) {
1471 return object.$className;
1472 }
1473 }
1474
1475 return 'Anonymous';
1476 },
1477
1478 /**
1479 * Convenient shorthand, see {@link Ext.ClassManager#getClass}.
1480 * @member Ext
1481 * @method getClass
1482 */
1483 getClass: alias(Manager, 'getClass'),
1484
1485 /**
1486 * Creates namespaces to be used for scoping variables and classes so that they are not global.
1487 * Specifying the last node of a namespace implicitly creates all other nodes. Usage:
1488 *
1489 * Ext.namespace('Company', 'Company.data');
1490 *
1491 * // equivalent and preferable to the above syntax
1492 * Ext.namespace('Company.data');
1493 *
1494 * Company.Widget = function() {
1495 * // ...
1496 * };
1497 *
1498 * Company.data.CustomStore = function(config) {
1499 * // ...
1500 * };
1501 *
1502 * @param {String} namespace1
1503 * @param {String} namespace2
1504 * @param {String} etc
1505 * @return {Object} The namespace object. If multiple arguments are passed, this will be the last namespace created.
1506 * @member Ext
1507 * @method namespace
1508 */
1509 namespace: alias(Manager, 'createNamespaces')
1510 });
1511
1512 /**
1513 * Old name for {@link Ext#widget}.
1514 * @deprecated 2.0.0 Please use {@link Ext#widget} instead.
1515 * @method createWidget
1516 * @member Ext
1517 */
1518 Ext.createWidget = Ext.widget;
1519
1520 /**
1521 * Convenient alias for {@link Ext#namespace Ext.namespace}.
1522 * @member Ext
1523 * @method ns
1524 */
1525 Ext.ns = Ext.namespace;
1526
1527 Class.registerPreprocessor('className', function(cls, data) {
1528 if (data.$className) {
1529 cls.$className = data.$className;
1530 //<debug>
1531 cls.displayName = cls.$className;
1532 //</debug>
1533 }
1534 }, true, 'first');
1535
1536 Class.registerPreprocessor('alias', function(cls, data) {
1537 var prototype = cls.prototype,
1538 xtypes = arrayFrom(data.xtype),
1539 aliases = arrayFrom(data.alias),
1540 widgetPrefix = 'widget.',
1541 widgetPrefixLength = widgetPrefix.length,
1542 xtypesChain = Array.prototype.slice.call(prototype.xtypesChain || []),
1543 xtypesMap = Ext.merge({}, prototype.xtypesMap || {}),
1544 i, ln, alias, xtype;
1545
1546 for (i = 0,ln = aliases.length; i < ln; i++) {
1547 alias = aliases[i];
1548
1549 //<debug error>
1550 if (typeof alias != 'string' || alias.length < 1) {
1551 throw new Error("[Ext.define] Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string");
1552 }
1553 //</debug>
1554
1555 if (alias.substring(0, widgetPrefixLength) === widgetPrefix) {
1556 xtype = alias.substring(widgetPrefixLength);
1557 Ext.Array.include(xtypes, xtype);
1558 }
1559 }
1560
1561 cls.xtype = data.xtype = xtypes[0];
1562 data.xtypes = xtypes;
1563
1564 for (i = 0,ln = xtypes.length; i < ln; i++) {
1565 xtype = xtypes[i];
1566
1567 if (!xtypesMap[xtype]) {
1568 xtypesMap[xtype] = true;
1569 xtypesChain.push(xtype);
1570 }
1571 }
1572
1573 data.xtypesChain = xtypesChain;
1574 data.xtypesMap = xtypesMap;
1575
1576 Ext.Function.interceptAfter(data, 'onClassCreated', function() {
1577 var mixins = prototype.mixins,
1578 key, mixin;
1579
1580 for (key in mixins) {
1581 if (mixins.hasOwnProperty(key)) {
1582 mixin = mixins[key];
1583
1584 xtypes = mixin.xtypes;
1585
1586 if (xtypes) {
1587 for (i = 0,ln = xtypes.length; i < ln; i++) {
1588 xtype = xtypes[i];
1589
1590 if (!xtypesMap[xtype]) {
1591 xtypesMap[xtype] = true;
1592 xtypesChain.push(xtype);
1593 }
1594 }
1595 }
1596 }
1597 }
1598 });
1599
1600 for (i = 0,ln = xtypes.length; i < ln; i++) {
1601 xtype = xtypes[i];
1602
1603 //<debug error>
1604 if (typeof xtype != 'string' || xtype.length < 1) {
1605 throw new Error("[Ext.define] Invalid xtype of: '" + xtype + "' for class: '" + name + "'; must be a valid non-empty string");
1606 }
1607 //</debug>
1608
1609 Ext.Array.include(aliases, widgetPrefix + xtype);
1610 }
1611
1612 data.alias = aliases;
1613
1614 }, ['xtype', 'alias']);
1615
1616 })(Ext.Class, Ext.Function.alias, Array.prototype.slice, Ext.Array.from, Ext.global);