]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/javascript/yui3/build/attribute/attribute-base.js
Release 6.5.0
[Github/sugarcrm.git] / jssource / src_files / include / javascript / yui3 / build / attribute / attribute-base.js
1 /*
2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
5 version: 3.3.0
6 build: 3167
7 */
8 YUI.add('attribute-base', function(Y) {
9
10     /**
11      * The State class maintains state for a collection of named items, with 
12      * a varying number of properties defined.
13      *
14      * It avoids the need to create a separate class for the item, and separate instances 
15      * of these classes for each item, by storing the state in a 2 level hash table, 
16      * improving performance when the number of items is likely to be large.
17      *
18      * @constructor
19      * @class State
20      */
21     Y.State = function() { 
22         /**
23          * Hash of attributes
24          * @property data
25          */
26         this.data = {};
27     };
28
29     Y.State.prototype = {
30
31         /**
32          * Adds a property to an item.
33          *
34          * @method add
35          * @param name {String} The name of the item.
36          * @param key {String} The name of the property.
37          * @param val {Any} The value of the property.
38          */
39         add : function(name, key, val) {
40             var d = this.data;
41             d[key] = d[key] || {};
42             d[key][name] = val;
43         },
44
45         /**
46          * Adds multiple properties to an item.
47          *
48          * @method addAll
49          * @param name {String} The name of the item.
50          * @param o {Object} A hash of property/value pairs.
51          */
52         addAll: function(name, o) {
53             var key;
54             for (key in o) {
55                 if (o.hasOwnProperty(key)) {
56                     this.add(name, key, o[key]);
57                 }
58             }
59         },
60
61         /**
62          * Removes a property from an item.
63          *
64          * @method remove
65          * @param name {String} The name of the item.
66          * @param key {String} The property to remove.
67          */
68         remove: function(name, key) {
69             var d = this.data;
70             if (d[key] && (name in d[key])) {
71                 delete d[key][name];
72             }
73         },
74
75         /**
76          * Removes multiple properties from an item, or remove the item completely.
77          *
78          * @method removeAll
79          * @param name {String} The name of the item.
80          * @param o {Object|Array} Collection of properties to delete. If not provided, the entire item is removed.
81          */
82         removeAll: function(name, o) {
83             var d = this.data;
84
85             Y.each(o || d, function(v, k) {
86                 if(Y.Lang.isString(k)) {
87                     this.remove(name, k);
88                 } else {
89                     this.remove(name, v);
90                 }
91             }, this);
92         },
93
94         /**
95          * For a given item, returns the value of the property requested, or undefined if not found.
96          *
97          * @method get
98          * @param name {String} The name of the item
99          * @param key {String} Optional. The property value to retrieve.
100          * @return {Any} The value of the supplied property.
101          */
102         get: function(name, key) {
103             var d = this.data;
104             return (d[key] && name in d[key]) ?  d[key][name] : undefined;
105         },
106
107         /**
108          * For the given item, returns a disposable object with all of the
109          * item's property/value pairs.
110          *
111          * @method getAll
112          * @param name {String} The name of the item
113          * @return {Object} An object with property/value pairs for the item.
114          */
115         getAll : function(name) {
116             var d = this.data, o;
117
118             Y.each(d, function(v, k) {
119                 if (name in d[k]) {
120                     o = o || {};
121                     o[k] = v[name];
122                 }
123             }, this);
124
125             return o;
126         }
127     };
128     /**
129      * The attribute module provides an augmentable Attribute implementation, which 
130      * adds configurable attributes and attribute change events to the class being 
131      * augmented. It also provides a State class, which is used internally by Attribute,
132      * but can also be used independently to provide a name/property/value data structure to
133      * store state.
134      *
135      * @module attribute
136      */
137
138     /**
139      * The attribute-base submodule provides core attribute handling support, with everything
140      * aside from complex attribute handling in the provider's constructor.
141      *
142      * @module attribute
143      * @submodule attribute-base
144      */
145     var O = Y.Object,
146         Lang = Y.Lang,
147         EventTarget = Y.EventTarget,
148
149         DOT = ".",
150         CHANGE = "Change",
151
152         // Externally configurable props
153         GETTER = "getter",
154         SETTER = "setter",
155         READ_ONLY = "readOnly",
156         WRITE_ONCE = "writeOnce",
157         INIT_ONLY = "initOnly",
158         VALIDATOR = "validator",
159         VALUE = "value",
160         VALUE_FN = "valueFn",
161         BROADCAST = "broadcast",
162         LAZY_ADD = "lazyAdd",
163         BYPASS_PROXY = "_bypassProxy",
164
165         // Used for internal state management
166         ADDED = "added",
167         INITIALIZING = "initializing",
168         INIT_VALUE = "initValue",
169         PUBLISHED = "published",
170         DEF_VALUE = "defaultValue",
171         LAZY = "lazy",
172         IS_LAZY_ADD = "isLazyAdd",
173
174         INVALID_VALUE,
175
176         MODIFIABLE = {};
177
178         // Properties which can be changed after the attribute has been added.
179         MODIFIABLE[READ_ONLY] = 1;
180         MODIFIABLE[WRITE_ONCE] = 1;
181         MODIFIABLE[GETTER] = 1;
182         MODIFIABLE[BROADCAST] = 1;
183
184     /**
185      * <p>
186      * Attribute provides configurable attribute support along with attribute change events. It is designed to be 
187      * augmented on to a host class, and provides the host with the ability to configure attributes to store and retrieve state, 
188      * along with attribute change events.
189      * </p>
190      * <p>For example, attributes added to the host can be configured:</p>
191      * <ul>
192      *     <li>As read only.</li>
193      *     <li>As write once.</li>
194      *     <li>With a setter function, which can be used to manipulate
195      *     values passed to Attribute's <a href="#method_set">set</a> method, before they are stored.</li>
196      *     <li>With a getter function, which can be used to manipulate stored values,
197      *     before they are returned by Attribute's <a href="#method_get">get</a> method.</li>
198      *     <li>With a validator function, to validate values before they are stored.</li>
199      * </ul>
200      *
201      * <p>See the <a href="#method_addAttr">addAttr</a> method, for the complete set of configuration
202      * options available for attributes</p>.
203      *
204      * <p><strong>NOTE:</strong> Most implementations will be better off extending the <a href="Base.html">Base</a> class, 
205      * instead of augmenting Attribute directly. Base augments Attribute and will handle the initial configuration 
206      * of attributes for derived classes, accounting for values passed into the constructor.</p>
207      *
208      * @class Attribute
209      * @uses EventTarget
210      */
211     function Attribute() {
212
213         var host = this, // help compression
214             attrs = this.constructor.ATTRS,
215             Base = Y.Base;
216
217         // Perf tweak - avoid creating event literals if not required.
218         host._ATTR_E_FACADE = {};
219
220         EventTarget.call(host, {emitFacade:true});
221
222         // _conf maintained for backwards compat
223         host._conf = host._state = new Y.State();
224
225         host._stateProxy = host._stateProxy || null;
226         host._requireAddAttr = host._requireAddAttr || false;
227
228         // ATTRS support for Node, which is not Base based
229         if ( attrs && !(Base && Y.instanceOf(host, Base))) {
230             host.addAttrs(this._protectAttrs(attrs));
231         }
232     }
233
234     /**
235      * <p>The value to return from an attribute setter in order to prevent the set from going through.</p>
236      *
237      * <p>You can return this value from your setter if you wish to combine validator and setter 
238      * functionality into a single setter function, which either returns the massaged value to be stored or 
239      * Attribute.INVALID_VALUE to prevent invalid values from being stored.</p>
240      *
241      * @property Attribute.INVALID_VALUE
242      * @type Object
243      * @static
244      * @final
245      */
246     Attribute.INVALID_VALUE = {};
247     INVALID_VALUE = Attribute.INVALID_VALUE;
248
249     /**
250      * The list of properties which can be configured for 
251      * each attribute (e.g. setter, getter, writeOnce etc.).
252      *
253      * This property is used internally as a whitelist for faster
254      * Y.mix operations.
255      *
256      * @property Attribute._ATTR_CFG
257      * @type Array
258      * @static
259      * @protected
260      */
261     Attribute._ATTR_CFG = [SETTER, GETTER, VALIDATOR, VALUE, VALUE_FN, WRITE_ONCE, READ_ONLY, LAZY_ADD, BROADCAST, BYPASS_PROXY];
262
263     Attribute.prototype = {
264         /**
265          * <p>
266          * Adds an attribute with the provided configuration to the host object.
267          * </p>
268          * <p>
269          * The config argument object supports the following properties:
270          * </p>
271          * 
272          * <dl>
273          *    <dt>value &#60;Any&#62;</dt>
274          *    <dd>The initial value to set on the attribute</dd>
275          *
276          *    <dt>valueFn &#60;Function | String&#62;</dt>
277          *    <dd>
278          *    <p>A function, which will return the initial value to set on the attribute. This is useful
279          *    for cases where the attribute configuration is defined statically, but needs to 
280          *    reference the host instance ("this") to obtain an initial value. If both the value and valueFn properties are defined, 
281          *    the value returned by the valueFn has precedence over the value property, unless it returns undefined, in which 
282          *    case the value property is used.</p>
283          *
284          *    <p>valueFn can also be set to a string, representing the name of the instance method to be used to retrieve the value.</p>
285          *    </dd>
286          *
287          *    <dt>readOnly &#60;boolean&#62;</dt>
288          *    <dd>Whether or not the attribute is read only. Attributes having readOnly set to true
289          *        cannot be modified by invoking the set method.</dd>
290          *
291          *    <dt>writeOnce &#60;boolean&#62; or &#60;string&#62;</dt>
292          *    <dd>
293          *        Whether or not the attribute is "write once". Attributes having writeOnce set to true, 
294          *        can only have their values set once, be it through the default configuration, 
295          *        constructor configuration arguments, or by invoking set.
296          *        <p>The writeOnce attribute can also be set to the string "initOnly", in which case the attribute can only be set during initialization
297          *        (when used with Base, this means it can only be set during construction)</p>
298          *    </dd>
299          *
300          *    <dt>setter &#60;Function | String&#62;</dt>
301          *    <dd>
302          *    <p>The setter function used to massage or normalize the value passed to the set method for the attribute. 
303          *    The value returned by the setter will be the final stored value. Returning
304          *    <a href="#property_Attribute.INVALID_VALUE">Attribute.INVALID_VALUE</a>, from the setter will prevent
305          *    the value from being stored.
306          *    </p>
307          *    
308          *    <p>setter can also be set to a string, representing the name of the instance method to be used as the setter function.</p>
309          *    </dd>
310          *      
311          *    <dt>getter &#60;Function | String&#62;</dt>
312          *    <dd>
313          *    <p>
314          *    The getter function used to massage or normalize the value returned by the get method for the attribute.
315          *    The value returned by the getter function is the value which will be returned to the user when they 
316          *    invoke get.
317          *    </p>
318          *
319          *    <p>getter can also be set to a string, representing the name of the instance method to be used as the getter function.</p>
320          *    </dd>
321          *
322          *    <dt>validator &#60;Function | String&#62;</dt>
323          *    <dd>
324          *    <p>
325          *    The validator function invoked prior to setting the stored value. Returning
326          *    false from the validator function will prevent the value from being stored.
327          *    </p>
328          *    
329          *    <p>validator can also be set to a string, representing the name of the instance method to be used as the validator function.</p>
330          *    </dd>
331          *    
332          *    <dt>broadcast &#60;int&#62;</dt>
333          *    <dd>If and how attribute change events for this attribute should be broadcast. See CustomEvent's <a href="CustomEvent.html#property_broadcast">broadcast</a> property for 
334          *    valid values. By default attribute change events are not broadcast.</dd>
335          *
336          *    <dt>lazyAdd &#60;boolean&#62;</dt>
337          *    <dd>Whether or not to delay initialization of the attribute until the first call to get/set it. 
338          *    This flag can be used to over-ride lazy initialization on a per attribute basis, when adding multiple attributes through 
339          *    the <a href="#method_addAttrs">addAttrs</a> method.</dd>
340          *
341          * </dl>
342          *
343          * <p>The setter, getter and validator are invoked with the value and name passed in as the first and second arguments, and with
344          * the context ("this") set to the host object.</p>
345          *
346          * <p>Configuration properties outside of the list mentioned above are considered private properties used internally by attribute, and are not intended for public use.</p>
347          * 
348          * @method addAttr
349          *
350          * @param {String} name The name of the attribute.
351          * @param {Object} config An object with attribute configuration property/value pairs, specifying the configuration for the attribute.
352          *
353          * <p>
354          * <strong>NOTE:</strong> The configuration object is modified when adding an attribute, so if you need 
355          * to protect the original values, you will need to merge the object.
356          * </p>
357          *
358          * @param {boolean} lazy (optional) Whether or not to add this attribute lazily (on the first call to get/set). 
359          *
360          * @return {Object} A reference to the host object.
361          *
362          * @chainable
363          */
364         addAttr: function(name, config, lazy) {
365
366
367             var host = this, // help compression
368                 state = host._state,
369                 value,
370                 hasValue;
371
372             lazy = (LAZY_ADD in config) ? config[LAZY_ADD] : lazy;
373
374             if (lazy && !host.attrAdded(name)) {
375                 state.add(name, LAZY, config || {});
376                 state.add(name, ADDED, true);
377             } else {
378
379
380                 if (!host.attrAdded(name) || state.get(name, IS_LAZY_ADD)) {
381
382                     config = config || {};
383
384                     hasValue = (VALUE in config);
385
386
387                     if(hasValue) {
388                         // We'll go through set, don't want to set value in config directly
389                         value = config.value;
390                         delete config.value;
391                     }
392
393                     config.added = true;
394                     config.initializing = true;
395
396                     state.addAll(name, config);
397
398                     if (hasValue) {
399                         // Go through set, so that raw values get normalized/validated
400                         host.set(name, value);
401                     }
402
403                     state.remove(name, INITIALIZING);
404                 }
405             }
406
407             return host;
408         },
409
410         /**
411          * Checks if the given attribute has been added to the host
412          *
413          * @method attrAdded
414          * @param {String} name The name of the attribute to check.
415          * @return {boolean} true if an attribute with the given name has been added, false if it hasn't. This method will return true for lazily added attributes.
416          */
417         attrAdded: function(name) {
418             return !!this._state.get(name, ADDED);
419         },
420
421         /**
422          * Updates the configuration of an attribute which has already been added.
423          * <p>
424          * The properties which can be modified through this interface are limited
425          * to the following subset of attributes, which can be safely modified
426          * after a value has already been set on the attribute: readOnly, writeOnce, 
427          * broadcast and getter.
428          * </p>
429          * @method modifyAttr
430          * @param {String} name The name of the attribute whose configuration is to be updated.
431          * @param {Object} config An object with configuration property/value pairs, specifying the configuration properties to modify.
432          */
433         modifyAttr: function(name, config) {
434             var host = this, // help compression
435                 prop, state;
436
437             if (host.attrAdded(name)) {
438
439                 if (host._isLazyAttr(name)) {
440                     host._addLazyAttr(name);
441                 }
442
443                 state = host._state;
444                 for (prop in config) {
445                     if (MODIFIABLE[prop] && config.hasOwnProperty(prop)) {
446                         state.add(name, prop, config[prop]);
447
448                         // If we reconfigured broadcast, need to republish
449                         if (prop === BROADCAST) {
450                             state.remove(name, PUBLISHED);
451                         }
452                     }
453                 }
454             }
455
456         },
457
458         /**
459          * Removes an attribute from the host object
460          *
461          * @method removeAttr
462          * @param {String} name The name of the attribute to be removed.
463          */
464         removeAttr: function(name) {
465             this._state.removeAll(name);
466         },
467
468         /**
469          * Returns the current value of the attribute. If the attribute
470          * has been configured with a 'getter' function, this method will delegate
471          * to the 'getter' to obtain the value of the attribute.
472          *
473          * @method get
474          *
475          * @param {String} name The name of the attribute. If the value of the attribute is an Object, 
476          * dot notation can be used to obtain the value of a property of the object (e.g. <code>get("x.y.z")</code>)
477          *
478          * @return {Any} The value of the attribute
479          */
480         get : function(name) {
481             return this._getAttr(name);
482         },
483
484         /**
485          * Checks whether or not the attribute is one which has been
486          * added lazily and still requires initialization.
487          *
488          * @method _isLazyAttr
489          * @private
490          * @param {String} name The name of the attribute
491          * @return {boolean} true if it's a lazily added attribute, false otherwise.
492          */
493         _isLazyAttr: function(name) {
494             return this._state.get(name, LAZY);
495         },
496
497         /**
498          * Finishes initializing an attribute which has been lazily added.
499          *
500          * @method _addLazyAttr
501          * @private
502          * @param {Object} name The name of the attribute
503          */
504         _addLazyAttr: function(name) {
505             var state = this._state,
506                 lazyCfg = state.get(name, LAZY);
507
508             state.add(name, IS_LAZY_ADD, true);
509             state.remove(name, LAZY);
510             this.addAttr(name, lazyCfg);
511         },
512
513         /**
514          * Sets the value of an attribute.
515          *
516          * @method set
517          * @chainable
518          *
519          * @param {String} name The name of the attribute. If the 
520          * current value of the attribute is an Object, dot notation can be used
521          * to set the value of a property within the object (e.g. <code>set("x.y.z", 5)</code>).
522          *
523          * @param {Any} value The value to set the attribute to.
524          *
525          * @param {Object} opts (Optional) Optional event data to be mixed into
526          * the event facade passed to subscribers of the attribute's change event. This 
527          * can be used as a flexible way to identify the source of a call to set, allowing 
528          * the developer to distinguish between set called internally by the host, vs. 
529          * set called externally by the application developer.
530          *
531          * @return {Object} A reference to the host object.
532          */
533         set : function(name, val, opts) {
534             return this._setAttr(name, val, opts);
535         },
536
537         /**
538          * Resets the attribute (or all attributes) to its initial value, as long as
539          * the attribute is not readOnly, or writeOnce.
540          *
541          * @method reset
542          * @param {String} name Optional. The name of the attribute to reset.  If omitted, all attributes are reset.
543          * @return {Object} A reference to the host object.
544          * @chainable
545          */
546         reset : function(name) {
547             var host = this,  // help compression
548                 added;
549
550             if (name) {
551                 if (host._isLazyAttr(name)) {
552                     host._addLazyAttr(name);
553                 }
554                 host.set(name, host._state.get(name, INIT_VALUE));
555             } else {
556                 added = host._state.data.added;
557                 Y.each(added, function(v, n) {
558                     host.reset(n);
559                 }, host);
560             }
561             return host;
562         },
563
564         /**
565          * Allows setting of readOnly/writeOnce attributes. See <a href="#method_set">set</a> for argument details.
566          *
567          * @method _set
568          * @protected
569          * @chainable
570          * 
571          * @param {String} name The name of the attribute.
572          * @param {Any} val The value to set the attribute to.
573          * @param {Object} opts (Optional) Optional event data to be mixed into
574          * the event facade passed to subscribers of the attribute's change event.
575          * @return {Object} A reference to the host object.
576          */
577         _set : function(name, val, opts) {
578             return this._setAttr(name, val, opts, true);
579         },
580
581         /**
582          * Provides the common implementation for the public get method,
583          * allowing Attribute hosts to over-ride either method.
584          *
585          * See <a href="#method_get">get</a> for argument details.
586          *
587          * @method _getAttr
588          * @protected
589          * @chainable
590          *
591          * @param {String} name The name of the attribute.
592          * @return {Any} The value of the attribute.
593          */
594         _getAttr : function(name) {
595             var host = this, // help compression
596                 fullName = name,
597                 state = host._state,
598                 path,
599                 getter,
600                 val,
601                 cfg;
602
603             if (name.indexOf(DOT) !== -1) {
604                 path = name.split(DOT);
605                 name = path.shift();
606             }
607
608             // On Demand - Should be rare - handles out of order valueFn references
609             if (host._tCfgs && host._tCfgs[name]) {
610                 cfg = {};
611                 cfg[name] = host._tCfgs[name];
612                 delete host._tCfgs[name];
613                 host._addAttrs(cfg, host._tVals);
614             }
615
616             // Lazy Init
617             if (host._isLazyAttr(name)) {
618                 host._addLazyAttr(name);
619             }
620
621             val = host._getStateVal(name);
622             getter = state.get(name, GETTER);
623
624             if (getter && !getter.call) {
625                 getter = this[getter];
626             }
627
628             val = (getter) ? getter.call(host, val, fullName) : val;
629             val = (path) ? O.getValue(val, path) : val;
630
631             return val;
632         },
633
634         /**
635          * Provides the common implementation for the public set and protected _set methods.
636          *
637          * See <a href="#method_set">set</a> for argument details.
638          *
639          * @method _setAttr
640          * @protected
641          * @chainable
642          *
643          * @param {String} name The name of the attribute.
644          * @param {Any} value The value to set the attribute to.
645          * @param {Object} opts (Optional) Optional event data to be mixed into
646          * the event facade passed to subscribers of the attribute's change event.
647          * @param {boolean} force If true, allows the caller to set values for 
648          * readOnly or writeOnce attributes which have already been set.
649          *
650          * @return {Object} A reference to the host object.
651          */
652         _setAttr : function(name, val, opts, force) {
653             var allowSet = true,
654                 state = this._state,
655                 stateProxy = this._stateProxy,
656                 data = state.data,
657                 initialSet,
658                 strPath,
659                 path,
660                 currVal,
661                 writeOnce,
662                 initializing;
663
664             if (name.indexOf(DOT) !== -1) {
665                 strPath = name;
666                 path = name.split(DOT);
667                 name = path.shift();
668             }
669
670             if (this._isLazyAttr(name)) {
671                 this._addLazyAttr(name);
672             }
673
674             initialSet = (!data.value || !(name in data.value));
675
676             if (stateProxy && name in stateProxy && !this._state.get(name, BYPASS_PROXY)) {
677                 // 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? 
678                 initialSet = false;
679             }
680
681             if (this._requireAddAttr && !this.attrAdded(name)) {
682             } else {
683
684                 writeOnce = state.get(name, WRITE_ONCE);
685                 initializing = state.get(name, INITIALIZING);
686
687                 if (!initialSet && !force) {
688
689                     if (writeOnce) {
690                         allowSet = false;
691                     }
692
693                     if (state.get(name, READ_ONLY)) {
694                         allowSet = false;
695                     }
696                 }
697
698                 if (!initializing && !force && writeOnce === INIT_ONLY) {
699                     allowSet = false;
700                 }
701
702                 if (allowSet) {
703                     // Don't need currVal if initialSet (might fail in custom getter if it always expects a non-undefined/non-null value)
704                     if (!initialSet) {
705                         currVal =  this.get(name);
706                     }
707
708                     if (path) {
709                        val = O.setValue(Y.clone(currVal), path, val);
710
711                        if (val === undefined) {
712                            allowSet = false;
713                        }
714                     }
715
716                     if (allowSet) {
717                         if (initializing) {
718                             this._setAttrVal(name, strPath, currVal, val);
719                         } else {
720                             this._fireAttrChange(name, strPath, currVal, val, opts);
721                         }
722                     }
723                 }
724             }
725
726             return this;
727         },
728
729         /**
730          * Utility method to help setup the event payload and fire the attribute change event.
731          * 
732          * @method _fireAttrChange
733          * @private
734          * @param {String} attrName The name of the attribute
735          * @param {String} subAttrName The full path of the property being changed, 
736          * if this is a sub-attribute value being change. Otherwise null.
737          * @param {Any} currVal The current value of the attribute
738          * @param {Any} newVal The new value of the attribute
739          * @param {Object} opts Any additional event data to mix into the attribute change event's event facade.
740          */
741         _fireAttrChange : function(attrName, subAttrName, currVal, newVal, opts) {
742             var host = this,
743                 eventName = attrName + CHANGE,
744                 state = host._state,
745                 facade;
746
747             if (!state.get(attrName, PUBLISHED)) {
748                 host.publish(eventName, {
749                     queuable:false,
750                     defaultTargetOnly: true, 
751                     defaultFn:host._defAttrChangeFn, 
752                     silent:true,
753                     broadcast : state.get(attrName, BROADCAST)
754                 });
755                 state.add(attrName, PUBLISHED, true);
756             }
757
758             facade = (opts) ? Y.merge(opts) : host._ATTR_E_FACADE;
759
760             // Not using the single object signature for fire({type:..., newVal:...}), since 
761             // we don't want to override type. Changed to the fire(type, {newVal:...}) signature.
762
763             // facade.type = eventName;
764             facade.attrName = attrName;
765             facade.subAttrName = subAttrName;
766             facade.prevVal = currVal;
767             facade.newVal = newVal;
768
769             // host.fire(facade);
770             host.fire(eventName, facade);
771         },
772
773         /**
774          * Default function for attribute change events.
775          *
776          * @private
777          * @method _defAttrChangeFn
778          * @param {EventFacade} e The event object for attribute change events.
779          */
780         _defAttrChangeFn : function(e) {
781             if (!this._setAttrVal(e.attrName, e.subAttrName, e.prevVal, e.newVal)) {
782                 // Prevent "after" listeners from being invoked since nothing changed.
783                 e.stopImmediatePropagation();
784             } else {
785                 e.newVal = this.get(e.attrName);
786             }
787         },
788
789         /**
790          * Gets the stored value for the attribute, from either the 
791          * internal state object, or the state proxy if it exits
792          * 
793          * @method _getStateVal
794          * @private
795          * @param {String} name The name of the attribute
796          * @return {Any} The stored value of the attribute
797          */
798         _getStateVal : function(name) {
799             var stateProxy = this._stateProxy;
800             return stateProxy && (name in stateProxy) && !this._state.get(name, BYPASS_PROXY) ? stateProxy[name] : this._state.get(name, VALUE);
801         },
802
803         /**
804          * Sets the stored value for the attribute, in either the 
805          * internal state object, or the state proxy if it exits
806          *
807          * @method _setStateVal
808          * @private
809          * @param {String} name The name of the attribute
810          * @param {Any} value The value of the attribute
811          */
812         _setStateVal : function(name, value) {
813             var stateProxy = this._stateProxy;
814             if (stateProxy && (name in stateProxy) && !this._state.get(name, BYPASS_PROXY)) {
815                 stateProxy[name] = value;
816             } else {
817                 this._state.add(name, VALUE, value);
818             }
819         },
820
821         /**
822          * Updates the stored value of the attribute in the privately held State object,
823          * if validation and setter passes.
824          *
825          * @method _setAttrVal
826          * @private
827          * @param {String} attrName The attribute name.
828          * @param {String} subAttrName The sub-attribute name, if setting a sub-attribute property ("x.y.z").
829          * @param {Any} prevVal The currently stored value of the attribute.
830          * @param {Any} newVal The value which is going to be stored.
831          * 
832          * @return {booolean} true if the new attribute value was stored, false if not.
833          */
834         _setAttrVal : function(attrName, subAttrName, prevVal, newVal) {
835
836             var host = this,
837                 allowSet = true,
838                 state = host._state,
839
840                 validator = state.get(attrName, VALIDATOR),
841                 setter = state.get(attrName, SETTER),
842                 initializing = state.get(attrName, INITIALIZING),
843                 prevValRaw = this._getStateVal(attrName),
844
845                 name = subAttrName || attrName,
846                 retVal,
847                 valid;
848
849             if (validator) {
850                 if (!validator.call) { 
851                     // Assume string - trying to keep critical path tight, so avoiding Lang check
852                     validator = this[validator];
853                 }
854                 if (validator) {
855                     valid = validator.call(host, newVal, name);
856
857                     if (!valid && initializing) {
858                         newVal = state.get(attrName, DEF_VALUE);
859                         valid = true; // Assume it's valid, for perf.
860                     }
861                 }
862             }
863
864             if (!validator || valid) {
865                 if (setter) {
866                     if (!setter.call) {
867                         // Assume string - trying to keep critical path tight, so avoiding Lang check
868                         setter = this[setter];
869                     }
870                     if (setter) {
871                         retVal = setter.call(host, newVal, name);
872
873                         if (retVal === INVALID_VALUE) {
874                             allowSet = false;
875                         } else if (retVal !== undefined){
876                             newVal = retVal;
877                         }
878                     }
879                 }
880
881                 if (allowSet) {
882                     if(!subAttrName && (newVal === prevValRaw) && !Lang.isObject(newVal)) {
883                         allowSet = false;
884                     } else {
885                         // Store value
886                         if (state.get(attrName, INIT_VALUE) === undefined) {
887                             state.add(attrName, INIT_VALUE, newVal);
888                         }
889                         host._setStateVal(attrName, newVal);
890                     }
891                 }
892
893             } else {
894                 allowSet = false;
895             }
896
897             return allowSet;
898         },
899
900         /**
901          * Sets multiple attribute values.
902          *
903          * @method setAttrs
904          * @param {Object} attrs  An object with attributes name/value pairs.
905          * @return {Object} A reference to the host object.
906          * @chainable
907          */
908         setAttrs : function(attrs, opts) {
909             return this._setAttrs(attrs, opts);
910         },
911
912         /**
913          * Implementation behind the public setAttrs method, to set multiple attribute values.
914          *
915          * @method _setAttrs
916          * @protected
917          * @param {Object} attrs  An object with attributes name/value pairs.
918          * @return {Object} A reference to the host object.
919          * @chainable
920          */
921         _setAttrs : function(attrs, opts) {
922             for (var attr in attrs) {
923                 if ( attrs.hasOwnProperty(attr) ) {
924                     this.set(attr, attrs[attr]);
925                 }
926             }
927             return this;
928         },
929
930         /**
931          * Gets multiple attribute values.
932          *
933          * @method getAttrs
934          * @param {Array | boolean} attrs Optional. An array of attribute names. If omitted, all attribute values are
935          * returned. If set to true, all attributes modified from their initial values are returned.
936          * @return {Object} An object with attribute name/value pairs.
937          */
938         getAttrs : function(attrs) {
939             return this._getAttrs(attrs);
940         },
941
942         /**
943          * Implementation behind the public getAttrs method, to get multiple attribute values.
944          *
945          * @method _getAttrs
946          * @protected
947          * @param {Array | boolean} attrs Optional. An array of attribute names. If omitted, all attribute values are
948          * returned. If set to true, all attributes modified from their initial values are returned.
949          * @return {Object} An object with attribute name/value pairs.
950          */
951         _getAttrs : function(attrs) {
952             var host = this,
953                 o = {}, 
954                 i, l, attr, val,
955                 modifiedOnly = (attrs === true);
956
957             attrs = (attrs && !modifiedOnly) ? attrs : O.keys(host._state.data.added);
958
959             for (i = 0, l = attrs.length; i < l; i++) {
960                 // Go through get, to honor cloning/normalization
961                 attr = attrs[i];
962                 val = host.get(attr);
963
964                 if (!modifiedOnly || host._getStateVal(attr) != host._state.get(attr, INIT_VALUE)) {
965                     o[attr] = host.get(attr); 
966                 }
967             }
968
969             return o;
970         },
971
972         /**
973          * Configures a group of attributes, and sets initial values.
974          *
975          * <p>
976          * <strong>NOTE:</strong> This method does not isolate the configuration object by merging/cloning. 
977          * The caller is responsible for merging/cloning the configuration object if required.
978          * </p>
979          *
980          * @method addAttrs
981          * @chainable
982          *
983          * @param {Object} cfgs An object with attribute name/configuration pairs.
984          * @param {Object} values An object with attribute name/value pairs, defining the initial values to apply.
985          * Values defined in the cfgs argument will be over-written by values in this argument unless defined as read only.
986          * @param {boolean} lazy Whether or not to delay the intialization of these attributes until the first call to get/set.
987          * Individual attributes can over-ride this behavior by defining a lazyAdd configuration property in their configuration.
988          * See <a href="#method_addAttr">addAttr</a>.
989          * 
990          * @return {Object} A reference to the host object.
991          */
992         addAttrs : function(cfgs, values, lazy) {
993             var host = this; // help compression
994             if (cfgs) {
995                 host._tCfgs = cfgs;
996                 host._tVals = host._normAttrVals(values);
997                 host._addAttrs(cfgs, host._tVals, lazy);
998                 host._tCfgs = host._tVals = null;
999             }
1000
1001             return host;
1002         },
1003
1004         /**
1005          * Implementation behind the public addAttrs method. 
1006          * 
1007          * This method is invoked directly by get if it encounters a scenario 
1008          * in which an attribute's valueFn attempts to obtain the 
1009          * value an attribute in the same group of attributes, which has not yet 
1010          * been added (on demand initialization).
1011          *
1012          * @method _addAttrs
1013          * @private
1014          * @param {Object} cfgs An object with attribute name/configuration pairs.
1015          * @param {Object} values An object with attribute name/value pairs, defining the initial values to apply.
1016          * Values defined in the cfgs argument will be over-written by values in this argument unless defined as read only.
1017          * @param {boolean} lazy Whether or not to delay the intialization of these attributes until the first call to get/set.
1018          * Individual attributes can over-ride this behavior by defining a lazyAdd configuration property in their configuration.
1019          * See <a href="#method_addAttr">addAttr</a>.
1020          */
1021         _addAttrs : function(cfgs, values, lazy) {
1022             var host = this, // help compression
1023                 attr,
1024                 attrCfg,
1025                 value;
1026
1027             for (attr in cfgs) {
1028                 if (cfgs.hasOwnProperty(attr)) {
1029
1030                     // Not Merging. Caller is responsible for isolating configs
1031                     attrCfg = cfgs[attr];
1032                     attrCfg.defaultValue = attrCfg.value;
1033
1034                     // Handle simple, complex and user values, accounting for read-only
1035                     value = host._getAttrInitVal(attr, attrCfg, host._tVals);
1036
1037                     if (value !== undefined) {
1038                         attrCfg.value = value;
1039                     }
1040
1041                     if (host._tCfgs[attr]) {
1042                         delete host._tCfgs[attr];
1043                     }
1044
1045                     host.addAttr(attr, attrCfg, lazy);
1046                 }
1047             }
1048         },
1049
1050         /**
1051          * Utility method to protect an attribute configuration
1052          * hash, by merging the entire object and the individual 
1053          * attr config objects. 
1054          *
1055          * @method _protectAttrs
1056          * @protected
1057          * @param {Object} attrs A hash of attribute to configuration object pairs.
1058          * @return {Object} A protected version of the attrs argument.
1059          */
1060         _protectAttrs : function(attrs) {
1061             if (attrs) {
1062                 attrs = Y.merge(attrs);
1063                 for (var attr in attrs) {
1064                     if (attrs.hasOwnProperty(attr)) {
1065                         attrs[attr] = Y.merge(attrs[attr]);
1066                     }
1067                 }
1068             }
1069             return attrs;
1070         },
1071
1072         /**
1073          * Utility method to normalize attribute values. The base implementation 
1074          * simply merges the hash to protect the original.
1075          *
1076          * @method _normAttrVals
1077          * @param {Object} valueHash An object with attribute name/value pairs
1078          *
1079          * @return {Object}
1080          *
1081          * @private
1082          */
1083         _normAttrVals : function(valueHash) {
1084             return (valueHash) ? Y.merge(valueHash) : null;
1085         },
1086
1087         /**
1088          * Returns the initial value of the given attribute from
1089          * either the default configuration provided, or the 
1090          * over-ridden value if it exists in the set of initValues 
1091          * provided and the attribute is not read-only.
1092          *
1093          * @param {String} attr The name of the attribute
1094          * @param {Object} cfg The attribute configuration object
1095          * @param {Object} initValues The object with simple and complex attribute name/value pairs returned from _normAttrVals
1096          *
1097          * @return {Any} The initial value of the attribute.
1098          *
1099          * @method _getAttrInitVal
1100          * @private
1101          */
1102         _getAttrInitVal : function(attr, cfg, initValues) {
1103             var val, valFn;
1104             // init value is provided by the user if it exists, else, provided by the config
1105             if (!cfg[READ_ONLY] && initValues && initValues.hasOwnProperty(attr)) {
1106                 val = initValues[attr];
1107             } else {
1108                 val = cfg[VALUE];
1109                 valFn = cfg[VALUE_FN];
1110  
1111                 if (valFn) {
1112                     if (!valFn.call) {
1113                         valFn = this[valFn];
1114                     }
1115                     if (valFn) {
1116                         val = valFn.call(this);
1117                     }
1118                 }
1119             }
1120
1121
1122             return val;
1123         },
1124
1125         /**
1126          * Returns an object with the configuration properties (and value)
1127          * for the given attrubute. If attrName is not provided, returns the
1128          * configuration properties for all attributes.
1129          *
1130          * @method _getAttrCfg
1131          * @protected
1132          * @param {String} name Optional. The attribute name. If not provided, the method will return the configuration for all attributes.
1133          * @return {Object} The configuration properties for the given attribute, or all attributes.
1134          */
1135         _getAttrCfg : function(name) {
1136             var o,
1137                 data = this._state.data;
1138
1139             if (data) {
1140                 o = {};
1141
1142                 Y.each(data, function(cfg, cfgProp) {
1143                     if (name) {
1144                         if(name in cfg) {
1145                             o[cfgProp] = cfg[name];
1146                         }
1147                     } else {
1148                         Y.each(cfg, function(attrCfg, attr) {
1149                            o[attr] = o[attr] || {};
1150                            o[attr][cfgProp] = attrCfg;
1151                         });
1152                     }
1153                 });
1154             }
1155
1156             return o;
1157         }
1158     };
1159
1160     // Basic prototype augment - no lazy constructor invocation.
1161     Y.mix(Attribute, EventTarget, false, null, 1);
1162
1163     Y.Attribute = Attribute;
1164
1165
1166 }, '3.3.0' ,{requires:['event-custom']});