/** * @class Ext.Class * * This is a low level factory that is used by {@link Ext#define Ext.define} and should not be used * directly in application code. * * The configs of this class are intended to be used in `Ext.define` calls to describe the class you * are declaring. For example: * * Ext.define('App.util.Thing', { * extend: 'App.util.Other', * * alias: 'util.thing', * * config: { * foo: 42 * } * }); * * Ext.Class is the factory and **not** the superclass of everything. For the base class that **all** * classes inherit from, see {@link Ext.Base}. */(function() {// @tag class// @define Ext.Class// @require Ext.Base// @require Ext.Util// @require Ext.util.Cache var ExtClass, Base = Ext.Base, baseStaticMembers = Base.$staticMembers, ruleKeySortFn = function (a, b) { // longest to shortest, by text if names are equal return (a.length - b.length) || ((a < b) ? -1 : ((a > b) ? 1 : 0)); }; // Creates a constructor that has nothing extra in its scope chain. function makeCtor (className) { function constructor () { // Opera has some problems returning from a constructor when Dragonfly isn't running. The || null seems to // be sufficient to stop it misbehaving. Known to be required against 10.53, 11.51 and 11.61. return this.constructor.apply(this, arguments) || null; } //<debug> if (className) { constructor.name = className; } //</debug> return constructor; } /** * @method constructor * Create a new anonymous class. * * @param Class * @param {Object} data An object represent the properties of this class * @param {Function} onCreated 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 = ExtClass = function(Class, data, onCreated) { if (typeof Class != 'function') { onCreated = data; data = Class; Class = null; } if (!data) { data = {}; } Class = ExtClass.create(Class, data); ExtClass.process(Class, data, onCreated); return Class; }; Ext.apply(ExtClass, { makeCtor: makeCtor, /** * @private */ onBeforeCreated: function(Class, data, hooks) { //<debug> Ext.classSystemMonitor && Ext.classSystemMonitor(Class, '>> Ext.Class#onBeforeCreated', arguments); //</debug> Class.addMembers(data); hooks.onCreated.call(Class, Class); //<debug> Ext.classSystemMonitor && Ext.classSystemMonitor(Class, '<< Ext.Class#onBeforeCreated', arguments); //</debug> }, /** * @private */ create: function (Class, data) { var i = baseStaticMembers.length, name; if (!Class) { Class = makeCtor( //<debug> data.$className //</debug> ); } while (i--) { name = baseStaticMembers[i]; Class[name] = Base[name]; } return Class; }, /** * @private */ process: function(Class, data, onCreated) { var preprocessorStack = data.preprocessors || ExtClass.defaultPreprocessors, registeredPreprocessors = this.preprocessors, hooks = { onBeforeCreated: this.onBeforeCreated }, preprocessors = [], preprocessor, preprocessorsProperties, i, ln, j, subLn, preprocessorProperty; delete data.preprocessors; Class._classHooks = hooks; for (i = 0,ln = preprocessorStack.length; i < ln; i++) { preprocessor = preprocessorStack[i]; if (typeof preprocessor == 'string') { preprocessor = registeredPreprocessors[preprocessor]; preprocessorsProperties = preprocessor.properties; if (preprocessorsProperties === true) { preprocessors.push(preprocessor.fn); } else if (preprocessorsProperties) { for (j = 0,subLn = preprocessorsProperties.length; j < subLn; j++) { preprocessorProperty = preprocessorsProperties[j]; if (data.hasOwnProperty(preprocessorProperty)) { preprocessors.push(preprocessor.fn); break; } } } } else { preprocessors.push(preprocessor); } } hooks.onCreated = onCreated ? onCreated : Ext.emptyFn; hooks.preprocessors = preprocessors; this.doProcess(Class, data, hooks); }, doProcess: function(Class, data, hooks) { var me = this, preprocessors = hooks.preprocessors, preprocessor = preprocessors.shift(), doProcess = me.doProcess; for ( ; preprocessor ; preprocessor = preprocessors.shift()) { // Returning false signifies an asynchronous preprocessor - it will call doProcess when we can continue if (preprocessor.call(me, Class, data, hooks, doProcess) === false) { return; } } hooks.onBeforeCreated.apply(me, arguments); }, /** * @private * */ preprocessors: {}, /** * Register a new pre-processor to be used during the class creation process * * @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); * } * }); * * @param {Function} fn.cls The created class * @param {Object} fn.data The set of properties passed in {@link Ext.Class} constructor * @param {Function} fn.fn The callback function that **must** to be executed when this * pre-processor finishes, regardless of whether the processing is synchronous or asynchronous. * @param properties * @param position * @param relativeTo * @return {Ext.Class} this * @private * @static */ registerPreprocessor: function(name, fn, properties, position, relativeTo) { if (!position) { position = 'last'; } if (!properties) { properties = [name]; } this.preprocessors[name] = { name: name, properties: properties || false, fn: fn }; this.setDefaultPreprocessorPosition(name, position, relativeTo); return this; }, /** * Retrieve a pre-processor callback function by its name, which has been registered before * * @param {String} name * @return {Function} preprocessor * @private * @static */ getPreprocessor: function(name) { return this.preprocessors[name]; }, /** * @private */ getPreprocessors: function() { return this.preprocessors; }, /** * @private */ defaultPreprocessors: [], /** * Retrieve the array stack of default pre-processors * @return {Function[]} defaultPreprocessors * @private * @static */ getDefaultPreprocessors: function() { return this.defaultPreprocessors; }, /** * Set the default array stack of default pre-processors * * @private * @param {Array} preprocessors * @return {Ext.Class} this * @static */ 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); * } * }).setDefaultPreprocessorPosition('debug', 'last'); * * @private * @param {String} name The pre-processor name. Note that it needs to be registered with * {@link Ext.Class#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 * @static */ setDefaultPreprocessorPosition: function(name, offset, relativeName) { var defaultPreprocessors = this.defaultPreprocessors, index; if (typeof offset == 'string') { 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) { Ext.Array.splice(defaultPreprocessors, Math.max(0, index + offset), 0, name); } return this; } }); /** * @cfg {String} extend * The parent class that this class extends. For example: * * Ext.define('Person', { * say: function(text) { alert(text); } * }); * * Ext.define('Developer', { * extend: 'Person', * say: function(text) { this.callParent(["print "+text]); } * }); */ ExtClass.registerPreprocessor('extend', function(Class, data, hooks) { //<debug> Ext.classSystemMonitor && Ext.classSystemMonitor(Class, 'Ext.Class#extendPreProcessor', arguments); //</debug> var Base = Ext.Base, basePrototype = Base.prototype, extend = data.extend, Parent, parentPrototype, i; delete data.extend; if (extend && extend !== Object) { Parent = extend; } else { Parent = Base; } parentPrototype = Parent.prototype; if (!Parent.$isClass) { for (i in basePrototype) { if (!parentPrototype[i]) { parentPrototype[i] = basePrototype[i]; } } } Class.extend(Parent); Class.triggerExtended.apply(Class, arguments); /** * @cfg {Object} eventedConfig * Config options defined within `eventedConfig` will auto-generate the setter / * getter methods (see {@link #cfg-config config} for more information on * auto-generated getter / setter methods). Additionally, when an * `eventedConfig` is set it will also fire a before{cfg}change and {cfg}change * event when the value of the eventedConfig is changed from its originally * defined value. * * **Note:** When creating a custom class you'll need to extend Ext.Evented * * Example custom class: * * Ext.define('MyApp.util.Test', { * extend: 'Ext.Evented', * * eventedConfig: { * foo: null * } * }); * * In this example, the `foo` config will initially be null. Changing it via * `setFoo` will fire the `beforefoochange` event. The call to the setter can be * halted by returning `false` from a listener on the **before** event. * * var test = Ext.create('MyApp.util.Test', { * listeners: { * beforefoochange: function (instance, newValue, oldValue) { * return newValue !== 'bar'; * }, * foochange: function (instance, newValue, oldValue) { * console.log('foo changed to:', newValue); * } * } * }); * * test.setFoo('bar'); * * The `before` event handler can be used to validate changes to `foo`. * Returning `false` will prevent the setter from changing the value of the * config. In the previous example the `beforefoochange` handler returns false * so `foo` will not be updated and `foochange` will not be fired. * * test.setFoo('baz'); * * Setting `foo` to 'baz' will not be prevented by the `before` handler. Foo * will be set to the value: 'baz' and the `foochange` event will be fired. */ if (data.onClassExtended) { Class.onExtended(data.onClassExtended, Class); delete data.onClassExtended; } }, true); // true to always run this preprocessor even w/o "extend" keyword /** * @cfg {Object} privates * The `privates` config is a list of methods intended to be used internally by the * framework. Methods are placed in a `privates` block to prevent developers from * accidentally overriding framework methods in custom classes. * * Ext.define('Computer', { * privates: { * runFactory: function(brand) { * // internal only processing of brand passed to factory * this.factory(brand); * } * }, * * factory: function (brand) {} * }); * * In order to override a method from a `privates` block, the overridden method must * also be placed in a `privates` block within the override class. * * Ext.define('Override.Computer', { * override: 'Computer', * privates: { * runFactory: function() { * // overriding logic * } * } * }); */ ExtClass.registerPreprocessor('privates', function(Class, data) { //<debug> Ext.classSystemMonitor && Ext.classSystemMonitor(Class, 'Ext.Class#privatePreprocessor', arguments); //</debug> var privates = data.privates, statics = privates.statics, privacy = privates.privacy || true; delete data.privates; delete privates.statics; // We have to add this preprocessor so that private getters/setters are picked up // by the config system. This also catches duplication in the public part of the // class since it is an error to override a private method with a public one. Class.addMembers(privates, false, privacy); if (statics) { Class.addMembers(statics, true, privacy); } }); //<feature classSystem.statics> /** * @cfg {Object} statics * List of static methods for this class. For example: * * 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'); */ ExtClass.registerPreprocessor('statics', function(Class, data) { //<debug> Ext.classSystemMonitor && Ext.classSystemMonitor(Class, 'Ext.Class#staticsPreprocessor', arguments); //</debug> Class.addStatics(data.statics); delete data.statics; }); //</feature> //<feature classSystem.inheritableStatics> /** * @cfg {Object} inheritableStatics * List of inheritable static methods for this class. * Otherwise just like {@link #statics} but subclasses inherit these methods. */ ExtClass.registerPreprocessor('inheritableStatics', function(Class, data) { //<debug> Ext.classSystemMonitor && Ext.classSystemMonitor(Class, 'Ext.Class#inheritableStaticsPreprocessor', arguments); //</debug> Class.addInheritableStatics(data.inheritableStatics); delete data.inheritableStatics; }); //</feature> Ext.createRuleFn = function (code) { return new Function('$c', 'with($c) { try { return (' + code + '); } catch(e) { return false;}}'); }; Ext.expressionCache = new Ext.util.Cache({ miss: Ext.createRuleFn }); Ext.ruleKeySortFn = ruleKeySortFn; Ext.getPlatformConfigKeys = function (platformConfig) { var ret = [], platform, rule; for (platform in platformConfig) { rule = Ext.expressionCache.get(platform); if (rule(Ext.platformTags)) { ret.push(platform); } } ret.sort(ruleKeySortFn); return ret; }; //<feature classSystem.platformConfig> /** * @cfg {Object} platformConfig * Allows setting config values for a class based on specific platforms. The value * of this config is an object whose properties are "rules" and whose values are * objects containing config values. * * For example: * * Ext.define('App.view.Foo', { * extend: 'Ext.panel.Panel', * * platformConfig: { * desktop: { * title: 'Some Rather Descriptive Title' * }, * * '!desktop': { * title: 'Short Title' * } * } * }); * * In the above, "desktop" and "!desktop" are (mutually exclusive) rules. Whichever * evaluates to `true` will have its configs applied to the class. In this case, only * the "title" property, but the object can contain any number of config properties. * In this case, the `platformConfig` is evaluated as part of the class and there is * no cost for each instance created. * * The rules are evaluated expressions in the context of the platform tags contained * in `{@link Ext#platformTags Ext.platformTags}`. Any properties of that object are * implicitly usable (as shown above). * * If a `platformConfig` specifies a config value, it will replace any values declared * on the class itself. * * Use of `platformConfig` on instances is handled by the config system when classes * call `{@link Ext.Base#initConfig initConfig}`. For example: * * Ext.create({ * xtype: 'panel', * * platformConfig: { * desktop: { * title: 'Some Rather Descriptive Title' * }, * * '!desktop': { * title: 'Short Title' * } * } * }); * * The following is equivalent to the above: * * if (Ext.platformTags.desktop) { * Ext.create({ * xtype: 'panel', * title: 'Some Rather Descriptive Title' * }); * } else { * Ext.create({ * xtype: 'panel', * title: 'Short Title' * }); * } * * To adjust configs based on dynamic conditions, see `{@link Ext.mixin.Responsive}`. */ ExtClass.registerPreprocessor('platformConfig', function(Class, data, hooks) { Class.addPlatformConfig(data); }); //</feature> //<feature classSystem.config> /** * @cfg {Object} config * * List of configuration options with their default values. * * __Note:__ You need to make sure {@link Ext.Base#initConfig} is called from your constructor if you are defining * your own class or singleton, unless you are extending a Component. Otherwise the generated getter and setter * methods will not be initialized. * * Each config item will have its own setter and getter method automatically generated inside the class prototype * during class creation time, if the class does not have those methods explicitly defined. * * As an example, let's convert the name property of a Person class to be a config item, then add extra age and * gender items. * * Ext.define('My.sample.Person', { * config: { * name: 'Mr. Unknown', * age: 0, * gender: 'Male' * }, * * constructor: function(config) { * this.initConfig(config); * * return this; * } * * // ... * }); * * Within the class, this.name still has the default value of "Mr. Unknown". However, it's now publicly accessible * without sacrificing encapsulation, via setter and getter methods. * * var jacky = new My.sample.Person({ * name: "Jacky", * age: 35 * }); * * alert(jacky.getAge()); // alerts 35 * alert(jacky.getGender()); // alerts "Male" * * jacky.setName("Mr. Nguyen"); * alert(jacky.getName()); // alerts "Mr. Nguyen" * * Notice that we changed the class constructor to invoke this.initConfig() and pass in the provided config object. * Two key things happened: * * - The provided config object when the class is instantiated is recursively merged with the default config object. * - All corresponding setter methods are called with the merged values. * * Beside storing the given values, throughout the frameworks, setters generally have two key responsibilities: * * - Filtering / validation / transformation of the given value before it's actually stored within the instance. * - Notification (such as firing events) / post-processing after the value has been set, or changed from a * previous value. * * By standardize this common pattern, the default generated setters provide two extra template methods that you * can put your own custom logic into, i.e: an "applyFoo" and "updateFoo" method for a "foo" config item, which are * executed before and after the value is actually set, respectively. Back to the example class, let's validate that * age must be a valid positive number, and fire an 'agechange' if the value is modified. * * Ext.define('My.sample.Person', { * config: { * // ... * }, * * constructor: { * // ... * }, * * applyAge: function(age) { * if (typeof age !== 'number' || age < 0) { * console.warn("Invalid age, must be a positive number"); * return; * } * * return age; * }, * * updateAge: function(newAge, oldAge) { * // age has changed from "oldAge" to "newAge" * this.fireEvent('agechange', this, newAge, oldAge); * } * * // ... * }); * * var jacky = new My.sample.Person({ * name: "Jacky", * age: 'invalid' * }); * * alert(jacky.getAge()); // alerts 0 * * alert(jacky.setAge(-100)); // alerts 0 * alert(jacky.getAge()); // alerts 0 * * alert(jacky.setAge(35)); // alerts 0 * alert(jacky.getAge()); // alerts 35 * * In other words, when leveraging the config feature, you mostly never need to define setter and getter methods * explicitly. Instead, "apply*" and "update*" methods should be implemented where necessary. Your code will be * consistent throughout and only contain the minimal logic that you actually care about. * * When it comes to inheritance, the default config of the parent class is automatically, recursively merged with * the child's default config. The same applies for mixins. */ ExtClass.registerPreprocessor('config', function(Class, data) { // Need to copy to the prototype here because that happens after preprocessors if (data.hasOwnProperty('$configPrefixed')) { Class.prototype.$configPrefixed = data.$configPrefixed; } Class.addConfig(data.config); // We need to remove this or it will be applied by addMembers and smash the // "config" placed on the prototype by Configurator (which includes *all* configs // such as cachedConfigs). delete data.config; }); //</feature> //<feature classSystem.cachedConfig> /** * @cfg {Object} cachedConfig * * This configuration works in a very similar manner to the {@link #config} option. * The difference is that the configurations are only ever processed when the first instance * of that class is created. The processed value is then stored on the class prototype and * will not be processed on subsequent instances of the class. Getters/setters will be generated * in exactly the same way as {@link #config}. * * This option is useful for expensive objects that can be shared across class instances. * The class itself ensures that the creation only occurs once. */ ExtClass.registerPreprocessor('cachedConfig', function(Class, data) { // Need to copy to the prototype here because that happens after preprocessors if (data.hasOwnProperty('$configPrefixed')) { Class.prototype.$configPrefixed = data.$configPrefixed; } Class.addCachedConfig(data.cachedConfig); // Remove this so it won't be placed on the prototype. delete data.cachedConfig; }); //</feature> //<feature classSystem.mixins> /** * @cfg {String[]/Object} mixins * List of classes to mix into this class. For example: * * Ext.define('CanSing', { * sing: function() { * alert("For he's a jolly good fellow...") * } * }); * * Ext.define('Musician', { * mixins: ['CanSing'] * }) * * In this case the Musician class will get a `sing` method from CanSing mixin. * * But what if the Musician already has a `sing` method? Or you want to mix * in two classes, both of which define `sing`? In such a cases it's good * to define mixins as an object, where you assign a name to each mixin: * * Ext.define('Musician', { * mixins: { * canSing: 'CanSing' * }, * * sing: function() { * // delegate singing operation to mixin * this.mixins.canSing.sing.call(this); * } * }) * * In this case the `sing` method of Musician will overwrite the * mixed in `sing` method. But you can access the original mixed in method * through special `mixins` property. */ ExtClass.registerPreprocessor('mixins', function(Class, data, hooks) { //<debug> Ext.classSystemMonitor && Ext.classSystemMonitor(Class, 'Ext.Class#mixinsPreprocessor', arguments); //</debug> var mixins = data.mixins, onCreated = hooks.onCreated; delete data.mixins; hooks.onCreated = function() { //<debug> Ext.classSystemMonitor && Ext.classSystemMonitor(Class, 'Ext.Class#mixinsPreprocessor#beforeCreated', arguments); //</debug> // Put back the original onCreated before processing mixins. This allows a // mixin to hook onCreated by access Class._classHooks. hooks.onCreated = onCreated; Class.mixin(mixins); // We must go back to hooks.onCreated here because it may have changed during // calls to onClassMixedIn. return hooks.onCreated.apply(this, arguments); }; }); //</feature> //<feature classSystem.backwardsCompatible> // Backwards compatible Ext.extend = function(Class, Parent, members) { //<debug> Ext.classSystemMonitor && Ext.classSystemMonitor(Class, 'Ext.Class#extend-backwards-compatible', arguments); //</debug> if (arguments.length === 2 && Ext.isObject(Parent)) { members = Parent; Parent = Class; Class = null; } var cls; if (!Parent) { throw new Error("[Ext.extend] Attempting to extend from a class which has not been loaded on the page."); } members.extend = Parent; members.preprocessors = [ 'extend' //<feature classSystem.statics> ,'statics' //</feature> //<feature classSystem.inheritableStatics> ,'inheritableStatics' //</feature> //<feature classSystem.mixins> ,'mixins' //</feature> //<feature classSystem.platformConfig> ,'platformConfig' //</feature> //<feature classSystem.config> ,'config' //</feature> ]; if (Class) { cls = new ExtClass(Class, members); // The 'constructor' is given as 'Class' but also needs to be on prototype cls.prototype.constructor = Class; } else { cls = new ExtClass(members); } cls.prototype.override = function(o) { for (var m in o) { if (o.hasOwnProperty(m)) { this[m] = o[m]; } } }; return cls; }; //</feature> /** * This object contains properties that describe the current device or platform. These * values can be used in `{@link Ext.Class#platformConfig platformConfig}` as well as * `{@link Ext.mixin.Responsive#responsiveConfig responsiveConfig}` statements. * * This object can be modified to include tags that are useful for the application. To * add custom properties, it is advisable to use a sub-object. For example: * * Ext.platformTags.app = { * mobile: true * }; * * @property {Object} platformTags * @property {Boolean} platformTags.phone * @property {Boolean} platformTags.tablet * @property {Boolean} platformTags.desktop * @property {Boolean} platformTags.touch Indicates touch inputs are available. * @property {Boolean} platformTags.safari * @property {Boolean} platformTags.chrome * @property {Boolean} platformTags.windows * @property {Boolean} platformTags.firefox * @property {Boolean} platformTags.ios True for iPad, iPhone and iPod. * @property {Boolean} platformTags.android * @property {Boolean} platformTags.blackberry * @property {Boolean} platformTags.tizen * @member Ext */}());