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  **/