上次,我們形成了兩種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,重構同事不同意,於是也就作罷,經過一輪討論,結構變成了這樣:
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
到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 });
這個時候我們應該怎麼做呢?當然是不破不立,先破後立,當然是要求業務團隊改!!!然後被無情的噴了回來,於是做了介面相容
翻新老元件,介面相容是必須的,如果不是底層機制發生顛覆,而顛覆可以帶來顛覆性的成績,介面還是不建議改!
這裡上面便是新介面的呼叫,下面是老介面的呼叫,效果如下:
程式碼&demo
原始碼:https://github.com/yexiaochai/cssui/tree/gh-pages
demo:http://yexiaochai.github.io/cssui/demo/debug.html#header
反饋:如果文中有何不足,請您留言