基于 jQuery 与 Bootstrap 简单封装一个表格分页的组件

最近遇到一个需求:页面上的数据可能会有很多条,需要将数据分页展示在表格中。项目用的是 jQuery 和 Bootstrap,本来想直接用 bootstrapTable 插件,但是需要额外引入 js 文件、语言包等等,样式、语言翻译啥的都不太好做,索性还是决定参照这个,基于 jQuery 和 Bootstrap,自己实现一个简单版的。因为项目提供的后端接口直接将查询到的所有数据返回,所以这个分页实际上只是个假分页罢了,抓住关键点——当前页展示的数据与当前页码以及每页展示数量有关,其它的就好办了。先明确要实现的效果。

  1. 表头根据指定数据动态生成
  2. 可以修改每页展示的数据条数
  3. 要显示当前页相关信息,包括当前页码、当前页数据是第几条到第几条、数据总条数
  4. 表格提供切换页码操作,可以切换上一页、下一页、首页和末页
  5. 表格提供多选操作,可以选择某一条数据或选中多条数据以及全选、全不选

所以整个分页组件可以分为三大部分:选择每页展示条数的选择框、展示数据的表格(可以多选、可以操作)、底部显示当前页相关信息以及切换页码的按钮。html 以及 css 部分的代码如下,注意要引入 jQuery 以及 Bootstrap 相关的 js 和 css 文件。

<div class="table-box" id="tableList">
	<div class="per-page row">
		<div class="col-xs-4">
			<span>每页展示span>
			<div class="dropdown selectPageSize">
				<input type="hidden" class="pageSize" value="10">
				<button class="form-control dropdown-toggle" data-toggle="dropdown">10button>
				<ul class="dropdown-menu" role="menu">
					<li data-value="3"><label>3label>li>
					<li data-value="5"><label>5label>li>
					<li data-value="10"><label>10label>li>
				ul>
			div>
		div>
	div>
	<table class="table"
		   data-multi-select="true"
		   data-has-id="true"
		   data-has-operate="true"
		   data-operate-items="onlyDelete">
		<thead>thead>
		<tbody>tbody>
	table>
	<div class="table-footer row">
		<div class="col-xs-1">
			<input type="checkbox" class="selectAll">
			<span>全选span>
		div>
		<div class="col-xs-11">
			<span class="dataInfo">span>
			<ul class="pagination">
				<li><span class="startPage" aria-label="Start" aria-hidden="true">«span>li>
				<li><span class="prevPage" aria-label="Previous" aria-hidden="true"><span>li>
				<li class="active"><span class="nowPage">1span>li>
				<li><span class="nextPage" aria-label="Next" aria-hidden="true">>span>li>
				<li><span class="endPage" aria-label="End" aria-hidden="true">»span>li>
			ul>
		div>
	div>
