/** * The base-build submodule provides Base.build functionality, which * can be used to create custom classes, by aggregating extensions onto * a main class. * * @module base * @submodule base-build * @for Base */ var BaseCore = Y.BaseCore, Base = Y.Base, L = Y.Lang, INITIALIZER = "initializer", DESTRUCTOR = "destructor", AGGREGATES = ["_PLUG", "_UNPLUG"], build; // Utility function used in `_buildCfg` to aggregate array values into a new // array from the sender constructor to the receiver constructor. function arrayAggregator(prop, r, s) { if (s[prop]) { r[prop] = (r[prop] || []).concat(s[prop]); } } // Utility function used in `_buildCfg` to aggregate `_ATTR_CFG` array // values from the sender constructor into a new array on receiver's // constructor, and clear the cached hash. function attrCfgAggregator(prop, r, s) { if (s._ATTR_CFG) { // Clear cached hash. r._ATTR_CFG_HASH = null; arrayAggregator.apply(null, arguments); } } // Utility function used in `_buildCfg` to aggregate ATTRS configs from one // the sender constructor to the receiver constructor. function attrsAggregator(prop, r, s) { BaseCore.modifyAttrs(r, s.ATTRS); } Base._build = function(name, main, extensions, px, sx, cfg) { var build = Base._build, builtClass = build._ctor(main, cfg), buildCfg = build._cfg(main, cfg, extensions), _mixCust = build._mixCust, dynamic = builtClass._yuibuild.dynamic, i, l, extClass, extProto, initializer, destructor; // Augment/Aggregate for (i = 0, l = extensions.length; i < l; i++) { extClass = extensions[i]; extProto = extClass.prototype; initializer = extProto[INITIALIZER]; destructor = extProto[DESTRUCTOR]; delete extProto[INITIALIZER]; delete extProto[DESTRUCTOR]; // Prototype, old non-displacing augment Y.mix(builtClass, extClass, true, null, 1); // Custom Statics _mixCust(builtClass, extClass, buildCfg); if (initializer) { extProto[INITIALIZER] = initializer; } if (destructor) { extProto[DESTRUCTOR] = destructor; } builtClass._yuibuild.exts.push(extClass); } if (px) { Y.mix(builtClass.prototype, px, true); } if (sx) { Y.mix(builtClass, build._clean(sx, buildCfg), true); _mixCust(builtClass, sx, buildCfg); } builtClass.prototype.hasImpl = build._impl; if (dynamic) { builtClass.NAME = name; builtClass.prototype.constructor = builtClass; // Carry along the reference to `modifyAttrs()` from `main`. builtClass.modifyAttrs = main.modifyAttrs; } return builtClass; }; build = Base._build; Y.mix(build, { _mixCust: function(r, s, cfg) { var aggregates, custom, statics, aggr, l, i; if (cfg) { aggregates = cfg.aggregates; custom = cfg.custom; statics = cfg.statics; } if (statics) { Y.mix(r, s, true, statics); } if (aggregates) { for (i = 0, l = aggregates.length; i < l; i++) { aggr = aggregates[i]; if (!r.hasOwnProperty(aggr) && s.hasOwnProperty(aggr)) { r[aggr] = L.isArray(s[aggr]) ? [] : {}; } Y.aggregate(r, s, true, [aggr]); } } if (custom) { for (i in custom) { if (custom.hasOwnProperty(i)) { custom[i](i, r, s); } } } }, _tmpl: function(main) { function BuiltClass() { BuiltClass.superclass.constructor.apply(this, arguments); } Y.extend(BuiltClass, main); return BuiltClass; }, _impl : function(extClass) { var classes = this._getClasses(), i, l, cls, exts, ll, j; for (i = 0, l = classes.length; i < l; i++) { cls = classes[i]; if (cls._yuibuild) { exts = cls._yuibuild.exts; ll = exts.length; for (j = 0; j < ll; j++) { if (exts[j] === extClass) { return true; } } } } return false; }, _ctor : function(main, cfg) { var dynamic = (cfg && false === cfg.dynamic) ? false : true, builtClass = (dynamic) ? build._tmpl(main) : main, buildCfg = builtClass._yuibuild; if (!buildCfg) { buildCfg = builtClass._yuibuild = {}; } buildCfg.id = buildCfg.id || null; buildCfg.exts = buildCfg.exts || []; buildCfg.dynamic = dynamic; return builtClass; }, _cfg : function(main, cfg, exts) { var aggr = [], cust = {}, statics = [], buildCfg, cfgAggr = (cfg && cfg.aggregates), cfgCustBuild = (cfg && cfg.custom), cfgStatics = (cfg && cfg.statics), c = main, i, l; // Prototype Chain while (c && c.prototype) { buildCfg = c._buildCfg; if (buildCfg) { if (buildCfg.aggregates) { aggr = aggr.concat(buildCfg.aggregates); } if (buildCfg.custom) { Y.mix(cust, buildCfg.custom, true); } if (buildCfg.statics) { statics = statics.concat(buildCfg.statics); } } c = c.superclass ? c.superclass.constructor : null; } // Exts if (exts) { for (i = 0, l = exts.length; i < l; i++) { c = exts[i]; buildCfg = c._buildCfg; if (buildCfg) { if (buildCfg.aggregates) { aggr = aggr.concat(buildCfg.aggregates); } if (buildCfg.custom) { Y.mix(cust, buildCfg.custom, true); } if (buildCfg.statics) { statics = statics.concat(buildCfg.statics); } } } } if (cfgAggr) { aggr = aggr.concat(cfgAggr); } if (cfgCustBuild) { Y.mix(cust, cfg.cfgBuild, true); } if (cfgStatics) { statics = statics.concat(cfgStatics); } return { aggregates: aggr, custom: cust, statics: statics }; }, _clean : function(sx, cfg) { var prop, i, l, sxclone = Y.merge(sx), aggregates = cfg.aggregates, custom = cfg.custom; for (prop in custom) { if (sxclone.hasOwnProperty(prop)) { delete sxclone[prop]; } } for (i = 0, l = aggregates.length; i < l; i++) { prop = aggregates[i]; if (sxclone.hasOwnProperty(prop)) { delete sxclone[prop]; } } return sxclone; } }); /** * <p> * Builds a custom constructor function (class) from the * main function, and array of extension functions (classes) * provided. The NAME field for the constructor function is * defined by the first argument passed in. * </p> * <p> * The cfg object supports the following properties * </p> * <dl> * <dt>dynamic <boolean></dt> * <dd> * <p>If true (default), a completely new class * is created which extends the main class, and acts as the * host on which the extension classes are augmented.</p> * <p>If false, the extensions classes are augmented directly to * the main class, modifying the main class' prototype.</p> * </dd> * <dt>aggregates <String[]></dt> * <dd>An array of static property names, which will get aggregated * on to the built class, in addition to the default properties build * will always aggregate as defined by the main class' static _buildCfg * property. * </dd> * </dl> * * @method build * @deprecated Use the more convenient Base.create and Base.mix methods instead * @static * @param {Function} name The name of the new class. Used to define the NAME property for the new class. * @param {Function} main The main class on which to base the built class * @param {Function[]} extensions The set of extension classes which will be * augmented/aggregated to the built class. * @param {Object} cfg Optional. Build configuration for the class (see description). * @return {Function} A custom class, created from the provided main and extension classes */ Base.build = function(name, main, extensions, cfg) { return build(name, main, extensions, null, null, cfg); }; /** * Creates a new class (constructor function) which extends the base class passed in as the second argument, * and mixes in the array of extensions provided. * * Prototype properties or methods can be added to the new class, using the px argument (similar to Y.extend). * * Static properties or methods can be added to the new class, using the sx argument (similar to Y.extend). * * **NOTE FOR COMPONENT DEVELOPERS**: Both the `base` class, and `extensions` can define static a `_buildCfg` * property, which acts as class creation meta-data, and drives how special static properties from the base * class, or extensions should be copied, aggregated or (custom) mixed into the newly created class. * * The `_buildCfg` property is a hash with 3 supported properties: `statics`, `aggregates` and `custom`, e.g: * * // If the Base/Main class is the thing introducing the property: * * MyBaseClass._buildCfg = { * * // Static properties/methods to copy (Alias) to the built class. * statics: ["CopyThisMethod", "CopyThisProperty"], * * // Static props to aggregate onto the built class. * aggregates: ["AggregateThisProperty"], * * // Static properties which need custom handling (e.g. deep merge etc.) * custom: { * "CustomProperty" : function(property, Receiver, Supplier) { * ... * var triggers = Receiver.CustomProperty.triggers; * Receiver.CustomProperty.triggers = triggers.concat(Supplier.CustomProperty.triggers); * ... * } * } * }; * * MyBaseClass.CopyThisMethod = function() {...}; * MyBaseClass.CopyThisProperty = "foo"; * MyBaseClass.AggregateThisProperty = {...}; * MyBaseClass.CustomProperty = { * triggers: [...] * } * * // Or, if the Extension is the thing introducing the property: * * MyExtension._buildCfg = { * statics : ... * aggregates : ... * custom : ... * } * * This way, when users pass your base or extension class to `Y.Base.create` or `Y.Base.mix`, they don't need to * know which properties need special handling. `Y.Base` has a buildCfg which defines `ATTRS` for custom mix handling * (to protect the static config objects), and `Y.Widget` has a buildCfg which specifies `HTML_PARSER` for * straight up aggregation. * * @method create * @static * @param {String} name The name of the newly created class. Used to define the NAME property for the new class. * @param {Function} main The base class which the new class should extend. * This class needs to be Base or a class derived from base (e.g. Widget). * @param {Function[]} extensions The list of extensions which will be mixed into the built class. * @param {Object} px The set of prototype properties/methods to add to the built class. * @param {Object} sx The set of static properties/methods to add to the built class. * @return {Function} The newly created class. */ Base.create = function(name, base, extensions, px, sx) { return build(name, base, extensions, px, sx); }; /** * <p>Mixes in a list of extensions to an existing class.</p> * @method mix * @static * @param {Function} main The existing class into which the extensions should be mixed. * The class needs to be Base or a class derived from Base (e.g. Widget) * @param {Function[]} extensions The set of extension classes which will mixed into the existing main class. * @return {Function} The modified main class, with extensions mixed in. */ Base.mix = function(main, extensions) { if (main._CACHED_CLASS_DATA) { main._CACHED_CLASS_DATA = null; } return build(null, main, extensions, null, null, {dynamic:false}); }; /** * The build configuration for the Base class. * * Defines the static fields which need to be aggregated when the Base class * is used as the main class passed to the * <a href="#method_Base.build">Base.build</a> method. * * @property _buildCfg * @type Object * @static * @final * @private */ BaseCore._buildCfg = { aggregates: AGGREGATES.concat(), custom: { ATTRS : attrsAggregator, _ATTR_CFG : attrCfgAggregator, _NON_ATTRS_CFG: arrayAggregator } }; // Makes sure Base and BaseCore use separate `_buildCfg` objects. Base._buildCfg = { aggregates: AGGREGATES.concat(), custom: { ATTRS : attrsAggregator, _ATTR_CFG : attrCfgAggregator, _NON_ATTRS_CFG: arrayAggregator } };