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