Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / core / src / class / Base.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.Base
19  *
20  * The root of all classes created with {@link Ext#define}.
21  *
22  * Ext.Base is the building block of all Ext classes. All classes in Ext inherit from Ext.Base.
23  * All prototype and static members of this class are inherited by all other classes.
24  */
25 (function(flexSetter) {
26
27 var Base = Ext.Base = function() {};
28     Base.prototype = {
29         $className: 'Ext.Base',
30
31         $class: Base,
32
33         /**
34          * Get the reference to the current class from which this object was instantiated. Unlike {@link Ext.Base#statics},
35          * `this.self` is scope-dependent and it's meant to be used for dynamic inheritance. See {@link Ext.Base#statics}
36          * for a detailed comparison
37          *
38          *     Ext.define('My.Cat', {
39          *         statics: {
40          *             speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
41          *         },
42          *
43          *         constructor: function() {
44          *             alert(this.self.speciesName); / dependent on 'this'
45          *
46          *             return this;
47          *         },
48          *
49          *         clone: function() {
50          *             return new this.self();
51          *         }
52          *     });
53          *
54          *
55          *     Ext.define('My.SnowLeopard', {
56          *         extend: 'My.Cat',
57          *         statics: {
58          *             speciesName: 'Snow Leopard'         // My.SnowLeopard.speciesName = 'Snow Leopard'
59          *         }
60          *     });
61          *
62          *     var cat = new My.Cat();                     // alerts 'Cat'
63          *     var snowLeopard = new My.SnowLeopard();     // alerts 'Snow Leopard'
64          *
65          *     var clone = snowLeopard.clone();
66          *     alert(Ext.getClassName(clone));             // alerts 'My.SnowLeopard'
67          *
68          * @type Ext.Class
69          * @protected
70          */
71         self: Base,
72
73         // Default constructor, simply returns `this`
74         constructor: function() {
75             return this;
76         },
77
78         //<feature classSystem.config>
79         /**
80          * Initialize configuration for this class. a typical example:
81          *
82          *     Ext.define('My.awesome.Class', {
83          *         // The default config
84          *         config: {
85          *             name: 'Awesome',
86          *             isAwesome: true
87          *         },
88          *
89          *         constructor: function(config) {
90          *             this.initConfig(config);
91          *
92          *             return this;
93          *         }
94          *     });
95          *
96          *     var awesome = new My.awesome.Class({
97          *         name: 'Super Awesome'
98          *     });
99          *
100          *     alert(awesome.getName()); // 'Super Awesome'
101          *
102          * @protected
103          * @param {Object} config
104          * @return {Object} mixins The mixin prototypes as key - value pairs
105          */
106         initConfig: function(config) {
107             if (!this.$configInited) {
108                 this.config = Ext.Object.merge({}, this.config || {}, config || {});
109
110                 this.applyConfig(this.config);
111
112                 this.$configInited = true;
113             }
114
115             return this;
116         },
117
118         /**
119          * @private
120          */
121         setConfig: function(config) {
122             this.applyConfig(config || {});
123
124             return this;
125         },
126
127         /**
128          * @private
129          */
130         applyConfig: flexSetter(function(name, value) {
131             var setter = 'set' + Ext.String.capitalize(name);
132
133             if (typeof this[setter] === 'function') {
134                 this[setter].call(this, value);
135             }
136
137             return this;
138         }),
139         //</feature>
140
141         /**
142          * Call the parent's overridden method. For example:
143          *
144          *     Ext.define('My.own.A', {
145          *         constructor: function(test) {
146          *             alert(test);
147          *         }
148          *     });
149          *
150          *     Ext.define('My.own.B', {
151          *         extend: 'My.own.A',
152          *
153          *         constructor: function(test) {
154          *             alert(test);
155          *
156          *             this.callParent([test + 1]);
157          *         }
158          *     });
159          *
160          *     Ext.define('My.own.C', {
161          *         extend: 'My.own.B',
162          *
163          *         constructor: function() {
164          *             alert("Going to call parent's overriden constructor...");
165          *
166          *             this.callParent(arguments);
167          *         }
168          *     });
169          *
170          *     var a = new My.own.A(1); // alerts '1'
171          *     var b = new My.own.B(1); // alerts '1', then alerts '2'
172          *     var c = new My.own.C(2); // alerts "Going to call parent's overriden constructor..."
173          *                              // alerts '2', then alerts '3'
174          *
175          * @protected
176          * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
177          * from the current method, for example: `this.callParent(arguments)`
178          * @return {Object} Returns the result from the superclass' method
179          */
180         callParent: function(args) {
181             var method = this.callParent.caller,
182                 parentClass, methodName;
183
184             if (!method.$owner) {
185                 //<debug error>
186                 if (!method.caller) {
187                     Ext.Error.raise({
188                         sourceClass: Ext.getClassName(this),
189                         sourceMethod: "callParent",
190                         msg: "Attempting to call a protected method from the public scope, which is not allowed"
191                     });
192                 }
193                 //</debug>
194
195                 method = method.caller;
196             }
197
198             parentClass = method.$owner.superclass;
199             methodName = method.$name;
200
201             //<debug error>
202             if (!(methodName in parentClass)) {
203                 Ext.Error.raise({
204                     sourceClass: Ext.getClassName(this),
205                     sourceMethod: methodName,
206                     msg: "this.callParent() was called but there's no such method (" + methodName +
207                          ") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")"
208                  });
209             }
210             //</debug>
211
212             return parentClass[methodName].apply(this, args || []);
213         },
214
215
216         /**
217          * Get the reference to the class from which this object was instantiated. Note that unlike {@link Ext.Base#self},
218          * `this.statics()` is scope-independent and it always returns the class from which it was called, regardless of what
219          * `this` points to during run-time
220          *
221          *     Ext.define('My.Cat', {
222          *         statics: {
223          *             totalCreated: 0,
224          *             speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
225          *         },
226          *
227          *         constructor: function() {
228          *             var statics = this.statics();
229          *
230          *             alert(statics.speciesName);     // always equals to 'Cat' no matter what 'this' refers to
231          *                                             // equivalent to: My.Cat.speciesName
232          *
233          *             alert(this.self.speciesName);   // dependent on 'this'
234          *
235          *             statics.totalCreated++;
236          *
237          *             return this;
238          *         },
239          *
240          *         clone: function() {
241          *             var cloned = new this.self;                      // dependent on 'this'
242          *
243          *             cloned.groupName = this.statics().speciesName;   // equivalent to: My.Cat.speciesName
244          *
245          *             return cloned;
246          *         }
247          *     });
248          *
249          *
250          *     Ext.define('My.SnowLeopard', {
251          *         extend: 'My.Cat',
252          *
253          *         statics: {
254          *             speciesName: 'Snow Leopard'     // My.SnowLeopard.speciesName = 'Snow Leopard'
255          *         },
256          *
257          *         constructor: function() {
258          *             this.callParent();
259          *         }
260          *     });
261          *
262          *     var cat = new My.Cat();                 // alerts 'Cat', then alerts 'Cat'
263          *
264          *     var snowLeopard = new My.SnowLeopard(); // alerts 'Cat', then alerts 'Snow Leopard'
265          *
266          *     var clone = snowLeopard.clone();
267          *     alert(Ext.getClassName(clone));         // alerts 'My.SnowLeopard'
268          *     alert(clone.groupName);                 // alerts 'Cat'
269          *
270          *     alert(My.Cat.totalCreated);             // alerts 3
271          *
272          * @protected
273          * @return {Ext.Class}
274          */
275         statics: function() {
276             var method = this.statics.caller,
277                 self = this.self;
278
279             if (!method) {
280                 return self;
281             }
282
283             return method.$owner;
284         },
285
286         /**
287          * Call the original method that was previously overridden with {@link Ext.Base#override}
288          *
289          *     Ext.define('My.Cat', {
290          *         constructor: function() {
291          *             alert("I'm a cat!");
292          *
293          *             return this;
294          *         }
295          *     });
296          *
297          *     My.Cat.override({
298          *         constructor: function() {
299          *             alert("I'm going to be a cat!");
300          *
301          *             var instance = this.callOverridden();
302          *
303          *             alert("Meeeeoooowwww");
304          *
305          *             return instance;
306          *         }
307          *     });
308          *
309          *     var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
310          *                               // alerts "I'm a cat!"
311          *                               // alerts "Meeeeoooowwww"
312          *
313          * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
314          * @return {Object} Returns the result after calling the overridden method
315          * @protected
316          */
317         callOverridden: function(args) {
318             var method = this.callOverridden.caller;
319
320             //<debug error>
321             if (!method.$owner) {
322                 Ext.Error.raise({
323                     sourceClass: Ext.getClassName(this),
324                     sourceMethod: "callOverridden",
325                     msg: "Attempting to call a protected method from the public scope, which is not allowed"
326                 });
327             }
328
329             if (!method.$previous) {
330                 Ext.Error.raise({
331                     sourceClass: Ext.getClassName(this),
332                     sourceMethod: "callOverridden",
333                     msg: "this.callOverridden was called in '" + method.$name +
334                          "' but this method has never been overridden"
335                  });
336             }
337             //</debug>
338
339             return method.$previous.apply(this, args || []);
340         },
341
342         destroy: function() {}
343     };
344
345     // These static properties will be copied to every newly created class with {@link Ext#define}
346     Ext.apply(Ext.Base, {
347         /**
348          * Create a new instance of this Class.
349          *
350          *     Ext.define('My.cool.Class', {
351          *         ...
352          *     });
353          *
354          *     My.cool.Class.create({
355          *         someConfig: true
356          *     });
357          *
358          * All parameters are passed to the constructor of the class.
359          *
360          * @return {Object} the created instance.
361          * @static
362          * @inheritable
363          */
364         create: function() {
365             return Ext.create.apply(Ext, [this].concat(Array.prototype.slice.call(arguments, 0)));
366         },
367
368         /**
369          * @private
370          * @inheritable
371          */
372         own: function(name, value) {
373             if (typeof value == 'function') {
374                 this.ownMethod(name, value);
375             }
376             else {
377                 this.prototype[name] = value;
378             }
379         },
380
381         /**
382          * @private
383          * @inheritable
384          */
385         ownMethod: function(name, fn) {
386             var originalFn;
387
388             if (typeof fn.$owner !== 'undefined' && fn !== Ext.emptyFn) {
389                 originalFn = fn;
390
391                 fn = function() {
392                     return originalFn.apply(this, arguments);
393                 };
394             }
395
396             //<debug>
397             var className;
398             className = Ext.getClassName(this);
399             if (className) {
400                 fn.displayName = className + '#' + name;
401             }
402             //</debug>
403             fn.$owner = this;
404             fn.$name = name;
405
406             this.prototype[name] = fn;
407         },
408
409         /**
410          * Add / override static properties of this class.
411          *
412          *     Ext.define('My.cool.Class', {
413          *         ...
414          *     });
415          *
416          *     My.cool.Class.addStatics({
417          *         someProperty: 'someValue',      // My.cool.Class.someProperty = 'someValue'
418          *         method1: function() { ... },    // My.cool.Class.method1 = function() { ... };
419          *         method2: function() { ... }     // My.cool.Class.method2 = function() { ... };
420          *     });
421          *
422          * @param {Object} members
423          * @return {Ext.Base} this
424          * @static
425          * @inheritable
426          */
427         addStatics: function(members) {
428             for (var name in members) {
429                 if (members.hasOwnProperty(name)) {
430                     this[name] = members[name];
431                 }
432             }
433
434             return this;
435         },
436
437         /**
438          * @private
439          * @param {Object} members
440          */
441         addInheritableStatics: function(members) {
442             var inheritableStatics,
443                 hasInheritableStatics,
444                 prototype = this.prototype,
445                 name, member;
446
447             inheritableStatics = prototype.$inheritableStatics;
448             hasInheritableStatics = prototype.$hasInheritableStatics;
449
450             if (!inheritableStatics) {
451                 inheritableStatics = prototype.$inheritableStatics = [];
452                 hasInheritableStatics = prototype.$hasInheritableStatics = {};
453             }
454
455             //<debug>
456             var className = Ext.getClassName(this);
457             //</debug>
458
459             for (name in members) {
460                 if (members.hasOwnProperty(name)) {
461                     member = members[name];
462                     //<debug>
463                     if (typeof member == 'function') {
464                         member.displayName = className + '.' + name;
465                     }
466                     //</debug>
467                     this[name] = member;
468
469                     if (!hasInheritableStatics[name]) {
470                         hasInheritableStatics[name] = true;
471                         inheritableStatics.push(name);
472                     }
473                 }
474             }
475
476             return this;
477         },
478
479         /**
480          * Add methods / properties to the prototype of this class.
481          *
482          *     Ext.define('My.awesome.Cat', {
483          *         constructor: function() {
484          *             ...
485          *         }
486          *     });
487          *
488          *      My.awesome.Cat.implement({
489          *          meow: function() {
490          *             alert('Meowww...');
491          *          }
492          *      });
493          *
494          *      var kitty = new My.awesome.Cat;
495          *      kitty.meow();
496          *
497          * @param {Object} members
498          * @static
499          * @inheritable
500          */
501         implement: function(members) {
502             var prototype = this.prototype,
503                 enumerables = Ext.enumerables,
504                 name, i, member;
505             //<debug>
506             var className = Ext.getClassName(this);
507             //</debug>
508             for (name in members) {
509                 if (members.hasOwnProperty(name)) {
510                     member = members[name];
511
512                     if (typeof member === 'function') {
513                         member.$owner = this;
514                         member.$name = name;
515                         //<debug>
516                         if (className) {
517                             member.displayName = className + '#' + name;
518                         }
519                         //</debug>
520                     }
521
522                     prototype[name] = member;
523                 }
524             }
525
526             if (enumerables) {
527                 for (i = enumerables.length; i--;) {
528                     name = enumerables[i];
529
530                     if (members.hasOwnProperty(name)) {
531                         member = members[name];
532                         member.$owner = this;
533                         member.$name = name;
534                         prototype[name] = member;
535                     }
536                 }
537             }
538         },
539
540         /**
541          * Borrow another class' members to the prototype of this class.
542          *
543          *     Ext.define('Bank', {
544          *         money: '$$$',
545          *         printMoney: function() {
546          *             alert('$$$$$$$');
547          *         }
548          *     });
549          *
550          *     Ext.define('Thief', {
551          *         ...
552          *     });
553          *
554          *     Thief.borrow(Bank, ['money', 'printMoney']);
555          *
556          *     var steve = new Thief();
557          *
558          *     alert(steve.money); // alerts '$$$'
559          *     steve.printMoney(); // alerts '$$$$$$$'
560          *
561          * @param {Ext.Base} fromClass The class to borrow members from
562          * @param {String/String[]} members The names of the members to borrow
563          * @return {Ext.Base} this
564          * @static
565          * @inheritable
566          */
567         borrow: function(fromClass, members) {
568             var fromPrototype = fromClass.prototype,
569                 i, ln, member;
570
571             members = Ext.Array.from(members);
572
573             for (i = 0, ln = members.length; i < ln; i++) {
574                 member = members[i];
575
576                 this.own(member, fromPrototype[member]);
577             }
578
579             return this;
580         },
581
582         /**
583          * Override prototype members of this class. Overridden methods can be invoked via
584          * {@link Ext.Base#callOverridden}
585          *
586          *     Ext.define('My.Cat', {
587          *         constructor: function() {
588          *             alert("I'm a cat!");
589          *
590          *             return this;
591          *         }
592          *     });
593          *
594          *     My.Cat.override({
595          *         constructor: function() {
596          *             alert("I'm going to be a cat!");
597          *
598          *             var instance = this.callOverridden();
599          *
600          *             alert("Meeeeoooowwww");
601          *
602          *             return instance;
603          *         }
604          *     });
605          *
606          *     var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
607          *                               // alerts "I'm a cat!"
608          *                               // alerts "Meeeeoooowwww"
609          *
610          * @param {Object} members
611          * @return {Ext.Base} this
612          * @static
613          * @inheritable
614          */
615         override: function(members) {
616             var prototype = this.prototype,
617                 enumerables = Ext.enumerables,
618                 name, i, member, previous;
619
620             if (arguments.length === 2) {
621                 name = members;
622                 member = arguments[1];
623
624                 if (typeof member == 'function') {
625                     if (typeof prototype[name] == 'function') {
626                         previous = prototype[name];
627                         member.$previous = previous;
628                     }
629
630                     this.ownMethod(name, member);
631                 }
632                 else {
633                     prototype[name] = member;
634                 }
635
636                 return this;
637             }
638
639             for (name in members) {
640                 if (members.hasOwnProperty(name)) {
641                     member = members[name];
642
643                     if (typeof member === 'function') {
644                         if (typeof prototype[name] === 'function') {
645                             previous = prototype[name];
646                             member.$previous = previous;
647                         }
648
649                         this.ownMethod(name, member);
650                     }
651                     else {
652                         prototype[name] = member;
653                     }
654                 }
655             }
656
657             if (enumerables) {
658                 for (i = enumerables.length; i--;) {
659                     name = enumerables[i];
660
661                     if (members.hasOwnProperty(name)) {
662                         if (typeof prototype[name] !== 'undefined') {
663                             previous = prototype[name];
664                             members[name].$previous = previous;
665                         }
666
667                         this.ownMethod(name, members[name]);
668                     }
669                 }
670             }
671
672             return this;
673         },
674
675         //<feature classSystem.mixins>
676         /**
677          * Used internally by the mixins pre-processor
678          * @private
679          * @inheritable
680          */
681         mixin: function(name, cls) {
682             var mixin = cls.prototype,
683                 my = this.prototype,
684                 key, fn;
685
686             for (key in mixin) {
687                 if (mixin.hasOwnProperty(key)) {
688                     if (typeof my[key] === 'undefined' && key !== 'mixins' && key !== 'mixinId') {
689                         if (typeof mixin[key] === 'function') {
690                             fn = mixin[key];
691
692                             if (typeof fn.$owner === 'undefined') {
693                                 this.ownMethod(key, fn);
694                             }
695                             else {
696                                 my[key] = fn;
697                             }
698                         }
699                         else {
700                             my[key] = mixin[key];
701                         }
702                     }
703                     //<feature classSystem.config>
704                     else if (key === 'config' && my.config && mixin.config) {
705                         Ext.Object.merge(my.config, mixin.config);
706                     }
707                     //</feature>
708                 }
709             }
710
711             if (typeof mixin.onClassMixedIn !== 'undefined') {
712                 mixin.onClassMixedIn.call(cls, this);
713             }
714
715             if (!my.hasOwnProperty('mixins')) {
716                 if ('mixins' in my) {
717                     my.mixins = Ext.Object.merge({}, my.mixins);
718                 }
719                 else {
720                     my.mixins = {};
721                 }
722             }
723
724             my.mixins[name] = mixin;
725         },
726         //</feature>
727
728         /**
729          * Get the current class' name in string format.
730          *
731          *     Ext.define('My.cool.Class', {
732          *         constructor: function() {
733          *             alert(this.self.getName()); // alerts 'My.cool.Class'
734          *         }
735          *     });
736          *
737          *     My.cool.Class.getName(); // 'My.cool.Class'
738          *
739          * @return {String} className
740          * @static
741          * @inheritable
742          */
743         getName: function() {
744             return Ext.getClassName(this);
745         },
746
747         /**
748          * Create aliases for existing prototype methods. Example:
749          *
750          *     Ext.define('My.cool.Class', {
751          *         method1: function() { ... },
752          *         method2: function() { ... }
753          *     });
754          *
755          *     var test = new My.cool.Class();
756          *
757          *     My.cool.Class.createAlias({
758          *         method3: 'method1',
759          *         method4: 'method2'
760          *     });
761          *
762          *     test.method3(); // test.method1()
763          *
764          *     My.cool.Class.createAlias('method5', 'method3');
765          *
766          *     test.method5(); // test.method3() -> test.method1()
767          *
768          * @param {String/Object} alias The new method name, or an object to set multiple aliases. See
769          * {@link Ext.Function#flexSetter flexSetter}
770          * @param {String/Object} origin The original method name
771          * @static
772          * @inheritable
773          * @method
774          */
775         createAlias: flexSetter(function(alias, origin) {
776             this.prototype[alias] = function() {
777                 return this[origin].apply(this, arguments);
778             }
779         })
780     });
781
782 })(Ext.Function.flexSetter);
783