/** * @author Jacky Nguyen * @markdown * @class Ext.Class Handles class creation throughout the whole framework. Note that most of the time {@link Ext#define} should be used instead, since it's a higher level wrapper that aliases to {@link Ext.ClassManager.create} to enable namespacing and dynamic dependency resolution. Basic syntax: Ext.define(className, properties); in which `properties` is an object represent a collection of properties that apply to the class. See {@link Ext.ClassManager.create} for more detailed instructions. Ext.define('Person', { name: 'Unknown', constructor: function(name) { if (name) { this.name = name; } return this; }, eat: function(foodType) { alert("I'm eating: " + foodType); return this; } }); var aaron = new Person("Aaron"); aaron.eat("Sandwich"); // alert("I'm eating: Sandwich"); Ext.Class has a powerful set of extensible {@link Ext.Class.registerPreprocessor pre-processors} which takes care of everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc. # Inheritance: # Ext.define('Developer', { extend: 'Person', constructor: function(name, isGeek) { this.isGeek = isGeek; // Apply a method from the parent class' prototype this.callParent([name]); return this; }, code: function(language) { alert("I'm coding in: " + language); this.eat("Bugs"); return this; } }); var jacky = new Developer("Jacky", true); jacky.code("JavaScript"); // alert("I'm coding in: JavaScript"); // alert("I'm eating: Bugs"); See {@link Ext.Base#callParent} for more details on calling superclass' methods # Mixins: # Ext.define('CanPlayGuitar', { playGuitar: function() { alert("F#...G...D...A"); } }); Ext.define('CanComposeSongs', { composeSongs: function() { ... } }); Ext.define('CanSing', { sing: function() { alert("I'm on the highway to hell...") } }); Ext.define('Musician', { extend: 'Person', mixins: { canPlayGuitar: 'CanPlayGuitar', canComposeSongs: 'CanComposeSongs', canSing: 'CanSing' } }) Ext.define('CoolPerson', { extend: 'Person', mixins: { canPlayGuitar: 'CanPlayGuitar', canSing: 'CanSing' }, sing: function() { alert("Ahem...."); this.mixins.canSing.sing.call(this); alert("[Playing guitar at the same time...]"); this.mixins.canPlayGuitar.playGuitar.call(this); } }); var me = new CoolPerson("Jacky"); me.sing(); // alert("Ahem..."); // alert("I'm on the highway to hell..."); // alert("[Playing guitar at the same time...]"); // alert("F#...G...D...A"); # Config: # Ext.define('SmartPhone', { config: { hasTouchScreen: false, operatingSystem: 'Other', price: 500 }, isExpensive: false, constructor: function(config) { this.initConfig(config); return this; }, applyPrice: function(price) { this.isExpensive = (price > 500); return price; }, applyOperatingSystem: function(operatingSystem) { if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) { return 'Other'; } return operatingSystem; } }); var iPhone = new SmartPhone({ hasTouchScreen: true, operatingSystem: 'iOS' }); iPhone.getPrice(); // 500; iPhone.getOperatingSystem(); // 'iOS' iPhone.getHasTouchScreen(); // true; iPhone.hasTouchScreen(); // true iPhone.isExpensive; // false; iPhone.setPrice(600); iPhone.getPrice(); // 600 iPhone.isExpensive; // true; iPhone.setOperatingSystem('AlienOS'); iPhone.getOperatingSystem(); // 'Other' # Statics: # Ext.define('Computer', { statics: { factory: function(brand) { // 'this' in static methods refer to the class itself return new this(brand); } }, constructor: function() { ... } }); var dellComputer = Computer.factory('Dell'); * Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing * static properties within class methods * */ (function() { var flexSetter = Ext.Function.flexSetter;
/** * @constructor * @param {Object} classData An object represent the properties of this class * @param {Function} createdFn Optional, the callback function to be executed when this class is fully created. * Note that the creation process can be asynchronous depending on the pre-processors used. * @return {Ext.Base} The newly created class */ Ext.Class = function(classData, createdFn) { var self = this.constructor, newClass = function() { return this.constructor.apply(this, arguments); }, preprocessors = Ext.Array.from(classData.preprocessors || self.getDefaultPreprocessors()), staticProp, process; for (staticProp in Ext.Base) { if (Ext.Base.hasOwnProperty(staticProp)) { newClass[staticProp] = Ext.Base[staticProp]; } } delete classData.preprocessors; process = function(cls, data) { var name = preprocessors.shift(); if (!name) { cls.implement(data); if (Ext.isFunction(createdFn)) { createdFn.call(cls); } return; } this.getPreprocessor(name).call(this, cls, data, process); }; process.call(self, newClass, classData); return newClass; }; Ext.apply(Ext.Class, { /** @private */ preprocessors: {},
/** * Register a new pre-processor to be used during the class creation process * * @member Ext.Class registerPreprocessor * @param {String} name The pre-processor's name * @param {Function} fn The callback function to be executed. Typical format: function(cls, data, fn) { // Your code here // Execute this when the processing is finished. // Asynchronous processing is perfectly ok if (fn) { fn.call(this, cls, data); } }); * Passed arguments for this function are: * * - `{Function} cls`: The created class * - `{Object} data`: The set of properties passed in {@link Ext.Class} constructor * - `{Function} fn`: The callback function that must to be executed when this pre-processor finishes, * regardless of whether the processing is synchronous or aynchronous * * @return {Ext.Class} this * @markdown */ registerPreprocessor: flexSetter(function(name, fn) { this.preprocessors[name] = fn; return this; }),
/** * Retrieve a pre-processor callback function by its name, which has been registered before * * @param {String} name * @return {Function} preprocessor */ getPreprocessor: function(name) { return this.preprocessors[name]; },
/** * Retrieve the array stack of default pre-processors * * @return {Function} defaultPreprocessors */ getDefaultPreprocessors: function() { return this.defaultPreprocessors || []; },
/** * Set the default array stack of default pre-processors * * @param {Array} preprocessors * @return {Ext.Class} this */ setDefaultPreprocessors: function(preprocessors) { this.defaultPreprocessors = Ext.Array.from(preprocessors); return this; },
/** * Insert this pre-processor at a specific position in the stack, optionally relative to * any existing pre-processor. For example: Ext.Class.registerPreprocessor('debug', function(cls, data, fn) { // Your code here if (fn) { fn.call(this, cls, data); } }).insertDefaultPreprocessor('debug', 'last'); * @param {String} name The pre-processor name. Note that it needs to be registered with * {@link Ext.registerPreprocessor registerPreprocessor} before this * @param {String} offset The insertion position. Four possible values are: * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument) * @param {String} relativeName * @return {Ext.Class} this * @markdown */ insertDefaultPreprocessor: function(name, offset, relativeName) { var defaultPreprocessors = this.defaultPreprocessors, index; if (Ext.isString(offset)) { if (offset === 'first') { defaultPreprocessors.unshift(name); return this; } else if (offset === 'last') { defaultPreprocessors.push(name); return this; } offset = (offset === 'after') ? 1 : -1; } index = Ext.Array.indexOf(defaultPreprocessors, relativeName); if (index !== -1) { defaultPreprocessors.splice(Math.max(0, index + offset), 0, name); } return this; } }); Ext.Class.registerPreprocessor({ extend: function(cls, data, fn) { var parent = (typeof data.extend === 'function') ? data.extend : Ext.Base, temp = function(){}; temp.prototype = parent.prototype; cls.prototype = new temp(); cls.prototype.self = cls; if (data.hasOwnProperty('constructor')) { cls.prototype.constructor = cls; } else { cls.prototype.constructor = parent.prototype.constructor; } cls.superclass = cls.prototype.superclass = parent.prototype; // Merge the parent class' config object without referencing it Ext.merge(cls.prototype.config, parent.prototype.config || {}); delete data.extend; if (fn) { fn.call(this, cls, data); } }, mixins: function(cls, data, fn) { var mixins = data.mixins; if (mixins) { cls.mixin(mixins); } delete data.mixins; if (fn) { fn.call(this, cls, data); } }, config: function(cls, data, fn) { var config = data.config; if (config) { // Should not use a for...in loop here, these automatically generated methods // need their own scope so that variables don't override each others Ext.Object.each(config, function(name) { var cName = Ext.String.capitalize(name), pName = '_' + name, apply = 'apply' + cName, setter = 'set' + cName, getter = 'get' + cName, reset = 'reset' + cName, prototype = cls.prototype; if (!(apply in prototype)) { prototype[apply] = function(val) { return val; }; } if (!(setter in prototype)) { prototype[setter] = function(val) { var ret = this[apply].call(this, val, this[pName]); if (ret !== undefined) { this[pName] = ret; } return this; }; } if (!(getter in prototype)) { prototype[getter] = function() { return this[pName]; }; } if (!(reset in prototype)) { prototype[reset] = function() { return this[setter].call(this, this.config[name]); }; } if (name.search(/^is|has/) !== -1) { if (!(name in prototype)) { prototype[name] = function() { return !!this[getter].apply(this, arguments); }; } } }); } if (fn) { fn.call(this, cls, data); } }, statics: function(cls, data, fn) { if (Ext.isObject(data.statics)) { var name, statics = data.statics; for (name in statics) { if (statics.hasOwnProperty(name)) { cls[name] = statics[name]; } } } delete data.statics; if (fn) { fn.call(this, cls, data); } } }); Ext.Class.setDefaultPreprocessors(['extend', 'mixins', 'config', 'statics']); })();