時間 2016-08-05 10:47:15 稀土掘金javascript
原文 http://liuyy.coding.me/2016/03/30/javscript/如何寫一個完善的jquery ui組件/java
主題 jQuery UIjquery
在平常工做中,經常會遇到下面這樣的需求git
咱們須要對上面的tab作處理,點擊相應的tab就顯示相應的內容github
最原始的寫法緩存
<div class="tab-container"> <ul class="nav nav-tabs"> <li name="tab1" class="active"><a href="javascript:;">tab1</a></li> <li name="tab2"><a href="javascript:;">tab2</a></li> <li name="tab3"><a href="javascript:;">tab3</a></li> </ul> <div id="tab1">this is tab1</div> <div id="tab2" style="display: none;">this is tab2</div> <div id="tab3" style="display: none;">this is tab3</div> </div>
$(function() { $('.tab-container').on('click', 'li', function() { var tabIds = ['tab1', 'tab2', 'tab3']; var id = $(this).attr('name'); //便利全部的tabid,判斷是當前點擊的id 就顯示,不然隱藏 $.each(tabIds, function(i, el) { if (id === el) { $('.tab-container li[name=' + el + ']').addClass('active'); $('#' + el).show(); } else { $('.tab-container li[name=' + el + ']').removeClass('active'); $('#' + el).hide(); } }); }); });
經過 li 標籤的事件綁定,每次都便利全部的tab,顯示點擊的,隱藏其餘的,這種作法能夠實現tab 切換的問題可是還有幾個缺點。dom
爲了解決上面的問題,咱們把這種邏輯封裝成一個tabs插件, jquery對插件的支持使用 $.fnjquery插件
$.fn = jQuery.prototype; //$.fn執行jQuery對象的原型
代碼以下:ide
$.fn.tabs = function(){ var $element = this;//上下文 var showName = null;//當前顯示tab 儲存 //初始化操做 $element.find('div').hide();//現將全部隱藏掉 $element.find('.nav li').removeClass('active');//消除全部的選擇狀態 showName = $element.find('.nav li:eq(0)').attr('name');//初始化設置顯示第一個tab //事件監聽 $element.on('click', 'li', function(){ show($(this).attr('name')); }); show(); //顯示邏輯 function show(name){ if(name === showName) return; if(name){ //先隱藏原來的 hide(showName); showName = name; } //顯示當前的 $('#' + showName, $ ).show(); $('.nav li[name=' + showName + ']', $element).addClass('active'); } function hide(name){ //jquery方法的第二個參數表示在哪一個dom搜索,默認爲document $('#' + name, $element).hide(); $('.nav li[name=' + name + ']', $element).removeClass('active'); } }; $(function() { $('.tab-container').tabs(); });
這樣實現以後,再別的地方須要調用只只須要調用 tabs 方法便可,可是,這種實現仍然是粗糙的。 show 和 hide 方法在每次調用tabs時都會從新建立,這樣比較消耗內存。性能
用面向對象處理,將一些方法和屬性放在原型中處理,更加高效。在編碼以前,咱們須要對這個組件進行分析,整個 tabs 由兩個部分組成,一個導航的菜單 menu , 一個是與之對應的內容(就是下面那個div) panel , 能夠把一個 menu 和 panel 組成一個對象 tab , 而 tab 有兩個方法 show 和 hide 控制它的顯示和隱藏,而咱們的 tabs 就是 tab 對象的集合,控制着每個tab的顯示和隱藏.
function Tabs($element){ //初始化每個tab對象 this.tabs = {};//儲存tab var self = this; $('div', $element).hide();//現將全部隱藏掉 $('.nav li', $element).removeClass('active');//消除全部的選擇狀態 this.showName = $('.nav li:eq(0)', $element).attr('name');//初始化設置顯示第一個tab $('.nav li', $element).each(function(){ var name = $(this).attr('name'); var $menu = $(this); var $panel = $('#' + name, $element); var tab = new Tab($menu, $panel); self.tabs[name] = tab;//緩存tab }); //事件監聽 $element.on('click', '.nav li', function(){ self.show($(this).attr('name')); }); this.show(); } Tabs.prototype.show = function(name){ if(name === this.showName) return; if(name){ this.tabs[this.showName].hide();//隱藏原來的 this.showName = name; } this.tabs[this.showName].show();//顯示當前的 }; function Tab($menu, $panel){ this.$menu = $menu; this.$panel = $panel; } Tab.prototype.show = function(){ this.$menu.addClass('active'); this.$panel.show(); }; Tab.prototype.hide = function(){ this.$menu.removeClass('active'); this.$panel.hide(); }; $.fn.tabs = function(){ new Tabs(this); };
用面向對象方法編寫的組件,擁有了不錯的性能,並且在代碼邏輯上更爲清晰,理解起來比以前的寫法要複雜一點。
再上面的代碼中咱們能夠看到,全部的tabs組件開始都是顯示的第一個,若是,咱們想在最初顯示第二個或是第三個,這個組件就沒法知足咱們的需求,由於它尚未加入參數(options)的處理。
一般在封裝時將可變,與核心邏輯無關的屬性,抽取出來成爲這個組件 options , 例如 tabs 組件中,它默認顯示的tab頁,以及選中tab時li上的樣式,這些咱們均可以提取出來,以保證的組件的靈活性
Tabs.defaults = { showName : null, //當前顯示tab名稱 activeClass : 'active', //當tab被選擇時的樣式 menuSelector : '.nav li', //找到menu的選擇器 menuAttr : 'name' //指定menu上指定屬於哪一個tab名稱的屬性 }; //存儲組件默認的參數 function Tabs($element, options){ //初始化每個tab對象 this.tabs = {};//儲存tab this.$element = $element; var op = this.options = $.extend({}, Tabs.defaults, options);//初始化參數 var self = this; $('div', $element).hide();//現將全部隱藏掉 $(op.menuSelector, $element).removeClass(op.activeClass);//消除全部的選擇狀態 if(!op.showName) this.showName = $(op.menuSelector + ':eq(0)', $element).attr(op.menuAttr);//初始化設置顯示第一個tab else this.showName = op.showName; var attrName = op.menuAttr; $('.nav li', $element).each(function(){ var name = $(this).attr(attrName); var $menu = $(this); var $panel = $('#' + name, $element); var tabOp = { activeClass : op.activeClass }; var tab = new Tab($menu, $panel, tabOp); self.tabs[name] = tab;//緩存tab }); //事件監聽 $element.on('click', op.menuSelector, function(){ self.show($(this).attr(attrName)); }); this.show(); } Tabs.prototype.show = function(name){ if(name === this.showName) return; if(name){ this.tabs[this.showName].hide();//隱藏原來的 this.showName = name; } this.tabs[this.showName].show();//顯示當前的 }; function Tab($menu, $panel, tabOp){ this.$menu = $menu; this.$panel = $panel; this.options = tabOp; } Tab.prototype.show = function(){ this.$menu.addClass(this.options.activeClass); this.$panel.show(); }; Tab.prototype.hide = function(){ this.$menu.removeClass(this.options.activeClass); this.$panel.hide(); }; $.fn.tabs = function(options){ new Tabs(this, options); }; $(function() { $('.tab-container').tabs({ showName : 'tab2' }); });
加入了參數處理以後,組件就有了不錯的靈活性,可是,組件內部發生改變時,再外面沒法得知,假如咱們有這樣一個需求,再某個 tab 顯示的時候,初始化這個tab的數據,這個時候咱們就須要給組件假如事件處理,當內部發生改變的時候,觸發某個事件,外部能夠監聽事件來觀察組件的變化,作相應的處理
Tabs.prototype.show = function(name){ if(name === this.showName) return; if(name){ this.tabs[this.showName].hide();//隱藏原來的 this.showName = name; } this.tabs[this.showName].show();//顯示當前的 this.$element.trigger('tabs.show', this.showName);//使用jQuery的事件處理機制對於組件自定義事件通常都是,組件名稱.事件名稱 }; $('.tab-container').on('tabs.show', function(e, name) { alert(name); });
不少的時候組件也會有一些方法讓外部調用,已達到手動改變組件狀態的效果,例如咱們須要再點擊一個按鈕以後顯示 tab2 ,這種時候咱們須要修改定義插件那部分的代碼,以暴露方法給外部調用
$.fn.tabs = function(options){ if($.type(options) === 'object'){ //實例化 var tabs = new Tabs(); this.data('tabs', tabs); }else{ var tabs = this.data('tabs'); var returnVal; if(tabs[options] && $.isFunction(tabs[options])){ returnVal = tabs[options].call(tabs, Array.prototype.slice.call(arguments,1)); } if(returnVal) return returnVal; } return this; } //調用方法 $('.tab-container').tabs('show', 'tab3');
到這一個完善的組件就構建完成啦,再編寫組件中還有一些須要注意的:
我本身有封裝一個jquery插件來定義組件,把組件定義經常使用的操做例如事件、外部調用方法,等等都有封裝
用jquery-component定義組件:
$.component('tabs', { //默認參數 options: { showName: null, //當前顯示tab名稱 activeClass: 'active', //當tab被選擇時的樣式 menuSelector: '.nav li', //找到menu的選擇器 menuAttr: 'name' //指定menu上指定屬於哪一個tab名稱的屬性 }, //實例化調用方法 init: function() { //初始化每個tab對象 this.tabs = {}; //儲存tab var $element = this.$element; var self = this; var op = this.options; $('div', $element).hide(); //現將全部隱藏掉 $(op.menuSelector, $element).removeClass(op.activeClass); //消除全部的選擇狀態 if (!op.showName) this.showName = $(op.menuSelector + ':eq(0)', $element).attr(op.menuAttr); //初始化設置顯示第一個tab else this.showName = op.showName; var attrName = op.menuAttr; $('.nav li', $element).each(function() { var name = $(this).attr(attrName); var $menu = $(this); var $panel = $('#' + name, $element); var tabOp = { activeClass: op.activeClass }; var tab = new Tab($menu, $panel, tabOp); self.tabs[name] = tab; //緩存tab }); //事件監聽 $element.on('click', op.menuSelector, function() { self.show($(this).attr(attrName)); }); this.show(); }, show: function(name) { if (name === this.showName) return; if (name) { this.tabs[this.showName].hide(); //隱藏原來的 this.showName = name; } this.tabs[this.showName].show(); //顯示當前的 this._trigger('show', this.showName);//對事件的封裝 }, destory : function(){ //當元素被銷燬的時候回自動調用這個方法 } }); function Tab($menu, $panel, tabOp) { this.$menu = $menu; this.$panel = $panel; this.options = tabOp; } Tab.prototype.show = function() { this.$menu.addClass(this.options.activeClass); this.$panel.show(); }; Tab.prototype.hide = function() { this.$menu.removeClass(this.options.activeClass); this.$panel.hide(); }; $(function() { $('.tab-container').tabs({ showName: 'tab2' }); $('.tab-container').on('tabs.show', function(e, name) { alert(name); }); });