extjs-gpl
Version:
GPL licensed version of Sencha Ext JS
305 lines (273 loc) • 9.37 kB
JavaScript
// @define Ext.Factory
/**
* @class Ext.Factory
* Manages factories for families of classes (classes with a common `alias` prefix). The
* factory for a class family is a function stored as a `static` on `Ext.Factory`. These
* are created either by directly calling `Ext.Factory.define` or by using the
* `Ext.mixin.Factoryable` interface.
*
* To illustrate, consider the layout system's use of aliases. The `hbox` layout maps to
* the `"layout.hbox"` alias that one typically provides via the `layout` config on a
* Container.
*
* Under the covers this maps to a call like this:
*
* Ext.Factory.layout('hbox');
*
* Or possibly:
*
* Ext.Factory.layout({
* type: 'hbox'
* });
*
* The value of the `layout` config is passed to the `Ext.Factory.layout` function. The
* exact signature of a factory method matches `{@link Ext.Factory#create}`.
*
* To define this factory directly, one could call `Ext.Factory.define` like so:
*
* Ext.Factory.define('layout', 'auto'); // "layout.auto" is the default type
*
* @since 5.0.0
*/
Ext.Factory = function (type) {
var me = this;
me.aliasPrefix = type + '.';
me.cache = {};
me.name = type.replace(me.fixNameRe, me.fixNameFn);
me.type = type;
};
Ext.Factory.prototype = {
/**
* @cfg {String} [aliasPrefix]
* The prefix to apply to `type` values to form a complete alias. This defaults to the
* proper value in most all cases and should not need to be specified.
*
* @since 5.0.0
*/
/**
* @cfg {String} [defaultProperty="type"]
* The config property to set when the factory is given a config that is a string.
*
* @since 5.0.0
*/
defaultProperty: 'type',
/**
* @cfg {String} [defaultType=null]
* An optional type to use if none is given to the factory at invocation. This is a
* suffix added to the `aliasPrefix`. For example, if `aliasPrefix="layout."` and
* `defaultType="hbox"` the default alias is `"layout.hbox"`. This is an alternative
* to `xclass` so only one should be provided.
*
* @since 5.0.0
*/
/**
* @cfg {String} [instanceProp="isInstance"]
* The property that identifies an object as instance vs a config.
*
* @since 5.0.0
*/
instanceProp: 'isInstance',
/**
* @cfg {String} [xclass=null]
* The full classname of the type of instance to create when none is provided to the
* factory. This is an alternative to `defaultType` so only one should be specified.
*
* @since 5.0.0
*/
/**
* @property {Ext.Class} [defaultClass=null]
* The Class reference of the type of instance to create when none is provided to the
* factory. This property is set from `xclass` when the factory instance is created.
* @private
* @readonly
*
* @since 5.0.0
*/
/**
* Creates an instance of this class family given configuration options.
*
* @param {Object/String} [config] The configuration or instance (if an Object) or
* just the type (if a String) describing the instance to create.
* @param {String} [config.xclass] The full class name of the class to create.
* @param {String} [config.type] The type string to add to the alias prefix for this
* factory.
* @param {String/Object} [defaultType] The type to create if no type is contained in the
* `config`, or an object containing a default set of configs.
* @return {Object} The newly created instance.
*
* @since 5.0.0
*/
create: function (config, defaultType) {
var me = this,
Manager = Ext.ClassManager,
cache = me.cache,
alias, className, klass, suffix;
if (config) {
if (config[me.instanceProp]) {
return config;
}
if (typeof config === 'string') {
suffix = config;
config = {};
config[me.defaultProperty] = suffix;
}
className = config.xclass;
suffix = config.type;
}
if (defaultType && defaultType.constructor === Object) {
config = Ext.apply({}, config, defaultType);
defaultType = defaultType.type;
}
if (className) {
if (!(klass = Manager.get(className))) {
return Manager.instantiate(className, config);
}
} else {
if (!(suffix = suffix || defaultType || me.defaultType)) {
klass = me.defaultClass;
}
//<debug>
if (!suffix && !klass) {
Ext.raise('No type specified for ' + me.type + '.create');
}
//</debug>
if (!klass && !(klass = cache[suffix])) {
alias = me.aliasPrefix + suffix;
className = Manager.getNameByAlias(alias);
// this is needed to support demand loading of the class
if (!(klass = className && Manager.get(className))) {
return Manager.instantiateByAlias(alias, config);
}
cache[suffix] = klass;
}
}
return klass.isInstance ? klass : new klass(config);
},
fixNameRe: /\.[a-z]/ig,
fixNameFn: function (match) {
return match.substring(1).toUpperCase();
},
clearCache: function() {
this.cache = {};
}
};
/**
* For example, the layout alias family could be defined like this:
*
* Ext.Factory.define('layout', {
* defaultType: 'auto'
* });
*
* To define multiple families at once:
*
* Ext.Factory.define({
* layout: {
* defaultType: 'auto'
* }
* });
*
* @param {String} type The alias prefix for type (e.g., "layout.").
* @param {Object/String} [config] An object specifying the config for the `Ext.Factory`
* to be created. If a string is passed it is treated as the `defaultType`.
* @return {Function}
* @static
* @since 5.0.0
*/
Ext.Factory.define = function (type, config) {
var Factory = Ext.Factory,
defaultClass, factory, fn;
if (type.constructor === Object) {
Ext.Object.each(type, Factory.define, Factory);
} else {
factory = new Ext.Factory(type);
if (config) {
if (config.constructor === Object) {
Ext.apply(factory, config);
if (typeof(defaultClass = factory.xclass) === 'string') {
factory.defaultClass = Ext.ClassManager.get(defaultClass);
}
} else {
factory.defaultType = config;
}
}
Factory[factory.name] = fn = factory.create.bind(factory);
fn.instance = factory;
}
return fn;
};
/**
* This mixin automates use of `Ext.Factory`. When mixed in to a class, the `alias` of the
* class is retrieved and combined with an optional `factoryConfig` property on that class
* to produce the configuration to pass to `Ext.Factory`.
*
* The factory method created by `Ext.Factory` is also added as a static method to the
* target class.
*
* Given a class declared like so:
*
* Ext.define('App.bar.Thing', {
* mixins: [
* 'Ext.mixin.Factoryable'
* ],
*
* alias: 'bar.thing', // this is detected by Factoryable
*
* factoryConfig: {
* defaultType: 'thing', // this is the default deduced from the alias
* // other configs
* },
*
* ...
* });
*
* The produced factory function can be used to create instances using the following
* forms:
*
* var obj;
*
* obj = App.bar.Thing.create('thing'); // same as "new App.bar.Thing()"
*
* obj = App.bar.Thing.create({
* type: 'thing' // same as above
* });
*
* obj = App.bar.Thing.create({
* xclass: 'App.bar.Thing' // same as above
* });
*
* var obj2 = App.bar.Thing.create(obj);
* // obj === obj2 (passing an instance returns the instance)
*
* Alternatively the produced factory is available as a static method of `Ext.Factory`.
*
* @since 5.0.0
*/
Ext.define('Ext.mixin.Factoryable', {
mixinId: 'factoryable',
onClassMixedIn: function (targetClass) {
var proto = targetClass.prototype,
factoryConfig = proto.factoryConfig,
alias = proto.alias,
config = {},
dot, createFn;
alias = alias && alias.length && alias[0];
if (alias && (dot = alias.lastIndexOf('.')) > 0) {
config.type = alias.substring(0, dot);
config.defaultType = alias.substring(dot+1);
}
if (factoryConfig) {
delete proto.factoryConfig;
Ext.apply(config, factoryConfig);
}
createFn = Ext.Factory.define(config.type, config);
if (targetClass.create === Ext.Base.create) {
// allow targetClass to override the create method
targetClass.create = createFn;
}
}
/**
* @property {Object} [factoryConfig]
* If this property is specified by the target class of this mixin its properties are
* used to configure the created `Ext.Factory`.
*/
});