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