/ext-4.0.7/src/core/src/class/Class.js
JavaScript | 559 lines | 263 code | 79 blank | 217 comment | 59 complexity | b48aebe8ee79fcbd07c8218293d5f037 MD5 | raw file
1/*
2
3This file is part of Ext JS 4
4
5Copyright (c) 2011 Sencha Inc
6
7Contact: http://www.sencha.com/contact
8
9GNU General Public License Usage
10This 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
12If 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