[js插件开发教程]原生js仿jquery架构扩展开发选项卡插件

jquery插件一般是这么干的: $.fn.插件名称 = function(){}, 把插件的名称加在.fn上,在源码里面实际上是扩展到构造函数的原型对象上,如果你没看过jquery的源代码,或者你曾经看过,但是不知道为什么把插件扩展到fn上,那么本篇文章就能解答你的疑惑。关于jquery插件开发方式,可以参考我的这篇文章:[js高手之路]jquery插件开发实战-选项卡详解

关于选项卡这个功能具体怎么做,不在这里详解,这个是入门级的功能,本文重在讨论插件开发的架构,扩展,以及参数设置。

如果你使用过jquery的选项卡插件,或者其他类型的插件,他们一般都是这么调用的:

$( ".tab" ).tabs( {} )

$(".tab").tabs( function(){} );

一种是传递参数定制插件行为

一种是传递函数定制插件行为

$(".tab") 选择到元素,然后返回的是jquery对象

tabs方法扩展在fn上就是扩展都jquery构造函数的原型对象上, 那么对象( $(".tab") )调用原型对象上的方法( tabs )当然就顺利成章了。

所以jquery插件扩展本质就是: 构造函数 + 原型对象扩展方法

定义一个构造+原型的方式,下面代码的原理,我在这篇文章有详细论述:[js高手之路] 设计模式系列课程 - jQuery的链式调用与灵活的构造函数

 1 var G = function( selectors, context ){
 2         return new G.fn.init( selectors, context );
 3     }
 4     G.fn = G.prototype = {
 5         length : 0,
 6         constructor : G,
 7         size : function(){
 8             return this.length;
 9         },
10         init : function( selector, context ){
11             this.length = 0;
12             context = context || document;
13             if ( selector.indexOf( '#' ) == 0 ){
14                 this[0] = document.getElementById( selector.substring( 1 ) );
15                 this.length = 1;
16             }else {
17                 var aNode = context.querySelectorAll( selector );
18                 for( var i = 0, len = aNode.length; i < len; i++ ) {
19                     this[i] = aNode[i];
20                 }
21                 this.length = len;
22             }
23             this.selector = selector;
24             this.context = context;
25             return this;
26         }
27     }
28 
29     G.fn.init.prototype = G.fn;
View Code

接下来,我们还要添加一个插件扩展机制:

 1 G.extend = G.fn.extend = function () {
 2         var i = 1,
 3             len = arguments.length,
 4             dst = arguments[0],
 5             j;
 6         if (dst.length === undefined) {
 7             dst.length = 0;
 8         }
 9         if (i == len) {
10             dst = this;
11             i--;
12         }
13         for (; i < len; i++) {
14             for (j in arguments[i]) {
15                 dst[j] = arguments[i][j];
16                 dst.length++;
17             }
18         }
19         return dst;
20     };
View Code

在这篇文章:[js高手之路] 设计模式系列课程 - jQuery的extend插件机制 有详细的论述,extend插件扩展机制

像使用jquery一样暴露接口:

var $ = function( selectors, context ){
return G( selectors, context );
}
window.$ = $;

这个插件的扩展机制和元素选择机制就完成了,如果要扩展插件,只要在

G.fn上扩展插件的名称即可,如:

 1 G.fn.tabs = function( options ){
 2     options = options || {};
 3     var defaults = {
 4         contentClass : 'tab-content',
 5         navClass : 'tab-nav',
 6         activeClass : 'active',
 7         triggerElements : '*',
 8         activeIndex : 0,
 9         evType : 'click',
10         effect : 'none'
11     };
12 
13     var opt = G.extend( {}, defaults, options );
14     return this;
15 }

这样,我们就在G的原型对象上扩展了一个tabs( 选项卡 )插件

options可以定制插件的行为:

contentClass : 'tab-content',     选项卡内容区域的class名称
navClass : 'tab-nav', 标签卡区域的class名称
activeClass : 'active', 标签卡默认选择的class名称:active
triggerElements : '*', 标签卡默认触发元素
activeIndex : 0, 默认选中第几个标签卡
evType : 'click', 选项卡触发的事件类型
effect : 'none' 是否有过渡特效:如透明度


var opt = G.extend( {}, defaults, options );

这一段是把定制的配置和默认配置合成到一个对象opt里面,后面的插件行为,就可以根据opt的配置进行定制,这是插件开发参数定制中,常用的一招。

