天天看點

【HTML5&CSS3進階03】Jser與Csser如何一起愉快的翻新老元件

上次,我們形成了兩種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,如需轉載請自行聯系原作者

繼續閱讀