div>
/* 整体的样式 */
.table-box {
	width: 80%;
	border-radius: 5px;
	margin: 20px auto;
	border: 1px solid #f2f2f2;
}
.per-page {
	margin: 15px -10px;
}
/* 选择框 */
.selectPageSize {
	display: inline-block;
	width: 50px;
	margin-left: 5px;
}
.dropdown-toggle {
	padding: 5px;
	height: 25px;
	line-height: 15px;
}
/* 选择框中的小三角 */
.dropdown-toggle::after {
	display: block;
	content: "";
	float: right;
	width: 0;
	height: 0;
	border-color: #FFFFFF transparent;
	border-style: solid;
	border-width: 4px 4px 0 4px;
	margin-top: 6px;
	border-top-color: #FFFFFF;
}
/* 下拉列表 */
.dropdown-menu {
	width: 100%;
	min-width: unset;
	padding: 0;
	overflow-y: auto;
}
.dropdown-menu > li > label {
	width: 100%;
	padding: 2px 10px;
	margin: 0;
	font-weight: 400;
	text-align: center;
}
/* 鼠标悬停在选择框某一项 */
.dropdown-menu > li > label:hover {
	color: #FFFFFF;
	background-color: #0066cc;
}
/* 表头 */
.table > thead > tr > th {
	text-align: center;
	font-weight: normal;
	line-height: 34px;
	border: none;
	background-color: #f2f2f2;
}
/* 可多选表格的多选框样式 */
.table[data-multi-select="true"] > thead > tr > th:first-child {
	width: 30px;
	line-height: 30px;
}
/* 单元格样式 */
.table > tbody > tr > td {
	text-align: center;
	border: none;
	line-height: 24px;
}
.table > tbody > tr:last-child {
	border-bottom: 1px solid #f2f2f2;
}
/* 可操作的表格 */
.table[data-has-operate="true"] > tbody .deleteThis {
	color: orange;
	cursor: pointer;
}
.table[data-has-operate="true"] > tbody .editThis {
	color: #0066cc;
	cursor: pointer;
}
.table-footer {
	margin: 15px -15px;
}
/* 可多选的表格要在表头以及表格下方提供多选框 */
.table[data-multi-select="true"] + .row > div:first-child {
	display: inline-flex;
	align-items: center;
	justify-content: flex-start;
	padding-left: 25px;
}
.table[data-multi-select="true"] + .row > div:first-child > input {
	margin: 0 5px 0 0;
}
.table[data-multi-select="true"] + .row > div:first-child > span {
	text-align: center;
	flex: 1;
}
.table-footer > div:last-child {
	display: inline-flex;
	align-items: center;
	justify-content: flex-end;
}
/* 分页部分的样式 */
.pagination {
	margin: 0 10px;
}
.pagination > li > span {
	padding: 0 10px;
	margin: 0 3px;
	border-radius: 4px;
	cursor: pointer;
}
.pagination > li > span:hover,
.pagination > li > span:focus {
	background-color: #FFFFFF;
}
.dropdown-toggle,
.dropdown-menu > li > label:hover,
.pagination > li.active > span,
.pagination > li.active > span:hover,
.pagination > li.active > span:focus {
	color: #FFFFFF;
	border-color: #0066cc;
	background-color: #0066cc;
}

组件基本的结构和样式已经完成,接下来是最关键的 js 部分。针对上面列出的5个效果,来一步步分析如何通过 js 实现。首先肯定要获取我们后续要操作的对象,基于 jQuery 封装一个方法,代码如下。

(function($){
	$.fn.initTable = function(data, isFirst, thName) {
		// data: 表格需要展示的所有数据 isFirst: 需要添加表头、绑定事件等 thName: 表头的名称
		isFirst = isFirst || 0;
		// nPanel: 当前表格组件对象
		var nPanel = this;
		// pageSize: 每页展示条数
		var pageSize = nPanel.find(".pageSize").val();
		// total: 数据总条数
		var total = data.length;
		// tPage: 页面总数
		var tPage = Math.ceil(total/perPage);
		// nPage: 当前页数
		var nPage = parseInt(nPanel.find(".nowPage").text());
		var table = nPanel.find(".table");
		// multi: 表格数据是否可多选
		var multi = table.attr("data-multi-select") === "true" ? 1 : 0;
		// hasID: 表是否有序号列
		var hasID = table.attr("data-has-id") === "true" ? 1 : 0;
		// hasOp: 表是否有操作列
		var hasOp = table.attr("data-has-operate") === "true" ? 1 : 0;
		// operate: 表的操作列有哪些操作
		var operate = table.attr("data-operate-items") || "all";
	}
})(jQuery);

先考虑如何生成表头,需要传入一个数组 thName,存放表头的名称,并非每次调用 initTable 方法时都需要重新生成表头以及给元素绑定事件,通过参数 isFirst 来判断,如果是第一次调用 initTable 方法则需要生成表头、给选择每页条数、多选框等绑定事件。在给每行的元素(比如多选框)绑定事件时要注意,因为表格每一行都是动态生成的,现在还获取不到目标元素,无法直接绑定事件,所以采用事件委托的方式,通过 jQuery 实现起来还是很简单的。对于页码切换的事件绑定则需要在每次调用 initTable 方法的时候重新绑定,否则还是基于最初的数据进行更改。代码如下。

