上次,我們形成了兩種header的布局,一種flexbox,一種float,最後與身邊做重構的同僚交流下來,選擇了float的布局。
事實上布局的選型不需要我關注,我的參與或者一些意見多數是自我提升,但要說html結構完全控制于csser的話就不一定了
在整個header元件的代碼過程中,我與重構同僚就一些地方發生了重複的交流,争論,今天就header元件的布局以及功能實作,聊一聊js與css的配合
然後header元件本身是一個老元件,我們順便探讨下,這類老元件應該如何翻新比較合适。
最初的結構
最開始重構的同僚給了我一個已經做好了的頁面:
我們針對其中一些小的體驗上做了讨論,并且知會到設計組,便改了,很順暢,然後我開始了愉快的代碼,這是其中一塊HTML的結構:
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标簽中的文字不說,因為其中可能表現的非常複雜,我們後面再說,其中的按鈕有以下功能:
① 第二行:回退按鈕
② 第三行:确認
PS:左邊采用float布局是以第一個元素在最右邊
③ 第四行:home标簽
④ 第五行:三個點,點選會出一個側邊欄
以上便是HTML的實作,但是對與程式員來說,頭部除了按鈕(btn)以外就隻有圖示(icon),是以以上的結構事實上js一般是不買賬的
Jser需要的結構
與重構同僚交流下來,原因是這樣的:
① 因為回退比較特殊,是以多了一個樣式,具體什麼我沒記住了
② icon代表背景圖,icond代表CSS3畫的,CSS3畫的可擴充性高,比如換顔色什麼的
③ ......
當時雙方的讨論還是比較激烈的,但是對icond全部變成icon,重構同僚不同意,于是也就作罷,經過一輪讨論,結構變成了這樣:
2 <span class="fl cm-header-icon"><i class="icon-back"></i></span>
做了很小的變化,将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>
但是由于多了一個映射關系,我的代碼便不好看了,并且業務邏輯還變得複雜了起來,于是帶着這些考量再次找到了重構同僚,重構同僚也很明事理,馬上答應改了:
5 <span class="fr cm-header-icon"><i class="icon-list"></i></span>
不考慮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>';
30 return str;
31 };
32 },
當發現某個按鈕不滿足需求或者有定制化需求時,便想辦法設定其itemFn即可,時候上這個代碼可以直接寫到初始化的json串去
花樣百出的title
到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);
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 });
這個時候我們應該怎麼做呢?當然是不破不立,先破後立,當然是要求業務團隊改!!!然後被無情的噴了回來,于是做了接口相容
翻新老元件,接口相容是必須的,如果不是底層機制發生颠覆,而颠覆可以帶來颠覆性的成績,接口還是不建議改!
這裡上面便是新接口的調用,下面是老接口的調用,效果如下:
代碼&demo
源碼:https://github.com/yexiaochai/cssui/tree/gh-pages
demo:http://yexiaochai.github.io/cssui/demo/debug.html#header
本文轉自葉小钗部落格園部落格,原文連結:http://www.cnblogs.com/yexiaochai/p/4095620.html,如需轉載請自行聯系原作者