Upgrade to ExtJS 4.0.0 - Released 04/26/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 Ext.define('My.cool.Class', {
343     ...
344 });
345
346 My.cool.Class.create({
347     someConfig: true
348 });
349          * @property create
350          * @static
351          * @type Function
352          * @markdown
353          */
354         create: function() {
355             return Ext.create.apply(Ext, [this].concat(Array.prototype.slice.call(arguments, 0)));
356         },
357
358         /**
359          * @private
360          */
361         own: flexSetter(function(name, value) {
362             if (typeof value === 'function') {
363                 this.ownMethod(name, value);
364             }
365             else {
366                 this.prototype[name] = value;
367             }
368         }),
369
370         /**
371          * @private
372          */
373         ownMethod: function(name, fn) {
374             var originalFn;
375
376             if (fn.$owner !== undefined && fn !== Ext.emptyFn) {
377                 originalFn = fn;
378
379                 fn = function() {
380                     return originalFn.apply(this, arguments);
381                 };
382             }
383
384             //<debug>
385             var className;
386             className = Ext.getClassName(this);
387             if (className) {
388                 fn.displayName = className + '#' + name;
389             }
390             //</debug>
391             fn.$owner = this;
392             fn.$name = name;
393
394             this.prototype[name] = fn;
395         },
396
397         /**
398          * Add / override static properties of this class.
399
400     Ext.define('My.cool.Class', {
401         ...
402     });
403
404     My.cool.Class.addStatics({
405         someProperty: 'someValue',      // My.cool.Class.someProperty = 'someValue'
406         method1: function() { ... },    // My.cool.Class.method1 = function() { ... };
407         method2: function() { ... }     // My.cool.Class.method2 = function() { ... };
408     });
409
410          * @property addStatics
411          * @static
412          * @type Function
413          * @param {Object} members
414          * @markdown
415          */
416         addStatics: function(members) {
417             for (var name in members) {
418                 if (members.hasOwnProperty(name)) {
419                     this[name] = members[name];
420                 }
421             }
422
423             return this;
424         },
425
426         /**
427          * Add methods / properties to the prototype of this class.
428
429     Ext.define('My.awesome.Cat', {
430         constructor: function() {
431             ...
432         }
433     });
434
435      My.awesome.Cat.implement({
436          meow: function() {
437             alert('Meowww...');
438          }
439      });
440
441      var kitty = new My.awesome.Cat;
442      kitty.meow();
443
444          * @property implement
445          * @static
446          * @type Function
447          * @param {Object} members
448          * @markdown
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          * @property borrow
512          * @static
513          * @type Function
514          * @param {Ext.Base} fromClass The class to borrow members from
515          * @param {Array/String} members The names of the members to borrow
516          * @return {Ext.Base} this
517          * @markdown
518          */
519         borrow: function(fromClass, members) {
520             var fromPrototype = fromClass.prototype,
521                 i, ln, member;
522
523             members = Ext.Array.from(members);
524
525             for (i = 0, ln = members.length; i < ln; i++) {
526                 member = members[i];
527
528                 this.own(member, fromPrototype[member]);
529             }
530
531             return this;
532         },
533
534         /**
535          * Override prototype members of this class. Overridden methods can be invoked via
536          * {@link Ext.Base#callOverridden}
537
538     Ext.define('My.Cat', {
539         constructor: function() {
540             alert("I'm a cat!");
541
542             return this;
543         }
544     });
545
546     My.Cat.override({
547         constructor: function() {
548             alert("I'm going to be a cat!");
549
550             var instance = this.callOverridden();
551
552             alert("Meeeeoooowwww");
553
554             return instance;
555         }
556     });
557
558     var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
559                               // alerts "I'm a cat!"
560                               // alerts "Meeeeoooowwww"
561
562          * @property override
563          * @static
564          * @type Function
565          * @param {Object} members
566          * @return {Ext.Base} this
567          * @markdown
568          */
569         override: function(members) {
570             var prototype = this.prototype,
571                 name, i, member, previous;
572
573             for (name in members) {
574                 if (members.hasOwnProperty(name)) {
575                     member = members[name];
576
577                     if (typeof member === 'function') {
578                         if (typeof prototype[name] === 'function') {
579                             previous = prototype[name];
580                             member.$previous = previous;
581                         }
582
583                         this.ownMethod(name, member);
584                     }
585                     else {
586                         prototype[name] = member;
587                     }
588                 }
589             }
590
591             if (Ext.enumerables) {
592                 var enumerables = Ext.enumerables;
593
594                 for (i = enumerables.length; i--;) {
595                     name = enumerables[i];
596
597                     if (members.hasOwnProperty(name)) {
598                         if (prototype[name] !== undefined) {
599                             previous = prototype[name];
600                             members[name].$previous = previous;
601                         }
602
603                         this.ownMethod(name, members[name]);
604                     }
605                 }
606             }
607
608             return this;
609         },
610
611         /**
612          * Used internally by the mixins pre-processor
613          * @private
614          */
615         mixin: flexSetter(function(name, cls) {
616             var mixin = cls.prototype,
617                 my = this.prototype,
618                 i, fn;
619
620             for (i in mixin) {
621                 if (mixin.hasOwnProperty(i)) {
622                     if (my[i] === undefined) {
623                         if (typeof mixin[i] === 'function') {
624                             fn = mixin[i];
625
626                             if (fn.$owner === undefined) {
627                                 this.ownMethod(i, fn);
628                             }
629                             else {
630                                 my[i] = fn;
631                             }
632                         }
633                         else {
634                             my[i] = mixin[i];
635                         }
636                     }
637                     else if (i === 'config' && my.config && mixin.config) {
638                         Ext.Object.merge(my.config, mixin.config);
639                     }
640                 }
641             }
642
643             if (my.mixins === undefined) {
644                 my.mixins = {};
645             }
646
647             my.mixins[name] = mixin;
648         }),
649
650         /**
651          * Get the current class' name in string format.
652
653     Ext.define('My.cool.Class', {
654         constructor: function() {
655             alert(this.self.getName()); // alerts 'My.cool.Class'
656         }
657     });
658
659     My.cool.Class.getName(); // 'My.cool.Class'
660
661          * @return {String} className
662          * @markdown
663          */
664         getName: function() {
665             return Ext.getClassName(this);
666         },
667
668         /**
669          * Create aliases for existing prototype methods. Example:
670
671     Ext.define('My.cool.Class', {
672         method1: function() { ... },
673         method2: function() { ... }
674     });
675
676     var test = new My.cool.Class();
677
678     My.cool.Class.createAlias({
679         method3: 'method1',
680         method4: 'method2'
681     });
682
683     test.method3(); // test.method1()
684
685     My.cool.Class.createAlias('method5', 'method3');
686
687     test.method5(); // test.method3() -> test.method1()
688
689          * @property createAlias
690          * @static
691          * @type Function
692          * @param {String/Object} alias The new method name, or an object to set multiple aliases. See
693          * {@link Ext.Function#flexSetter flexSetter}
694          * @param {String/Object} origin The original method name
695          * @markdown
696          */
697         createAlias: flexSetter(function(alias, origin) {
698             this.prototype[alias] = this.prototype[origin];
699         })
700     });
701
702 })(Ext.Function.flexSetter);