// 首次调用该方法,添加表头、绑定事件、应用样式等
if (isFirst === 1) {
	if (multi) {
		nPanel.find(".table-footer").find("div:first-child").show();
		nPanel.find(".table-footer").find("div:last-child").removeClass("col-xs-offset-1");

		// 选中所有条目
		nPanel.on("click", ".selectAll", function(){
			nPanel.find(":checkbox:not(:disabled)").prop("checked", $(this).is(":checked"));
		});

		// 当全选按钮被选中时,取消选中表格任意一条的同时取消选中全选
		table.on("click", ":checkbox:not(:disabled)", function(){
			if (nPanel.find(".selectAll").is(":checked") && !$(this).is(":checked")) {
				nPanel.find(".selectAll").prop("checked", false);
			}
		});
	} else {
		nPanel.find(".table-footer").find("div:first-child").hide();
		nPanel.find(".table-footer").find("div:last-child").addClass("col-xs-offset-1");
	}

	// 根据传入的表头名称,动态生成表头
	// 因为表头只有一行,就可以直接生成一个 tr 元素,不需要文档碎片
	var thEle = $("");
	thName.forEach(function(v, i){
		if (i === 0) {
			// 表格是否可多选
			if (multi) {
				thEle.append("");
			}
			// 表格是否有序号
			if (hasID) {
				thEle.append("序号");
			}
		}
		thEle.append(""+v+"");
		// 表格是否可操作
		if (i === thName.length-1 && hasOp) {
			thEle.append("操作");
		}
	});
	table.find("thead").append(thEle);

	// 选择每页展示数量
	$(".dropdown-menu").on("click", "li", function(){
		var inputBox = $(this).parent().siblings("input");
		var nowValue = $(this).attr("data-value");
		var nowText = $(this).find("label").text();
		if (nowValue !== inputBox.val()) {
			// 新值与旧值对比,发生了变化才更改
			$(this).parent().prev().text(nowText);
			inputBox.val(nowValue);
			// 由于直接修改 input 值不会触发 change 事件,因此使用 trigger 方法触发自定义事件 changed,并将当前值传给它
			inputBox.trigger("changed", nowValue);
		}
	});
} else {
	// 不是第一次调用则将需要先移除之前的事件,重新绑定事件
	$(".pageSize").off("changed");
	$(".startPage, .prevPage, .endPage, .nextPage").off("click");
}

// 监听自定义事件 changed,更新表格当前页数据
$(".pageSize").on("changed", function(e, v){
	pageSize = v;
	tPage = Math.ceil(total/pageSize);
	nPage = nPage > tPage ? tPage : nPage;
	changeRange();
});

// 跳转到第一页或上一页
$(".startPage, .prevPage").on("click", function(){
	if (nPage > 1) {
		nPage = $(this).hasClass("startPage") ? 1 : nPage-1;
		changeRange();
	} else {
		alert("当前已经在第一页");
	}
});

// 跳转到最后一页或下一页
$(".endPage, .nextPage").on("click", function(){
	if (nPage < tPage) {
		nPage = $(this).hasClass("endPage") ? tPage : nPage+1;
		changeRange();
	} else {
		alert("当前已经在最后一页");
	}
});

现在表头有了,数据也有了,接下来要将它们放到表格。因为在修改页码以及每页展示数量时,都需要重新渲染表格主体内容,所以将这部分代码放到 changeRange 函数中方便直接调用。思路大概是这样:已知当前页码和每页展示数量,就可以知道当前页需要展示的数据是哪些(数组中第几条到第几条的数据)。接下来就是dom操作了,遍历当前页的数据,每一条数据对应表格的一行(tr),具体的数据项放到对应单元格(td),遍历完之后利用文档碎片一次性插入到 tbody 中完成显示。代码如下。

