API Docs for: 3.10.3
Show:

File: attribute/js/AttributeCore.js

  1. /*For log lines*/
  2. /*jshint maxlen:200*/
  3.  
  4. /**
  5. * The attribute module provides an augmentable Attribute implementation, which
  6. * adds configurable attributes and attribute change events to the class being
  7. * augmented. It also provides a State class, which is used internally by Attribute,
  8. * but can also be used independently to provide a name/property/value data structure to
  9. * store state.
  10. *
  11. * @module attribute
  12. */
  13.  
  14. /**
  15. * The attribute-core submodule provides the lightest level of attribute handling support
  16. * without Attribute change events, or lesser used methods such as reset(), modifyAttrs(),
  17. * and removeAttr().
  18. *
  19. * @module attribute
  20. * @submodule attribute-core
  21. */
  22. var O = Y.Object,
  23. Lang = Y.Lang,
  24.  
  25. DOT = ".",
  26.  
  27. // Externally configurable props
  28. GETTER = "getter",
  29. SETTER = "setter",
  30. READ_ONLY = "readOnly",
  31. WRITE_ONCE = "writeOnce",
  32. INIT_ONLY = "initOnly",
  33. VALIDATOR = "validator",
  34. VALUE = "value",
  35. VALUE_FN = "valueFn",
  36. LAZY_ADD = "lazyAdd",
  37.  
  38. // Used for internal state management
  39. ADDED = "added",
  40. BYPASS_PROXY = "_bypassProxy",
  41. INIT_VALUE = "initValue",
  42. LAZY = "lazy",
  43.  
  44. INVALID_VALUE;
  45.  
  46. /**
  47. * <p>
  48. * AttributeCore provides the lightest level of configurable attribute support. It is designed to be
  49. * augmented on to a host class, and provides the host with the ability to configure
  50. * attributes to store and retrieve state, <strong>but without support for attribute change events</strong>.
  51. * </p>
  52. * <p>For example, attributes added to the host can be configured:</p>
  53. * <ul>
  54. * <li>As read only.</li>
  55. * <li>As write once.</li>
  56. * <li>With a setter function, which can be used to manipulate
  57. * values passed to Attribute's <a href="#method_set">set</a> method, before they are stored.</li>
  58. * <li>With a getter function, which can be used to manipulate stored values,
  59. * before they are returned by Attribute's <a href="#method_get">get</a> method.</li>
  60. * <li>With a validator function, to validate values before they are stored.</li>
  61. * </ul>
  62. *
  63. * <p>See the <a href="#method_addAttr">addAttr</a> method, for the complete set of configuration
  64. * options available for attributes.</p>
  65. *
  66. * <p>Object/Classes based on AttributeCore can augment <a href="AttributeObservable.html">AttributeObservable</a>
  67. * (with true for overwrite) and <a href="AttributeExtras.html">AttributeExtras</a> to add attribute event and
  68. * additional, less commonly used attribute methods, such as `modifyAttr`, `removeAttr` and `reset`.</p>
  69. *
  70. * @class AttributeCore
  71. * @param attrs {Object} The attributes to add during construction (passed through to <a href="#method_addAttrs">addAttrs</a>).
  72. * These can also be defined on the constructor being augmented with Attribute by defining the ATTRS property on the constructor.
  73. * @param values {Object} The initial attribute values to apply (passed through to <a href="#method_addAttrs">addAttrs</a>).
  74. * These are not merged/cloned. The caller is responsible for isolating user provided values if required.
  75. * @param lazy {boolean} Whether or not to add attributes lazily (passed through to <a href="#method_addAttrs">addAttrs</a>).
  76. */
  77. function AttributeCore(attrs, values, lazy) {
  78. // HACK: Fix #2531929
  79. // Complete hack, to make sure the first clone of a node value in IE doesn't doesn't hurt state - maintains 3.4.1 behavior.
  80. // Too late in the release cycle to do anything about the core problem.
  81. // The root issue is that cloning a Y.Node instance results in an object which barfs in IE, when you access it's properties (since 3.3.0).
  82. this._yuievt = null;
  83.  
  84. this._initAttrHost(attrs, values, lazy);
  85. }
  86.  
  87. /**
  88. * <p>The value to return from an attribute setter in order to prevent the set from going through.</p>
  89. *
  90. * <p>You can return this value from your setter if you wish to combine validator and setter
  91. * functionality into a single setter function, which either returns the massaged value to be stored or
  92. * AttributeCore.INVALID_VALUE to prevent invalid values from being stored.</p>
  93. *
  94. * @property INVALID_VALUE
  95. * @type Object
  96. * @static
  97. * @final
  98. */
  99. AttributeCore.INVALID_VALUE = {};
  100. INVALID_VALUE = AttributeCore.INVALID_VALUE;
  101.  
  102. /**
  103. * The list of properties which can be configured for
  104. * each attribute (e.g. setter, getter, writeOnce etc.).
  105. *
  106. * This property is used internally as a whitelist for faster
  107. * Y.mix operations.
  108. *
  109. * @property _ATTR_CFG
  110. * @type Array
  111. * @static
  112. * @protected
  113. */
  114. AttributeCore._ATTR_CFG = [SETTER, GETTER, VALIDATOR, VALUE, VALUE_FN, WRITE_ONCE, READ_ONLY, LAZY_ADD, BYPASS_PROXY];
  115.  
  116. /**
  117. * Utility method to protect an attribute configuration hash, by merging the
  118. * entire object and the individual attr config objects.
  119. *
  120. * @method protectAttrs
  121. * @static
  122. * @param {Object} attrs A hash of attribute to configuration object pairs.
  123. * @return {Object} A protected version of the `attrs` argument.
  124. */
  125. AttributeCore.protectAttrs = function (attrs) {
  126. if (attrs) {
  127. attrs = Y.merge(attrs);
  128. for (var attr in attrs) {
  129. if (attrs.hasOwnProperty(attr)) {
  130. attrs[attr] = Y.merge(attrs[attr]);
  131. }
  132. }
  133. }
  134.  
  135. return attrs;
  136. };
  137.  
  138. AttributeCore.prototype = {
  139.  
  140. /**
  141. * Constructor logic for attributes. Initializes the host state, and sets up the inital attributes passed to the
  142. * constructor.
  143. *
  144. * @method _initAttrHost
  145. * @param attrs {Object} The attributes to add during construction (passed through to <a href="#method_addAttrs">addAttrs</a>).
  146. * These can also be defined on the constructor being augmented with Attribute by defining the ATTRS property on the constructor.
  147. * @param values {Object} The initial attribute values to apply (passed through to <a href="#method_addAttrs">addAttrs</a>).
  148. * These are not merged/cloned. The caller is responsible for isolating user provided values if required.
  149. * @param lazy {boolean} Whether or not to add attributes lazily (passed through to <a href="#method_addAttrs">addAttrs</a>).
  150. * @private
  151. */
  152. _initAttrHost : function(attrs, values, lazy) {
  153. this._state = new Y.State();
  154. this._initAttrs(attrs, values, lazy);
  155. },
  156.  
  157. /**
  158. * <p>
  159. * Adds an attribute with the provided configuration to the host object.
  160. * </p>
  161. * <p>
  162. * The config argument object supports the following properties:
  163. * </p>
  164. *
  165. * <dl>
  166. * <dt>value &#60;Any&#62;</dt>
  167. * <dd>The initial value to set on the attribute</dd>
  168. *
  169. * <dt>valueFn &#60;Function | String&#62;</dt>
  170. * <dd>
  171. * <p>A function, which will return the initial value to set on the attribute. This is useful
  172. * for cases where the attribute configuration is defined statically, but needs to
  173. * reference the host instance ("this") to obtain an initial value. If both the value and valueFn properties are defined,
  174. * the value returned by the valueFn has precedence over the value property, unless it returns undefined, in which
  175. * case the value property is used.</p>
  176. *
  177. * <p>valueFn can also be set to a string, representing the name of the instance method to be used to retrieve the value.</p>
  178. * </dd>
  179. *
  180. * <dt>readOnly &#60;boolean&#62;</dt>
  181. * <dd>Whether or not the attribute is read only. Attributes having readOnly set to true
  182. * cannot be modified by invoking the set method.</dd>
  183. *
  184. * <dt>writeOnce &#60;boolean&#62; or &#60;string&#62;</dt>
  185. * <dd>
  186. * Whether or not the attribute is "write once". Attributes having writeOnce set to true,
  187. * can only have their values set once, be it through the default configuration,
  188. * constructor configuration arguments, or by invoking set.
  189. * <p>The writeOnce attribute can also be set to the string "initOnly",
  190. * in which case the attribute can only be set during initialization
  191. * (when used with Base, this means it can only be set during construction)</p>
  192. * </dd>
  193. *
  194. * <dt>setter &#60;Function | String&#62;</dt>
  195. * <dd>
  196. * <p>The setter function used to massage or normalize the value passed to the set method for the attribute.
  197. * The value returned by the setter will be the final stored value. Returning
  198. * <a href="#property_Attribute.INVALID_VALUE">Attribute.INVALID_VALUE</a>, from the setter will prevent
  199. * the value from being stored.
  200. * </p>
  201. *
  202. * <p>setter can also be set to a string, representing the name of the instance method to be used as the setter function.</p>
  203. * </dd>
  204. *
  205. * <dt>getter &#60;Function | String&#62;</dt>
  206. * <dd>
  207. * <p>
  208. * The getter function used to massage or normalize the value returned by the get method for the attribute.
  209. * The value returned by the getter function is the value which will be returned to the user when they
  210. * invoke get.
  211. * </p>
  212. *
  213. * <p>getter can also be set to a string, representing the name of the instance method to be used as the getter function.</p>
  214. * </dd>
  215. *
  216. * <dt>validator &#60;Function | String&#62;</dt>
  217. * <dd>
  218. * <p>
  219. * The validator function invoked prior to setting the stored value. Returning
  220. * false from the validator function will prevent the value from being stored.
  221. * </p>
  222. *
  223. * <p>validator can also be set to a string, representing the name of the instance method to be used as the validator function.</p>
  224. * </dd>
  225. *
  226. * <dt>lazyAdd &#60;boolean&#62;</dt>
  227. * <dd>Whether or not to delay initialization of the attribute until the first call to get/set it.
  228. * This flag can be used to over-ride lazy initialization on a per attribute basis, when adding multiple attributes through
  229. * the <a href="#method_addAttrs">addAttrs</a> method.</dd>
  230. *
  231. * </dl>
  232. *
  233. * <p>The setter, getter and validator are invoked with the value and name passed in as the first and second arguments, and with
  234. * the context ("this") set to the host object.</p>
  235. *
  236. * <p>Configuration properties outside of the list mentioned above are considered private properties used internally by attribute,
  237. * and are not intended for public use.</p>
  238. *
  239. * @method addAttr
  240. *
  241. * @param {String} name The name of the attribute.
  242. * @param {Object} config An object with attribute configuration property/value pairs, specifying the configuration for the attribute.
  243. *
  244. * <p>
  245. * <strong>NOTE:</strong> The configuration object is modified when adding an attribute, so if you need
  246. * to protect the original values, you will need to merge the object.
  247. * </p>
  248. *
  249. * @param {boolean} lazy (optional) Whether or not to add this attribute lazily (on the first call to get/set).
  250. *
  251. * @return {Object} A reference to the host object.
  252. *
  253. * @chainable
  254. */
  255. addAttr : function(name, config, lazy) {
  256.  
  257. Y.log('Adding attribute: ' + name, 'info', 'attribute');
  258.  
  259. var host = this, // help compression
  260. state = host._state,
  261. data = state.data,
  262. storedCfg = data[name],
  263. value,
  264. added,
  265. hasValue;
  266.  
  267. config = config || {};
  268.  
  269. if (LAZY_ADD in config) {
  270. lazy = config[LAZY_ADD];
  271. }
  272.  
  273. added = state.get(name, ADDED);
  274.  
  275. if (lazy && !added) {
  276.  
  277. // LAST MINUTE PRE 3.10.0 RELEASE WORKAROUND.
  278.  
  279. // Revisit for 3.11.0 with the planned change to add all attributes
  280. // at once, instead of filtering for each class which is the root
  281. // of the issue.
  282.  
  283. // This is to account for the case when an attribute value gets set,
  284. // before it's actual config is added formally. We end up blowing away
  285. // the previously set value in that case, with the config. Prior to
  286. // the performance fixes, we just used to merge, so we didn't see the
  287. // issue.
  288.  
  289. if (!storedCfg) {
  290. storedCfg = data[name] = {};
  291. }
  292.  
  293. storedCfg.lazy = config;
  294. storedCfg.added = true;
  295.  
  296. } else {
  297.  
  298. if (added && !config.isLazyAdd) { Y.log('Attribute: ' + name + ' already exists. Cannot add it again without removing it first', 'warn', 'attribute'); }
  299.  
  300. if (!added || config.isLazyAdd) {
  301.  
  302. hasValue = (VALUE in config);
  303.  
  304. if (config.readOnly && !hasValue) { Y.log('readOnly attribute: ' + name + ', added without an initial value. Value will be set on initial call to set', 'warn', 'attribute');}
  305.  
  306. if (hasValue) {
  307.  
  308. // We'll go through set, don't want to set value in config directly
  309.  
  310. // PERF TODO: VALIDATE: See if setting this to undefined is sufficient. We use to delete before.
  311. // In certain code paths/use cases, undefined may not be the same as not present.
  312. // If not, we can set it to some known fixed value (like INVALID_VALUE, say INITIALIZING_VALUE) for performance,
  313. // to avoid a delete which seems to help a lot.
  314.  
  315. value = config.value;
  316. config.value = undefined;
  317. } else {
  318. // LAST MINUTE PRE 3.10.0 RELEASE WORKAROUND.
  319. if (storedCfg && (VALUE in storedCfg)) {
  320. config.value = storedCfg.value;
  321. }
  322. }
  323.  
  324. config.added = true;
  325. config.initializing = true;
  326.  
  327. data[name] = config;
  328.  
  329. if (hasValue) {
  330. // Go through set, so that raw values get normalized/validated
  331. host.set(name, value);
  332. }
  333.  
  334. config.initializing = false;
  335. }
  336. }
  337.  
  338. return host;
  339. },
  340.  
  341. /**
  342. * Checks if the given attribute has been added to the host
  343. *
  344. * @method attrAdded
  345. * @param {String} name The name of the attribute to check.
  346. * @return {boolean} true if an attribute with the given name has been added, false if it hasn't.
  347. * This method will return true for lazily added attributes.
  348. */
  349. attrAdded: function(name) {
  350. return !!(this._state.get(name, ADDED));
  351. },
  352.  
  353. /**
  354. * Returns the current value of the attribute. If the attribute
  355. * has been configured with a 'getter' function, this method will delegate
  356. * to the 'getter' to obtain the value of the attribute.
  357. *
  358. * @method get
  359. *
  360. * @param {String} name The name of the attribute. If the value of the attribute is an Object,
  361. * dot notation can be used to obtain the value of a property of the object (e.g. <code>get("x.y.z")</code>)
  362. *
  363. * @return {Any} The value of the attribute
  364. */
  365. get : function(name) {
  366. return this._getAttr(name);
  367. },
  368.  
  369. /**
  370. * Checks whether or not the attribute is one which has been
  371. * added lazily and still requires initialization.
  372. *
  373. * @method _isLazyAttr
  374. * @private
  375. * @param {String} name The name of the attribute
  376. * @return {boolean} true if it's a lazily added attribute, false otherwise.
  377. */
  378. _isLazyAttr: function(name) {
  379. return this._state.get(name, LAZY);
  380. },
  381.  
  382. /**
  383. * Finishes initializing an attribute which has been lazily added.
  384. *
  385. * @method _addLazyAttr
  386. * @private
  387. * @param {Object} name The name of the attribute
  388. * @param {Object} [lazyCfg] Optional config hash for the attribute. This is added for performance
  389. * along the critical path, where the calling method has already obtained lazy config from state.
  390. */
  391. _addLazyAttr: function(name, lazyCfg) {
  392. var state = this._state;
  393.  
  394. lazyCfg = lazyCfg || state.get(name, LAZY);
  395.  
  396. if (lazyCfg) {
  397.  
  398. // PERF TODO: For App's id override, otherwise wouldn't be
  399. // needed. It expects to find it in the cfg for it's
  400. // addAttr override. Would like to remove, once App override is
  401. // removed.
  402. state.data[name].lazy = undefined;
  403.  
  404. lazyCfg.isLazyAdd = true;
  405.  
  406. this.addAttr(name, lazyCfg);
  407. }
  408. },
  409.  
  410. /**
  411. * Sets the value of an attribute.
  412. *
  413. * @method set
  414. * @chainable
  415. *
  416. * @param {String} name The name of the attribute. If the
  417. * current value of the attribute is an Object, dot notation can be used
  418. * to set the value of a property within the object (e.g. <code>set("x.y.z", 5)</code>).
  419. * @param {Any} value The value to set the attribute to.
  420. * @param {Object} [opts] Optional data providing the circumstances for the change.
  421. * @return {Object} A reference to the host object.
  422. */
  423. set : function(name, val, opts) {
  424. return this._setAttr(name, val, opts);
  425. },
  426.  
  427. /**
  428. * Allows setting of readOnly/writeOnce attributes. See <a href="#method_set">set</a> for argument details.
  429. *
  430. * @method _set
  431. * @protected
  432. * @chainable
  433. *
  434. * @param {String} name The name of the attribute.
  435. * @param {Any} val The value to set the attribute to.
  436. * @param {Object} [opts] Optional data providing the circumstances for the change.
  437. * @return {Object} A reference to the host object.
  438. */
  439. _set : function(name, val, opts) {
  440. return this._setAttr(name, val, opts, true);
  441. },
  442.  
  443. /**
  444. * Provides the common implementation for the public set and protected _set methods.
  445. *
  446. * See <a href="#method_set">set</a> for argument details.
  447. *
  448. * @method _setAttr
  449. * @protected
  450. * @chainable
  451. *
  452. * @param {String} name The name of the attribute.
  453. * @param {Any} value The value to set the attribute to.
  454. * @param {Object} [opts] Optional data providing the circumstances for the change.
  455. * @param {boolean} force If true, allows the caller to set values for
  456. * readOnly or writeOnce attributes which have already been set.
  457. *
  458. * @return {Object} A reference to the host object.
  459. */
  460. _setAttr : function(name, val, opts, force) {
  461. var allowSet = true,
  462. state = this._state,
  463. stateProxy = this._stateProxy,
  464. cfg,
  465. initialSet,
  466. strPath,
  467. path,
  468. currVal,
  469. writeOnce,
  470. initializing;
  471.  
  472. if (name.indexOf(DOT) !== -1) {
  473. strPath = name;
  474.  
  475. path = name.split(DOT);
  476. name = path.shift();
  477. }
  478.  
  479. cfg = state.data[name] || {};
  480.  
  481. if (cfg.lazy) {
  482. cfg = cfg.lazy;
  483. this._addLazyAttr(name, cfg);
  484. }
  485.  
  486. initialSet = (cfg.value === undefined);
  487.  
  488. if (stateProxy && name in stateProxy && !cfg._bypassProxy) {
  489. // TODO: Value is always set for proxy. Can we do any better? Maybe take a snapshot as the initial value for the first call to set?
  490. initialSet = false;
  491. }
  492.  
  493. writeOnce = cfg.writeOnce;
  494. initializing = cfg.initializing;
  495.  
  496. if (!initialSet && !force) {
  497.  
  498. if (writeOnce) {
  499. Y.log('Set attribute:' + name + ', aborted; Attribute is writeOnce', 'warn', 'attribute');
  500. allowSet = false;
  501. }
  502.  
  503. if (cfg.readOnly) {
  504. Y.log('Set attribute:' + name + ', aborted; Attribute is readOnly', 'warn', 'attribute');
  505. allowSet = false;
  506. }
  507. }
  508.  
  509. if (!initializing && !force && writeOnce === INIT_ONLY) {
  510. Y.log('Set attribute:' + name + ', aborted; Attribute is writeOnce: "initOnly"', 'warn', 'attribute');
  511. allowSet = false;
  512. }
  513.  
  514. if (allowSet) {
  515. // Don't need currVal if initialSet (might fail in custom getter if it always expects a non-undefined/non-null value)
  516. if (!initialSet) {
  517. currVal = this.get(name);
  518. }
  519.  
  520. if (path) {
  521. val = O.setValue(Y.clone(currVal), path, val);
  522.  
  523. if (val === undefined) {
  524. Y.log('Set attribute path:' + strPath + ', aborted; Path is invalid', 'warn', 'attribute');
  525. allowSet = false;
  526. }
  527. }
  528.  
  529. if (allowSet) {
  530. if (!this._fireAttrChange || initializing) {
  531. this._setAttrVal(name, strPath, currVal, val, opts, cfg);
  532. } else {
  533. // HACK - no real reason core needs to know about _fireAttrChange, but
  534. // it adds fn hops if we want to break it out. Not sure it's worth it for this critical path
  535. this._fireAttrChange(name, strPath, currVal, val, opts, cfg);
  536. }
  537. }
  538. }
  539.  
  540. return this;
  541. },
  542.  
  543. /**
  544. * Provides the common implementation for the public get method,
  545. * allowing Attribute hosts to over-ride either method.
  546. *
  547. * See <a href="#method_get">get</a> for argument details.
  548. *
  549. * @method _getAttr
  550. * @protected
  551. * @chainable
  552. *
  553. * @param {String} name The name of the attribute.
  554. * @return {Any} The value of the attribute.
  555. */
  556. _getAttr : function(name) {
  557. var fullName = name,
  558. tCfgs = this._tCfgs,
  559. path,
  560. getter,
  561. val,
  562. attrCfg,
  563. cfg;
  564.  
  565. if (name.indexOf(DOT) !== -1) {
  566. path = name.split(DOT);
  567. name = path.shift();
  568. }
  569.  
  570. // On Demand - Should be rare - handles out of order valueFn references
  571. if (tCfgs && tCfgs[name]) {
  572. cfg = {};
  573. cfg[name] = tCfgs[name];
  574. delete tCfgs[name];
  575. this._addAttrs(cfg, this._tVals);
  576. }
  577.  
  578. attrCfg = this._state.data[name] || {};
  579.  
  580. // Lazy Init
  581. if (attrCfg.lazy) {
  582. attrCfg = attrCfg.lazy;
  583. this._addLazyAttr(name, attrCfg);
  584. }
  585.  
  586. val = this._getStateVal(name, attrCfg);
  587.  
  588. getter = attrCfg.getter;
  589.  
  590. if (getter && !getter.call) {
  591. getter = this[getter];
  592. }
  593.  
  594. val = (getter) ? getter.call(this, val, fullName) : val;
  595. val = (path) ? O.getValue(val, path) : val;
  596.  
  597. return val;
  598. },
  599.  
  600. /**
  601. * Gets the stored value for the attribute, from either the
  602. * internal state object, or the state proxy if it exits
  603. *
  604. * @method _getStateVal
  605. * @private
  606. * @param {String} name The name of the attribute
  607. * @param {Object} [cfg] Optional config hash for the attribute. This is added for performance along the critical path,
  608. * where the calling method has already obtained the config from state.
  609. *
  610. * @return {Any} The stored value of the attribute
  611. */
  612. _getStateVal : function(name, cfg) {
  613. var stateProxy = this._stateProxy;
  614.  
  615. if (!cfg) {
  616. cfg = this._state.getAll(name) || {};
  617. }
  618.  
  619. return (stateProxy && (name in stateProxy) && !(cfg._bypassProxy)) ? stateProxy[name] : cfg.value;
  620. },
  621.  
  622. /**
  623. * Sets the stored value for the attribute, in either the
  624. * internal state object, or the state proxy if it exits
  625. *
  626. * @method _setStateVal
  627. * @private
  628. * @param {String} name The name of the attribute
  629. * @param {Any} value The value of the attribute
  630. */
  631. _setStateVal : function(name, value) {
  632. var stateProxy = this._stateProxy;
  633. if (stateProxy && (name in stateProxy) && !this._state.get(name, BYPASS_PROXY)) {
  634. stateProxy[name] = value;
  635. } else {
  636. this._state.add(name, VALUE, value);
  637. }
  638. },
  639.  
  640. /**
  641. * Updates the stored value of the attribute in the privately held State object,
  642. * if validation and setter passes.
  643. *
  644. * @method _setAttrVal
  645. * @private
  646. * @param {String} attrName The attribute name.
  647. * @param {String} subAttrName The sub-attribute name, if setting a sub-attribute property ("x.y.z").
  648. * @param {Any} prevVal The currently stored value of the attribute.
  649. * @param {Any} newVal The value which is going to be stored.
  650. * @param {Object} [opts] Optional data providing the circumstances for the change.
  651. * @param {Object} [attrCfg] Optional config hash for the attribute. This is added for performance along the critical path,
  652. * where the calling method has already obtained the config from state.
  653. *
  654. * @return {booolean} true if the new attribute value was stored, false if not.
  655. */
  656. _setAttrVal : function(attrName, subAttrName, prevVal, newVal, opts, attrCfg) {
  657.  
  658. var host = this,
  659. allowSet = true,
  660. cfg = attrCfg || this._state.data[attrName] || {},
  661. validator = cfg.validator,
  662. setter = cfg.setter,
  663. initializing = cfg.initializing,
  664. prevRawVal = this._getStateVal(attrName, cfg),
  665. name = subAttrName || attrName,
  666. retVal,
  667. valid;
  668.  
  669. if (validator) {
  670. if (!validator.call) {
  671. // Assume string - trying to keep critical path tight, so avoiding Lang check
  672. validator = this[validator];
  673. }
  674. if (validator) {
  675. valid = validator.call(host, newVal, name, opts);
  676.  
  677. if (!valid && initializing) {
  678. newVal = cfg.defaultValue;
  679. valid = true; // Assume it's valid, for perf.
  680. }
  681. }
  682. }
  683.  
  684. if (!validator || valid) {
  685. if (setter) {
  686. if (!setter.call) {
  687. // Assume string - trying to keep critical path tight, so avoiding Lang check
  688. setter = this[setter];
  689. }
  690. if (setter) {
  691. retVal = setter.call(host, newVal, name, opts);
  692.  
  693. if (retVal === INVALID_VALUE) {
  694. if (initializing) {
  695. Y.log('Attribute: ' + attrName + ', setter returned Attribute.INVALID_VALUE for value:' + newVal + ', initializing to default value', 'warn', 'attribute');
  696. newVal = cfg.defaultValue;
  697. } else {
  698. Y.log('Attribute: ' + attrName + ', setter returned Attribute.INVALID_VALUE for value:' + newVal, 'warn', 'attribute');
  699. allowSet = false;
  700. }
  701. } else if (retVal !== undefined){
  702. Y.log('Attribute: ' + attrName + ', raw value: ' + newVal + ' modified by setter to:' + retVal, 'info', 'attribute');
  703. newVal = retVal;
  704. }
  705. }
  706. }
  707.  
  708. if (allowSet) {
  709. if(!subAttrName && (newVal === prevRawVal) && !Lang.isObject(newVal)) {
  710. Y.log('Attribute: ' + attrName + ', value unchanged:' + newVal, 'warn', 'attribute');
  711. allowSet = false;
  712. } else {
  713. // Store value
  714. if (!(INIT_VALUE in cfg)) {
  715. cfg.initValue = newVal;
  716. }
  717. host._setStateVal(attrName, newVal);
  718. }
  719. }
  720.  
  721. } else {
  722. Y.log('Attribute:' + attrName + ', Validation failed for value:' + newVal, 'warn', 'attribute');
  723. allowSet = false;
  724. }
  725.  
  726. return allowSet;
  727. },
  728.  
  729. /**
  730. * Sets multiple attribute values.
  731. *
  732. * @method setAttrs
  733. * @param {Object} attrs An object with attributes name/value pairs.
  734. * @param {Object} [opts] Optional data providing the circumstances for the change.
  735. * @return {Object} A reference to the host object.
  736. * @chainable
  737. */
  738. setAttrs : function(attrs, opts) {
  739. return this._setAttrs(attrs, opts);
  740. },
  741.  
  742. /**
  743. * Implementation behind the public setAttrs method, to set multiple attribute values.
  744. *
  745. * @method _setAttrs
  746. * @protected
  747. * @param {Object} attrs An object with attributes name/value pairs.
  748. * @param {Object} [opts] Optional data providing the circumstances for the change
  749. * @return {Object} A reference to the host object.
  750. * @chainable
  751. */
  752. _setAttrs : function(attrs, opts) {
  753. var attr;
  754. for (attr in attrs) {
  755. if ( attrs.hasOwnProperty(attr) ) {
  756. this.set(attr, attrs[attr], opts);
  757. }
  758. }
  759. return this;
  760. },
  761.  
  762. /**
  763. * Gets multiple attribute values.
  764. *
  765. * @method getAttrs
  766. * @param {Array | boolean} attrs Optional. An array of attribute names. If omitted, all attribute values are
  767. * returned. If set to true, all attributes modified from their initial values are returned.
  768. * @return {Object} An object with attribute name/value pairs.
  769. */
  770. getAttrs : function(attrs) {
  771. return this._getAttrs(attrs);
  772. },
  773.  
  774. /**
  775. * Implementation behind the public getAttrs method, to get multiple attribute values.
  776. *
  777. * @method _getAttrs
  778. * @protected
  779. * @param {Array | boolean} attrs Optional. An array of attribute names. If omitted, all attribute values are
  780. * returned. If set to true, all attributes modified from their initial values are returned.
  781. * @return {Object} An object with attribute name/value pairs.
  782. */
  783. _getAttrs : function(attrs) {
  784. var obj = {},
  785. attr, i, len,
  786. modifiedOnly = (attrs === true);
  787.  
  788. // TODO - figure out how to get all "added"
  789. if (!attrs || modifiedOnly) {
  790. attrs = O.keys(this._state.data);
  791. }
  792.  
  793. for (i = 0, len = attrs.length; i < len; i++) {
  794. attr = attrs[i];
  795.  
  796. if (!modifiedOnly || this._getStateVal(attr) != this._state.get(attr, INIT_VALUE)) {
  797. // Go through get, to honor cloning/normalization
  798. obj[attr] = this.get(attr);
  799. }
  800. }
  801.  
  802. return obj;
  803. },
  804.  
  805. /**
  806. * Configures a group of attributes, and sets initial values.
  807. *
  808. * <p>
  809. * <strong>NOTE:</strong> This method does not isolate the configuration object by merging/cloning.
  810. * The caller is responsible for merging/cloning the configuration object if required.
  811. * </p>
  812. *
  813. * @method addAttrs
  814. * @chainable
  815. *
  816. * @param {Object} cfgs An object with attribute name/configuration pairs.
  817. * @param {Object} values An object with attribute name/value pairs, defining the initial values to apply.
  818. * Values defined in the cfgs argument will be over-written by values in this argument unless defined as read only.
  819. * @param {boolean} lazy Whether or not to delay the intialization of these attributes until the first call to get/set.
  820. * Individual attributes can over-ride this behavior by defining a lazyAdd configuration property in their configuration.
  821. * See <a href="#method_addAttr">addAttr</a>.
  822. *
  823. * @return {Object} A reference to the host object.
  824. */
  825. addAttrs : function(cfgs, values, lazy) {
  826. if (cfgs) {
  827. this._tCfgs = cfgs;
  828. this._tVals = (values) ? this._normAttrVals(values) : null;
  829. this._addAttrs(cfgs, this._tVals, lazy);
  830. this._tCfgs = this._tVals = null;
  831. }
  832.  
  833. return this;
  834. },
  835.  
  836. /**
  837. * Implementation behind the public addAttrs method.
  838. *
  839. * This method is invoked directly by get if it encounters a scenario
  840. * in which an attribute's valueFn attempts to obtain the
  841. * value an attribute in the same group of attributes, which has not yet
  842. * been added (on demand initialization).
  843. *
  844. * @method _addAttrs
  845. * @private
  846. * @param {Object} cfgs An object with attribute name/configuration pairs.
  847. * @param {Object} values An object with attribute name/value pairs, defining the initial values to apply.
  848. * Values defined in the cfgs argument will be over-written by values in this argument unless defined as read only.
  849. * @param {boolean} lazy Whether or not to delay the intialization of these attributes until the first call to get/set.
  850. * Individual attributes can over-ride this behavior by defining a lazyAdd configuration property in their configuration.
  851. * See <a href="#method_addAttr">addAttr</a>.
  852. */
  853. _addAttrs : function(cfgs, values, lazy) {
  854. var tCfgs = this._tCfgs,
  855. tVals = this._tVals,
  856. attr,
  857. attrCfg,
  858. value;
  859.  
  860. for (attr in cfgs) {
  861. if (cfgs.hasOwnProperty(attr)) {
  862.  
  863. // Not Merging. Caller is responsible for isolating configs
  864. attrCfg = cfgs[attr];
  865. attrCfg.defaultValue = attrCfg.value;
  866.  
  867. // Handle simple, complex and user values, accounting for read-only
  868. value = this._getAttrInitVal(attr, attrCfg, tVals);
  869.  
  870. if (value !== undefined) {
  871. attrCfg.value = value;
  872. }
  873.  
  874. if (tCfgs[attr]) {
  875. tCfgs[attr] = undefined;
  876. }
  877.  
  878. this.addAttr(attr, attrCfg, lazy);
  879. }
  880. }
  881. },
  882.  
  883. /**
  884. * Utility method to protect an attribute configuration
  885. * hash, by merging the entire object and the individual
  886. * attr config objects.
  887. *
  888. * @method _protectAttrs
  889. * @protected
  890. * @param {Object} attrs A hash of attribute to configuration object pairs.
  891. * @return {Object} A protected version of the attrs argument.
  892. * @deprecated Use `AttributeCore.protectAttrs()` or
  893. * `Attribute.protectAttrs()` which are the same static utility method.
  894. */
  895. _protectAttrs : AttributeCore.protectAttrs,
  896.  
  897. /**
  898. * Utility method to normalize attribute values. The base implementation
  899. * simply merges the hash to protect the original.
  900. *
  901. * @method _normAttrVals
  902. * @param {Object} valueHash An object with attribute name/value pairs
  903. *
  904. * @return {Object} An object literal with 2 properties - "simple" and "complex",
  905. * containing simple and complex attribute values respectively keyed
  906. * by the top level attribute name, or null, if valueHash is falsey.
  907. *
  908. * @private
  909. */
  910. _normAttrVals : function(valueHash) {
  911. var vals,
  912. subvals,
  913. path,
  914. attr,
  915. v, k;
  916.  
  917. if (!valueHash) {
  918. return null;
  919. }
  920.  
  921. vals = {};
  922.  
  923. for (k in valueHash) {
  924. if (valueHash.hasOwnProperty(k)) {
  925. if (k.indexOf(DOT) !== -1) {
  926. path = k.split(DOT);
  927. attr = path.shift();
  928.  
  929. subvals = subvals || {};
  930.  
  931. v = subvals[attr] = subvals[attr] || [];
  932. v[v.length] = {
  933. path : path,
  934. value: valueHash[k]
  935. };
  936. } else {
  937. vals[k] = valueHash[k];
  938. }
  939. }
  940. }
  941.  
  942. return { simple:vals, complex:subvals };
  943. },
  944.  
  945. /**
  946. * Returns the initial value of the given attribute from
  947. * either the default configuration provided, or the
  948. * over-ridden value if it exists in the set of initValues
  949. * provided and the attribute is not read-only.
  950. *
  951. * @param {String} attr The name of the attribute
  952. * @param {Object} cfg The attribute configuration object
  953. * @param {Object} initValues The object with simple and complex attribute name/value pairs returned from _normAttrVals
  954. *
  955. * @return {Any} The initial value of the attribute.
  956. *
  957. * @method _getAttrInitVal
  958. * @private
  959. */
  960. _getAttrInitVal : function(attr, cfg, initValues) {
  961. var val = cfg.value,
  962. valFn = cfg.valueFn,
  963. tmpVal,
  964. initValSet = false,
  965. readOnly = cfg.readOnly,
  966. simple,
  967. complex,
  968. i,
  969. l,
  970. path,
  971. subval,
  972. subvals;
  973.  
  974. if (!readOnly && initValues) {
  975. // Simple Attributes
  976. simple = initValues.simple;
  977. if (simple && simple.hasOwnProperty(attr)) {
  978. val = simple[attr];
  979. initValSet = true;
  980. }
  981. }
  982.  
  983. if (valFn && !initValSet) {
  984. if (!valFn.call) {
  985. valFn = this[valFn];
  986. }
  987. if (valFn) {
  988. tmpVal = valFn.call(this, attr);
  989. val = tmpVal;
  990. }
  991. }
  992.  
  993. if (!readOnly && initValues) {
  994.  
  995. // Complex Attributes (complex values applied, after simple, in case both are set)
  996. complex = initValues.complex;
  997.  
  998. if (complex && complex.hasOwnProperty(attr) && (val !== undefined) && (val !== null)) {
  999. subvals = complex[attr];
  1000. for (i = 0, l = subvals.length; i < l; ++i) {
  1001. path = subvals[i].path;
  1002. subval = subvals[i].value;
  1003. O.setValue(val, path, subval);
  1004. }
  1005. }
  1006. }
  1007.  
  1008. return val;
  1009. },
  1010.  
  1011. /**
  1012. * Utility method to set up initial attributes defined during construction,
  1013. * either through the constructor.ATTRS property, or explicitly passed in.
  1014. *
  1015. * @method _initAttrs
  1016. * @protected
  1017. * @param attrs {Object} The attributes to add during construction (passed through to <a href="#method_addAttrs">addAttrs</a>).
  1018. * These can also be defined on the constructor being augmented with Attribute by defining the ATTRS property on the constructor.
  1019. * @param values {Object} The initial attribute values to apply (passed through to <a href="#method_addAttrs">addAttrs</a>).
  1020. * These are not merged/cloned. The caller is responsible for isolating user provided values if required.
  1021. * @param lazy {boolean} Whether or not to add attributes lazily (passed through to <a href="#method_addAttrs">addAttrs</a>).
  1022. */
  1023. _initAttrs : function(attrs, values, lazy) {
  1024. // ATTRS support for Node, which is not Base based
  1025. attrs = attrs || this.constructor.ATTRS;
  1026.  
  1027. var Base = Y.Base,
  1028. BaseCore = Y.BaseCore,
  1029. baseInst = (Base && Y.instanceOf(this, Base)),
  1030. baseCoreInst = (!baseInst && BaseCore && Y.instanceOf(this, BaseCore));
  1031.  
  1032. if (attrs && !baseInst && !baseCoreInst) {
  1033. this.addAttrs(Y.AttributeCore.protectAttrs(attrs), values, lazy);
  1034. }
  1035. }
  1036. };
  1037.  
  1038. Y.AttributeCore = AttributeCore;
  1039.