Sort a table by clicking its headers
Assume that we want to sort any column of the following table:
<table id="sortMe" class="table">
...
</table>
Sort rows
const table = document.getElementById('sortMe');
const headers = table.querySelectorAll('th');
[].forEach.call(headers, function (header, index) {
header.addEventListener('click', function () {
sortColumn(index);
});
});
The sortColumn(index)
function mentioned above will sort all rows by given column index
. To do so:
- We can use the
Array
's sort()
method to sort the current rows
- Then, remove all the current rows
- And append the sorted rows
const tableBody = table.querySelector('tbody');
const rows = tableBody.querySelectorAll('tr');
const sortColumn = function (index) {
const newRows = Array.from(rows);
newRows.sort(function (rowA, rowB) {
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;
}
});
[].forEach.call(rows, function (row) {
tableBody.removeChild(row);
});
newRows.forEach(function (newRow) {
tableBody.appendChild(newRow);
});
};
As you see, an array provides a built-in
sort
method which accepts a function to compare two items. In our case, two cells of the column are compared based on its
HTML content:
newRows.sort(function(rowA, rowB) {
const cellA = rowA.querySelectorAll('td')[index].innerHTML;
const cellB = rowB.querySelectorAll('td')[index].innerHTML;
...
});
It works well with the cells whose content are string, not numbers or another type such as date. Going to the next section to see how we can support those cases.
Support other types
We add a custom attribute to each header to indicate the type of its cells:
<thead>
<tr>
<th data-type="number">No.</th>
<th>First name</th>
<th>Last name</th>
</tr>
</thead>
For example, the No. column would have a data-type="number"
attribute. If the attribute is missing, the content types of cells are string. We need a function to transform the content of cells from string to another type:
const transform = function (index, content) {
const type = headers[index].getAttribute('data-type');
switch (type) {
case 'number':
return parseFloat(content);
case 'string':
default:
return content;
}
};
The sample code demonstrates the number
and string
columns, but you are free to support more types such as date.
Now we improve the sortColumn
function a little bit to support the custom content types. Instead of comparing the raw content, we compare the values which are converted based on the content type:
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;
case a < b:
return -1;
case a === b:
return 0;
}
});
Support both directions
At the moment, clicking a header sorts all the rows. We should reverse the direction if user clicks the header again. To do so, we prepare a variable to manage the sorting directions of all headers:
const directions = Array.from(headers).map(function (header) {
return '';
});
directions
is an array which each item can be either asc
or desc
indicating the sorting direction in the associate column. The sortColumn()
function now involves more logics to compare two rows based on the current direction:
const sortColumn = function(index) {
const direction = directions[index] || 'asc';
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;
}
});
...
directions[index] = direction === 'asc' ? 'desc' : 'asc';
...
};
Demo
Sort a table by clicking its headers
See also