原生JS 实现复杂的表格排序和全选功能

838 阅读4分钟

效果

动画.gif

总结

总结一下这个项目,你实现了以下功能:

  • 点击表头进行升序/降序排序,根据数据类型进行排序(数字/字符串)
  • 点击全选/全不选按钮,控制所有复选框的状态
  • 点击任意一个复选框,控制全选按钮的状态

所用技术

使用了事件监听器、DOM 操作、正则表达式、数组排序等 JavaScript 技术。同时,熟悉了闭包的概念,学会了如何在闭包中记录上一次的状态。

开始

链接

git@github.com:Jumpawaltz/JavaScript-table-sort-test.git

HTML

<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>表格排序</title>
	<link rel="stylesheet" href="./css/index.css">
</head>

<body>
	<table id="table">
		<thead>
			<tr>
				<th><input type="checkbox" id="checkAll"></th>
				<th>编号</th>
				<th>姓名</th>
				<th>年龄</th>
				<th>职位</th>
			</tr>
		</thead>
		<tbody>
			<tr>
				<td><input type="checkbox"></td>
				<td>3</td>
				<td>王同学</td>
				<td>24</td>
				<td>C++程序员</td>
			</tr>
			<tr>
				<td><input type="checkbox"></td>
				<td>5</td>
				<td>张同学</td>
				<td>54</td>
				<td>Web前端</td>
			</tr>
			<tr>
				<td><input type="checkbox"></td>
				<td>22</td>
				<td>玛丽</td>
				<td>30</td>
				<td>测试仪</td>
			</tr>
			<tr>
				<td><input type="checkbox"></td>
				<td>6 </td>
				<td>艾伦</td>
				<td>21</td>
				<td>交互设计师</td>
			</tr>
			<tr>
				<td><input type="checkbox"></td>
				<td>17</td>
				<td>小明同学</td>
				<td>28</td>
				<td>PHP程序员</td>
			</tr>
		</tbody>
	</table>
	<script src="./index.js"></script>
</body>

</html>

CSS

* {
	margin: 0;
	padding: 0;
	box-sizing: border-box;
	list-style: none;
}

#table {
	width: 400px;
	margin: 0 auto;
	margin-top: 40px;
	text-align: center;
	border-collapse: collapse;
}

tr:hover {
	background-color: #e0e0e0;
}

th {
	cursor: pointer;
	color: #666;
}

td {
	color: #555;
}

th,
td {
	border: 1px solid #ccc;
	text-align: center;
	height: 40px;
}
input[type='checkbox'] {
	height: 18px;
	width: 18px;
	cursor: pointer;
	padding: 5px;
	background-color: red;
}

JavaScript

(function () {
	let table = document.querySelector('#table');
	let ths = document.querySelectorAll('th');
	let tbody = document.querySelector('tbody');
	let checkAll = document.querySelector('#checkAll');
	let rows = tbody.querySelectorAll('tr');
	let checkOneList = tbody.querySelectorAll('[type=checkbox]');

	// 点击表头进行升序/降序排序,根据数据类型进行排序(数字/字符串)
	// 点击全选/全不选按钮,控制所有复选框的状态
	// 点击任意一个复选框,控制全选按钮的状态
	// 在实现这些功能的过程中,你使用了事件监听器、DOM 操作、正则表达式、数组排序等 JavaScript 技术。同时,你也熟悉了闭包的概念,学会了如何在闭包中记录上一次的状态。

	function init() {
		autoEvents();
	}

	// 所有事件绑定
	// 给所有CheckBox绑定时间,和表头绑定事件,实现升序和降序, 动态根据数据类型进行排序(字符串排序,数字排序)
	function autoEvents() {
		// 单选按钮绑定, 使用事件委托
		tbody.addEventListener('click', checkList);
		//全选事件绑定
		checkAll.addEventListener('click', onCheckAllClick);
		// 表头事件绑定
		ths.forEach((node, index) => {
			node.addEventListener('click', thsFun(index, thsrow));
		});
	}
	function thsFun(index, fn) {
		let order = 'asc';
		return function () {
			let orderList = fn(order, index);
			console.log('orderList', orderList);
			order = order === 'asc' ? 'desc' : 'asc';
			Array.prototype.slice.call(orderList).forEach((orderItem) => {
				tbody.appendChild(orderItem);
			});
		};
	}
	// thead里面th 根据数据类型进行排序,实现顺序和降序
	function thsrow(order, index) {
		let type =
			document.querySelectorAll('tbody>tr')[0].children[index].innerText;
		let isNumber = /^\d+$/.test(type);
		console.log('type', type);
		let orderList;
		orderList = Array.prototype.slice.call(rows).sort(function (a, b) {
			a = a.querySelectorAll('td')[index].innerHTML;
			b = b.querySelectorAll('td')[index].innerHTML;
			if (isNumber) {
				if (order === 'asc') {
					return a - b;
				} else if (order === 'desc') {
					return b - a;
				}
			} else if (order === 'asc') {
				return a.localeCompare(b, 'zh');
			} else if (order === 'desc') {
				return b.localeCompare(a, 'zh');
			}
		});
		return orderList;
	}

	// 点击全选按钮,控制所有复选框的状态
	function onCheckAllClick(e) {
		e.stopPropagation();
		let status = this.checked;
		console.log('status', status);
		checkOneList.forEach((node) => {
			node.checked = status;
		});
	}
	// 点击任意一个复选框,控制全选按钮的状态
	function checkList(e) {
		let times = 0;
		if (e.target.type === 'checkbox') {
			e.target.checked;
			checkOneList.forEach((node) => {
				node.checked && times++;
			});
			checkAll.checked = times === checkOneList.length;
		}
	}
	init();
})();