这样做的好处,可以防止污染默认配置defaults

1  var tabContent = this[0].querySelector( "." + opt.contentClass );
2         var tabContentEle = tabContent.children;
3         var tabNavEle = this[0].querySelectorAll( "." + opt.navClass + '>' + opt.triggerElements );
4 
5         var _contentLen = tabContentEle.length;
6         var _index = opt.activeIndex;

获取对应的元素。

有了选项卡的元素和配置,我们就开始做业务处理(为所有选项卡添加处理的事件,进行选项卡切换)

定义一个专门的对象_api = {}, 扩展业务api

 1 G.fn.tabs = function( options ){
 2         options = options || {};
 3         var defaults = {
 4             contentClass : 'tab-content',
 5             navClass : 'tab-nav',
 6             activeClass : 'active',
 7             triggerElements : '*',
 8             activeIndex : 0,
 9             evType : 'click',
10             effect : 'none'
11         };
12 
13         var opt = G.extend( {}, defaults, options );
14 
15         var tabContent = this[0].querySelector( "." + opt.contentClass );
16         var tabContentEle = tabContent.children;
17         var tabNavEle = this[0].querySelectorAll( "." + opt.navClass + '>' + opt.triggerElements );
18 
19         var _contentLen = tabContentEle.length;
20         var _index = opt.activeIndex;
21 
22         var _api = {};
23 
24         _api.setIndex = function( index ){
25             //当前标签加上active样式,其余标签删除active样式
26             for ( var i = 0; i < _contentLen; i++ ) {
27                 if ( tabNavEle[i].classList.contains( 'active' ) ) {
28                     tabNavEle[i].classList.remove('active');
29                 }
30             }
31             tabNavEle[index].classList.add( 'active' );
32             switch ( opt.effect ){
33                 case 'fade':
34                     break;
35                 default:
36                     for ( var i = 0; i < _contentLen; i++ ) {
37                         tabContentEle[i].style.display = 'none';
38                     }
39                     tabContentEle[index].style.display = 'block';
40                     _index = index;
41             }
42         }
43 
44         _api.setIndex( _index ); //默认的选项卡
45 
46         //所有的标签绑定事件
47         for( var i = 0, len = tabNavEle.length; i < len; i++ ) {
48             tabNavEle[i].index = i;
49             tabNavEle[i].addEventListener( opt.evType, function(){
50                 var i = this.index;
51                 _api.setIndex( i );
52             }, false );
53         }
54 
55         return this;
56     }
View Code

完整的插件代码:

  1 /**
  2  * Created by ghostwu(吴华).
  3  */
  4 (function(){
  5     var G = function( selectors, context ){
  6         return new G.fn.init( selectors, context );
  7     }
  8     G.fn = G.prototype = {
  9         length : 0,
 10         constructor : G,
 11         size : function(){
 12             return this.length;
 13         },
 14         init : function( selector, context ){
 15             this.length = 0;
 16             context = context || document;
 17             if ( selector.indexOf( '#' ) == 0 ){
 18                 this[0] = document.getElementById( selector.substring( 1 ) );
 19                 this.length = 1;
 20             }else {
 21                 var aNode = context.querySelectorAll( selector );
 22                 for( var i = 0, len = aNode.length; i < len; i++ ) {
 23                     this[i] = aNode[i];
 24                 }
 25                 this.length = len;
 26             }
 27             this.selector = selector;
 28             this.context = context;
 29             return this;
 30         }
 31     }
 32 
 33     G.fn.init.prototype = G.fn;
 34     G.extend = G.fn.extend = function () {
 35         var i = 1,
 36             len = arguments.length,
 37             dst = arguments[0],
 38             j;
 39         if (dst.length === undefined) {
 40             dst.length = 0;
 41         }
 42         if (i == len) {
 43             dst = this;
 44             i--;
 45         }
 46         for (; i < len; i++) {
 47             for (j in arguments[i]) {
 48                 dst[j] = arguments[i][j];
 49                 dst.length++;
 50             }
 51         }
 52         return dst;
 53     };
 54 
 55     G.fn.tabs = function( options ){
 56         options = options || {};
 57         var defaults = {
 58             contentClass : 'tab-content',
 59             navClass : 'tab-nav',
 60             activeClass : 'active',
 61             triggerElements : '*',
 62             activeIndex : 0,
 63             evType : 'click',
 64             effect : 'none'
 65         };
 66 
 67         var opt = G.extend( {}, defaults, options );
 68 
 69         var tabContent = this[0].querySelector( "." + opt.contentClass );
 70         var tabContentEle = tabContent.children;
 71         var tabNavEle = this[0].querySelectorAll( "." + opt.navClass + '>' + opt.triggerElements );
 72 
 73         var _contentLen = tabContentEle.length;
 74         var _index = opt.activeIndex;
 75 
 76         var _api = {};
 77 
 78         _api.setIndex = function( index ){
 79             //当前标签加上active样式,其余标签删除active样式
 80             for ( var i = 0; i < _contentLen; i++ ) {
 81                 if ( tabNavEle[i].classList.contains( 'active' ) ) {
 82                     tabNavEle[i].classList.remove('active');
 83                 }
 84             }
 85             tabNavEle[index].classList.add( 'active' );
 86             switch ( opt.effect ){
 87                 case 'fade':
 88                     break;
 89                 default:
 90                     for ( var i = 0; i < _contentLen; i++ ) {
 91                         tabContentEle[i].style.display = 'none';
 92                     }
 93                     tabContentEle[index].style.display = 'block';
 94                     _index = index;
 95             }
 96         }
 97 
 98         _api.setIndex( _index ); //默认的选项卡
 99 
100         //所有的标签绑定事件
101         for( var i = 0, len = tabNavEle.length; i < len; i++ ) {
102             tabNavEle[i].index = i;
103             tabNavEle[i].addEventListener( opt.evType, function(){
104                 var i = this.index;
105                 _api.setIndex( i );
106             }, false );
107         }
108 
109         return this;
110     }
111 
112     var $ = function( selectors, context ){
113         return G( selectors, context );
114     }
115     window.$ = $;
116 })();
View Code

