Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / core / src / class / Class.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.Class
19  *
20  * Handles class creation throughout the framework. This is a low level factory that is used by Ext.ClassManager and generally
21  * should not be used directly. If you choose to use Ext.Class you will lose out on the namespace, aliasing and depency loading
22  * features made available by Ext.ClassManager. The only time you would use Ext.Class directly is to create an anonymous class.
23  *
24  * If you wish to create a class you should use {@link Ext#define Ext.define} which aliases
25  * {@link Ext.ClassManager#create Ext.ClassManager.create} to enable namespacing and dynamic dependency resolution.
26  *
27  * Ext.Class is the factory and **not** the superclass of everything. For the base class that **all** Ext classes inherit
28  * from, see {@link Ext.Base}.
29  */
30 (function() {
31
32     var Class,
33         Base = Ext.Base,
34         baseStaticProperties = [],
35         baseStaticProperty;
36
37     for (baseStaticProperty in Base) {
38         if (Base.hasOwnProperty(baseStaticProperty)) {
39             baseStaticProperties.push(baseStaticProperty);
40         }
41     }
42
43     /**
44      * @method constructor
45      * Creates new class.
46      * @param {Object} classData An object represent the properties of this class
47      * @param {Function} createdFn (Optional) The callback function to be executed when this class is fully created.
48      * Note that the creation process can be asynchronous depending on the pre-processors used.
49      * @return {Ext.Base} The newly created class
50      */
51     Ext.Class = Class = function(newClass, classData, onClassCreated) {
52         if (typeof newClass != 'function') {
53             onClassCreated = classData;
54             classData = newClass;
55             newClass = function() {
56                 return this.constructor.apply(this, arguments);
57             };
58         }
59
60         if (!classData) {
61             classData = {};
62         }
63
64         var preprocessorStack = classData.preprocessors || Class.getDefaultPreprocessors(),
65             registeredPreprocessors = Class.getPreprocessors(),
66             index = 0,
67             preprocessors = [],
68             preprocessor, staticPropertyName, process, i, j, ln;
69
70         for (i = 0, ln = baseStaticProperties.length; i < ln; i++) {
71             staticPropertyName = baseStaticProperties[i];
72             newClass[staticPropertyName] = Base[staticPropertyName];
73         }
74
75         delete classData.preprocessors;
76
77         for (j = 0, ln = preprocessorStack.length; j < ln; j++) {
78             preprocessor = preprocessorStack[j];
79
80             if (typeof preprocessor == 'string') {
81                 preprocessor = registeredPreprocessors[preprocessor];
82
83                 if (!preprocessor.always) {
84                     if (classData.hasOwnProperty(preprocessor.name)) {
85                         preprocessors.push(preprocessor.fn);
86                     }
87                 }
88                 else {
89                     preprocessors.push(preprocessor.fn);
90                 }
91             }
92             else {
93                 preprocessors.push(preprocessor);
94             }
95         }
96
97         classData.onClassCreated = onClassCreated || Ext.emptyFn;
98
99         classData.onBeforeClassCreated = function(cls, data) {
100             onClassCreated = data.onClassCreated;
101
102             delete data.onBeforeClassCreated;
103             delete data.onClassCreated;
104
105             cls.implement(data);
106
107             onClassCreated.call(cls, cls);
108         };
109
110         process = function(cls, data) {
111             preprocessor = preprocessors[index++];
112
113             if (!preprocessor) {
114                 data.onBeforeClassCreated.apply(this, arguments);
115                 return;
116             }
117
118             if (preprocessor.call(this, cls, data, process) !== false) {
119                 process.apply(this, arguments);
120             }
121         };
122
123         process.call(Class, newClass, classData);
124
125         return newClass;
126     };
127
128     Ext.apply(Class, {
129
130         /** @private */
131         preprocessors: {},
132
133         /**
134          * Register a new pre-processor to be used during the class creation process
135          *
136          * @member Ext.Class
137          * @param {String} name The pre-processor's name
138          * @param {Function} fn The callback function to be executed. Typical format:
139          *
140          *     function(cls, data, fn) {
141          *         // Your code here
142          *
143          *         // Execute this when the processing is finished.
144          *         // Asynchronous processing is perfectly ok
145          *         if (fn) {
146          *             fn.call(this, cls, data);
147          *         }
148          *     });
149          *
150          * @param {Function} fn.cls The created class
151          * @param {Object} fn.data The set of properties passed in {@link Ext.Class} constructor
152          * @param {Function} fn.fn The callback function that **must** to be executed when this pre-processor finishes,
153          * regardless of whether the processing is synchronous or aynchronous
154          *
155          * @return {Ext.Class} this
156          * @static
157          */
158         registerPreprocessor: function(name, fn, always) {
159             this.preprocessors[name] = {
160                 name: name,
161                 always: always ||  false,
162                 fn: fn
163             };
164
165             return this;
166         },
167
168         /**
169          * Retrieve a pre-processor callback function by its name, which has been registered before
170          *
171          * @param {String} name
172          * @return {Function} preprocessor
173          * @static
174          */
175         getPreprocessor: function(name) {
176             return this.preprocessors[name];
177         },
178
179         getPreprocessors: function() {
180             return this.preprocessors;
181         },
182
183         /**
184          * Retrieve the array stack of default pre-processors
185          *
186          * @return {Function[]} defaultPreprocessors
187          * @static
188          */
189         getDefaultPreprocessors: function() {
190             return this.defaultPreprocessors || [];
191         },
192
193         /**
194          * Set the default array stack of default pre-processors
195          *
196          * @param {Function/Function[]} preprocessors
197          * @return {Ext.Class} this
198          * @static
199          */
200         setDefaultPreprocessors: function(preprocessors) {
201             this.defaultPreprocessors = Ext.Array.from(preprocessors);
202
203             return this;
204         },
205
206         /**
207          * Inserts this pre-processor at a specific position in the stack, optionally relative to
208          * any existing pre-processor. For example:
209          *
210          *     Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {
211          *         // Your code here
212          *
213          *         if (fn) {
214          *             fn.call(this, cls, data);
215          *         }
216          *     }).setDefaultPreprocessorPosition('debug', 'last');
217          *
218          * @param {String} name The pre-processor name. Note that it needs to be registered with
219          * {@link #registerPreprocessor registerPreprocessor} before this
220          * @param {String} offset The insertion position. Four possible values are:
221          * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
222          * @param {String} relativeName
223          * @return {Ext.Class} this
224          * @static
225          */
226         setDefaultPreprocessorPosition: function(name, offset, relativeName) {
227             var defaultPreprocessors = this.defaultPreprocessors,
228                 index;
229
230             if (typeof offset == 'string') {
231                 if (offset === 'first') {
232                     defaultPreprocessors.unshift(name);
233
234                     return this;
235                 }
236                 else if (offset === 'last') {
237                     defaultPreprocessors.push(name);
238
239                     return this;
240                 }
241
242                 offset = (offset === 'after') ? 1 : -1;
243             }
244
245             index = Ext.Array.indexOf(defaultPreprocessors, relativeName);
246
247             if (index !== -1) {
248                 Ext.Array.splice(defaultPreprocessors, Math.max(0, index + offset), 0, name);
249             }
250
251             return this;
252         }
253     });
254
255     /**
256      * @cfg {String} extend
257      * The parent class that this class extends. For example:
258      *
259      *     Ext.define('Person', {
260      *         say: function(text) { alert(text); }
261      *     });
262      *
263      *     Ext.define('Developer', {
264      *         extend: 'Person',
265      *         say: function(text) { this.callParent(["print "+text]); }
266      *     });
267      */
268     Class.registerPreprocessor('extend', function(cls, data) {
269         var extend = data.extend,
270             base = Ext.Base,
271             basePrototype = base.prototype,
272             prototype = function() {},
273             parent, i, k, ln, staticName, parentStatics,
274             parentPrototype, clsPrototype;
275
276         if (extend && extend !== Object) {
277             parent = extend;
278         }
279         else {
280             parent = base;
281         }
282
283         parentPrototype = parent.prototype;
284
285         prototype.prototype = parentPrototype;
286         clsPrototype = cls.prototype = new prototype();
287
288         if (!('$class' in parent)) {
289             for (i in basePrototype) {
290                 if (!parentPrototype[i]) {
291                     parentPrototype[i] = basePrototype[i];
292                 }
293             }
294         }
295
296         clsPrototype.self = cls;
297
298         cls.superclass = clsPrototype.superclass = parentPrototype;
299
300         delete data.extend;
301
302         //<feature classSystem.inheritableStatics>
303         // Statics inheritance
304         parentStatics = parentPrototype.$inheritableStatics;
305
306         if (parentStatics) {
307             for (k = 0, ln = parentStatics.length; k < ln; k++) {
308                 staticName = parentStatics[k];
309
310                 if (!cls.hasOwnProperty(staticName)) {
311                     cls[staticName] = parent[staticName];
312                 }
313             }
314         }
315         //</feature>
316
317         //<feature classSystem.config>
318         // Merge the parent class' config object without referencing it
319         if (parentPrototype.config) {
320             clsPrototype.config = Ext.Object.merge({}, parentPrototype.config);
321         }
322         else {
323             clsPrototype.config = {};
324         }
325         //</feature>
326
327         //<feature classSystem.onClassExtended>
328         if (clsPrototype.$onExtended) {
329             clsPrototype.$onExtended.call(cls, cls, data);
330         }
331
332         if (data.onClassExtended) {
333             clsPrototype.$onExtended = data.onClassExtended;
334             delete data.onClassExtended;
335         }
336         //</feature>
337
338     }, true);
339
340     //<feature classSystem.statics>
341     /**
342      * @cfg {Object} statics
343      * List of static methods for this class. For example:
344      *
345      *     Ext.define('Computer', {
346      *          statics: {
347      *              factory: function(brand) {
348      *                  // 'this' in static methods refer to the class itself
349      *                  return new this(brand);
350      *              }
351      *          },
352      *
353      *          constructor: function() { ... }
354      *     });
355      *
356      *     var dellComputer = Computer.factory('Dell');
357      */
358     Class.registerPreprocessor('statics', function(cls, data) {
359         cls.addStatics(data.statics);
360
361         delete data.statics;
362     });
363     //</feature>
364
365     //<feature classSystem.inheritableStatics>
366     /**
367      * @cfg {Object} inheritableStatics
368      * List of inheritable static methods for this class.
369      * Otherwise just like {@link #statics} but subclasses inherit these methods.
370      */
371     Class.registerPreprocessor('inheritableStatics', function(cls, data) {
372         cls.addInheritableStatics(data.inheritableStatics);
373
374         delete data.inheritableStatics;
375     });
376     //</feature>
377
378     //<feature classSystem.config>
379     /**
380      * @cfg {Object} config
381      * List of configuration options with their default values, for which automatically
382      * accessor methods are generated.  For example:
383      *
384      *     Ext.define('SmartPhone', {
385      *          config: {
386      *              hasTouchScreen: false,
387      *              operatingSystem: 'Other',
388      *              price: 500
389      *          },
390      *          constructor: function(cfg) {
391      *              this.initConfig(cfg);
392      *          }
393      *     });
394      *
395      *     var iPhone = new SmartPhone({
396      *          hasTouchScreen: true,
397      *          operatingSystem: 'iOS'
398      *     });
399      *
400      *     iPhone.getPrice(); // 500;
401      *     iPhone.getOperatingSystem(); // 'iOS'
402      *     iPhone.getHasTouchScreen(); // true;
403      *     iPhone.hasTouchScreen(); // true
404      */
405     Class.registerPreprocessor('config', function(cls, data) {
406         var prototype = cls.prototype;
407
408         Ext.Object.each(data.config, function(name) {
409             var cName = name.charAt(0).toUpperCase() + name.substr(1),
410                 pName = name,
411                 apply = 'apply' + cName,
412                 setter = 'set' + cName,
413                 getter = 'get' + cName;
414
415             if (!(apply in prototype) && !data.hasOwnProperty(apply)) {
416                 data[apply] = function(val) {
417                     return val;
418                 };
419             }
420
421             if (!(setter in prototype) && !data.hasOwnProperty(setter)) {
422                 data[setter] = function(val) {
423                     var ret = this[apply].call(this, val, this[pName]);
424
425                     if (typeof ret != 'undefined') {
426                         this[pName] = ret;
427                     }
428
429                     return this;
430                 };
431             }
432
433             if (!(getter in prototype) && !data.hasOwnProperty(getter)) {
434                 data[getter] = function() {
435                     return this[pName];
436                 };
437             }
438         });
439
440         Ext.Object.merge(prototype.config, data.config);
441         delete data.config;
442     });
443     //</feature>
444
445     //<feature classSystem.mixins>
446     /**
447      * @cfg {Object} mixins
448      * List of classes to mix into this class. For example:
449      *
450      *     Ext.define('CanSing', {
451      *          sing: function() {
452      *              alert("I'm on the highway to hell...")
453      *          }
454      *     });
455      *
456      *     Ext.define('Musician', {
457      *          extend: 'Person',
458      *
459      *          mixins: {
460      *              canSing: 'CanSing'
461      *          }
462      *     })
463      */
464     Class.registerPreprocessor('mixins', function(cls, data) {
465         var mixins = data.mixins,
466             name, mixin, i, ln;
467
468         delete data.mixins;
469
470         Ext.Function.interceptBefore(data, 'onClassCreated', function(cls) {
471             if (mixins instanceof Array) {
472                 for (i = 0,ln = mixins.length; i < ln; i++) {
473                     mixin = mixins[i];
474                     name = mixin.prototype.mixinId || mixin.$className;
475
476                     cls.mixin(name, mixin);
477                 }
478             }
479             else {
480                 for (name in mixins) {
481                     if (mixins.hasOwnProperty(name)) {
482                         cls.mixin(name, mixins[name]);
483                     }
484                 }
485             }
486         });
487     });
488
489     //</feature>
490
491     Class.setDefaultPreprocessors([
492         'extend'
493         //<feature classSystem.statics>
494         ,'statics'
495         //</feature>
496         //<feature classSystem.inheritableStatics>
497         ,'inheritableStatics'
498         //</feature>
499         //<feature classSystem.config>
500         ,'config'
501         //</feature>
502         //<feature classSystem.mixins>
503         ,'mixins'
504         //</feature>
505     ]);
506
507     //<feature classSystem.backwardsCompatible>
508     // Backwards compatible
509     Ext.extend = function(subclass, superclass, members) {
510         if (arguments.length === 2 && Ext.isObject(superclass)) {
511             members = superclass;
512             superclass = subclass;
513             subclass = null;
514         }
515
516         var cls;
517
518         if (!superclass) {
519             Ext.Error.raise("Attempting to extend from a class which has not been loaded on the page.");
520         }
521
522         members.extend = superclass;
523         members.preprocessors = [
524             'extend'
525             //<feature classSystem.statics>
526             ,'statics'
527             //</feature>
528             //<feature classSystem.inheritableStatics>
529             ,'inheritableStatics'
530             //</feature>
531             //<feature classSystem.mixins>
532             ,'mixins'
533             //</feature>
534             //<feature classSystem.config>
535             ,'config'
536             //</feature>
537         ];
538
539         if (subclass) {
540             cls = new Class(subclass, members);
541         }
542         else {
543             cls = new Class(members);
544         }
545
546         cls.prototype.override = function(o) {
547             for (var m in o) {
548                 if (o.hasOwnProperty(m)) {
549                     this[m] = o[m];
550                 }
551             }
552         };
553
554         return cls;
555     };
556     //</feature>
557
558 })();
559