整体思路

  1. 使用 init() 进行初始化操作,只暴露出 init接口
  2. init 里运行时间绑定事件
  3. 给全选按钮绑定
  4. 给其他单选绑定,使用事件委托
  5. 自定义排序,

这段代码实现了一个表格的排序、全选和单选功能。具体实现过程如下:

  1. 获取表格中需要用到的 DOM 元素,包括表头、表体、所有行、全选复选框和每个行的复选框。
let table = document.querySelector('#table');
let ths = document.querySelectorAll('th');
let tbody = document.querySelector('tbody');
let checkAll = document.querySelector('#checkAll');
let rows = tbody.querySelectorAll('tr');
let checkOneList = tbody.querySelectorAll('[type=checkbox]');
  1. 实现表格排序功能。通过点击表头触发事件,根据当前表头所在列的数据类型(数字或字符串),动态地进行升序或降序排序。具体实现过程如下:

    • 首先为表头绑定点击事件,每次点击触发一个返回闭包的函数,闭包中保存了排序的状态和排序后的行列表。
    ths.forEach((node, index) => {
      node.addEventListener('click', thsFun(index, thsrow));
    });
    
    • 点击表头时,调用返回的闭包函数,根据当前的排序状态(升序或降序)和当前列的数据类型进行排序,返回排序后的行列表。
    function thsFun(index, fn) {
      let order = 'asc';
      return function () {
        let orderList = fn(order, index);
        order = order === 'asc' ? 'desc' : 'asc';
        // 将排序后的行列表添加到表体中
        Array.prototype.slice.call(orderList).forEach((orderItem) => {
          tbody.appendChild(orderItem);
        });
      };
    }
    
    function thsrow(order, index) {
      // 获取当前列的数据类型
      let type = document.querySelectorAll('tbody>tr')[0].children[index].innerText;
      let isNumber = /^\d+$/.test(type);
      let orderList;
      orderList = Array.prototype.slice.call(rows).sort(function (a, b) {
        a = a.querySelectorAll('td')[index].innerHTML;
        b = b.querySelectorAll('td')[index].innerHTML;
        // 根据数据类型进行排序
        if (isNumber) {
          if (order === 'asc') {
            return a - b;
          } else if (order === 'desc') {
            return b - a;
          }
        } else if (order === 'asc') {
          return a.localeCompare(b, 'zh');
        } else if (order === 'desc') {
          return b.localeCompare(a, 'zh');
        }
      });
      return orderList;
    }
    
  2. 实现全选和单选功能。通过点击全选复选框或每个行的复选框触发事件,控制所有或当前行的复选框的状态。具体实现过程如下:

为全选复选框绑定点击事件,每次点击时遍历所有行的复选框,将它们的状态设置为全选复选框的状态。

```js
checkAll.addEventListener('click', onCheckAllClick);
function onCheckAllClick(e) {
  e.stopPropagation();
  let status = this.checked;
  checkOneList.forEach((node) => {
    node.checked = status;
  });
}
```

3. 排序功能的实现

为了实现点击表头进行升序/降序排序的功能,代码使用了闭包来保存上一次的排序状态,同时使用了事件监听器和数组排序等技术。在表头事件绑定的函数thsFun中,首先定义了一个变量order,用来保存当前的排序状态(默认为升序)。接着返回了一个匿名函数,这个匿名函数内部又定义了一个变量orderList,用来保存根据当前表头的索引进行排序后的结果。在这个匿名函数内部,代码先使用了一个三元运算符判断当前排序状态是升序还是降序,然后再把order设置为相反的状态。接着使用slice方法将orderList转换成数组,然后通过forEach方法将排序后的结果逐一添加到表格的tbody中,完成了表格的排序。

  1. 全选/全不选功能和单选功能的实现

全选/全不选和单选的功能是通过事件委托实现的。在初始化函数init中,代码通过autoEvents函数绑定了全选事件和单选事件。在全选事件处理函数onCheckAllClick中,代码首先使用stopPropagation方法阻止事件的冒泡,然后获取当前全选复选框的选中状态,并通过forEach方法将所有单选复选框的选中状态设置为与当前全选复选框相同的状态。在单选事件处理函数checkList中,代码首先判断当前点击的元素是否为复选框,然后使用一个times变量统计当前选中的单选复选框的个数。最后,代码通过判断times和所有单选复选框的个数是否相同来设置全选复选框的选中状态。

总的来说,这段代码使用了闭包、事件监听器、DOM 操作、正则表达式、数组排序等多种 JavaScript 技术,实现了一个完整的表格排序和复选框选中状态控制的功能。