JS选项卡从简单到高级

本文专门针对JavaScript选项卡来详细说明选项卡插件的开发和定制。从最简单的到高级复杂一点的,涉及到的知识较多,其中包括jQuery、面向对象、插件模块化以及基于AngularJS指令的组件化选项卡。阅读本文需要一定的JavaScript基础。

最简单的选项卡


HTML结构

1
2
3
4
5
6
7
8
9
10
11
12
<div class="tab-box">
<div class="tab-header">
<a class="tab-btn active" href="javascript:void(0)">选项一a>
<a class="tab-btn" href="javascript:void(0)">选项二a>
<a class="tab-btn" href="javascript:void(0)">选项三a>
div>
<div class="tab-content">
<div class="tab-item active">内容一div>
<div class="tab-item">内容二div>
<div class="tab-item">内容三div>
div>
div>

CSS样式(请看源码)

JavaScript代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$(function(){
//先获取所有的元素
var $tab = $('.tab-box');
var $btns = $tab.find('.tab-btn');
var $items = $tab.find('.tab-item');

//给按钮加事件
$btns.on('click', function(event) {
event.preventDefault();
//取得点击的索引
var index = $(this).index();

//激活对应的样式
$(this).addClass('active').siblings().removeClass('active');
$items.eq(index).addClass('active').siblings().removeClass('active');
});
});

解析:结构很简单,tab-header为所有按钮区域,tab-btn为单个按钮;tab-content为所有内容区域,tab-item为单个内容选项,激活状态都为active。效果如图(凑合着看吧 ~_~):

JS选项卡从简单到高级

给每个按钮添加click事件,然后设置对应按钮和内容的样式就行了,so easy吧~ 这是一个基础的选项卡,功能简单,但是不能达到复用的目的。只能复制代码然后使用,很明显这不是我们想要的生活。我们想要的是找对象,于是世界上出现了OOP(Object Oriented Programming)面向对象编程。下面来看看怎么通过面向对象来实现代码复用。

面向对象的选项卡


面向对象的基本构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Constructor
function Tab(){

}

//Defaults
Tab.defaults = {
event: 'click'
};

//Prototype
Tab.prototype = {
method: function(){
}
};

这是基本的结构,而我们要做的就是将我们需要的选项卡看成是一个对象Tab,而此对象有很多的属性defaults,例如按钮的样式,内容的样式等等。同时也有很多的方法prototype,例如初始化,事件添加啊等等……
基于此我们的构造函数可能就长这样了↓(部分代码省略,亲可以在下面下载源码看看)

选项卡的基本构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
var Tab = (function(){
//Constructor
function Tab(options, container){
//此插件依赖jQuery,如果没有加载jQuery就抛出异常
if (typeof jQuery === 'undefined') {
throw new Error('Tab requires jQuery!');
}

this.init();
}

//Defaults
Tab.defaults = {
container: '.tab-box',//容器的class
headerClass: '.tab-header',//按钮父容器class
btnClass: '.tab-btn',//按钮项class
btnActiveClass: 'active',//按钮激活的样式
contentClass: '.tab-content',//内容父容器class
itemClass: '.tab-item',//内容项class
itemActiveClass: 'active',//内容激活的样式
event: 'click'//触法的事件
};

//Prototype
Tab.prototype = {
/* 初始化 */
init: function(){

},
/* 激活选项卡 */
activeTab: function(){

},
/* 添加选项卡 */
addTab: function(title, content){

},
/* 删除选项卡 */
removeTab: function(){

}
};

return Tab;
}());

解析:这只是代码的大致结构,下面有提供下载源码的链接。由于此插件依赖于jQuery,在开始我就判断了jQuery是否加载,如果没有就抛出异常。Tab.defaults里面都是存放的一些默认的属性,当使用的时候如果传递options对象的话,那么就会覆盖默认的属性。

实例化插件

代码当中先声明了一个Tab然后里面套了一个闭包返回构造函数本身,这样做的目的就是为了内部代码的私有化,不会影响其它的外部代码,只提供了Tab对象给外部访问。那么怎么调用这段代码呢?
1
2
3
4
5
6
$(function(){
$('.tab-box').each(function() {
//创建新的选项卡实例
new Tab(null, this);
});
});

每一个新的选项卡都通过new Tab来创建一个新的选项卡实例,这样就达到了代码的高效复用并且更加容易维护。先遍历所有的选项卡$('.tab-box').each然后初始化每个选项卡,是不是很简单?

封装成jQuery插件,模块化


jQuery插件封装

相信大家在实际的开发中看到这样一些代码:
1
2
3
4
5
$('img').lazyload();

$('form').submit();

$('input').datetimepicker();

上面第一个是使用图片懒加载插件lazyload的例子,这样写无疑大大减少了代码的书写,而且看起来好像更加优雅了。这是jQuery插件的特有写法,而这里我们也可以这么做,只需要在最后加上这样一段代码。

1
2
3
4
5
6
//jQuery Plugin
$.fn.JQTab = function(options){
return this.each(function(){
new Tab(options, this);
});
};

使用时我们就可以这样任性了。

1
$('.tab-box').JQTab();

插件模块化

在针对多人开发,项目文件比较多的时候我们通常需要使用模块化开发来达到高效的团队协作、代码文件的组织。于是RequireJS和Seajs出现了。它们的出现很方便的帮我们解决了代码的模块化和文件的依赖管理,大大提高了开发效率。那么我们来将以上代码来封装成模块化的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
define(function(){
var Tab = function(){

};

Tab.defaults = {

};

Tab.prototype = {
init: function(){

}
};

return Tab;
});

