1 /**
  2  * @fileOverview attribute management
  3  * @author  yiminghe@gmail.com, lifesinger@gmail.com
  4  */
  5 KISSY.add('base/attribute', function (S, undefined) {
  6 
  7     // atomic flag
  8     Attribute.INVALID = {};
  9 
 10     var INVALID = Attribute.INVALID;
 11 
 12     /**
 13      *
 14      * @param host
 15      * @param method
 16      * @return method if fn or host[method]
 17      */
 18     function normalFn(host, method) {
 19         if (S.isString(method)) {
 20             return host[method];
 21         }
 22         return method;
 23     }
 24 
 25 
 26     /**
 27      * fire attribute value change
 28      */
 29     function __fireAttrChange(self, when, name, prevVal, newVal, subAttrName, attrName) {
 30         attrName = attrName || name;
 31         return self.fire(when + S.ucfirst(name) + 'Change', {
 32             attrName:attrName,
 33             subAttrName:subAttrName,
 34             prevVal:prevVal,
 35             newVal:newVal
 36         });
 37     }
 38 
 39 
 40     /**
 41      *
 42      * @param obj
 43      * @param name
 44      * @param [create]
 45      * @return non-empty property value of obj
 46      */
 47     function ensureNonEmpty(obj, name, create) {
 48         var ret = obj[name] || {};
 49         if (create) {
 50             obj[name] = ret;
 51         }
 52         return ret;
 53     }
 54 
 55     /**
 56      *
 57      * @param self
 58      * @return non-empty attr config holder
 59      */
 60     function getAttrs(self) {
 61         /**
 62          * attribute meta information
 63          {
 64          attrName: {
 65          getter: function,
 66          setter: function,
 67          // 注意:只能是普通对象以及系统内置类型,而不能是 new Xx(),否则用 valueFn 替代
 68          value: v, // default value
 69          valueFn: function
 70          }
 71          }
 72          */
 73         return ensureNonEmpty(self, "__attrs", true);
 74     }
 75 
 76     /**
 77      *
 78      * @param self
 79      * @return non-empty attr value holder
 80      */
 81     function getAttrVals(self) {
 82         /**
 83          * attribute value
 84          {
 85          attrName: attrVal
 86          }
 87          */
 88         return ensureNonEmpty(self, "__attrVals", true);
 89     }
 90 
 91     /**
 92      * o, [x,y,z] => o[x][y][z]
 93      * @param o
 94      * @param path
 95      */
 96     function getValueByPath(o, path) {
 97         for (var i = 0, len = path.length;
 98              o != undefined && i < len;
 99              i++) {
100             o = o[path[i]];
101         }
102         return o;
103     }
104 
105     /**
106      * o, [x,y,z], val => o[x][y][z]=val
107      * @param o
108      * @param path
109      * @param val
110      */
111     function setValueByPath(o, path, val) {
112         var len = path.length - 1,
113             s = o;
114         if (len >= 0) {
115             for (var i = 0; i < len; i++) {
116                 o = o[path[i]];
117             }
118             if (o != undefined) {
119                 o[path[i]] = val;
120             } else {
121                 s = undefined;
122             }
123         }
124         return s;
125     }
126 
127     function getPathNamePair(self, name) {
128         var declared = self.hasAttr(name), path;
129 
130         if (
131         // 声明过,那么 xx.yy 当做普通属性
132             !declared &&
133                 name.indexOf(".") !== -1) {
134             path = name.split(".");
135             name = path.shift();
136         }
137 
138         return {
139             path:path,
140             name:name
141         };
142     }
143 
144     function getValueBySubValue(prevVal, path, value) {
145         var tmp = value;
146         if (path) {
147             if (prevVal === undefined) {
148                 tmp = {};
149             } else {
150                 tmp = S.clone(prevVal);
151             }
152             setValueByPath(tmp, path, value);
153         }
154         return tmp;
155     }
156 
157     function setInternal(self, name, value, opts, attrs) {
158         opts = opts || {};
159 
160         var ret,
161             path,
162             subVal,
163             prevVal,
164             pathNamePair = getPathNamePair(self, name),
165             fullName = name;
166 
167         name = pathNamePair.name;
168         path = pathNamePair.path;
169         prevVal = self.get(name);
170 
171         if (path) {
172             subVal = getValueByPath(prevVal, path);
173         }
174 
175         // if no change, just return
176         if (!path && prevVal === value) {
177             return undefined;
178         } else if (path && subVal === value) {
179             return undefined;
180         }
181 
182         value = getValueBySubValue(prevVal, path, value);
183 
184         // check before event
185         if (!opts['silent']) {
186             if (false === __fireAttrChange(self, 'before', name, prevVal, value, fullName)) {
187                 return false;
188             }
189         }
190         // set it
191         ret = self.__set(name, value, opts);
192 
193         if (ret === false) {
194             return ret;
195         }
196 
197         // fire after event
198         if (!opts['silent']) {
199             value = getAttrVals(self)[name];
200             __fireAttrChange(self, 'after', name, prevVal, value, fullName);
201             if (!attrs) {
202                 __fireAttrChange(self,
203                     '', '*',
204                     [prevVal], [value],
205                     [fullName], [name]);
206             } else {
207                 attrs.push({
208                     prevVal:prevVal,
209                     newVal:value,
210                     attrName:name,
211                     subAttrName:fullName
212                 });
213             }
214         }
215         return self;
216     }
217 
218     /**
219      * @class <p>
220      * Attribute provides configurable attribute support along with attribute change events. It is designed to be
221      * augmented on to a host class, and provides the host with the ability to configure attributes to store and retrieve state,
222      * along with attribute change events.
223      * </p>
224      * <p>For example, attributes added to the host can be configured:</p>
225      * <ul>
226      *     <li>With a setter function, which can be used to manipulate
227      *     values passed to Attribute's {@link Attribute#set} method, before they are stored.</li>
228      *     <li>With a getter function, which can be used to manipulate stored values,
229      *     before they are returned by Attribute's {@link Attribute#get} method.</li>
230      *     <li>With a validator function, to validate values before they are stored.</li>
231      * </ul>
232      *
233      * <p>See the {@link Attribute#addAttr} method, for the complete set of configuration
234      * options available for attributes</p>.
235      *
236      * <p><strong>NOTE:</strong> Most implementations will be better off extending the {@link Base} class,
237      * instead of augmenting Attribute directly. Base augments Attribute and will handle the initial configuration
238      * of attributes for derived classes, accounting for values passed into the constructor.</p>
239      * @name Attribute
240      */
241     function Attribute() {
242     }
243 
244     S.augment(Attribute,
245         /**
246          * @lends Attribute.prototype
247          */
248         {
249 
250             /**
251              * @return un-cloned attr config collections
252              */
253             getAttrs:function () {
254                 return getAttrs(this);
255             },
256 
257             /**
258              * @return un-cloned attr value collections
259              */
260             getAttrVals:function () {
261                 var self = this,
262                     o = {},
263                     a,
264                     attrs = getAttrs(self);
265                 for (a in attrs) {
266                     o[a] = self.get(a);
267                 }
268                 return o;
269             },
270 
271             /**
272              * Adds an attribute with the provided configuration to the host object.
273              * @param {String} name attrName
274              * @param {Object} attrConfig The config supports the following properties
275              * @param [attrConfig.value] simple object or system native object
276              * @param [attrConfig.valueFn] a function which can return current attribute's default value
277              * @param {Function(*)} [attrConfig.setter] call when set attribute's value
278              *                                          pass current attribute's value as parameter
279              *                                          if return value is not undefined,set returned value as real value
280              * @param {Function(*)} [attrConfig.getter] call when get attribute's value
281              *                                          pass current attribute's value as parameter
282              *                                          return getter's returned value to invoker
283              * @param {Function(*)} [attrConfig.validator]  call before set attribute's value
284              *                                              if return false,cancel this set action
285              * @param {Boolean} [override] whether override existing attribute config ,default true
286              */
287             addAttr:function (name, attrConfig, override) {
288                 var self = this,
289                     attrs = getAttrs(self),
290                     cfg = S.clone(attrConfig);
291                 if (!attrs[name]) {
292                     attrs[name] = cfg;
293                 } else {
294                     S.mix(attrs[name], cfg, override);
295                 }
296                 return self;
297             },
298 
299             /**
300              * Configures a group of attributes, and sets initial values.
301              * @param {Object} attrConfigs  An object with attribute name/configuration pairs.
302              * @param {Object} initialValues user defined initial values
303              */
304             addAttrs:function (attrConfigs, initialValues) {
305                 var self = this;
306                 S.each(attrConfigs, function (attrConfig, name) {
307                     self.addAttr(name, attrConfig);
308                 });
309                 if (initialValues) {
310                     self.set(initialValues);
311                 }
312                 return self;
313             },
314 
315             /**
316              * Checks if the given attribute has been added to the host.
317              */
318             hasAttr:function (name) {
319                 return name && getAttrs(this).hasOwnProperty(name);
320             },
321 
322             /**
323              * Removes an attribute from the host object.
324              */
325             removeAttr:function (name) {
326                 var self = this;
327 
328                 if (self.hasAttr(name)) {
329                     delete getAttrs(self)[name];
330                     delete getAttrVals(self)[name];
331                 }
332 
333                 return self;
334             },
335 
336 
337             /**
338              * Sets the value of an attribute.
339              * @param {String|Object} name attribute's name or attribute name and value map
340              * @param [value] attribute's value
341              * @param {Object} [opts] some options
342              * @param {Boolean} [opts.silent] whether fire change event
343              * @returns {Boolean} whether pass validator
344              */
345             set:function (name, value, opts) {
346                 var self = this;
347                 if (S.isPlainObject(name)) {
348                     opts = value;
349                     var all = Object(name),
350                         attrs = [],
351                         e,
352                         errors = [];
353                     for (name in all) {
354                         // bulk validation
355                         // if any one failed,all values are not set
356                         if ((e = validate(self, name, all[name], all)) !== undefined) {
357                             errors.push(e);
358                         }
359                     }
360                     if (errors.length) {
361                         if (opts && opts.error) {
362                             opts.error(errors);
363                         }
364                         return false;
365                     }
366                     for (name in all) {
367                         setInternal(self, name, all[name], opts, attrs);
368                     }
369                     var attrNames = [],
370                         prevVals = [],
371                         newVals = [],
372                         subAttrNames = [];
373                     S.each(attrs, function (attr) {
374                         prevVals.push(attr.prevVal);
375                         newVals.push(attr.newVal);
376                         attrNames.push(attr.attrName);
377                         subAttrNames.push(attr.subAttrName);
378                     });
379                     if (attrNames.length) {
380                         __fireAttrChange(self,
381                             '',
382                             '*',
383                             prevVals,
384                             newVals,
385                             subAttrNames,
386                             attrNames);
387                     }
388                     return self;
389                 }
390                 return setInternal(self, name, value, opts);
391             },
392 
393             /**
394              * internal use, no event involved, just set.
395              * @protected overriden by mvc/model
396              */
397             __set:function (name, value, opts) {
398                 var self = this,
399                     setValue,
400                 // if host does not have meta info corresponding to (name,value)
401                 // then register on demand in order to collect all data meta info
402                 // 一定要注册属性元数据,否则其他模块通过 _attrs 不能枚举到所有有效属性
403                 // 因为属性在声明注册前可以直接设置值
404                     e,
405                     attrConfig = ensureNonEmpty(getAttrs(self), name, true),
406                     setter = attrConfig['setter'];
407 
408                 // validator check
409                 e = validate(self, name, value);
410 
411                 if (e !== undefined) {
412                     if (opts.error) {
413                         opts.error(e);
414                     }
415                     return false;
416                 }
417 
418                 // if setter has effect
419                 if (setter && (setter = normalFn(self, setter))) {
420                     setValue = setter.call(self, value, name);
421                 }
422 
423                 if (setValue === INVALID) {
424                     return false;
425                 }
426 
427                 if (setValue !== undefined) {
428                     value = setValue;
429                 }
430 
431 
432                 // finally set
433                 getAttrVals(self)[name] = value;
434             },
435 
436             /**
437              * Gets the current value of the attribute.
438              * @param {String} name attribute's name
439              */
440             get:function (name) {
441                 var self = this,
442                     dot = ".",
443                     path,
444                     declared = self.hasAttr(name),
445                     attrVals = getAttrVals(self),
446                     attrConfig,
447                     getter, ret;
448 
449                 if (!declared && name.indexOf(dot) !== -1) {
450                     path = name.split(dot);
451                     name = path.shift();
452                 }
453 
454                 attrConfig = ensureNonEmpty(getAttrs(self), name);
455                 getter = attrConfig['getter'];
456 
457                 // get user-set value or default value
458                 //user-set value takes privilege
459                 ret = name in attrVals ?
460                     attrVals[name] :
461                     self.__getDefAttrVal(name);
462 
463                 // invoke getter for this attribute
464                 if (getter && (getter = normalFn(self, getter))) {
465                     ret = getter.call(self, ret, name);
466                 }
467 
468                 if (path) {
469                     ret = getValueByPath(ret, path);
470                 }
471 
472                 return ret;
473             },
474 
475             /**
476              * get default attribute value from valueFn/value
477              * @private
478              * @param name
479              */
480             __getDefAttrVal:function (name) {
481                 var self = this,
482                     attrs = getAttrs(self),
483                     attrConfig = ensureNonEmpty(attrs, name),
484                     valFn = attrConfig.valueFn,
485                     val;
486 
487                 if (valFn && (valFn = normalFn(self, valFn))) {
488                     val = valFn.call(self);
489                     if (val !== undefined) {
490                         attrConfig.value = val;
491                     }
492                     delete attrConfig.valueFn;
493                     attrs[name] = attrConfig;
494                 }
495 
496                 return attrConfig.value;
497             },
498 
499             /**
500              * Resets the value of an attribute.just reset what addAttr set  (not what invoker set when call new Xx(cfg))
501              * @param {String} name name of attribute
502              * @param {Object} [opts] some options
503              * @param {Boolean} [opts.silent] whether fire change event
504              */
505             reset:function (name, opts) {
506                 var self = this;
507 
508                 if (S.isString(name)) {
509                     if (self.hasAttr(name)) {
510                         // if attribute does not have default value, then set to undefinedined.
511                         return self.set(name, self.__getDefAttrVal(name), opts);
512                     }
513                     else {
514                         return self;
515                     }
516                 }
517 
518                 opts = name;
519 
520                 var attrs = getAttrs(self),
521                     values = {};
522 
523                 // reset all
524                 for (name in attrs) {
525                     values[name] = self.__getDefAttrVal(name);
526                 }
527 
528                 self.set(values, opts);
529                 return self;
530             }
531         });
532 
533     function validate(self, name, value, all) {
534         var path, prevVal, pathNamePair;
535 
536         pathNamePair = getPathNamePair(self, name);
537 
538         name = pathNamePair.name;
539         path = pathNamePair.path;
540 
541         if (path) {
542             prevVal = self.get(name);
543             value = getValueBySubValue(prevVal, path, value);
544         }
545         var attrConfig = ensureNonEmpty(getAttrs(self), name, true),
546             e,
547             validator = attrConfig['validator'];
548         if (validator && (validator = normalFn(self, validator))) {
549             e = validator.call(self, value, name, all);
550             // undefined and true validate successfully
551             if (e !== undefined && e !== true) {
552                 return e;
553             }
554         }
555         return undefined;
556     }
557 
558     return Attribute;
559 });
560 
561 /**
562  *  2011-10-18
563  *    get/set sub attribute value ,set("x.y",val) x 最好为 {} ,不要是 new Clz() 出来的
564  *    add validator
565  */
566