// 修改表格主体内容
function changeRange(){
	// 清空表格主体部分
	table.find("tbody").empty();

	// 更新当前页码
	nPanel.find(".nowPage").text(nPage);

	// 当前页第一条数据在数组中的索引号
	var pageStart = (nPage-1)*pageSize;

	// 当前页最后一条数据的序号
	var pageEnd = nPage*pageSize > total ? total : nPage*pageSize;

	// 取消选定所有多选框
	if (multi) {
		nPanel.find(":checkbox").prop("checked", false);
	}

	// 更新当前页的信息显示
	nPanel.find(".dataInfo").text("第"+((total === 0) ? 0 : (pageStart+1))+"到第"+pageEnd+"条,总共"+total+"条");

	// 利用碎片化文档,避免频繁操作 DOM
	var dom = document.createDocumentFragment();
	if (total > 0) {
		// 获取当前页的数据,利用数组的 slice 方法截取数据片段
		var nowPageData = data.slice(pageStart, pageEnd);

		// 遍历数组,将数据放到对应表格 td 中,得到表格主体 tbody 的内容
		nowPageData.forEach(function(v, i){
			var trEle = $("");
			Object.keys(v).forEach(function(k, j){
				if (j == 0 && hasID) {
					trEle.append(""+(pageStart+1+i)+"");
				}
				trEle.append(""+v[k]+"");
			});
			// 表格可以多选,添加多选框
			if (multi) {
				trEle.prepend("");
			}
			// 表格可以操作,添加操作项
			if (hasOp) {
				if (operate === "onlyDelete") {
					// 操作项只有删除
					trEle.append("删除");
				} else if (operate === "all") {
					// 默认操作项有编辑和删除
					trEle.append("编辑 | 删除");
				}
			}
			dom.appendChild(trEle[0]);
		});
	} else if (total === 0) {
		var thNum = table.find("thead").find("th").length;
		dom.appendChild($("+thNum+">没有数据")[0]);
	}
	// 将存放了表格主体内容的碎片化文档放到 DOM 中,完成一次性更新表格
	table.find("tbody").append(dom);
}

整体 js 代码如下。

(function($){
	$.fn.initTable = function(data, isFirst, thName) {
		// data: 表格需要展示的所有数据 isFirst: 需要添加表头、绑定事件等 thName: 表头的名称
		isFirst = isFirst || 0;

		// nPanel: 当前表格组件对象
		var nPanel = this;
		// pageSize: 每页展示条数
		var pageSize = nPanel.find(".pageSize").val();
		// total: 数据总条数
		var total = data.length;
		// tPage: 页面总数
		var tPage = Math.ceil(total/pageSize);
		// nPage: 当前页数
		var nPage = parseInt(nPanel.find(".nowPage").text());
		// 表格对象
		var table = nPanel.find(".table");
		// multi: 表格数据是否可多选
		var multi = table.attr("data-multi-select") === "true" ? 1 : 0;
		// hasID: 表是否有序号列
		var hasID = table.attr("data-has-id") === "true" ? 1 : 0;
		// hasOp: 表是否有操作列
		var hasOp = table.attr("data-has-operate") === "true" ? 1 : 0;
		// operate: 表的操作列有哪些操作
		var operate = table.attr("data-operate-items") || "all";

		// 首次调用该方法,添加表头、绑定事件、应用样式等
		if (isFirst === 1) {
			if (multi) {
				nPanel.find(".table-footer").find("div:first-child").show();
				nPanel.find(".table-footer").find("div:last-child").removeClass("col-xs-offset-1");

				// 选中所有条目
				nPanel.on("click", ".selectAll", function(){
					nPanel.find(":checkbox:not(:disabled)").prop("checked", $(this).is(":checked"));
				});

				// 当全选按钮被选中时,取消选中任意一条规则的同时取消选中全选
				table.on("click", ":checkbox:not(:disabled)", function(){
					if (nPanel.find(".selectAll").is(":checked") && !$(this).is(":checked")) {
						nPanel.find(".selectAll").prop("checked", false);
					}
				});
			} else {
				nPanel.find(".table-footer").find("div:first-child").hide();
				nPanel.find(".table-footer").find("div:last-child").addClass("col-xs-offset-1");
			}
			// 生成表头,获取对象的属性名
			var thEle = $("");
			thName.forEach(function(v, i){
				if (i === 0) {
					if (multi) {
						thEle.append("");
					}
					if (hasID) {
						thEle.append("序号");
					}
				}
				thEle.append(""+v+"");
				if (i === thName.length-1 && hasOp) {
					thEle.append("操作");
				}
			});
			table.find("thead").append(thEle);
			table.find("thead").attr("data-number", thName.length+multi+hasID+hasOp);

			// 选择每页展示数量
			$(".dropdown-menu").on("click", "li", function(){
				var inputBox = $(this).parent().siblings("input");
				var nowValue = $(this).attr("data-value");
				var nowText = $(this).find("label").text();
				if (nowValue !== inputBox.val()) {
					// 新值与旧值对比,发生了变化才更改
					$(this).parent().prev().text(nowText);
					inputBox.val(nowValue);
					// 由于val方法直接修改input值不会触发change事件,因此使用 trigger 方法触发自定义事件 changed,并将当前值传给它
					inputBox.trigger("changed", nowValue);
				}
			});
		}
		
		// 监听自定义事件 changed,更新表格当前页数据
		nPanel.on("changed", ".pageSize", function(e, v){
			pageSize = v;
			tPage = Math.ceil(total/pageSize);
			if (nPage > tPage) {
				nPage = tPage;
			}
			changeRange();
		});

		// 跳转到第一页或上一页
		nPanel.on("click", ".startPage, .prevPage", function(){
			if (nPage > 1) {
				nPage = $(this).hasClass("startPage") ? 1 : nPage-1;
				changeRange();
			} else {
				alert("当前已经在第一页");
			}
		});

		// 跳转到最后一页或下一页
		nPanel.on("click", ".endPage, .nextPage", function(){
			if (nPage < tPage) {
				nPage = $(this).hasClass("endPage") ? tPage : nPage+1;
				changeRange();
			} else {
				alert("当前已经在最后一页");
			}
		});
		
		changeRange();

		// 获取指定页的数据
		function changeRange(){
			// 清空表格主体部分
			table.find("tbody").empty();

			// 更新当前页码
			nPanel.find(".nowPage").text(nPage);

			// 当前页第一条数据在数组中的索引号
			var pageStart = (nPage-1)*pageSize;

			// 当前页最后一条数据的序号
			var pageEnd = nPage*pageSize > total ? total : nPage*pageSize;

			// 取消任意多选框的选择
			if (multi) {
				nPanel.find(":checkbox").prop("checked", false);
			}

			// 更新当前页的信息显示
			nPanel.find(".dataInfo").text("第"+((total === 0) ? 0 : (pageStart+1))+"到第"+pageEnd+"条,总共"+total+"条");

			// 利用碎片化文档,避免频繁操作 DOM
			var dom = document.createDocumentFragment();
			if (total > 0) {
				// 获取当前页的数据,利用数组的 slice 方法截取数据片段
				var nowPageData = data.slice(pageStart, pageEnd);

				// 遍历数组,将数据放到对应表格 td 中,得到表格主体 tbody 的内容
				nowPageData.forEach(function(v, i){
					var trEle = $("+(pageStart+i)+">");
					Object.keys(v).forEach(function(k, j){
						if (j == 0 && hasID) {
							trEle.append(""+(pageStart+1+i)+"");
						}
						trEle.append(""+v[k]+"");
					});
					// 表格可以多选,添加多选框
					if (multi) {
						trEle.prepend("");
					}
					// 表格可以操作,添加操作项
					if (hasOp) {
						if (operate === "onlyDelete") {
							// 操作项只有删除
							trEle.append("删除");
						} else if (operate === "all") {
							// 默认操作项有编辑和删除
							trEle.append("编辑 | 删除");
						}
					}
					dom.appendChild(trEle[0]);
				});
			} else if (total === 0) {
				var thNum = table.find("thead").find("th").length;
				dom.appendChild($("+thNum+">没有数据")[0]);
			}
			// 将存放了表格主体内容的碎片化文档放到 DOM 中,完成一次性更新表格
			table.find("tbody").append(dom);
		}
	}
})(jQuery);

现在放一些数据,添加一个按钮删除第一行数据,展示一下效果吧

var tableData = [
	{
		name: 'Alice',
		mark1: 90,
		mark2: 89,
		mark3: 100
	},
	{
		name: 'Bob',
		mark1: 80,
		mark2: 90,
		mark3: 90
	},
	{
		name: 'Cindy',
		mark1: 82,
		mark2: 86,
		mark3: 84
	},
	{
		name: 'Daisy',
		mark1: 88,
		mark2: 79,
		mark3: 80
	},
	{
		name: 'Frack',
		mark1: 72,
		mark2: 60,
		mark3: 70
	},
	{
		name: 'Daniel',
		mark1: 62,
		mark2: 67,
		mark3: 60
	}
]
$("#tableList").initTable(tableData, 1, ["姓名", "语文", "数学", "英语"]);

$("#deleteFirst").on("click", function(){
	tableData.splice(0, 1);
	$("#tableList").initTable(tableData);
});

基于 jQuery 与 Bootstrap 简单封装一个表格分页的组件_第1张图片
如果对你有用,麻烦点个赞~有需要改进的地方,也欢迎在评论区留言!

你可能感兴趣的:(JavaScript,bootstrap,jQuery,bootstrap,jquery,javascript)