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