2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
8 YUI.add('base-core', function (Y, NAME) {
11 * The base module provides the Base class, which objects requiring attribute and custom event support can extend.
12 * The module also provides two ways to reuse code - It augments Base with the Plugin.Host interface which provides
13 * plugin support and also provides the BaseCore.build method which provides a way to build custom classes using extensions.
19 * <p>The base-core module provides the BaseCore class, the lightest version of Base,
20 * which provides Base's basic lifecycle management and ATTRS construction support,
21 * but doesn't fire init/destroy or attribute change events.</p>
23 * <p>It mixes in AttributeCore, which is the lightest version of Attribute</p>
26 * @submodule base-core
31 INITIALIZED = "initialized",
32 DESTROYED = "destroyed",
33 INITIALIZER = "initializer",
35 OBJECT_CONSTRUCTOR = Object.prototype.constructor,
38 DESTRUCTOR = "destructor",
40 AttributeCore = Y.AttributeCore,
42 _wlmix = function(r, s, wlhash) {
53 * The BaseCore class, is the lightest version of Base, and provides Base's
54 * basic lifecycle management and ATTRS construction support, but doesn't
55 * fire init/destroy or attribute change events.
57 * BaseCore also handles the chaining of initializer and destructor methods across
58 * the hierarchy as part of object construction and destruction. Additionally, attributes
59 * configured through the static <a href="#property_BaseCore.ATTRS">ATTRS</a>
60 * property for each class in the hierarchy will be initialized by BaseCore.
62 * Classes which require attribute support, but don't intend to use/expose attribute
63 * change events can extend BaseCore instead of Base for optimal kweight and
64 * runtime performance.
66 * **3.11.0 BACK COMPAT NOTE FOR COMPONENT DEVELOPERS**
68 * Prior to version 3.11.0, ATTRS would get added a class at a time. That is:
71 * for each (class in the hierarchy) {
72 * Call the class Extension constructors.
74 * Add the class ATTRS.
76 * Call the class initializer
77 * Call the class Extension initializers.
81 * As of 3.11.0, ATTRS from all classes in the hierarchy are added in one `addAttrs` call
82 * before **any** initializers are called. That is, the flow becomes:
85 * for each (class in the hierarchy) {
86 * Call the class Extension constructors.
89 * Add ATTRS for all classes
91 * for each (class in the hierarchy) {
92 * Call the class initializer.
93 * Call the class Extension initializers.
97 * Adding all ATTRS at once fixes subtle edge-case issues with subclass ATTRS overriding
98 * superclass `setter`, `getter` or `valueFn` definitions and being unable to get/set attributes
99 * defined by the subclass. It also leaves us with a cleaner order of operation flow moving
102 * However, it may require component developers to upgrade their components, for the following
105 * 1. It impacts components which may have `setter`, `getter` or `valueFn` code which
106 * expects a superclass' initializer to have run.
108 * This is expected to be rare, but to support it, Base now supports a `_preAddAttrs()`, method
109 * hook (same signature as `addAttrs`). Components can implement this method on their prototype
110 * for edge cases which do require finer control over the order in which attributes are added
111 * (see widget-htmlparser for example).
113 * 2. Extension developers may need to move code from Extension constructors to `initializer`s
115 * Older extensions, which were written before `initializer` support was added, had a lot of
116 * initialization code in their constructors. For example, code which acccessed superclass
117 * attributes. With the new flow this code would not be able to see attributes. The recommendation
118 * is to move this initialization code to an `initializer` on the Extension, which was the
119 * recommendation for anything created after `initializer` support for Extensions was added.
123 * @uses AttributeCore
124 * @param {Object} cfg Object with configuration property name/value pairs.
125 * The object can be used to provide initial values for the objects published
128 function BaseCore(cfg) {
129 if (!this._BaseInvoked) {
130 this._BaseInvoked = true;
132 Y.log('constructor called', 'life', 'base');
135 else { Y.log('Based constructor called more than once. Ignoring duplicate calls', 'life', 'base'); }
139 * The list of properties which can be configured for each attribute
140 * (e.g. setter, getter, writeOnce, readOnly etc.)
142 * @property _ATTR_CFG
147 BaseCore._ATTR_CFG = AttributeCore._ATTR_CFG.concat("cloneDefaultValue");
150 * The array of non-attribute configuration properties supported by this class.
152 * For example `BaseCore` defines a "plugins" configuration property which
153 * should not be set up as an attribute. This property is primarily required so
154 * that when <a href="#property__allowAdHocAttrs">`_allowAdHocAttrs`</a> is enabled by a class,
155 * non-attribute configuration properties don't get added as ad-hoc attributes.
157 * @property _NON_ATTRS_CFG
162 BaseCore._NON_ATTRS_CFG = ["plugins"];
165 * This property controls whether or not instances of this class should
166 * allow users to add ad-hoc attributes through the constructor configuration
169 * AdHoc attributes are attributes which are not defined by the class, and are
170 * not handled by the MyClass._NON_ATTRS_CFG
172 * @property _allowAdHocAttrs
174 * @default undefined (false)
179 * The string to be used to identify instances of this class.
181 * Classes extending BaseCore, should define their own
182 * static NAME property, which should be camelCase by
183 * convention (e.g. MyClass.NAME = "myClass";).
189 BaseCore.NAME = "baseCore";
192 * The default set of attributes which will be available for instances of this class, and
193 * their configuration. In addition to the configuration properties listed by
194 * AttributeCore's <a href="AttributeCore.html#method_addAttr">addAttr</a> method,
195 * the attribute can also be configured with a "cloneDefaultValue" property, which
196 * defines how the statically defined value field should be protected
197 * ("shallow", "deep" and false are supported values).
199 * By default if the value is an object literal or an array it will be "shallow"
200 * cloned, to protect the default value.
208 * Flag indicating whether or not this object
209 * has been through the init lifecycle phase.
211 * @attribute initialized
222 * Flag indicating whether or not this object
223 * has been through the destroy lifecycle phase.
225 * @attribute destroyed
237 Provides a way to safely modify a `Y.BaseCore` subclass' static `ATTRS`
238 after the class has been defined or created.
240 BaseCore-based classes cache information about the class hierarchy in order
241 to efficiently create instances. This cache includes includes the aggregated
242 `ATTRS` configs. If the static `ATTRS` configs need to be modified after the
243 class has been defined or create, then use this method which will make sure
244 to clear any cached data before making any modifications.
247 @param {Function} [ctor] The constructor function whose `ATTRS` should be
248 modified. If a `ctor` function is not specified, then `this` is assumed
249 to be the constructor which hosts the `ATTRS`.
250 @param {Object} configs The collection of `ATTRS` configs to mix with the
251 existing attribute configurations.
255 BaseCore.modifyAttrs = function (ctor, configs) {
256 // When called without a constructor, assume `this` is the constructor.
257 if (typeof ctor !== 'function') {
262 var attrs, attr, name;
264 // Eagerly create the `ATTRS` object if it doesn't already exist.
265 attrs = ctor.ATTRS || (ctor.ATTRS = {});
268 // Clear cache because it has ATTRS aggregation data which is about
270 ctor._CACHED_CLASS_DATA = null;
272 for (name in configs) {
273 if (configs.hasOwnProperty(name)) {
274 attr = attrs[name] || (attrs[name] = {});
275 Y.mix(attr, configs[name], true);
281 BaseCore.prototype = {
284 * Internal construction logic for BaseCore.
287 * @param {Object} config The constructor configuration object
290 _initBase : function(config) {
291 Y.log('init called', 'life', 'base');
295 this._initAttribute(config);
297 // If Plugin.Host has been augmented [ through base-pluginhost ], setup it's
298 // initial state, but don't initialize Plugins yet. That's done after initialization.
299 var PluginHost = Y.Plugin && Y.Plugin.Host;
300 if (this._initPlugins && PluginHost) {
301 PluginHost.call(this);
304 if (this._lazyAddAttrs !== false) { this._lazyAddAttrs = true; }
307 * The string used to identify the class of this object.
309 * @deprecated Use this.constructor.NAME
313 this.name = this.constructor.NAME;
315 this.init.apply(this, arguments);
319 * Initializes AttributeCore
321 * @method _initAttribute
324 _initAttribute: function() {
325 AttributeCore.call(this);
329 * Init lifecycle method, invoked during construction. Sets up attributes
330 * and invokes initializers for the class hierarchy.
334 * @param {Object} cfg Object with configuration property name/value pairs
335 * @return {BaseCore} A reference to this object
337 init: function(cfg) {
338 Y.log('init called', 'life', 'base');
346 * Internal initialization implementation for BaseCore
351 _baseInit: function(cfg) {
352 this._initHierarchy(cfg);
354 if (this._initPlugins) {
355 // Need to initPlugins manually, to handle constructor parsing, static Plug parsing
356 this._initPlugins(cfg);
358 this._set(INITIALIZED, true);
362 * Destroy lifecycle method. Invokes destructors for the class hierarchy.
365 * @return {BaseCore} A reference to this object
368 destroy: function() {
374 * Internal destroy implementation for BaseCore
376 * @method _baseDestroy
379 _baseDestroy : function() {
380 if (this._destroyPlugins) {
381 this._destroyPlugins();
383 this._destroyHierarchy();
384 this._set(DESTROYED, true);
388 * Returns the class hierarchy for this object, with BaseCore being the last class in the array.
390 * @method _getClasses
392 * @return {Function[]} An array of classes (constructor functions), making up the class hierarchy for this object.
393 * This value is cached the first time the method, or _getAttrCfgs, is invoked. Subsequent invocations return the
396 _getClasses : function() {
397 if (!this._classes) {
398 this._initHierarchyData();
400 return this._classes;
404 * Returns an aggregated set of attribute configurations, by traversing
405 * the class hierarchy.
407 * @method _getAttrCfgs
409 * @return {Object} The hash of attribute configurations, aggregated across classes in the hierarchy
410 * This value is cached the first time the method, or _getClasses, is invoked. Subsequent invocations return
413 _getAttrCfgs : function() {
415 this._initHierarchyData();
421 * A helper method used to isolate the attrs config for this instance to pass to `addAttrs`,
422 * from the static cached ATTRS for the class.
424 * @method _getInstanceAttrCfgs
427 * @param {Object} allCfgs The set of all attribute configurations for this instance.
428 * Attributes will be removed from this set, if they belong to the filtered class, so
429 * that by the time all classes are processed, allCfgs will be empty.
431 * @return {Object} The set of attributes to be added for this instance, suitable
432 * for passing through to `addAttrs`.
434 _getInstanceAttrCfgs : function(allCfgs) {
444 allSubAttrs = allCfgs._subAttrs,
445 attrCfgProperties = this._attrCfgHash();
447 for (attr in allCfgs) {
449 if (allCfgs.hasOwnProperty(attr) && attr !== "_subAttrs") {
451 attrCfg = allCfgs[attr];
453 // Need to isolate from allCfgs, because we're going to set values etc.
454 cfg = cfgs[attr] = _wlmix({}, attrCfg, attrCfgProperties);
458 if (val && (typeof val === "object")) {
459 this._cloneDefaultValue(attr, cfg);
462 if (allSubAttrs && allSubAttrs.hasOwnProperty(attr)) {
463 subAttrs = allCfgs._subAttrs[attr];
465 for (subAttrPath in subAttrs) {
466 subAttr = subAttrs[subAttrPath];
469 O.setValue(cfg.value, subAttr.path, subAttr.value);
480 * @method _filterAdHocAttrs
483 * @param {Object} allAttrs The set of all attribute configurations for this instance.
484 * Attributes will be removed from this set, if they belong to the filtered class, so
485 * that by the time all classes are processed, allCfgs will be empty.
486 * @param {Object} userVals The config object passed in by the user, from which adhoc attrs are to be filtered.
487 * @return {Object} The set of adhoc attributes passed in, in the form
488 * of an object with attribute name/configuration pairs.
490 _filterAdHocAttrs : function(allAttrs, userVals) {
492 nonAttrs = this._nonAttrs,
497 for (attr in userVals) {
498 if (!allAttrs[attr] && !nonAttrs[attr] && userVals.hasOwnProperty(attr)) {
510 * A helper method used by _getClasses and _getAttrCfgs, which determines both
511 * the array of classes and aggregate set of attribute configurations
512 * across the class hierarchy for the instance.
514 * @method _initHierarchyData
517 _initHierarchyData : function() {
519 var ctor = this.constructor,
520 cachedClassData = ctor._CACHED_CLASS_DATA,
526 needsAttrCfgHash = !ctor._ATTR_CFG_HASH,
532 // Start with `this` instance's constructor.
535 if (!cachedClassData) {
539 classes[classes.length] = c;
543 attrs[attrs.length] = c.ATTRS;
546 // Aggregate ATTR cfg whitelist.
547 if (needsAttrCfgHash) {
548 attrCfg = c._ATTR_CFG;
549 attrCfgHash = attrCfgHash || {};
552 for (i = 0, l = attrCfg.length; i < l; i += 1) {
553 attrCfgHash[attrCfg[i]] = true;
558 // Commenting out the if. We always aggregate, since we don't
559 // know if we'll be needing this on the instance or not.
560 // if (this._allowAdHocAttrs) {
561 nonAttrsCfg = c._NON_ATTRS_CFG;
563 for (i = 0, l = nonAttrsCfg.length; i < l; i++) {
564 nonAttrs[nonAttrsCfg[i]] = true;
569 c = c.superclass ? c.superclass.constructor : null;
572 // Cache computed `_ATTR_CFG_HASH` on the constructor.
573 if (needsAttrCfgHash) {
574 ctor._ATTR_CFG_HASH = attrCfgHash;
577 cachedClassData = ctor._CACHED_CLASS_DATA = {
580 attrs : this._aggregateAttrs(attrs)
585 this._classes = cachedClassData.classes;
586 this._attrs = cachedClassData.attrs;
587 this._nonAttrs = cachedClassData.nonAttrs;
591 * Utility method to define the attribute hash used to filter/whitelist property mixes for
592 * this class for iteration performance reasons.
594 * @method _attrCfgHash
597 _attrCfgHash: function() {
598 return this.constructor._ATTR_CFG_HASH;
602 * This method assumes that the value has already been checked to be an object.
603 * Since it's on a critical path, we don't want to re-do the check.
605 * @method _cloneDefaultValue
606 * @param {Object} cfg
609 _cloneDefaultValue : function(attr, cfg) {
612 clone = cfg.cloneDefaultValue;
614 if (clone === DEEP || clone === true) {
615 Y.log('Cloning default value for attribute:' + attr, 'info', 'base');
616 cfg.value = Y.clone(val);
617 } else if (clone === SHALLOW) {
618 Y.log('Merging default value for attribute:' + attr, 'info', 'base');
619 cfg.value = Y.merge(val);
620 } else if ((clone === undefined && (OBJECT_CONSTRUCTOR === val.constructor || L.isArray(val)))) {
621 cfg.value = Y.clone(val);
623 // else if (clone === false), don't clone the static default value.
624 // It's intended to be used by reference.
628 * A helper method, used by _initHierarchyData to aggregate
629 * attribute configuration across the instances class hierarchy.
631 * The method will protect the attribute configuration value to protect the statically defined
632 * default value in ATTRS if required (if the value is an object literal, array or the
633 * attribute configuration has cloneDefaultValue set to shallow or deep).
635 * @method _aggregateAttrs
637 * @param {Array} allAttrs An array of ATTRS definitions across classes in the hierarchy
638 * (subclass first, Base last)
639 * @return {Object} The aggregate set of ATTRS definitions for the instance
641 _aggregateAttrs : function(allAttrs) {
649 cfgPropsHash = this._attrCfgHash(),
654 for (i = allAttrs.length-1; i >= 0; --i) {
658 for (attr in attrs) {
659 if (attrs.hasOwnProperty(attr)) {
661 // PERF TODO: Do we need to merge here, since we're merging later in getInstanceAttrCfgs
662 // Should we move this down to only merge if we hit the path or valueFn ifs below?
663 cfg = _wlmix({}, attrs[attr], cfgPropsHash);
666 if (attr.indexOf(DOT) !== -1) {
667 path = attr.split(DOT);
671 aggAttr = aggAttrs[attr];
673 if (path && aggAttr && aggAttr.value) {
675 subAttrsHash = aggAttrs._subAttrs;
678 subAttrsHash = aggAttrs._subAttrs = {};
681 if (!subAttrsHash[attr]) {
682 subAttrsHash[attr] = {};
685 subAttrsHash[attr][path.join(DOT)] = {
693 aggAttrs[attr] = cfg;
695 if (aggAttr.valueFn && VALUE in cfg) {
696 aggAttr.valueFn = null;
699 // Mix into existing config.
700 _wlmix(aggAttr, cfg, cfgPropsHash);
712 * Initializes the class hierarchy for the instance, which includes
713 * initializing attributes for each class defined in the class's
714 * static <a href="#property_BaseCore.ATTRS">ATTRS</a> property and
715 * invoking the initializer method on the prototype of each class in the hierarchy.
717 * @method _initHierarchy
718 * @param {Object} userVals Object with configuration property name/value pairs
721 _initHierarchy : function(userVals) {
723 var lazy = this._lazyAddAttrs,
736 classes = this._getClasses(),
737 attrCfgs = this._getAttrCfgs(),
738 cl = classes.length - 1;
741 for (ci = cl; ci >= 0; ci--) {
743 constr = classes[ci];
744 constrProto = constr.prototype;
745 exts = constr._yuibuild && constr._yuibuild.exts;
747 // Using INITIALIZER in hasOwnProperty check, for performance reasons (helps IE6 avoid GC thresholds when
748 // referencing string literals). Not using it in apply, again, for performance "." is faster.
750 if (constrProto.hasOwnProperty(INITIALIZER)) {
751 // Store initializer while we're here and looping
752 initializers[initializers.length] = constrProto.initializer;
756 for (ei = 0, el = exts.length; ei < el; ei++) {
761 ext.apply(this, arguments);
763 extProto = ext.prototype;
764 if (extProto.hasOwnProperty(INITIALIZER)) {
765 // Store initializer while we're here and looping
766 initializers[initializers.length] = extProto.initializer;
773 instanceAttrs = this._getInstanceAttrCfgs(attrCfgs);
775 if (this._preAddAttrs) {
776 this._preAddAttrs(instanceAttrs, userVals, lazy);
779 if (this._allowAdHocAttrs) {
780 this.addAttrs(this._filterAdHocAttrs(attrCfgs, userVals), userVals, lazy);
783 this.addAttrs(instanceAttrs, userVals, lazy);
786 for (i = 0, l = initializers.length; i < l; i++) {
787 initializers[i].apply(this, arguments);
792 * Destroys the class hierarchy for this instance by invoking
793 * the destructor method on the prototype of each class in the hierarchy.
795 * @method _destroyHierarchy
798 _destroyHierarchy : function() {
801 ci, cl, ei, el, exts, extProto,
802 classes = this._getClasses();
804 for (ci = 0, cl = classes.length; ci < cl; ci++) {
805 constr = classes[ci];
806 constrProto = constr.prototype;
807 exts = constr._yuibuild && constr._yuibuild.exts;
810 for (ei = 0, el = exts.length; ei < el; ei++) {
811 extProto = exts[ei].prototype;
812 if (extProto.hasOwnProperty(DESTRUCTOR)) {
813 extProto.destructor.apply(this, arguments);
818 if (constrProto.hasOwnProperty(DESTRUCTOR)) {
819 constrProto.destructor.apply(this, arguments);
825 * Default toString implementation. Provides the constructor NAME
826 * and the instance guid, if set.
829 * @return {String} String representation for this object
831 toString: function() {
832 return this.name + "[" + Y.stamp(this, true) + "]";
836 // Straightup augment, no wrapper functions
837 Y.mix(BaseCore, AttributeCore, false, null, 1);
840 BaseCore.prototype.constructor = BaseCore;
842 Y.BaseCore = BaseCore;
845 }, '3.13.0', {"requires": ["attribute-core"]});