/**
* @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}.
*/
/* eslint-disable indent */
(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>
if (Ext.classSystemMonitor) {
Ext.classSystemMonitor(Class, '>> Ext.Class#onBeforeCreated', arguments);
}
//</debug>
Class.addMembers(data);
hooks.onCreated.call(Class, Class);
//<debug>
if (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) {
var Base = Ext.Base,
basePrototype = Base.prototype,
extend = data.extend,
Parent, parentPrototype, i;
//<debug>
if (Ext.classSystemMonitor) {
Ext.classSystemMonitor(Class, 'Ext.Class#extendPreProcessor', arguments);
}
//</debug>
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) {
var privates = data.privates,
statics = privates.statics,
privacy = privates.privacy || true;
//<debug>
if (Ext.classSystemMonitor) {
Ext.classSystemMonitor(Class, 'Ext.Class#privatePreprocessor', arguments);
}
//</debug>
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>
if (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>
if (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.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) {
var mixins = data.mixins,
onCreated = hooks.onCreated;
//<debug>
if (Ext.classSystemMonitor) {
Ext.classSystemMonitor(Class, 'Ext.Class#mixinsPreprocessor', arguments);
}
//</debug>
delete data.mixins;
hooks.onCreated = function() {
//<debug>
/* eslint-disable-next-line max-len */
if (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) {
var cls, m;
//<debug>
if (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;
}
if (!Parent) {
throw new Error("[Ext.extend] Attempting to extend from a class which has not " +
"been loaded on the page.");
}
members.extend = Parent;
/* eslint-disable comma-style */
members.preprocessors = [
'extend'
//<feature classSystem.statics>
, 'statics'
//</feature>
//<feature classSystem.inheritableStatics>
, 'inheritableStatics'
//</feature>
//<feature classSystem.mixins>
, 'mixins'
//</feature>
//<feature classSystem.config>
, 'config'
//</feature>
];
/* eslint-enable comma-style */
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 (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
*/
}());