]> git.proxmox.com Git - extjs.git/blame - extjs/packages/core/src/class/Class.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / core / src / class / Class.js
CommitLineData
6527f429
DM
1/**\r
2 * @class Ext.Class\r
3 *\r
4 * This is a low level factory that is used by {@link Ext#define Ext.define} and should not be used\r
5 * directly in application code.\r
6 * \r
7 * The configs of this class are intended to be used in `Ext.define` calls to describe the class you\r
8 * are declaring. For example:\r
9 * \r
10 * Ext.define('App.util.Thing', {\r
11 * extend: 'App.util.Other',\r
12 * \r
13 * alias: 'util.thing',\r
14 * \r
15 * config: {\r
16 * foo: 42\r
17 * }\r
18 * });\r
19 *\r
20 * Ext.Class is the factory and **not** the superclass of everything. For the base class that **all**\r
21 * classes inherit from, see {@link Ext.Base}.\r
22 */\r
23(function() {\r
24// @tag class\r
25// @define Ext.Class\r
26// @require Ext.Base\r
27// @require Ext.Util\r
28// @require Ext.util.Cache\r
29 var ExtClass,\r
30 Base = Ext.Base,\r
31 baseStaticMembers = Base.$staticMembers,\r
32 ruleKeySortFn = function (a, b) {\r
33 // longest to shortest, by text if names are equal\r
34 return (a.length - b.length) || ((a < b) ? -1 : ((a > b) ? 1 : 0));\r
35 };\r
36\r
37 // Creates a constructor that has nothing extra in its scope chain.\r
38 function makeCtor (className) {\r
39 function constructor () {\r
40 // Opera has some problems returning from a constructor when Dragonfly isn't running. The || null seems to\r
41 // be sufficient to stop it misbehaving. Known to be required against 10.53, 11.51 and 11.61.\r
42 return this.constructor.apply(this, arguments) || null;\r
43 }\r
44 //<debug>\r
45 if (className) {\r
46 constructor.name = className;\r
47 }\r
48 //</debug>\r
49 return constructor;\r
50 }\r
51\r
52 /**\r
53 * @method constructor\r
54 * Create a new anonymous class.\r
55 *\r
56 * @param {Object} data An object represent the properties of this class\r
57 * @param {Function} onCreated Optional, the callback function to be executed when this class is fully created.\r
58 * Note that the creation process can be asynchronous depending on the pre-processors used.\r
59 *\r
60 * @return {Ext.Base} The newly created class\r
61 */\r
62 Ext.Class = ExtClass = function(Class, data, onCreated) {\r
63 if (typeof Class != 'function') {\r
64 onCreated = data;\r
65 data = Class;\r
66 Class = null;\r
67 }\r
68\r
69 if (!data) {\r
70 data = {};\r
71 }\r
72\r
73 Class = ExtClass.create(Class, data);\r
74\r
75 ExtClass.process(Class, data, onCreated);\r
76\r
77 return Class;\r
78 };\r
79\r
80 Ext.apply(ExtClass, {\r
81 \r
82 makeCtor: makeCtor,\r
83 \r
84 /**\r
85 * @private\r
86 */\r
87 onBeforeCreated: function(Class, data, hooks) {\r
88 //<debug>\r
89 Ext.classSystemMonitor && Ext.classSystemMonitor(Class, '>> Ext.Class#onBeforeCreated', arguments);\r
90 //</debug>\r
91 \r
92 Class.addMembers(data);\r
93\r
94 hooks.onCreated.call(Class, Class);\r
95\r
96 //<debug>\r
97 Ext.classSystemMonitor && Ext.classSystemMonitor(Class, '<< Ext.Class#onBeforeCreated', arguments);\r
98 //</debug>\r
99 },\r
100\r
101 /**\r
102 * @private\r
103 */\r
104 create: function (Class, data) {\r
105 var i = baseStaticMembers.length,\r
106 name;\r
107\r
108 if (!Class) {\r
109 Class = makeCtor(\r
110 //<debug>\r
111 data.$className\r
112 //</debug>\r
113 );\r
114 }\r
115\r
116 while (i--) {\r
117 name = baseStaticMembers[i];\r
118 Class[name] = Base[name];\r
119 }\r
120\r
121 return Class;\r
122 },\r
123\r
124 /**\r
125 * @private\r
126 */\r
127 process: function(Class, data, onCreated) {\r
128 var preprocessorStack = data.preprocessors || ExtClass.defaultPreprocessors,\r
129 registeredPreprocessors = this.preprocessors,\r
130 hooks = {\r
131 onBeforeCreated: this.onBeforeCreated\r
132 },\r
133 preprocessors = [],\r
134 preprocessor, preprocessorsProperties,\r
135 i, ln, j, subLn, preprocessorProperty;\r
136\r
137 delete data.preprocessors;\r
138 Class._classHooks = hooks;\r
139\r
140 for (i = 0,ln = preprocessorStack.length; i < ln; i++) {\r
141 preprocessor = preprocessorStack[i];\r
142\r
143 if (typeof preprocessor == 'string') {\r
144 preprocessor = registeredPreprocessors[preprocessor];\r
145 preprocessorsProperties = preprocessor.properties;\r
146\r
147 if (preprocessorsProperties === true) {\r
148 preprocessors.push(preprocessor.fn);\r
149 }\r
150 else if (preprocessorsProperties) {\r
151 for (j = 0,subLn = preprocessorsProperties.length; j < subLn; j++) {\r
152 preprocessorProperty = preprocessorsProperties[j];\r
153\r
154 if (data.hasOwnProperty(preprocessorProperty)) {\r
155 preprocessors.push(preprocessor.fn);\r
156 break;\r
157 }\r
158 }\r
159 }\r
160 }\r
161 else {\r
162 preprocessors.push(preprocessor);\r
163 }\r
164 }\r
165\r
166 hooks.onCreated = onCreated ? onCreated : Ext.emptyFn;\r
167 hooks.preprocessors = preprocessors;\r
168\r
169 this.doProcess(Class, data, hooks);\r
170 },\r
171 \r
172 doProcess: function(Class, data, hooks) {\r
173 var me = this,\r
174 preprocessors = hooks.preprocessors,\r
175 preprocessor = preprocessors.shift(),\r
176 doProcess = me.doProcess;\r
177\r
178 for ( ; preprocessor ; preprocessor = preprocessors.shift()) {\r
179 // Returning false signifies an asynchronous preprocessor - it will call doProcess when we can continue\r
180 if (preprocessor.call(me, Class, data, hooks, doProcess) === false) {\r
181 return;\r
182 }\r
183 }\r
184 hooks.onBeforeCreated.apply(me, arguments);\r
185 },\r
186\r
187 /**\r
188 * @private\r
189 * */\r
190 preprocessors: {},\r
191\r
192 /**\r
193 * Register a new pre-processor to be used during the class creation process\r
194 *\r
195 * @param {String} name The pre-processor's name\r
196 * @param {Function} fn The callback function to be executed. Typical format:\r
197 *\r
198 * function(cls, data, fn) {\r
199 * // Your code here\r
200 *\r
201 * // Execute this when the processing is finished.\r
202 * // Asynchronous processing is perfectly ok\r
203 * if (fn) {\r
204 * fn.call(this, cls, data);\r
205 * }\r
206 * });\r
207 *\r
208 * @param {Function} fn.cls The created class\r
209 * @param {Object} fn.data The set of properties passed in {@link Ext.Class} constructor\r
210 * @param {Function} fn.fn The callback function that **must** to be executed when this\r
211 * pre-processor finishes, regardless of whether the processing is synchronous or asynchronous.\r
212 * @return {Ext.Class} this\r
213 * @private\r
214 * @static\r
215 */\r
216 registerPreprocessor: function(name, fn, properties, position, relativeTo) {\r
217 if (!position) {\r
218 position = 'last';\r
219 }\r
220\r
221 if (!properties) {\r
222 properties = [name];\r
223 }\r
224\r
225 this.preprocessors[name] = {\r
226 name: name,\r
227 properties: properties || false,\r
228 fn: fn\r
229 };\r
230\r
231 this.setDefaultPreprocessorPosition(name, position, relativeTo);\r
232\r
233 return this;\r
234 },\r
235\r
236 /**\r
237 * Retrieve a pre-processor callback function by its name, which has been registered before\r
238 *\r
239 * @param {String} name\r
240 * @return {Function} preprocessor\r
241 * @private\r
242 * @static\r
243 */\r
244 getPreprocessor: function(name) {\r
245 return this.preprocessors[name];\r
246 },\r
247\r
248 /**\r
249 * @private\r
250 */\r
251 getPreprocessors: function() {\r
252 return this.preprocessors;\r
253 },\r
254\r
255 /**\r
256 * @private\r
257 */\r
258 defaultPreprocessors: [],\r
259\r
260 /**\r
261 * Retrieve the array stack of default pre-processors\r
262 * @return {Function[]} defaultPreprocessors\r
263 * @private\r
264 * @static\r
265 */\r
266 getDefaultPreprocessors: function() {\r
267 return this.defaultPreprocessors;\r
268 },\r
269\r
270 /**\r
271 * Set the default array stack of default pre-processors\r
272 *\r
273 * @private\r
274 * @param {Array} preprocessors\r
275 * @return {Ext.Class} this\r
276 * @static\r
277 */\r
278 setDefaultPreprocessors: function(preprocessors) {\r
279 this.defaultPreprocessors = Ext.Array.from(preprocessors);\r
280\r
281 return this;\r
282 },\r
283\r
284 /**\r
285 * Insert this pre-processor at a specific position in the stack, optionally relative to\r
286 * any existing pre-processor. For example:\r
287 *\r
288 * Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {\r
289 * // Your code here\r
290 *\r
291 * if (fn) {\r
292 * fn.call(this, cls, data);\r
293 * }\r
294 * }).setDefaultPreprocessorPosition('debug', 'last');\r
295 *\r
296 * @private\r
297 * @param {String} name The pre-processor name. Note that it needs to be registered with\r
298 * {@link Ext.Class#registerPreprocessor registerPreprocessor} before this\r
299 * @param {String} offset The insertion position. Four possible values are:\r
300 * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)\r
301 * @param {String} relativeName\r
302 * @return {Ext.Class} this\r
303 * @static\r
304 */\r
305 setDefaultPreprocessorPosition: function(name, offset, relativeName) {\r
306 var defaultPreprocessors = this.defaultPreprocessors,\r
307 index;\r
308\r
309 if (typeof offset == 'string') {\r
310 if (offset === 'first') {\r
311 defaultPreprocessors.unshift(name);\r
312\r
313 return this;\r
314 }\r
315 else if (offset === 'last') {\r
316 defaultPreprocessors.push(name);\r
317\r
318 return this;\r
319 }\r
320\r
321 offset = (offset === 'after') ? 1 : -1;\r
322 }\r
323\r
324 index = Ext.Array.indexOf(defaultPreprocessors, relativeName);\r
325\r
326 if (index !== -1) {\r
327 Ext.Array.splice(defaultPreprocessors, Math.max(0, index + offset), 0, name);\r
328 }\r
329\r
330 return this;\r
331 }\r
332 });\r
333\r
334 /**\r
335 * @cfg {String} extend\r
336 * The parent class that this class extends. For example:\r
337 *\r
338 * Ext.define('Person', {\r
339 * say: function(text) { alert(text); }\r
340 * });\r
341 *\r
342 * Ext.define('Developer', {\r
343 * extend: 'Person',\r
344 * say: function(text) { this.callParent(["print "+text]); }\r
345 * });\r
346 */\r
347 ExtClass.registerPreprocessor('extend', function(Class, data, hooks) {\r
348 //<debug>\r
349 Ext.classSystemMonitor && Ext.classSystemMonitor(Class, 'Ext.Class#extendPreProcessor', arguments);\r
350 //</debug>\r
351 \r
352 var Base = Ext.Base,\r
353 basePrototype = Base.prototype,\r
354 extend = data.extend,\r
355 Parent, parentPrototype, i;\r
356\r
357 delete data.extend;\r
358\r
359 if (extend && extend !== Object) {\r
360 Parent = extend;\r
361 }\r
362 else {\r
363 Parent = Base;\r
364 }\r
365\r
366 parentPrototype = Parent.prototype;\r
367\r
368 if (!Parent.$isClass) {\r
369 for (i in basePrototype) {\r
370 if (!parentPrototype[i]) {\r
371 parentPrototype[i] = basePrototype[i];\r
372 }\r
373 }\r
374 }\r
375\r
376 Class.extend(Parent);\r
377\r
378 Class.triggerExtended.apply(Class, arguments);\r
379\r
380 if (data.onClassExtended) {\r
381 Class.onExtended(data.onClassExtended, Class);\r
382 delete data.onClassExtended;\r
383 }\r
384\r
385 }, true); // true to always run this preprocessor even w/o "extend" keyword\r
386\r
387 /**\r
388 * @cfg {Object} privates\r
389 * The `privates` config is a list of methods intended to be used internally by the \r
390 * framework. Methods are placed in a `privates` block to prevent developers from \r
391 * accidentally overriding framework methods in custom classes.\r
392 *\r
393 * Ext.define('Computer', {\r
394 * privates: {\r
395 * runFactory: function(brand) {\r
396 * // internal only processing of brand passed to factory\r
397 * this.factory(brand);\r
398 * }\r
399 * },\r
400 * \r
401 * factory: function (brand) {}\r
402 * });\r
403 * \r
404 * In order to override a method from a `privates` block, the overridden method must \r
405 * also be placed in a `privates` block within the override class.\r
406 * \r
407 * Ext.define('Override.Computer', {\r
408 * override: 'Computer',\r
409 * privates: {\r
410 * runFactory: function() {\r
411 * // overriding logic\r
412 * }\r
413 * }\r
414 * });\r
415 */\r
416 ExtClass.registerPreprocessor('privates', function(Class, data) {\r
417 //<debug>\r
418 Ext.classSystemMonitor && Ext.classSystemMonitor(Class, 'Ext.Class#privatePreprocessor', arguments);\r
419 //</debug>\r
420\r
421 var privates = data.privates,\r
422 statics = privates.statics,\r
423 privacy = privates.privacy || true;\r
424\r
425 delete data.privates;\r
426 delete privates.statics;\r
427\r
428 // We have to add this preprocessor so that private getters/setters are picked up\r
429 // by the config system. This also catches duplication in the public part of the\r
430 // class since it is an error to override a private method with a public one.\r
431 Class.addMembers(privates, false, privacy);\r
432 if (statics) {\r
433 Class.addMembers(statics, true, privacy);\r
434 }\r
435 });\r
436\r
437 //<feature classSystem.statics>\r
438 /**\r
439 * @cfg {Object} statics\r
440 * List of static methods for this class. For example:\r
441 *\r
442 * Ext.define('Computer', {\r
443 * statics: {\r
444 * factory: function(brand) {\r
445 * // 'this' in static methods refer to the class itself\r
446 * return new this(brand);\r
447 * }\r
448 * },\r
449 *\r
450 * constructor: function() { ... }\r
451 * });\r
452 *\r
453 * var dellComputer = Computer.factory('Dell');\r
454 */\r
455 ExtClass.registerPreprocessor('statics', function(Class, data) {\r
456 //<debug>\r
457 Ext.classSystemMonitor && Ext.classSystemMonitor(Class, 'Ext.Class#staticsPreprocessor', arguments);\r
458 //</debug>\r
459 \r
460 Class.addStatics(data.statics);\r
461\r
462 delete data.statics;\r
463 });\r
464 //</feature>\r
465\r
466 //<feature classSystem.inheritableStatics>\r
467 /**\r
468 * @cfg {Object} inheritableStatics\r
469 * List of inheritable static methods for this class.\r
470 * Otherwise just like {@link #statics} but subclasses inherit these methods.\r
471 */\r
472 ExtClass.registerPreprocessor('inheritableStatics', function(Class, data) {\r
473 //<debug>\r
474 Ext.classSystemMonitor && Ext.classSystemMonitor(Class, 'Ext.Class#inheritableStaticsPreprocessor', arguments);\r
475 //</debug>\r
476 \r
477 Class.addInheritableStatics(data.inheritableStatics);\r
478\r
479 delete data.inheritableStatics;\r
480 });\r
481 //</feature>\r
482\r
483 Ext.createRuleFn = function (code) {\r
484 return new Function('$c', 'with($c) { return (' + code + '); }');\r
485 };\r
486 Ext.expressionCache = new Ext.util.Cache({\r
487 miss: Ext.createRuleFn\r
488 });\r
489\r
490 Ext.ruleKeySortFn = ruleKeySortFn;\r
491 Ext.getPlatformConfigKeys = function (platformConfig) {\r
492 var ret = [],\r
493 platform, rule;\r
494\r
495 for (platform in platformConfig) {\r
496 rule = Ext.expressionCache.get(platform);\r
497 if (rule(Ext.platformTags)) {\r
498 ret.push(platform);\r
499 }\r
500 }\r
501\r
502 ret.sort(ruleKeySortFn);\r
503 return ret;\r
504 };\r
505\r
506 //<feature classSystem.platformConfig>\r
507 /**\r
508 * @cfg {Object} platformConfig\r
509 * Allows setting config values for a class based on specific platforms. The value\r
510 * of this config is an object whose properties are "rules" and whose values are\r
511 * objects containing config values.\r
512 *\r
513 * For example:\r
514 *\r
515 * Ext.define('App.view.Foo', {\r
516 * extend: 'Ext.panel.Panel',\r
517 *\r
518 * platformConfig: {\r
519 * desktop: {\r
520 * title: 'Some Rather Descriptive Title'\r
521 * },\r
522 *\r
523 * '!desktop': {\r
524 * title: 'Short Title'\r
525 * }\r
526 * }\r
527 * });\r
528 *\r
529 * In the above, "desktop" and "!desktop" are (mutually exclusive) rules. Whichever\r
530 * evaluates to `true` will have its configs applied to the class. In this case, only\r
531 * the "title" property, but the object can contain any number of config properties.\r
532 * In this case, the `platformConfig` is evaluated as part of the class and there is\r
533 * not cost for each instance created.\r
534 *\r
535 * The rules are evaluated expressions in the context of the platform tags contained\r
536 * in `{@link Ext#platformTags Ext.platformTags}`. Any properties of that object are\r
537 * implicitly usable (as shown above).\r
538 *\r
539 * If a `platformConfig` specifies a config value, it will replace any values declared\r
540 * on the class itself.\r
541 *\r
542 * Use of `platformConfig` on instances is handled by the config system when classes\r
543 * call `{@link Ext.Base#initConfig initConfig}`. For example:\r
544 *\r
545 * Ext.create({\r
546 * xtype: 'panel',\r
547 *\r
548 * platformConfig: {\r
549 * desktop: {\r
550 * title: 'Some Rather Descriptive Title'\r
551 * },\r
552 *\r
553 * '!desktop': {\r
554 * title: 'Short Title'\r
555 * }\r
556 * }\r
557 * });\r
558 *\r
559 * The following is equivalent to the above:\r
560 *\r
561 * if (Ext.platformTags.desktop) {\r
562 * Ext.create({\r
563 * xtype: 'panel',\r
564 * title: 'Some Rather Descriptive Title'\r
565 * });\r
566 * } else {\r
567 * Ext.create({\r
568 * xtype: 'panel',\r
569 * title: 'Short Title'\r
570 * });\r
571 * }\r
572 *\r
573 * To adjust configs based on dynamic conditions, see `{@link Ext.mixin.Responsive}`.\r
574 */\r
575 ExtClass.registerPreprocessor('platformConfig', function(Class, data, hooks) {\r
576 var platformConfigs = data.platformConfig,\r
577 config = data.config,\r
578 added, classConfigs, configs, configurator, hoisted, keys, name, value,\r
579 i, ln;\r
580\r
581 delete data.platformConfig;\r
582\r
583 \r
584 //<debug>\r
585 if (platformConfigs instanceof Array) {\r
586 throw new Error('platformConfigs must be specified as an object.');\r
587 }\r
588 //</debug>\r
589\r
590 configurator = Class.getConfigurator();\r
591 classConfigs = configurator.configs;\r
592\r
593 // Get the keys shortest to longest (ish).\r
594 keys = Ext.getPlatformConfigKeys(platformConfigs);\r
595\r
596 // To leverage the Configurator#add method, we want to generate potentially\r
597 // two objects to pass in: "added" and "hoisted". For any properties in an\r
598 // active platformConfig rule that set proper Configs in the base class, we\r
599 // need to put them in "added". If instead of the proper Config coming from\r
600 // a base class, it comes from this class's config block, we still need to\r
601 // put that config in "added" but we also need move the class-level config\r
602 // out of "config" and into "hoisted".\r
603 //\r
604 // This will ensure that the config defined at the class level is added to\r
605 // the Configurator first.\r
606 for (i = 0, ln = keys.length; i < ln; ++i) {\r
607 configs = platformConfigs[keys[i]];\r
608 hoisted = added = null;\r
609\r
610 for (name in configs) {\r
611 value = configs[name];\r
612\r
613 // We have a few possibilities for each config name:\r
614\r
615 if (config && name in config) {\r
616 // It is a proper Config defined by this class.\r
617\r
618 (added || (added = {}))[name] = value;\r
619 (hoisted || (hoisted = {}))[name] = config[name];\r
620 delete config[name];\r
621 } else if (name in classConfigs) {\r
622 // It is a proper Config defined by a base class.\r
623\r
624 (added || (added = {}))[name] = value;\r
625 } else {\r
626 // It is just a property to put on the prototype.\r
627\r
628 data[name] = value;\r
629 }\r
630 }\r
631\r
632 if (hoisted) {\r
633 configurator.add(hoisted);\r
634 }\r
635 if (added) {\r
636 configurator.add(added);\r
637 }\r
638 }\r
639 });\r
640 //</feature>\r
641\r
642 //<feature classSystem.config>\r
643 /**\r
644 * @cfg {Object} config\r
645 *\r
646 * List of configuration options with their default values.\r
647 *\r
648 * __Note:__ You need to make sure {@link Ext.Base#initConfig} is called from your constructor if you are defining\r
649 * your own class or singleton, unless you are extending a Component. Otherwise the generated getter and setter\r
650 * methods will not be initialized.\r
651 *\r
652 * Each config item will have its own setter and getter method automatically generated inside the class prototype\r
653 * during class creation time, if the class does not have those methods explicitly defined.\r
654 *\r
655 * As an example, let's convert the name property of a Person class to be a config item, then add extra age and\r
656 * gender items.\r
657 *\r
658 * Ext.define('My.sample.Person', {\r
659 * config: {\r
660 * name: 'Mr. Unknown',\r
661 * age: 0,\r
662 * gender: 'Male'\r
663 * },\r
664 *\r
665 * constructor: function(config) {\r
666 * this.initConfig(config);\r
667 *\r
668 * return this;\r
669 * }\r
670 *\r
671 * // ...\r
672 * });\r
673 *\r
674 * Within the class, this.name still has the default value of "Mr. Unknown". However, it's now publicly accessible\r
675 * without sacrificing encapsulation, via setter and getter methods.\r
676 *\r
677 * var jacky = new Person({\r
678 * name: "Jacky",\r
679 * age: 35\r
680 * });\r
681 *\r
682 * alert(jacky.getAge()); // alerts 35\r
683 * alert(jacky.getGender()); // alerts "Male"\r
684 *\r
685 * jacky.walk(10); // alerts "Jacky is walking 10 steps"\r
686 *\r
687 * jacky.setName("Mr. Nguyen");\r
688 * alert(jacky.getName()); // alerts "Mr. Nguyen"\r
689 *\r
690 * jacky.walk(10); // alerts "Mr. Nguyen is walking 10 steps"\r
691 *\r
692 * Notice that we changed the class constructor to invoke this.initConfig() and pass in the provided config object.\r
693 * Two key things happened:\r
694 *\r
695 * - The provided config object when the class is instantiated is recursively merged with the default config object.\r
696 * - All corresponding setter methods are called with the merged values.\r
697 *\r
698 * Beside storing the given values, throughout the frameworks, setters generally have two key responsibilities:\r
699 *\r
700 * - Filtering / validation / transformation of the given value before it's actually stored within the instance.\r
701 * - Notification (such as firing events) / post-processing after the value has been set, or changed from a\r
702 * previous value.\r
703 *\r
704 * By standardize this common pattern, the default generated setters provide two extra template methods that you\r
705 * can put your own custom logics into, i.e: an "applyFoo" and "updateFoo" method for a "foo" config item, which are\r
706 * executed before and after the value is actually set, respectively. Back to the example class, let's validate that\r
707 * age must be a valid positive number, and fire an 'agechange' if the value is modified.\r
708 *\r
709 * Ext.define('My.sample.Person', {\r
710 * config: {\r
711 * // ...\r
712 * },\r
713 *\r
714 * constructor: {\r
715 * // ...\r
716 * },\r
717 *\r
718 * applyAge: function(age) {\r
719 * if (typeof age !== 'number' || age < 0) {\r
720 * console.warn("Invalid age, must be a positive number");\r
721 * return;\r
722 * }\r
723 *\r
724 * return age;\r
725 * },\r
726 *\r
727 * updateAge: function(newAge, oldAge) {\r
728 * // age has changed from "oldAge" to "newAge"\r
729 * this.fireEvent('agechange', this, newAge, oldAge);\r
730 * }\r
731 *\r
732 * // ...\r
733 * });\r
734 *\r
735 * var jacky = new Person({\r
736 * name: "Jacky",\r
737 * age: 'invalid'\r
738 * });\r
739 *\r
740 * alert(jacky.getAge()); // alerts 0\r
741 *\r
742 * alert(jacky.setAge(-100)); // alerts 0\r
743 * alert(jacky.getAge()); // alerts 0\r
744 *\r
745 * alert(jacky.setAge(35)); // alerts 0\r
746 * alert(jacky.getAge()); // alerts 35\r
747 *\r
748 * In other words, when leveraging the config feature, you mostly never need to define setter and getter methods\r
749 * explicitly. Instead, "apply*" and "update*" methods should be implemented where necessary. Your code will be\r
750 * consistent throughout and only contain the minimal logic that you actually care about.\r
751 *\r
752 * When it comes to inheritance, the default config of the parent class is automatically, recursively merged with\r
753 * the child's default config. The same applies for mixins.\r
754 */\r
755 ExtClass.registerPreprocessor('config', function(Class, data) {\r
756 // Need to copy to the prototype here because that happens after preprocessors\r
757 if (data.hasOwnProperty('$configPrefixed')) {\r
758 Class.prototype.$configPrefixed = data.$configPrefixed;\r
759 }\r
760 Class.addConfig(data.config);\r
761\r
762 // We need to remove this or it will be applied by addMembers and smash the\r
763 // "config" placed on the prototype by Configurator (which includes *all* configs\r
764 // such as cachedConfigs).\r
765 delete data.config;\r
766 });\r
767 //</feature>\r
768 \r
769 //<feature classSystem.cachedConfig>\r
770 /**\r
771 * @cfg {Object} cachedConfig\r
772 * \r
773 * This configuration works in a very similar manner to the {@link #config} option.\r
774 * The difference is that the configurations are only ever processed when the first instance\r
775 * of that class is created. The processed value is then stored on the class prototype and\r
776 * will not be processed on subsequent instances of the class. Getters/setters will be generated\r
777 * in exactly the same way as {@link #config}.\r
778 * \r
779 * This option is useful for expensive objects that can be shared across class instances. \r
780 * The class itself ensures that the creation only occurs once.\r
781 */\r
782 ExtClass.registerPreprocessor('cachedConfig', function(Class, data) {\r
783 // Need to copy to the prototype here because that happens after preprocessors\r
784 if (data.hasOwnProperty('$configPrefixed')) {\r
785 Class.prototype.$configPrefixed = data.$configPrefixed;\r
786 }\r
787 Class.addCachedConfig(data.cachedConfig);\r
788\r
789 // Remove this so it won't be placed on the prototype.\r
790 delete data.cachedConfig;\r
791 });\r
792 //</feature>\r
793\r
794 //<feature classSystem.mixins>\r
795 /**\r
796 * @cfg {String[]/Object} mixins\r
797 * List of classes to mix into this class. For example:\r
798 *\r
799 * Ext.define('CanSing', {\r
800 * sing: function() {\r
801 * alert("For he's a jolly good fellow...")\r
802 * }\r
803 * });\r
804 *\r
805 * Ext.define('Musician', {\r
806 * mixins: ['CanSing']\r
807 * })\r
808 *\r
809 * In this case the Musician class will get a `sing` method from CanSing mixin.\r
810 *\r
811 * But what if the Musician already has a `sing` method? Or you want to mix\r
812 * in two classes, both of which define `sing`? In such a cases it's good\r
813 * to define mixins as an object, where you assign a name to each mixin:\r
814 *\r
815 * Ext.define('Musician', {\r
816 * mixins: {\r
817 * canSing: 'CanSing'\r
818 * },\r
819 * \r
820 * sing: function() {\r
821 * // delegate singing operation to mixin\r
822 * this.mixins.canSing.sing.call(this);\r
823 * }\r
824 * })\r
825 *\r
826 * In this case the `sing` method of Musician will overwrite the\r
827 * mixed in `sing` method. But you can access the original mixed in method\r
828 * through special `mixins` property.\r
829 */\r
830 ExtClass.registerPreprocessor('mixins', function(Class, data, hooks) {\r
831 //<debug>\r
832 Ext.classSystemMonitor && Ext.classSystemMonitor(Class, 'Ext.Class#mixinsPreprocessor', arguments);\r
833 //</debug>\r
834 \r
835 var mixins = data.mixins,\r
836 onCreated = hooks.onCreated;\r
837\r
838 delete data.mixins;\r
839\r
840 hooks.onCreated = function() {\r
841 //<debug>\r
842 Ext.classSystemMonitor && Ext.classSystemMonitor(Class, 'Ext.Class#mixinsPreprocessor#beforeCreated', arguments);\r
843 //</debug>\r
844\r
845 // Put back the original onCreated before processing mixins. This allows a\r
846 // mixin to hook onCreated by access Class._classHooks.\r
847 hooks.onCreated = onCreated;\r
848\r
849 Class.mixin(mixins);\r
850\r
851 // We must go back to hooks.onCreated here because it may have changed during\r
852 // calls to onClassMixedIn.\r
853 return hooks.onCreated.apply(this, arguments);\r
854 };\r
855 });\r
856 //</feature>\r
857\r
858\r
859 //<feature classSystem.backwardsCompatible>\r
860 // Backwards compatible\r
861 Ext.extend = function(Class, Parent, members) {\r
862 //<debug>\r
863 Ext.classSystemMonitor && Ext.classSystemMonitor(Class, 'Ext.Class#extend-backwards-compatible', arguments);\r
864 //</debug>\r
865 \r
866 if (arguments.length === 2 && Ext.isObject(Parent)) {\r
867 members = Parent;\r
868 Parent = Class;\r
869 Class = null;\r
870 }\r
871\r
872 var cls;\r
873\r
874 if (!Parent) {\r
875 throw new Error("[Ext.extend] Attempting to extend from a class which has not been loaded on the page.");\r
876 }\r
877\r
878 members.extend = Parent;\r
879 members.preprocessors = [\r
880 'extend'\r
881 //<feature classSystem.statics>\r
882 ,'statics'\r
883 //</feature>\r
884 //<feature classSystem.inheritableStatics>\r
885 ,'inheritableStatics'\r
886 //</feature>\r
887 //<feature classSystem.mixins>\r
888 ,'mixins'\r
889 //</feature>\r
890 //<feature classSystem.platformConfig>\r
891 ,'platformConfig'\r
892 //</feature>\r
893 //<feature classSystem.config>\r
894 ,'config'\r
895 //</feature>\r
896 ];\r
897\r
898 if (Class) {\r
899 cls = new ExtClass(Class, members);\r
900 // The 'constructor' is given as 'Class' but also needs to be on prototype\r
901 cls.prototype.constructor = Class;\r
902 } else {\r
903 cls = new ExtClass(members);\r
904 }\r
905\r
906 cls.prototype.override = function(o) {\r
907 for (var m in o) {\r
908 if (o.hasOwnProperty(m)) {\r
909 this[m] = o[m];\r
910 }\r
911 }\r
912 };\r
913\r
914 return cls;\r
915 };\r
916 //</feature>\r
917\r
918 /**\r
919 * This object contains properties that describe the current device or platform. These\r
920 * values can be used in `{@link Ext.Class#platformConfig platformConfig}` as well as\r
921 * `{@link Ext.mixin.Responsive#responsiveConfig responsiveConfig}` statements.\r
922 *\r
923 * This object can be modified to include tags that are useful for the application. To\r
924 * add custom properties, it is advisable to use a sub-object. For example:\r
925 *\r
926 * Ext.platformTags.app = {\r
927 * mobile: true\r
928 * };\r
929 *\r
930 * @property {Object} platformTags\r
931 * @property {Boolean} platformTags.phone\r
932 * @property {Boolean} platformTags.tablet\r
933 * @property {Boolean} platformTags.desktop\r
934 * @property {Boolean} platformTags.touch Indicates touch inputs are available.\r
935 * @property {Boolean} platformTags.safari\r
936 * @property {Boolean} platformTags.chrome\r
937 * @property {Boolean} platformTags.windows\r
938 * @property {Boolean} platformTags.firefox\r
939 * @property {Boolean} platformTags.ios True for iPad, iPhone and iPod.\r
940 * @property {Boolean} platformTags.android\r
941 * @property {Boolean} platformTags.blackberry\r
942 * @property {Boolean} platformTags.tizen\r
943 * @member Ext\r
944 */\r
945}());\r