使用的时候我们只需要引入对应的模块,以RequireJS为例。

1
2
3
require(['tab'], function(Tab){
new Tab(null, $('#tab-element'));
});

上面的代码基本能够满足我们的绝大多数需求,但是有时为了方便在其它的项目中使用,而此时该项目可能使用的是不同的模块化规范,那么我们必须更改代码的包装方式,很麻烦。为了解决如此多的烦恼我们可以将以上代码稍微更改,达到兼容AMD、CMD以及CommonJS的规范,来实现通用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(function(root, factory) {
if (typeof exports === 'object') {
//CommonJS规范
var jquery = require('jquery');
module.exports = factory(jquery);
}
else if (typeof define === 'function' && (define.amd || define.cmd)) {
//AMD、CMD规范
define('Tab', ['jquery'], factory);
}
else {
//全局的
root['Tab'] = factory(root.jQuery);
}
}(this, function($) {
var Tab = function() {

};

return Tab;
}));

是不是很爽?一段代码通用多个项目,其实这样的代码很常见,经常我们在使用其它的一些开源插件或框架的时候就能看到它们的身影。

如果此时的你已经觉得枯燥无味了,那么下面的东西绝对是干货!干货!干货!,重要的事情说三遍。

AngularJS指令组件化选项卡



最近几年这么流行AngularJS,那么,这里也顺便说一下,好让大家解解馋。目前国内外很多的项目在使用AngularJS,它是一个MVVM框架(有人说它不是,各抒已见吧)。其中AngularJS的directive指令功能很强大,在这里的选项卡就是使用指令实现的。你看完下面的代码就会发现竟然如此的简单优雅(是么~_~)。

HTML结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

<html ng-app="tabApp">
<head>
<meta charset="UTF-8">
<title>JavaScript选项卡title>
<link rel="stylesheet" type="text/css" href="style.css">
<script type="text/javascript" src="http://apps.bdimg.com/libs/jquery/1.9.1/jquery.min.js">script>
<script type="text/javascript" src="http://apps.bdimg.com/libs/angular.js/1.2.14/angular.min.js">script>
<script type="text/javascript" src="angular-simple.js">script>
head>
<body ng-controller="tabDemoController">

<div ui-tab="tabOptions1">div>

<div ui-tab="tabOptions2">div>

body>
html>

什么?HTML里面就这么多?是不是有点儿难以置信?眼尖的朋友可能看到了两个关键地方:html头部的ng-app="tabApp",以及body标签上的ng-controller="tabDemoController",它们分别是应用的名称和控制器的名称(不理解?没关系,先看看官网的文档吧AngularJS API Docs

定义选项卡模块

1
var tabApp = angular.module('tabApp', []);

这里的tabApp就是与html标签上的ng-app="tabApp"对应,就是应用的名称。当angular文件加载完成的时候回寻找ng-app指令,如果找到了就开始启动应用。ngApp参考官网的API

定义选项卡指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
tabApp.directive('uiTab', function(){
//指令uiTab
return {
scope: {
uiTab: '=?'
},
replace: true,
template: '
'
+''
+'
'
+'
{{tab.content}}
'

+'
'

+'
'
,

link: function(scope, element, attr){
scope.title = scope.uiTab.items[0].title;

element.on(scope.uiTab.event || 'click', '.tab-btn', function(event) {
event.preventDefault();

scope.$apply(function(){
scope.title = angular.element(event.currentTarget).scope().tab.title;
});
});
}
}
});

解析:其中scope: { uiTab: '=?' },表示会创建独立作用域,起到组件隔离保护的作用。replace: true表示会替换掉之前的元素,使用下面的template模板来渲染。而link函数就是我们的主要代码逻辑。那么我们代码写好了,怎么使用它呢?那么我们的控制器来了。控制器是干嘛的呢?在这里你可以将它理解为放置数据和组件参数设置的地方(还是数据)ngController参考官网的API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
tabApp .controller('tabDemoController', ['$scope', function($scope){
//选项卡一
$scope.tabOptions1 = {
event: 'click',
items: [{
title: '选项一',
content: '内容一'
},{
title: '选项二',
content: '内容二'
},{
title: '选项三',
content: '内容三'
}]
};

//选项卡二
$scope.tabOptions2 = {
event: 'mouseenter',
items: [{
title: '新闻一',
content: '新闻内容一'
},{
title: '新闻二',
content: '新闻内容二'
},{
title: '新闻三',
content: '新闻内容三'
}]
};
}]);

解析:页面当中有两个选项卡,它们的参数分别是tabOptions1tabOptions12,其中的items参数就是选项卡数据
,包括标题title和内容content。第一个选项卡点击click的时候才会触发,而第二个鼠标移入mouseenter的时候会触发。这是因为我们在传递参数的时候设置的不同。
下面是效果图:

JS选项卡从简单到高级

AngularJS很重要的一点是双向数据绑定,如果以jQuery的方式来看以上代码可能会给你犯糊涂。使用Angular时一定要记住从数据的方向来思考问题。上面controller中只是提供了一些数据参数的设置,我们的选项卡就初始化好了。而且HTML代码中仅仅只有一个

,很简洁,方便复用。我们使用不同的选项卡就定义不同的数据参数就行了,数据与结构的分离。这就是AngularJS指令的强大之处。

其实JavaScript选项卡还有很多其它的功能,比如选项卡的懒加载,选项卡的切换动画,这里我就不啰嗦了。写了这么多希望能对你有所帮助。最后附上 源码链接