时间 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); }); });