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