1 /** 2 * @fileOverview UIBase 3 * @author yiminghe@gmail.com, lifesinger@gmail.com 4 */ 5 KISSY.add('component/uibase/base', function (S, Base, Node, Manager, undefined) { 6 7 var UI_SET = '_uiSet', 8 SRC_NODE = 'srcNode', 9 ATTRS = 'ATTRS', 10 HTML_PARSER = 'HTML_PARSER', 11 ucfirst = S.ucfirst, 12 noop = S.noop; 13 14 15 /** 16 * @name UIBase 17 * @memberOf Component 18 * @extends Base 19 * @class 20 * UIBase for class-based component. 21 */ 22 function UIBase(config) { 23 var self = this, id; 24 25 // 读取用户设置的属性值并设置到自身 26 Base.apply(self, arguments); 27 28 // register instance if config id 29 if (id = self.get("id")) { 30 Manager.addComponent(id, self); 31 } 32 33 34 // 根据 srcNode 设置属性值 35 // 按照类层次执行初始函数,主类执行 initializer 函数,扩展类执行构造器函数 36 initHierarchy(self, config); 37 38 var listener, 39 n, 40 plugins = self.get("plugins"), 41 listeners = self.get("listeners"); 42 43 constructPlugins(plugins); 44 45 actionPlugins(self, plugins, "initializer"); 46 47 for (n in listeners) { 48 listener = listeners[n]; 49 self.on(n, listener.fn || listener, listener.scope); 50 } 51 52 53 // 是否自动渲染 54 config && config.autoRender && self.render(); 55 } 56 57 /** 58 * 模拟多继承 59 * init attr using constructors ATTRS meta info 60 */ 61 function initHierarchy(host, config) { 62 63 var c = host.constructor; 64 65 while (c) { 66 67 // 从 markup 生成相应的属性项 68 if (config && 69 config[SRC_NODE] && 70 c[HTML_PARSER]) { 71 if ((config[SRC_NODE] = Node.one(config[SRC_NODE]))) { 72 applyParser.call(host, config, c[HTML_PARSER]); 73 } 74 } 75 76 c = c.superclass && c.superclass.constructor; 77 } 78 79 callMethodByHierarchy(host, "initializer", "constructor"); 80 81 } 82 83 function callMethodByHierarchy(host, mainMethod, extMethod) { 84 var c = host.constructor, 85 extChains = [], 86 ext, 87 main, 88 exts, 89 t; 90 91 // define 92 while (c) { 93 94 // 收集扩展类 95 t = []; 96 if (exts = c.__ks_exts) { 97 for (var i = 0; i < exts.length; i++) { 98 ext = exts[i]; 99 if (ext) { 100 if (extMethod != "constructor") { 101 //只调用真正自己构造器原型的定义,继承原型链上的不要管 102 if (ext.prototype.hasOwnProperty(extMethod)) { 103 ext = ext.prototype[extMethod]; 104 } else { 105 ext = null; 106 } 107 } 108 ext && t.push(ext); 109 } 110 } 111 } 112 113 // 收集主类 114 // 只调用真正自己构造器原型的定义,继承原型链上的不要管 !important 115 // 所以不用自己在 renderUI 中调用 superclass.renderUI 了,UIBase 构造器自动搜寻 116 // 以及 initializer 等同理 117 if (c.prototype.hasOwnProperty(mainMethod) && (main = c.prototype[mainMethod])) { 118 t.push(main); 119 } 120 121 // 原地 reverse 122 if (t.length) { 123 extChains.push.apply(extChains, t.reverse()); 124 } 125 126 c = c.superclass && c.superclass.constructor; 127 } 128 129 // 初始化函数 130 // 顺序:父类的所有扩展类函数 -> 父类对应函数 -> 子类的所有扩展函数 -> 子类对应函数 131 for (i = extChains.length - 1; i >= 0; i--) { 132 extChains[i] && extChains[i].call(host); 133 } 134 } 135 136 /** 137 * 销毁组件 138 * 顺序: 子类 destructor -> 子类扩展 destructor -> 父类 destructor -> 父类扩展 destructor 139 */ 140 function destroyHierarchy(host) { 141 var c = host.constructor, 142 extensions, 143 d, 144 i; 145 146 while (c) { 147 // 只触发该类真正的析构器,和父亲没关系,所以不要在子类析构器中调用 superclass 148 if (c.prototype.hasOwnProperty("destructor")) { 149 c.prototype.destructor.apply(host); 150 } 151 152 if ((extensions = c.__ks_exts)) { 153 for (i = extensions.length - 1; i >= 0; i--) { 154 d = extensions[i] && extensions[i].prototype.__destructor; 155 d && d.apply(host); 156 } 157 } 158 159 c = c.superclass && c.superclass.constructor; 160 } 161 } 162 163 function applyParser(config, parser) { 164 var host = this, p, v, srcNode = config[SRC_NODE]; 165 166 // 从 parser 中,默默设置属性,不触发事件 167 for (p in parser) { 168 if (parser.hasOwnProperty(p) && 169 // 用户设置过那么这里不从 dom 节点取 170 // 用户设置 > html parser > default value 171 config[p] === undefined) { 172 v = parser[p]; 173 // 函数 174 if (S.isFunction(v)) { 175 host.__set(p, v.call(host, srcNode)); 176 } 177 // 单选选择器 178 else if (S.isString(v)) { 179 host.__set(p, srcNode.one(v)); 180 } 181 // 多选选择器 182 else if (S.isArray(v) && v[0]) { 183 host.__set(p, srcNode.all(v[0])) 184 } 185 } 186 } 187 } 188 189 /** 190 * 根据属性变化设置 UI 191 */ 192 function bindUI(self) { 193 var attrs = self.getAttrs(), 194 attr, m; 195 196 for (attr in attrs) { 197 if (attrs.hasOwnProperty(attr)) { 198 m = UI_SET + ucfirst(attr); 199 if (self[m]) { 200 // 自动绑定事件到对应函数 201 (function (attr, m) { 202 self.on('after' + ucfirst(attr) + 'Change', function (ev) { 203 // fix! 防止冒泡过来的 204 if (ev.target === self) { 205 self[m](ev.newVal, ev); 206 } 207 }); 208 })(attr, m); 209 } 210 } 211 } 212 } 213 214 /** 215 * 根据当前(初始化)状态来设置 UI 216 */ 217 function syncUI(self) { 218 var v, 219 f, 220 attrs = self.getAttrs(); 221 for (var a in attrs) { 222 if (attrs.hasOwnProperty(a)) { 223 var m = UI_SET + ucfirst(a); 224 //存在方法,并且用户设置了初始值或者存在默认值,就同步状态 225 if ((f = self[m]) 226 // 用户如果设置了显式不同步,就不同步,比如一些值从 html 中读取,不需要同步再次设置 227 && attrs[a].sync !== false 228 && (v = self.get(a)) !== undefined) { 229 f.call(self, v); 230 } 231 } 232 } 233 } 234 235 S.extend(UIBase, Base, 236 /** 237 * @lends Component.UIBase.prototype 238 */ 239 { 240 241 /** 242 * Create dom structure of this component. 243 */ 244 create:function () { 245 var self = this; 246 // 是否生成过节点 247 if (!self.get("created")) { 248 /** 249 * @name Component.UIBase#beforeCreateDom 250 * @description fired before root node is created 251 * @event 252 * @param e 253 */ 254 self.fire('beforeCreateDom'); 255 callMethodByHierarchy(self, "createDom", "__createDom"); 256 self.__set("created", true); 257 /** 258 * @name Component.UIBase#afterCreateDom 259 * @description fired when root node is created 260 * @event 261 * @param e 262 */ 263 self.fire('afterCreateDom'); 264 actionPlugins(self, self.get("plugins"), "createDom"); 265 } 266 return self; 267 }, 268 269 /** 270 * Put dom structure of this component to document and bind event. 271 */ 272 render:function () { 273 var self = this; 274 // 是否已经渲染过 275 if (!self.get("rendered")) { 276 var plugins = self.get("plugins"); 277 self.create(undefined); 278 279 /** 280 * @name Component.UIBase#beforeRenderUI 281 * @description fired when root node is ready 282 * @event 283 * @param e 284 */ 285 286 self.fire('beforeRenderUI'); 287 callMethodByHierarchy(self, "renderUI", "__renderUI"); 288 289 /** 290 * @name Component.UIBase#afterRenderUI 291 * @description fired after root node is rendered into dom 292 * @event 293 * @param e 294 */ 295 296 self.fire('afterRenderUI'); 297 actionPlugins(self, plugins, "renderUI"); 298 299 /** 300 * @name Component.UIBase#beforeBindUI 301 * @description fired before component 's internal event is bind. 302 * @event 303 * @param e 304 */ 305 306 self.fire('beforeBindUI'); 307 bindUI(self); 308 callMethodByHierarchy(self, "bindUI", "__bindUI"); 309 310 /** 311 * @name Component.UIBase#afterBindUI 312 * @description fired when component 's internal event is bind. 313 * @event 314 * @param e 315 */ 316 317 self.fire('afterBindUI'); 318 actionPlugins(self, plugins, "bindUI"); 319 320 /** 321 * @name Component.UIBase#beforeSyncUI 322 * @description fired before component 's internal state is synchronized. 323 * @event 324 * @param e 325 */ 326 327 self.fire('beforeSyncUI'); 328 329 syncUI(self); 330 callMethodByHierarchy(self, "syncUI", "__syncUI"); 331 332 /** 333 * @name Component.UIBase#afterSyncUI 334 * @description fired after component 's internal state is synchronized. 335 * @event 336 * @param e 337 */ 338 339 self.fire('afterSyncUI'); 340 actionPlugins(self, plugins, "syncUI"); 341 self.__set("rendered", true); 342 } 343 return self; 344 }, 345 346 /** 347 * For overridden. DOM creation logic of subclass component. 348 * @protected 349 * @function 350 */ 351 createDom:noop, 352 353 /** 354 * For overridden. Render logic of subclass component. 355 * @protected 356 * @function 357 */ 358 renderUI:noop, 359 360 /** 361 * For overridden. Bind logic for subclass component. 362 * @protected 363 * @function 364 */ 365 bindUI:noop, 366 367 /** 368 * For overridden. Sync attribute with ui. 369 * protected 370 * @function 371 */ 372 syncUI:noop, 373 374 375 /** 376 * Destroy this component. 377 */ 378 destroy:function () { 379 var self = this, 380 id, 381 plugins = self.get("plugins"); 382 actionPlugins(self, plugins, "destructor"); 383 destroyHierarchy(self); 384 self.fire('destroy'); 385 self.detach(); 386 // remove instance if set id 387 if (id = self.get("id")) { 388 Manager.removeComponent(id); 389 } 390 return self; 391 } 392 }, { 393 394 ATTRS:/** 395 * @lends Component.UIBase# 396 */ 397 { 398 /** 399 * Whether this component is rendered. 400 * @type Boolean 401 */ 402 rendered:{ 403 value:false 404 }, 405 /** 406 * Whether this component 's dom structure is created. 407 * @type Boolean 408 */ 409 created:{ 410 value:false 411 }, 412 413 /** 414 * Config listener on created. 415 * @example 416 * <code> 417 * { 418 * click:{ 419 * scope:{x:1}, 420 * fn:function(){ 421 * alert(this.x); 422 * } 423 * } 424 * } 425 * or 426 * { 427 * click:function(){ 428 * alert(this.x); 429 * } 430 * } 431 * </code> 432 */ 433 listeners:{ 434 value:{} 435 }, 436 437 /** 438 * Plugins 439 * @type Function[]|Object[] 440 */ 441 plugins:{ 442 value:[] 443 }, 444 445 /** 446 * Get xclass of current component instance. 447 * Readonly and only for json config. 448 * @type String 449 */ 450 xclass:{ 451 valueFn:function () { 452 return Manager.getXClassByConstructor(this.constructor); 453 } 454 } 455 } 456 457 }); 458 459 460 function constructPlugins(plugins) { 461 S.each(plugins, function (plugin, i) { 462 if (S.isFunction(plugin)) { 463 plugins[i] = new plugin(); 464 } 465 }); 466 } 467 468 469 function actionPlugins(self, plugins, action) { 470 S.each(plugins, function (plugin) { 471 if (plugin[action]) { 472 plugin[action](self); 473 } 474 }); 475 } 476 477 478 function create(base, extensions, px, sx) { 479 var args = S.makeArray(arguments), t; 480 481 if (S.isObject(extensions)) { 482 sx = px; 483 px = extensions; 484 extensions = []; 485 } 486 487 var name = "UIBaseDerived"; 488 489 if (S.isString(t = args[args.length - 1])) { 490 name = t; 491 } 492 493 function C() { 494 UIBase.apply(this, arguments); 495 } 496 497 // debug mode , give the right name for constructor 498 // refer : http://limu.iteye.com/blog/1136712 499 S.log("UIBase.extend : " + name, eval("C=function " + name.replace(/[-.]/g, "_") + "(){ UIBase.apply(this, arguments);}")); 500 501 S.extend(C, base, px, sx); 502 503 if (extensions) { 504 C.__ks_exts = extensions; 505 506 var desc = { 507 // ATTRS: 508 // HTML_PARSER: 509 }, constructors = extensions['concat'](C); 510 511 // [ex1,ex2],扩展类后面的优先,ex2 定义的覆盖 ex1 定义的 512 // 主类最优先 513 S.each(constructors, function (ext) { 514 if (ext) { 515 // 合并 ATTRS/HTML_PARSER 到主类 516 S.each([ATTRS, HTML_PARSER], function (K) { 517 if (ext[K]) { 518 desc[K] = desc[K] || {}; 519 // 不覆盖主类上的定义,因为继承层次上扩展类比主类层次高 520 // 但是值是对象的话会深度合并 521 // 注意:最好值是简单对象,自定义 new 出来的对象就会有问题(用 function return 出来)! 522 S.mix(desc[K], ext[K], { 523 deep:true 524 }); 525 } 526 }); 527 } 528 }); 529 530 S.each(desc, function (v, k) { 531 C[k] = v; 532 }); 533 534 var prototype = {}; 535 536 // 主类最优先 537 S.each(constructors, function (ext) { 538 if (ext) { 539 var proto = ext.prototype; 540 // 合并功能代码到主类,不覆盖 541 for (var p in proto) { 542 // 不覆盖主类,但是主类的父类还是覆盖吧 543 if (proto.hasOwnProperty(p)) { 544 prototype[p] = proto[p]; 545 } 546 } 547 } 548 }); 549 550 S.each(prototype, function (v, k) { 551 C.prototype[k] = v; 552 }); 553 } 554 return C; 555 } 556 557 558 S.mix(UIBase, 559 /** 560 * @lends Component.UIBase 561 */ 562 { 563 /** 564 * Parse attribute from existing dom node. 565 * @example 566 * Overlay.HTML_PARSER={ 567 * // el: root element of current component. 568 * "isRed":function(el){ 569 * return el.hasClass("ks-red"); 570 * } 571 * }; 572 */ 573 HTML_PARSER:{}, 574 575 /** 576 * Create a new class which extends UIBase . 577 * @param {Function[]} extensions Class constructors for extending. 578 * @param {Object} px Object to be mixed into new class 's prototype. 579 * @param {Object} sx Object to be mixed into new class. 580 * @returns {UIBase} A new class which extends UIBase . 581 */ 582 extend:function extend(extensions, px, sx) { 583 var args = S.makeArray(arguments), 584 ret, 585 last = args[args.length - 1]; 586 args.unshift(this); 587 if (last.xclass) { 588 args.pop(); 589 args.push(last.xclass); 590 } 591 ret = create.apply(UIBase, args); 592 if (last.xclass) { 593 Manager.setConstructorByXClass(last.xclass, { 594 constructor:ret, 595 priority:last.priority 596 }); 597 } 598 ret.extend = extend; 599 return ret; 600 } 601 }); 602 603 return UIBase; 604 }, { 605 requires:["base", "node", "../manager"] 606 }); 607 /** 608 * Refer: 609 * - http://martinfowler.com/eaaDev/uiArchs.html 610 * 611 * render 和 create 区别 612 * - render 包括 create ,以及把生成的节点放在 document 中 613 * - create 仅仅包括创建节点 614 **/