假设我们要对下表的任何列进行排序:
<table id="sortMe" class="table">
...
</table>
对行进行排序
// Query the table
const table = document.getElementById('sortMe');
// Query the headers
const headers = table.querySelectorAll('th');
// Loop over the headers
[].forEach.call(headers, function (header, index) {
header.addEventListener('click', function () {
// This function will sort the column
sortColumn(index);
});
});
上面提到的
sortColumn(index)
函数将按给定的列对所有行进行排序index
。为此:
// Query all rows
const tableBody = table.querySelector('tbody');
const rows = tableBody.querySelectorAll('tr');
const sortColumn = function (index) {
// Clone the rows
const newRows = Array.from(rows);
// Sort rows by the content of cells
newRows.sort(function (rowA, rowB) {
// Get the content of cells
const cellA = rowA.querySelectorAll('td')[index].innerHTML;
const cellB = rowB.querySelectorAll('td')[index].innerHTML;
switch (true) {
case cellA > cellB:
return 1;
case cellA < cellB:
return -1;
case cellA === cellB:
return 0;
}
});
// Remove old rows
[].forEach.call(rows, function (row) {
tableBody.removeChild(row);
});
// Append new row
newRows.forEach(function (newRow) {
tableBody.appendChild(newRow);
});
};
如您所见,数组提供了一个内置
sort
方法,该方法接受一个函数来比较两个项目。在我们的例子中,列的两个单元格根据其HTML 内容进行比较:
newRows.sort(function(rowA, rowB) {
// Get the content of cells
const cellA = rowA.querySelectorAll('td')[index].innerHTML;
const cellB = rowB.querySelectorAll('td')[index].innerHTML;
...
});
它适用于内容为字符串而不是数字或其他类型(如日期)的单元格。转到下一部分,看看我们如何支持这些案例。
支持其他类型
我们为每个标题添加一个自定义属性以指示其单元格的类型:
<thead>
<tr>
<th data-type="number">No.</th>
<th>First name</th>
<th>Last name</th>
</tr>
</thead>
例如,编号列将具有一个
data-type="number"
属性。如果缺少该属性,则单元格的内容类型为字符串。我们需要一个函数将单元格的内容从字符串转换为另一种类型:
// Transform the content of given cell in given column
const transform = function (index, content) {
// Get the data type of column
const type = headers[index].getAttribute('data-type');
switch (type) {
case 'number':
return parseFloat(content);
case 'string':
default:
return content;
}
};
示例代码演示了
number
和string
列,但您可以自由支持更多类型,例如日期。
现在我们
sortColumn
稍微改进一下功能以支持自定义内容类型。我们不比较原始内容,而是比较基于内容类型转换的值:
newRows.sort(function (rowA, rowB) {
const cellA = rowA.querySelectorAll('td')[index].innerHTML;
const cellB = rowB.querySelectorAll('td')[index].innerHTML;
// Transform the content of cells
const a = transform(index, cellA);
const b = transform(index, cellB);
// And compare them
switch (true) {
case a > b:
return 1;
case a < b:
return -1;
case a === b:
return 0;
}
});
支持双向
目前,单击标题会对所有行进行排序。如果用户再次单击标题,我们应该反转方向。为此,我们准备了一个变量来管理所有标题的排序方向:
// Track sort directions
const directions = Array.from(headers).map(function (header) {
return '';
});
directions
是一个数组,其中每个项目可以是asc
或desc
指示关联列中的排序方向。该sortColumn()
函数现在涉及更多逻辑来根据当前方向比较两行:
const sortColumn = function(index) {
// Get the current direction
const direction = directions[index] || 'asc';
// A factor based on the direction
const multiplier = (direction === 'asc') ? 1 : -1;
...
newRows.sort(function(rowA, rowB) {
const cellA = rowA.querySelectorAll('td')[index].innerHTML;
const cellB = rowB.querySelectorAll('td')[index].innerHTML;
const a = transform(index, cellA);
const b = transform(index, cellB);
switch (true) {
case a > b: return 1 * multiplier;
case a < b: return -1 * multiplier;
case a === b: return 0;
}
});
...
// Reverse the direction
directions[index] = direction === 'asc' ? 'desc' : 'asc';
...
};
演示
通过单击表头对表进行排序
<html lang="en">
<head>
<meta charset="utf-8" />
<title>HTML DOM - Sort a table by clicking its headers</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/css/demo.css" />
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter&family=Source+Code+Pro&display=swap"
/>
<style>
.table {
border-collapse: collapse;
}
.table__header {
background-color: transparent;
border: none;
cursor: pointer;
}
.table,
.table th,
.table td {
border: 1px solid #ccc;
}
.table th,
.table td {
padding: 0.5rem;
}
.table th {
cursor: pointer;
text-decoration: underline;
}
</style>
</head>
<body>
<table id="sortMe" class="table">
<thead>
<tr>
<th data-type="number"><button class="table__header">No.</button></th>
<th><button class="table__header">First name</button></th>
<th><button class="table__header">Last name</button></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Andrea</td>
<td>Ross</td>
</tr>
<tr>
<td>2</td>
<td>Penelope</td>
<td>Mills</td>
</tr>
<tr>
<td>3</td>
<td>Sarah</td>
<td>Grant</td>
</tr>
<tr>
<td>4</td>
<td>Vanessa</td>
<td>Roberts</td>
</tr>
<tr>
<td>5</td>
<td>Oliver</td>
<td>Alsop</td>
</tr>
<tr>
<td>6</td>
<td>Jennifer</td>
<td>Forsyth</td>
</tr>
<tr>
<td>7</td>
<td>Michelle</td>
<td>King</td>
</tr>
<tr>
<td>8</td>
<td>Steven</td>
<td>Kelly</td>
</tr>
<tr>
<td>9</td>
<td>Julian</td>
<td>Ferguson</td>
</tr>
<tr>
<td>10</td>
<td>Chloe</td>
<td>Ince</td>
</tr>
</tbody>
</table>
<script>
document.addEventListener('DOMContentLoaded', function () {
const table = document.getElementById('sortMe');
const headers = table.querySelectorAll('th');
const tableBody = table.querySelector('tbody');
const rows = tableBody.querySelectorAll('tr');
// Track sort directions
const directions = Array.from(headers).map(function (header) {
return '';
});
// Transform the content of given cell in given column
const transform = function (index, content) {
// Get the data type of column
const type = headers[index].getAttribute('data-type');
switch (type) {
case 'number':
return parseFloat(content);
case 'string':
default:
return content;
}
};
const sortColumn = function (index) {
// Get the current direction
const direction = directions[index] || 'asc';
// A factor based on the direction
const multiplier = direction === 'asc' ? 1 : -1;
const newRows = Array.from(rows);
newRows.sort(function (rowA, rowB) {
const cellA = rowA.querySelectorAll('td')[index].innerHTML;
const cellB = rowB.querySelectorAll('td')[index].innerHTML;
const a = transform(index, cellA);
const b = transform(index, cellB);
switch (true) {
case a > b:
return 1 * multiplier;
case a < b:
return -1 * multiplier;
case a === b:
return 0;
}
});
// Remove old rows
[].forEach.call(rows, function (row) {
tableBody.removeChild(row);
});
// Reverse the direction
directions[index] = direction === 'asc' ? 'desc' : 'asc';
// Append new row
newRows.forEach(function (newRow) {
tableBody.appendChild(newRow);
});
};
[].forEach.call(headers, function (header, index) {
header.addEventListener('click', function () {
sortColumn(index);
});
});
});
</script>
</body>
</html>