Форк Rambox
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

303 lines
9.1 KiB

/**
* @class Ext.Config
* This class manages a config property. Instances of this type are created and cached as
* classes declare their config properties. One instance of this class is created per
* config property name.
*
* Ext.define('MyClass', {
* config: {
* foo: 42
* }
* });
*
* This uses the cached `Ext.Config` instance for the "foo" property.
*
* When config properties apply options to config properties a prototype chained object is
* created from the cached instance. For example:
*
* Ext.define('MyClass', {
* config: {
* foo: {
* $value: 42,
* lazy: true
* }
* }
* });
*
* This creates a prototype chain to the cached "foo" instance of `Ext.Config` and applies
* the `lazy` option to that new instance. This chained instance is then kept by the
* `Ext.Configurator` for that class.
* @private
*/
Ext.Config = function (name) {
// @define Ext.class.Config
// @define Ext.Config
var me = this,
capitalizedName = name.charAt(0).toUpperCase() + name.substr(1);
/**
* @property {String} name
* The name of this config property.
* @readonly
* @private
* @since 5.0.0
*/
me.name = name;
/**
* @property {Object} names
* This object holds the cached names used to lookup properties or methods for this
* config property. The properties of this object are explained in the context of an
* example property named "foo".
*
* @property {String} names.internal The default backing property ("_foo").
*
* @property {String} names.initializing The property that is `true` when the config
* is being initialized ("isFooInitializing").
*
* @property {String} names.apply The name of the applier method ("applyFoo").
*
* @property {String} names.update The name of the updater method ("updateFoo").
*
* @property {String} names.get The name of the getter method ("getFoo").
*
* @property {String} names.set The name of the setter method ("setFoo").
*
* @property {String} names.initGet The name of the initializing getter ("initGetFoo").
*
* @property {String} names.doSet The name of the evented setter ("doSetFoo").
*
* @property {String} names.changeEvent The name of the change event ("foochange").
*
* @readonly
* @private
* @since 5.0.0
*/
me.names = {
internal: '_' + name,
initializing: 'is' + capitalizedName + 'Initializing',
apply: 'apply' + capitalizedName,
update: 'update' + capitalizedName,
get: 'get' + capitalizedName,
set: 'set' + capitalizedName,
initGet: 'initGet' + capitalizedName,
doSet : 'doSet' + capitalizedName,
changeEvent: name.toLowerCase() + 'change'
};
// This allows folks to prototype chain on top of these objects and yet still cache
// generated methods at the bottom of the chain.
me.root = me;
};
Ext.Config.map = {};
Ext.Config.get = function (name) {
var map = Ext.Config.map,
ret = map[name] || (map[name] = new Ext.Config(name));
return ret;
};
Ext.Config.prototype = {
self: Ext.Config,
isConfig: true,
/**
* @cfg {Boolean} [cached=false]
* When set as `true` the config property will be stored on the class prototype once
* the first instance has had a chance to process the default value.
* @private
* @since 5.0.0
*/
/**
* @cfg {Boolean} [lazy=false]
* When set as `true` the config property will not be immediately initialized during
* the `initConfig` call.
* @private
* @since 5.0.0
*/
/**
* @cfg {Function} [merge]
* This function if supplied will be called as classes or instances provide values
* that need to be combined with inherited values. The function should return the
* value that will be the config value. Further calls may receive such returned
* values as `oldValue`.
*
* @cfg {Mixed} merge.newValue The new value to merge with the old.
*
* @cfg {Mixed} merge.oldValue The current value prior to `newValue` being merged.
*
* @cfg {Mixed} merge.target The class or instance to which the merged config value
* will be applied.
*
* @cfg {Ext.Class} merge.mixinClass The mixin providing the `newValue` or `null` if
* the `newValue` is not being provided by a mixin.
*/
getGetter: function () {
return this.getter || (this.root.getter = this.makeGetter());
},
getInitGetter: function () {
return this.initGetter || (this.root.initGetter = this.makeInitGetter());
},
getSetter: function () {
return this.setter || (this.root.setter = this.makeSetter());
},
/**
* Returns the name of the property that stores this config on the given instance or
* class prototype.
* @param {Object} target
* @return {String}
*/
getInternalName: function (target) {
return target.$configPrefixed ? this.names.internal : this.name;
},
mergeNew: function (newValue, oldValue, target, mixinClass) {
var ret, key;
if (!oldValue) {
ret = newValue;
} else if (!newValue) {
ret = oldValue;
} else {
ret = Ext.Object.chain(oldValue);
for (key in newValue) {
if (!mixinClass || !(key in ret)) {
ret[key] = newValue[key];
}
}
}
return ret;
},
/**
* Merges the `newValue` and the `oldValue` assuming that these are basically objects
* the represent sets. For example something like:
*
* {
* foo: true,
* bar: true
* }
*
* The merge process converts arrays like the following into the above:
*
* [ 'foo', 'bar' ]
*
* @param {String/String[]/Object} newValue
* @param {Object} oldValue
* @param {Boolean} [preserveExisting=false]
* @return {Object}
* @private
* @since 5.0.0
*/
mergeSets: function (newValue, oldValue, preserveExisting) {
var ret = oldValue ? Ext.Object.chain(oldValue) : {},
i, val;
if (newValue instanceof Array) {
for (i = newValue.length; i--; ) {
val = newValue[i];
if (!preserveExisting || !(val in ret)) {
ret[val] = true;
}
}
} else if (newValue) {
if (newValue.constructor === Object) {
for (i in newValue) {
val = newValue[i];
if (!preserveExisting || !(i in ret)) {
ret[i] = val;
}
}
} else if (!preserveExisting || !(newValue in ret)) {
ret[newValue] = true;
}
}
return ret;
},
//--------------------------------------------------
// Factories
makeGetter: function () {
var name = this.name,
prefixedName = this.names.internal;
return function () {
var internalName = this.$configPrefixed ? prefixedName : name;
return this[internalName];
};
},
makeInitGetter: function () {
var name = this.name,
names = this.names,
setName = names.set,
getName = names.get,
initializingName = names.initializing;
return function () {
var me = this;
me[initializingName] = true;
// Remove the initGetter from the instance now that the value has been set.
delete me[getName];
me[setName](me.config[name]);
delete me[initializingName];
return me[getName].apply(me, arguments);
};
},
makeSetter: function () {
var name = this.name,
names = this.names,
prefixedName = names.internal,
getName = names.get,
applyName = names.apply,
updateName = names.update,
setter;
// http://jsperf.com/method-call-apply-or-direct
// http://jsperf.com/method-detect-invoke
setter = function (value) {
var me = this,
internalName = me.$configPrefixed ? prefixedName : name,
oldValue = me[internalName];
// Remove the initGetter from the instance now that the value has been set.
delete me[getName];
if (!me[applyName] || (value = me[applyName](value, oldValue)) !== undefined) {
// The old value might have been changed at this point
// (after the apply call chain) so it should be read again
if (value !== (oldValue = me[internalName])) {
me[internalName] = value;
if (me[updateName]) {
me[updateName](value, oldValue);
}
}
}
return me;
};
setter.$isDefault = true;
return setter;
}
};