选项卡布局:

 1 <!DOCTYPE html>
 2 <html>
 3 <head lang="en">
 4     <!--作者:ghostwu(吴华)-->
 5     <meta charset="UTF-8">
 6     <title>选项卡插件 - by ghostwu</title>
 7     <link rel="stylesheet" href="css/tab.css"/>
 8     <script src="./js/tab.js"></script>
 9     <script>
10         window.onload = function () {
11 //            console.log( $(".tab1 .tab-nav li") );
12 //            $( ".tab1" ).tabs( { 'evType' : 'mouseenter' } );
13             $( ".tab1" ).tabs();
14         }
15     </script>
16 </head>
17 <body>
18 <div class="main">
19     <div class="tab tab1">
20         <ul class="tab-nav">
21             <li class="active"><a href="javascript:;">标签1</a></li>
22             <li><a href="javascript:;">标签2</a></li>
23             <li><a href="javascript:;">标签3</a></li>
24             <li><a href="javascript:;">标签4</a></li>
25         </ul>
26         <div class="tab-content">
27             <p>内容1</p>
28             <p>内容2</p>
29             <p>内容3</p>
30             <p>内容4</p>
31         </div>
32     </div>
33 </div>
34 </body>
35 </html>
View Code

选项卡插件样式:

 1 * {
 2     margin: 0;
 3     padding: 0;
 4 }
 5 body {
 6     font-size: 14px;
 7     font-family: Tahoma, Verdana,"Microsoft Yahei";
 8 }
 9 a{
10     text-decoration: none;
11     color:#000;
12 }
13 ul,li{
14     list-style-type: none;
15 }
16 img {
17     border:none;
18 }
19 .main {
20     width:960px;
21     margin:20px auto;
22 }
23 .tab{
24     margin: 0 auto 20px;
25 }
26 .tab1 .tab-nav{
27     margin-bottom:8px;
28 }
29 .tab .tab-nav {
30     overflow:hidden;
31 }
32 .tab1 .tab-nav .active{
33     border-bottom:1px solid #000;
34 }
35 .tab1 .tab-nav li {
36     float:left;
37     margin:0 10px;
38 }
39 .tab1 .tab-nav li a {
40     line-height:40px;
41     display:block;
42 }
43 .tab1 .tab-content {
44     height:250px;
45     overflow:hidden;
46 }
47 .tab1 .tab-content p {
48     height:250px;
49     background:#eee;
50 }
View Code

最终效果:

 

posted @ 2017-10-20 16:49  ghostwu  阅读(971)  评论(2编辑  收藏
Copyright ©2017 ghostwu