Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / docs / source / Class.html
1 <!DOCTYPE html><html><head><title>Sencha Documentation Project</title><link rel="stylesheet" href="../reset.css" type="text/css"><link rel="stylesheet" href="../prettify.css" type="text/css"><link rel="stylesheet" href="../prettify_sa.css" type="text/css"><script type="text/javascript" src="../prettify.js"></script></head><body onload="prettyPrint()"><pre class="prettyprint"><pre><span id='Ext-Class'>/**
2 </span> * @author Jacky Nguyen &lt;jacky@sencha.com&gt;
3  * @docauthor Jacky Nguyen &lt;jacky@sencha.com&gt;
4  * @class Ext.Class
5  * 
6  * Handles class creation throughout the whole framework. Note that most of the time {@link Ext#define Ext.define} should
7  * be used instead, since it's a higher level wrapper that aliases to {@link Ext.ClassManager#create}
8  * to enable namespacing and dynamic dependency resolution.
9  * 
10  * # Basic syntax: #
11  * 
12  *     Ext.define(className, properties);
13  * 
14  * in which `properties` is an object represent a collection of properties that apply to the class. See
15  * {@link Ext.ClassManager#create} for more detailed instructions.
16  * 
17  *     Ext.define('Person', {
18  *          name: 'Unknown',
19  * 
20  *          constructor: function(name) {
21  *              if (name) {
22  *                  this.name = name;
23  *              }
24  * 
25  *              return this;
26  *          },
27  * 
28  *          eat: function(foodType) {
29  *              alert(&quot;I'm eating: &quot; + foodType);
30  * 
31  *              return this;
32  *          }
33  *     });
34  * 
35  *     var aaron = new Person(&quot;Aaron&quot;);
36  *     aaron.eat(&quot;Sandwich&quot;); // alert(&quot;I'm eating: Sandwich&quot;);
37  * 
38  * Ext.Class has a powerful set of extensible {@link Ext.Class#registerPreprocessor pre-processors} which takes care of
39  * everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc.
40  * 
41  * # Inheritance: #
42  * 
43  *     Ext.define('Developer', {
44  *          extend: 'Person',
45  * 
46  *          constructor: function(name, isGeek) {
47  *              this.isGeek = isGeek;
48  * 
49  *              // Apply a method from the parent class' prototype
50  *              this.callParent([name]);
51  * 
52  *              return this;
53  * 
54  *          },
55  * 
56  *          code: function(language) {
57  *              alert(&quot;I'm coding in: &quot; + language);
58  * 
59  *              this.eat(&quot;Bugs&quot;);
60  * 
61  *              return this;
62  *          }
63  *     });
64  * 
65  *     var jacky = new Developer(&quot;Jacky&quot;, true);
66  *     jacky.code(&quot;JavaScript&quot;); // alert(&quot;I'm coding in: JavaScript&quot;);
67  *                               // alert(&quot;I'm eating: Bugs&quot;);
68  * 
69  * See {@link Ext.Base#callParent} for more details on calling superclass' methods
70  * 
71  * # Mixins: #
72  * 
73  *     Ext.define('CanPlayGuitar', {
74  *          playGuitar: function() {
75  *             alert(&quot;F#...G...D...A&quot;);
76  *          }
77  *     });
78  * 
79  *     Ext.define('CanComposeSongs', {
80  *          composeSongs: function() { ... }
81  *     });
82  * 
83  *     Ext.define('CanSing', {
84  *          sing: function() {
85  *              alert(&quot;I'm on the highway to hell...&quot;)
86  *          }
87  *     });
88  * 
89  *     Ext.define('Musician', {
90  *          extend: 'Person',
91  * 
92  *          mixins: {
93  *              canPlayGuitar: 'CanPlayGuitar',
94  *              canComposeSongs: 'CanComposeSongs',
95  *              canSing: 'CanSing'
96  *          }
97  *     })
98  * 
99  *     Ext.define('CoolPerson', {
100  *          extend: 'Person',
101  * 
102  *          mixins: {
103  *              canPlayGuitar: 'CanPlayGuitar',
104  *              canSing: 'CanSing'
105  *          },
106  * 
107  *          sing: function() {
108  *              alert(&quot;Ahem....&quot;);
109  * 
110  *              this.mixins.canSing.sing.call(this);
111  * 
112  *              alert(&quot;[Playing guitar at the same time...]&quot;);
113  * 
114  *              this.playGuitar();
115  *          }
116  *     });
117  * 
118  *     var me = new CoolPerson(&quot;Jacky&quot;);
119  * 
120  *     me.sing(); // alert(&quot;Ahem...&quot;);
121  *                // alert(&quot;I'm on the highway to hell...&quot;);
122  *                // alert(&quot;[Playing guitar at the same time...]&quot;);
123  *                // alert(&quot;F#...G...D...A&quot;);
124  * 
125  * # Config: #
126  * 
127  *     Ext.define('SmartPhone', {
128  *          config: {
129  *              hasTouchScreen: false,
130  *              operatingSystem: 'Other',
131  *              price: 500
132  *          },
133  * 
134  *          isExpensive: false,
135  * 
136  *          constructor: function(config) {
137  *              this.initConfig(config);
138  * 
139  *              return this;
140  *          },
141  * 
142  *          applyPrice: function(price) {
143  *              this.isExpensive = (price &gt; 500);
144  * 
145  *              return price;
146  *          },
147  * 
148  *          applyOperatingSystem: function(operatingSystem) {
149  *              if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) {
150  *                  return 'Other';
151  *              }
152  * 
153  *              return operatingSystem;
154  *          }
155  *     });
156  * 
157  *     var iPhone = new SmartPhone({
158  *          hasTouchScreen: true,
159  *          operatingSystem: 'iOS'
160  *     });
161  * 
162  *     iPhone.getPrice(); // 500;
163  *     iPhone.getOperatingSystem(); // 'iOS'
164  *     iPhone.getHasTouchScreen(); // true;
165  *     iPhone.hasTouchScreen(); // true
166  * 
167  *     iPhone.isExpensive; // false;
168  *     iPhone.setPrice(600);
169  *     iPhone.getPrice(); // 600
170  *     iPhone.isExpensive; // true;
171  * 
172  *     iPhone.setOperatingSystem('AlienOS');
173  *     iPhone.getOperatingSystem(); // 'Other'
174  * 
175  * # Statics: #
176  * 
177  *     Ext.define('Computer', {
178  *          statics: {
179  *              factory: function(brand) {
180  *                 // 'this' in static methods refer to the class itself
181  *                  return new this(brand);
182  *              }
183  *          },
184  * 
185  *          constructor: function() { ... }
186  *     });
187  * 
188  *     var dellComputer = Computer.factory('Dell');
189  * 
190  * Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing
191  * static properties within class methods
192  *
193  */
194 (function() {
195
196     var Class,
197         Base = Ext.Base,
198         baseStaticProperties = [],
199         baseStaticProperty;
200
201     for (baseStaticProperty in Base) {
202         if (Base.hasOwnProperty(baseStaticProperty)) {
203             baseStaticProperties.push(baseStaticProperty);
204         }
205     }
206
207 <span id='Ext-Class-method-constructor'><span id='Ext-Class'>    /**
208 </span></span>     * @constructor
209      * @param {Object} classData An object represent the properties of this class
210      * @param {Function} createdFn Optional, the callback function to be executed when this class is fully created.
211      * Note that the creation process can be asynchronous depending on the pre-processors used.
212      * @return {Ext.Base} The newly created class
213      */
214     Ext.Class = Class = function(newClass, classData, onClassCreated) {
215         if (typeof newClass !== 'function') {
216             onClassCreated = classData;
217             classData = newClass;
218             newClass = function() {
219                 return this.constructor.apply(this, arguments);
220             };
221         }
222
223         if (!classData) {
224             classData = {};
225         }
226
227         var preprocessorStack = classData.preprocessors || Class.getDefaultPreprocessors(),
228             registeredPreprocessors = Class.getPreprocessors(),
229             index = 0,
230             preprocessors = [],
231             preprocessor, preprocessors, staticPropertyName, process, i, j, ln;
232
233         for (i = 0, ln = baseStaticProperties.length; i &lt; ln; i++) {
234             staticPropertyName = baseStaticProperties[i];
235             newClass[staticPropertyName] = Base[staticPropertyName];
236         }
237
238         delete classData.preprocessors;
239
240         for (j = 0, ln = preprocessorStack.length; j &lt; ln; j++) {
241             preprocessor = preprocessorStack[j];
242
243             if (typeof preprocessor === 'string') {
244                 preprocessor = registeredPreprocessors[preprocessor];
245
246                 if (!preprocessor.always) {
247                     if (classData.hasOwnProperty(preprocessor.name)) {
248                         preprocessors.push(preprocessor.fn);
249                     }
250                 }
251                 else {
252                     preprocessors.push(preprocessor.fn);
253                 }
254             }
255             else {
256                 preprocessors.push(preprocessor);
257             }
258         }
259
260         classData.onClassCreated = onClassCreated;
261
262         classData.onBeforeClassCreated = function(cls, data) {
263             onClassCreated = data.onClassCreated;
264
265             delete data.onBeforeClassCreated;
266             delete data.onClassCreated;
267
268             cls.implement(data);
269
270             if (onClassCreated) {
271                 onClassCreated.call(cls, cls);
272             }
273         };
274
275         process = function(cls, data) {
276             preprocessor = preprocessors[index++];
277
278             if (!preprocessor) {
279                 data.onBeforeClassCreated.apply(this, arguments);
280                 return;
281             }
282
283             if (preprocessor.call(this, cls, data, process) !== false) {
284                 process.apply(this, arguments);
285             }
286         };
287
288         process.call(Class, newClass, classData);
289
290         return newClass;
291     };
292
293     Ext.apply(Class, {
294
295 <span id='Ext-Class-property-preprocessors'>        /** @private */
296 </span>        preprocessors: {},
297
298 <span id='Ext-Class-method-registerPreprocessor'>        /**
299 </span>         * Register a new pre-processor to be used during the class creation process
300          *
301          * @member Ext.Class registerPreprocessor
302          * @param {String} name The pre-processor's name
303          * @param {Function} fn The callback function to be executed. Typical format:
304
305     function(cls, data, fn) {
306         // Your code here
307
308         // Execute this when the processing is finished.
309         // Asynchronous processing is perfectly ok
310         if (fn) {
311             fn.call(this, cls, data);
312         }
313     });
314
315          * Passed arguments for this function are:
316          *
317          * - `{Function} cls`: The created class
318          * - `{Object} data`: The set of properties passed in {@link Ext.Class} constructor
319          * - `{Function} fn`: The callback function that &lt;b&gt;must&lt;/b&gt; to be executed when this pre-processor finishes,
320          * regardless of whether the processing is synchronous or aynchronous
321          *
322          * @return {Ext.Class} this
323          * @markdown
324          */
325         registerPreprocessor: function(name, fn, always) {
326             this.preprocessors[name] = {
327                 name: name,
328                 always: always ||  false,
329                 fn: fn
330             };
331
332             return this;
333         },
334
335 <span id='Ext-Class-method-getPreprocessor'>        /**
336 </span>         * Retrieve a pre-processor callback function by its name, which has been registered before
337          *
338          * @param {String} name
339          * @return {Function} preprocessor
340          */
341         getPreprocessor: function(name) {
342             return this.preprocessors[name];
343         },
344
345         getPreprocessors: function() {
346             return this.preprocessors;
347         },
348
349 <span id='Ext-Class-method-getDefaultPreprocessors'>        /**
350 </span>         * Retrieve the array stack of default pre-processors
351          *
352          * @return {Function} defaultPreprocessors
353          */
354         getDefaultPreprocessors: function() {
355             return this.defaultPreprocessors || [];
356         },
357
358 <span id='Ext-Class-method-setDefaultPreprocessors'>        /**
359 </span>         * Set the default array stack of default pre-processors
360          *
361          * @param {Array} preprocessors
362          * @return {Ext.Class} this
363          */
364         setDefaultPreprocessors: function(preprocessors) {
365             this.defaultPreprocessors = Ext.Array.from(preprocessors);
366
367             return this;
368         },
369
370 <span id='Ext-Class-method-setDefaultPreprocessorPosition'>        /**
371 </span>         * Insert this pre-processor at a specific position in the stack, optionally relative to
372          * any existing pre-processor. For example:
373
374     Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {
375         // Your code here
376
377         if (fn) {
378             fn.call(this, cls, data);
379         }
380     }).insertDefaultPreprocessor('debug', 'last');
381
382          * @param {String} name The pre-processor name. Note that it needs to be registered with
383          * {@link Ext#registerPreprocessor registerPreprocessor} before this
384          * @param {String} offset The insertion position. Four possible values are:
385          * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
386          * @param {String} relativeName
387          * @return {Ext.Class} this
388          * @markdown
389          */
390         setDefaultPreprocessorPosition: function(name, offset, relativeName) {
391             var defaultPreprocessors = this.defaultPreprocessors,
392                 index;
393
394             if (typeof offset === 'string') {
395                 if (offset === 'first') {
396                     defaultPreprocessors.unshift(name);
397
398                     return this;
399                 }
400                 else if (offset === 'last') {
401                     defaultPreprocessors.push(name);
402
403                     return this;
404                 }
405
406                 offset = (offset === 'after') ? 1 : -1;
407             }
408
409             index = Ext.Array.indexOf(defaultPreprocessors, relativeName);
410
411             if (index !== -1) {
412                 defaultPreprocessors.splice(Math.max(0, index + offset), 0, name);
413             }
414
415             return this;
416         }
417     });
418
419     Class.registerPreprocessor('extend', function(cls, data) {
420         var extend = data.extend,
421             base = Ext.Base,
422             basePrototype = base.prototype,
423             prototype = function() {},
424             parent, i, k, ln, staticName, parentStatics,
425             parentPrototype, clsPrototype;
426
427         if (extend &amp;&amp; extend !== Object) {
428             parent = extend;
429         }
430         else {
431             parent = base;
432         }
433
434         parentPrototype = parent.prototype;
435
436         prototype.prototype = parentPrototype;
437         clsPrototype = cls.prototype = new prototype();
438
439         if (!('$class' in parent)) {
440             for (i in basePrototype) {
441                 if (!parentPrototype[i]) {
442                     parentPrototype[i] = basePrototype[i];
443                 }
444             }
445         }
446
447         clsPrototype.self = cls;
448
449         cls.superclass = clsPrototype.superclass = parentPrototype;
450
451         delete data.extend;
452
453         // Statics inheritance
454         parentStatics = parentPrototype.$inheritableStatics;
455
456         if (parentStatics) {
457             for (k = 0, ln = parentStatics.length; k &lt; ln; k++) {
458                 staticName = parentStatics[k];
459
460                 if (!cls.hasOwnProperty(staticName)) {
461                     cls[staticName] = parent[staticName];
462                 }
463             }
464         }
465
466         // Merge the parent class' config object without referencing it
467         if (parentPrototype.config) {
468             clsPrototype.config = Ext.Object.merge({}, parentPrototype.config);
469         }
470         else {
471             clsPrototype.config = {};
472         }
473
474         if (clsPrototype.$onExtended) {
475             clsPrototype.$onExtended.call(cls, cls, data);
476         }
477
478         if (data.onClassExtended) {
479             clsPrototype.$onExtended = data.onClassExtended;
480             delete data.onClassExtended;
481         }
482
483     }, true);
484
485     Class.registerPreprocessor('statics', function(cls, data) {
486         var statics = data.statics,
487             name;
488
489         for (name in statics) {
490             if (statics.hasOwnProperty(name)) {
491                 cls[name] = statics[name];
492             }
493         }
494
495         delete data.statics;
496     });
497
498     Class.registerPreprocessor('inheritableStatics', function(cls, data) {
499         var statics = data.inheritableStatics,
500             inheritableStatics,
501             prototype = cls.prototype,
502             name;
503
504         inheritableStatics = prototype.$inheritableStatics;
505
506         if (!inheritableStatics) {
507             inheritableStatics = prototype.$inheritableStatics = [];
508         }
509
510         for (name in statics) {
511             if (statics.hasOwnProperty(name)) {
512                 cls[name] = statics[name];
513                 inheritableStatics.push(name);
514             }
515         }
516
517         delete data.inheritableStatics;
518     });
519
520     Class.registerPreprocessor('mixins', function(cls, data) {
521         cls.mixin(data.mixins);
522
523         delete data.mixins;
524     });
525
526     Class.registerPreprocessor('config', function(cls, data) {
527         var prototype = cls.prototype;
528
529         Ext.Object.each(data.config, function(name) {
530             var cName = name.charAt(0).toUpperCase() + name.substr(1),
531                 pName = name,
532                 apply = 'apply' + cName,
533                 setter = 'set' + cName,
534                 getter = 'get' + cName;
535
536             if (!(apply in prototype) &amp;&amp; !data.hasOwnProperty(apply)) {
537                 data[apply] = function(val) {
538                     return val;
539                 };
540             }
541
542             if (!(setter in prototype) &amp;&amp; !data.hasOwnProperty(setter)) {
543                 data[setter] = function(val) {
544                     var ret = this[apply].call(this, val, this[pName]);
545
546                     if (ret !== undefined) {
547                         this[pName] = ret;
548                     }
549
550                     return this;
551                 };
552             }
553
554             if (!(getter in prototype) &amp;&amp; !data.hasOwnProperty(getter)) {
555                 data[getter] = function() {
556                     return this[pName];
557                 };
558             }
559         });
560
561         Ext.Object.merge(prototype.config, data.config);
562         delete data.config;
563     });
564
565     Class.setDefaultPreprocessors(['extend', 'statics', 'inheritableStatics', 'mixins', 'config']);
566
567     // Backwards compatible
568     Ext.extend = function(subclass, superclass, members) {
569         if (arguments.length === 2 &amp;&amp; Ext.isObject(superclass)) {
570             members = superclass;
571             superclass = subclass;
572             subclass = null;
573         }
574
575         var cls;
576
577         if (!superclass) {
578             Ext.Error.raise(&quot;Attempting to extend from a class which has not been loaded on the page.&quot;);
579         }
580
581         members.extend = superclass;
582         members.preprocessors = ['extend', 'mixins', 'config', 'statics'];
583
584         if (subclass) {
585             cls = new Class(subclass, members);
586         }
587         else {
588             cls = new Class(members);
589         }
590
591         cls.prototype.override = function(o) {
592             for (var m in o) {
593                 if (o.hasOwnProperty(m)) {
594                     this[m] = o[m];
595                 }
596             }
597         };
598
599         return cls;
600     };
601
602 })();
603 </pre></pre></body></html>