上次,咱们造成了两种header的布局,一种flexbox,一种float,最后与身边作重构的同事交流下来,选择了float的布局。javascript
事实上布局的选型不须要我关注,个人参与或者一些意见多数是自我提高,但要说html结构彻底控制于csser的话就不必定了css
在整个header组件的代码过程当中,我与重构同事就一些地方发生了重复的交流,争论,今天就header组件的布局以及功能实现,聊一聊js与css的配合html
而后header组件自己是一个老组件,咱们顺便探讨下,这类老组件应该如何翻新比较合适。html5
最开始重构的同事给了我一个已经作好了的页面:java
咱们针对其中一些小的体验上作了讨论,而且知会到设计组,便改了,很顺畅,而后我开始了愉快的代码,这是其中一块HTML的结构:git
1 <header class="cm-header" style="top: 50px;"> 2 <span class="fl cm-header-icon icon-back "></span> 3 <span class="fr cm-header-btn">确认</span> 4 <span class="fr cm-header-icon"><i class="icon-home"></i></span> 5 <span class="fr cm-header-icon"><i class="icond-list"></i></span> 6 <h1 class="cm-page-title"> 7 页面标题</h1> 8 </header>
这里除去h1标签中的文字不说,由于其中可能表现的很是复杂,咱们后面再说,其中的按钮有如下功能:程序员
① 第二行:回退按钮github
② 第三行:确认json
PS:左边采用float布局因此第一个元素在最右边数据结构
③ 第四行:home标签
④ 第五行:三个点,点击会出一个侧边栏
以上即是HTML的实现,可是对与程序员来讲,头部除了按钮(btn)之外就只有图标(icon),因此以上的结构事实上js通常是不买帐的
与重构同事交流下来,缘由是这样的:
① 由于回退比较特殊,因此多了一个样式,具体什么我没记住了
② icon表明背景图,icond表明CSS3画的,CSS3画的可扩展性高,好比换颜色什么的
③ ......
当时双方的讨论仍是比较激烈的,可是对icond所有变成icon,重构同事不一样意,因而也就做罢,通过一轮讨论,结构变成了这样:
1 <header class="cm-header" style="top: 50px;"> 2 <span class="fl cm-header-icon"><i class="icon-back"></i></span> 3 <span class="fr cm-header-btn">确认</span> 4 <span class="fr cm-header-icon"><i class="icon-home"></i></span> 5 <span class="fr cm-header-icon"><i class="icond-list"></i></span> 6 <h1 class="cm-page-title"> 7 页面标题</h1> 8 </header>
作了很小的变化,将back的结构与其它icon类型按钮作了统一,因而我开始了愉快的代码
PS:注意,icond与icon类型的标签会不一样程度的在header处出现,没法控制
由于公司的header一直便存在,我作的过程当中必须考虑到两个方面的问题:
① 方便扩展可是要作到接口兼容
② 须要经过各个标签的tagname与Hybrid进行联调
也就是说,每一个标签叫什么名字,是已经定死了的,甚至一些标签的回调也被限制了,我这里的数据结构大概以下:
1 { 2 left: [], 3 center: [], 4 right: [ 5 { 6 'tagname': 'home', callback: function () { 7 console.log('返回'); 8 } 9 }, 10 { 'tagname': 'search' }, 11 { 12 'tagname': 'list', callback: function (e) { 13 //...... 14 } 15 }, 16 { 'tagname': 'tel', 'number': '56973144' }, 17 { 18 'tagname': 'commit', 'value': '登陆', callback: function () { 19 console.log('登陆'); 20 } 21 }, 22 { 23 'tagname': 'custom', 'value': '定制化', 24 itemFn: function () { 25 return '<span class="cm-header-btn fr js_custom">定制化</span>'; 26 }, 27 callback: function () { 28 console.log('定制化'); 29 } 30 } 31 ]
能够看到,一个tagname一个按钮,而如今问题来了:咱们并不知道某个tagname应该是icon或者是icond
可是根据是否存在value字段,咱们是能够判断其是否应该具备i子标签,这个时候咱们是怎么解决的呢?
创建tagname与classname的映射关系,好比:
1 var map = { 2 'home': 'icon', 3 'list': 'icond' 4 }
固然,这种作法,天然十分让人感到难受,若是小图标统一为icon,我在模板中能够统一如此代码:
1 <span class="cm-header-icon <%=dir %> js_<%=item.tagname %>" > 2 <% if(item.value) { %> 3 <%=item.value %> 4 <% } else { %> 5 <i class="icon-<%=item.tagname %>"></i> 6 <% } %> 7 </span>
可是因为多了一个映射关系,个人代码便很差看了,而且业务逻辑还变得复杂了起来,因而带着这些考量再次找到了重构同事,重构同事也很明事理,立刻答应改了:
1 <header class="cm-header" style="top: 50px;"> 2 <span class="fl cm-header-icon"><i class="icon-back"></i></span> 3 <span class="fr cm-header-btn">确认</span> 4 <span class="fr cm-header-icon"><i class="icon-home"></i></span> 5 <span class="fr cm-header-icon"><i class="icon-list"></i></span> 6 <h1 class="cm-page-title"> 7 页面标题</h1> 8 </header>
不考虑h1中的样式的话,搞定上面的代码,对咱们来讲,真的是太简单了啊!!!
1 <header class="cm-header"> 2 <% 3 var i = 0, len = 0, j = 0, keyagain = 0; 4 var left = left; 5 var right = right.reverse(); 6 var item = null; 7 var dir; 8 var btnObj = null; 9 %> 10 <%for(keyagain=0; keyagain < 2; keyagain++) { %> 11 <% 12 if(keyagain == 0) { dir = 'fl'; btnObj = left; } else { dir = 'fr'; btnObj = right; } 13 %> 14 <% for(i = 0, len = btnObj.length; i < len; i++) { %> 15 <% item = btnObj[i]; %> 16 <%if(typeof item.itemFn == 'function') { %> 17 <%=item.itemFn() %> 18 <%} else { %> 19 <span class="cm-header-icon <%=dir %> js_<%=item.tagname %>" > 20 <% if(item.value) { %> 21 <%=item.value %> 22 <% } else { %> 23 <i class="icon-<%=item.tagname %>"></i> 24 <% } %> 25 </span> 26 <%} %> 27 <%} %> 28 <%} %> 29 </header>
PS:从代码着色来看,js中用到的left与Right是关键字,这个得处理...
能够看到,一个循环,咱们即可以轻易的生成左边和右边的按钮,可是立刻问题来了,咱们须要扩展怎么办,上面就会有如下问题:
① tel标签默认是a标签,咱们这里倒是span标签
1 <a href="tel:56973144" class="cm-header-btn fr js_tel "><i class="icon-tel"></i></a>
② back按钮咱们通常会作成a标签,用以解决javascript出错在Hybrid的假死问题
说白了,就是虽然标签按钮应该有统一的结构,可是须要保留定制化的能力
这里定制化的工做交给了各个标签的itemFn这个函数,他返回一个字符串,而且具备必定规则,这里取一个代码片断:
1 handleSpecialParam: function (data) { 2 var k, i, len, item; 3 for (k in data) { 4 if (_.isArray(data[k])) { 5 for (i = 0, len = data[k].length; i < len; i++) { 6 item = data[k][i]; 7 if (this['customtHandle_' + item.tagname]) { 8 this['customtHandle_' + item.tagname](data[k][i], k); 9 } //if 10 } //for 11 } //if 12 } //for 13 }, 14 15 _getDir: function (dir) { 16 var kv = { left: 'fl', right: 'fr' }; 17 return kv[dir]; 18 }, 19 20 //处理back的按钮逻辑 21 customtHandle_back: function (item, dir) { 22 dir = this._getDir(dir); 23 item.itemFn = function () { 24 var str = '<a href="http://m.ctrip.com/html5/" class="cm-header-btn ' + dir + ' js_' + item.tagname + ' " >'; 25 if (item.value) { 26 str += item.value + '</a>'; 27 } else { 28 str += '<i class="icon-' + item.tagname + '"></i></a>'; 29 } 30 return str; 31 }; 32 },
当发现某个按钮不知足需求或者有定制化需求时,便想办法设置其itemFn便可,时候上这个代码能够直接写到初始化的json串去
到title时,发现其表现便五花八门了,这个时候通常是根据不一样的类型生成不一样的HTML结构,框架给默认的几个选项,不支持便本身定制itemFn
1 <% item = center; %> 2 <%if(typeof item.itemFn == 'function') { %> 3 <%=item.itemFn() %> 4 <%} else if(item.tagname=='title' || item.tagname=='subtitle') { %> 5 <h1 class="cm-page-title js_<%=item.tagname %>" > 6 <%if(typeof(item.value) == 'object' && item.title.value == 2) { %> 7 <span class="cm-title-l"><%=item.value[0]%></span> 8 <span class="cm-title-s"><%=item.value[1]%></span> 9 <%} else { %> 10 <%=item.value %> 11 <%} %> 12 </h1> 13 <%} else if(item.tagname=='select'){ %> 14 <h1 class="cm-page-select-title js_<%=item.tagname %>" > 15 <%=item.value %> 16 </h1> 17 <%} else if(item.tagname=='tabs') { %> 18 <h1 class="cm-page-tabs-title js_<%=item.tagname %>" > 19 <%for(j = 0; j < item.data.items.length; j ++) { %> 20 <span data-key="<%=item.data.items[j].id %>" class="<%if(item.data.index===j){ %>active<%} %>" ><%=item.data.items[j].name %></span> 21 <% } %> 22 </h1> 23 <% } else{ %> 24 25 <%} %>
header组件自己继承至Abstract.View这个类,因此只要设置
this.events = {}
便能以事件代理的方式将事件绑定至根元素,而header的事件通常就是click事件:
1 setEventsParam: function () { 2 var item, data = this.datamodel.left.concat(this.datamodel.right).concat(this.datamodel.center); 3 4 for (var i = 0, len = data.length; i < len; i++) { 5 item = data[i]; 6 if (_.isFunction(item.callback)) { 7 this.events['click .js_' + item.tagname] = $.proxy(item.callback, this.viewScope); 8 } 9 } 10 },
这里有一个须要注意的点即是,事件绑定的钩子即是咱们的tagname,这个是惟一的,咱们会为每一个标签动态生成“.js_tagname”的类,以方便事件绑定
以前便说了,该组件是一个老组件的翻新,因而各个业务团队已经使用了,好比原来是这样调用的:
1 this.header.set({ 2 title: '基本Header使用', 3 subtitle: '中间副标题', 4 back: true, 5 backtext: '取消', 6 tel: { number: 1111 }, 7 home: true, 8 search: true, 9 btn: { title: "登陆", id: 'confirmBtn', classname: 'header_r' }, 10 events: { 11 returnHandler: function () { 12 console.log('back'); 13 }, 14 homeHandler: function (e) { 15 } 16 } 17 });
而如今咱们指望的调用方式是这样的:
1 this.header.set({ 2 left: [], 3 center: {}, 4 right: [ 5 { tagname: 'home', callback: function () { } }, 6 { tagname: 'tagname', value: 'value', data: {}, itemFn: function(){}, callback: function () { } } 7 ] 8 });
这个时候咱们应该怎么作呢?固然是不破不立,先破后立,固然是要求业务团队改!!!而后被无情的喷了回来,因而作了接口兼容
翻新老组件,接口兼容是必须的,若是不是底层机制发生颠覆,而颠覆能够带来颠覆性的成绩,接口仍是不建议改!
这里上面即是新接口的调用,下面是老接口的调用,效果以下:
源码:https://github.com/yexiaochai/cssui/tree/gh-pages
demo:http://yexiaochai.github.io/cssui/demo/debug.html#header
反馈:若是文中有何不足,请您留言