3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
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.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * @author Jacky Nguyen <jacky@sencha.com>
17 * @docauthor Jacky Nguyen <jacky@sencha.com>
20 * Handles class creation throughout the whole framework. Note that most of the time {@link Ext#define Ext.define} should
21 * be used instead, since it's a higher level wrapper that aliases to {@link Ext.ClassManager#create}
22 * to enable namespacing and dynamic dependency resolution.
26 * Ext.define(className, properties);
28 * in which `properties` is an object represent a collection of properties that apply to the class. See
29 * {@link Ext.ClassManager#create} for more detailed instructions.
31 * Ext.define('Person', {
34 * constructor: function(name) {
42 * eat: function(foodType) {
43 * alert("I'm eating: " + foodType);
49 * var aaron = new Person("Aaron");
50 * aaron.eat("Sandwich"); // alert("I'm eating: Sandwich");
52 * Ext.Class has a powerful set of extensible {@link Ext.Class#registerPreprocessor pre-processors} which takes care of
53 * everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc.
57 * Ext.define('Developer', {
60 * constructor: function(name, isGeek) {
61 * this.isGeek = isGeek;
63 * // Apply a method from the parent class' prototype
64 * this.callParent([name]);
70 * code: function(language) {
71 * alert("I'm coding in: " + language);
79 * var jacky = new Developer("Jacky", true);
80 * jacky.code("JavaScript"); // alert("I'm coding in: JavaScript");
81 * // alert("I'm eating: Bugs");
83 * See {@link Ext.Base#callParent} for more details on calling superclass' methods
87 * Ext.define('CanPlayGuitar', {
88 * playGuitar: function() {
89 * alert("F#...G...D...A");
93 * Ext.define('CanComposeSongs', {
94 * composeSongs: function() { ... }
97 * Ext.define('CanSing', {
99 * alert("I'm on the highway to hell...")
103 * Ext.define('Musician', {
107 * canPlayGuitar: 'CanPlayGuitar',
108 * canComposeSongs: 'CanComposeSongs',
113 * Ext.define('CoolPerson', {
117 * canPlayGuitar: 'CanPlayGuitar',
124 * this.mixins.canSing.sing.call(this);
126 * alert("[Playing guitar at the same time...]");
132 * var me = new CoolPerson("Jacky");
134 * me.sing(); // alert("Ahem...");
135 * // alert("I'm on the highway to hell...");
136 * // alert("[Playing guitar at the same time...]");
137 * // alert("F#...G...D...A");
141 * Ext.define('SmartPhone', {
143 * hasTouchScreen: false,
144 * operatingSystem: 'Other',
148 * isExpensive: false,
150 * constructor: function(config) {
151 * this.initConfig(config);
156 * applyPrice: function(price) {
157 * this.isExpensive = (price > 500);
162 * applyOperatingSystem: function(operatingSystem) {
163 * if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) {
167 * return operatingSystem;
171 * var iPhone = new SmartPhone({
172 * hasTouchScreen: true,
173 * operatingSystem: 'iOS'
176 * iPhone.getPrice(); // 500;
177 * iPhone.getOperatingSystem(); // 'iOS'
178 * iPhone.getHasTouchScreen(); // true;
179 * iPhone.hasTouchScreen(); // true
181 * iPhone.isExpensive; // false;
182 * iPhone.setPrice(600);
183 * iPhone.getPrice(); // 600
184 * iPhone.isExpensive; // true;
186 * iPhone.setOperatingSystem('AlienOS');
187 * iPhone.getOperatingSystem(); // 'Other'
191 * Ext.define('Computer', {
193 * factory: function(brand) {
194 * // 'this' in static methods refer to the class itself
195 * return new this(brand);
199 * constructor: function() { ... }
202 * var dellComputer = Computer.factory('Dell');
204 * Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing
205 * static properties within class methods
212 baseStaticProperties = [],
215 for (baseStaticProperty in Base) {
216 if (Base.hasOwnProperty(baseStaticProperty)) {
217 baseStaticProperties.push(baseStaticProperty);
222 * @method constructor
224 * @param {Object} classData An object represent the properties of this class
225 * @param {Function} createdFn Optional, the callback function to be executed when this class is fully created.
226 * Note that the creation process can be asynchronous depending on the pre-processors used.
227 * @return {Ext.Base} The newly created class
229 Ext.Class = Class = function(newClass, classData, onClassCreated) {
230 if (typeof newClass !== 'function') {
231 onClassCreated = classData;
232 classData = newClass;
233 newClass = function() {
234 return this.constructor.apply(this, arguments);
242 var preprocessorStack = classData.preprocessors || Class.getDefaultPreprocessors(),
243 registeredPreprocessors = Class.getPreprocessors(),
246 preprocessor, preprocessors, staticPropertyName, process, i, j, ln;
248 for (i = 0, ln = baseStaticProperties.length; i < ln; i++) {
249 staticPropertyName = baseStaticProperties[i];
250 newClass[staticPropertyName] = Base[staticPropertyName];
253 delete classData.preprocessors;
255 for (j = 0, ln = preprocessorStack.length; j < ln; j++) {
256 preprocessor = preprocessorStack[j];
258 if (typeof preprocessor === 'string') {
259 preprocessor = registeredPreprocessors[preprocessor];
261 if (!preprocessor.always) {
262 if (classData.hasOwnProperty(preprocessor.name)) {
263 preprocessors.push(preprocessor.fn);
267 preprocessors.push(preprocessor.fn);
271 preprocessors.push(preprocessor);
275 classData.onClassCreated = onClassCreated;
277 classData.onBeforeClassCreated = function(cls, data) {
278 onClassCreated = data.onClassCreated;
280 delete data.onBeforeClassCreated;
281 delete data.onClassCreated;
285 if (onClassCreated) {
286 onClassCreated.call(cls, cls);
290 process = function(cls, data) {
291 preprocessor = preprocessors[index++];
294 data.onBeforeClassCreated.apply(this, arguments);
298 if (preprocessor.call(this, cls, data, process) !== false) {
299 process.apply(this, arguments);
303 process.call(Class, newClass, classData);
314 * Register a new pre-processor to be used during the class creation process
316 * @member Ext.Class registerPreprocessor
317 * @param {String} name The pre-processor's name
318 * @param {Function} fn The callback function to be executed. Typical format:
320 function(cls, data, fn) {
323 // Execute this when the processing is finished.
324 // Asynchronous processing is perfectly ok
326 fn.call(this, cls, data);
330 * Passed arguments for this function are:
332 * - `{Function} cls`: The created class
333 * - `{Object} data`: The set of properties passed in {@link Ext.Class} constructor
334 * - `{Function} fn`: The callback function that <b>must</b> to be executed when this pre-processor finishes,
335 * regardless of whether the processing is synchronous or aynchronous
337 * @return {Ext.Class} this
340 registerPreprocessor: function(name, fn, always) {
341 this.preprocessors[name] = {
343 always: always || false,
351 * Retrieve a pre-processor callback function by its name, which has been registered before
353 * @param {String} name
354 * @return {Function} preprocessor
356 getPreprocessor: function(name) {
357 return this.preprocessors[name];
360 getPreprocessors: function() {
361 return this.preprocessors;
365 * Retrieve the array stack of default pre-processors
367 * @return {Function} defaultPreprocessors
369 getDefaultPreprocessors: function() {
370 return this.defaultPreprocessors || [];
374 * Set the default array stack of default pre-processors
376 * @param {Array} preprocessors
377 * @return {Ext.Class} this
379 setDefaultPreprocessors: function(preprocessors) {
380 this.defaultPreprocessors = Ext.Array.from(preprocessors);
386 * Insert this pre-processor at a specific position in the stack, optionally relative to
387 * any existing pre-processor. For example:
389 Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {
393 fn.call(this, cls, data);
395 }).insertDefaultPreprocessor('debug', 'last');
397 * @param {String} name The pre-processor name. Note that it needs to be registered with
398 * {@link Ext#registerPreprocessor registerPreprocessor} before this
399 * @param {String} offset The insertion position. Four possible values are:
400 * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
401 * @param {String} relativeName
402 * @return {Ext.Class} this
405 setDefaultPreprocessorPosition: function(name, offset, relativeName) {
406 var defaultPreprocessors = this.defaultPreprocessors,
409 if (typeof offset === 'string') {
410 if (offset === 'first') {
411 defaultPreprocessors.unshift(name);
415 else if (offset === 'last') {
416 defaultPreprocessors.push(name);
421 offset = (offset === 'after') ? 1 : -1;
424 index = Ext.Array.indexOf(defaultPreprocessors, relativeName);
427 Ext.Array.splice(defaultPreprocessors, Math.max(0, index + offset), 0, name);
435 * @cfg {String} extend
436 * The parent class that this class extends. For example:
438 * Ext.define('Person', {
439 * say: function(text) { alert(text); }
442 * Ext.define('Developer', {
444 * say: function(text) { this.callParent(["print "+text]); }
447 Class.registerPreprocessor('extend', function(cls, data) {
448 var extend = data.extend,
450 basePrototype = base.prototype,
451 prototype = function() {},
452 parent, i, k, ln, staticName, parentStatics,
453 parentPrototype, clsPrototype;
455 if (extend && extend !== Object) {
462 parentPrototype = parent.prototype;
464 prototype.prototype = parentPrototype;
465 clsPrototype = cls.prototype = new prototype();
467 if (!('$class' in parent)) {
468 for (i in basePrototype) {
469 if (!parentPrototype[i]) {
470 parentPrototype[i] = basePrototype[i];
475 clsPrototype.self = cls;
477 cls.superclass = clsPrototype.superclass = parentPrototype;
481 // Statics inheritance
482 parentStatics = parentPrototype.$inheritableStatics;
485 for (k = 0, ln = parentStatics.length; k < ln; k++) {
486 staticName = parentStatics[k];
488 if (!cls.hasOwnProperty(staticName)) {
489 cls[staticName] = parent[staticName];
494 // Merge the parent class' config object without referencing it
495 if (parentPrototype.config) {
496 clsPrototype.config = Ext.Object.merge({}, parentPrototype.config);
499 clsPrototype.config = {};
502 if (clsPrototype.$onExtended) {
503 clsPrototype.$onExtended.call(cls, cls, data);
506 if (data.onClassExtended) {
507 clsPrototype.$onExtended = data.onClassExtended;
508 delete data.onClassExtended;
514 * @cfg {Object} statics
515 * List of static methods for this class. For example:
517 * Ext.define('Computer', {
519 * factory: function(brand) {
520 * // 'this' in static methods refer to the class itself
521 * return new this(brand);
525 * constructor: function() { ... }
528 * var dellComputer = Computer.factory('Dell');
530 Class.registerPreprocessor('statics', function(cls, data) {
531 var statics = data.statics,
534 for (name in statics) {
535 if (statics.hasOwnProperty(name)) {
536 cls[name] = statics[name];
544 * @cfg {Object} inheritableStatics
545 * List of inheritable static methods for this class.
546 * Otherwise just like {@link #statics} but subclasses inherit these methods.
548 Class.registerPreprocessor('inheritableStatics', function(cls, data) {
549 var statics = data.inheritableStatics,
551 prototype = cls.prototype,
554 inheritableStatics = prototype.$inheritableStatics;
556 if (!inheritableStatics) {
557 inheritableStatics = prototype.$inheritableStatics = [];
560 for (name in statics) {
561 if (statics.hasOwnProperty(name)) {
562 cls[name] = statics[name];
563 inheritableStatics.push(name);
567 delete data.inheritableStatics;
571 * @cfg {Object} mixins
572 * List of classes to mix into this class. For example:
574 * Ext.define('CanSing', {
576 * alert("I'm on the highway to hell...")
580 * Ext.define('Musician', {
588 Class.registerPreprocessor('mixins', function(cls, data) {
589 cls.mixin(data.mixins);
595 * @cfg {Object} config
596 * List of configuration options with their default values, for which automatically
597 * accessor methods are generated. For example:
599 * Ext.define('SmartPhone', {
601 * hasTouchScreen: false,
602 * operatingSystem: 'Other',
605 * constructor: function(cfg) {
606 * this.initConfig(cfg);
610 * var iPhone = new SmartPhone({
611 * hasTouchScreen: true,
612 * operatingSystem: 'iOS'
615 * iPhone.getPrice(); // 500;
616 * iPhone.getOperatingSystem(); // 'iOS'
617 * iPhone.getHasTouchScreen(); // true;
618 * iPhone.hasTouchScreen(); // true
620 Class.registerPreprocessor('config', function(cls, data) {
621 var prototype = cls.prototype;
623 Ext.Object.each(data.config, function(name) {
624 var cName = name.charAt(0).toUpperCase() + name.substr(1),
626 apply = 'apply' + cName,
627 setter = 'set' + cName,
628 getter = 'get' + cName;
630 if (!(apply in prototype) && !data.hasOwnProperty(apply)) {
631 data[apply] = function(val) {
636 if (!(setter in prototype) && !data.hasOwnProperty(setter)) {
637 data[setter] = function(val) {
638 var ret = this[apply].call(this, val, this[pName]);
640 if (ret !== undefined) {
648 if (!(getter in prototype) && !data.hasOwnProperty(getter)) {
649 data[getter] = function() {
655 Ext.Object.merge(prototype.config, data.config);
659 Class.setDefaultPreprocessors(['extend', 'statics', 'inheritableStatics', 'mixins', 'config']);
661 // Backwards compatible
662 Ext.extend = function(subclass, superclass, members) {
663 if (arguments.length === 2 && Ext.isObject(superclass)) {
664 members = superclass;
665 superclass = subclass;
672 Ext.Error.raise("Attempting to extend from a class which has not been loaded on the page.");
675 members.extend = superclass;
676 members.preprocessors = ['extend', 'mixins', 'config', 'statics'];
679 cls = new Class(subclass, members);
682 cls = new Class(members);
685 cls.prototype.override = function(o) {
687 if (o.hasOwnProperty(m)) {