JavaScript - Sort Tables by any Header Column
Contents
About
Fancy interfaces allow sorting tables dynamically by any column. One approach is to store the table data in JavaScipt and add buttons that regenerate the table, but that's a lot of extra code! The follow JavaScript is pretty awesome, because you don't really need to add any extra HTML code - it just O(log(n)) reorders the <tr> tags inside your tables, and will recognize number values as different to string values. If you get rid of the "sortable" in the JavaScript itself it will apply to all tables, but if you have a real HTML file, I think that setting <table class="sortable"> is useful because many of your tables may be navigational or not need sorting.
I modified this JavaScript from a great code snippit by Nick Grealy, and enhanced it to work smarty with nested tables, a "sortable" CSS class for tables and "data-sort-value" attributes for <td> cells that might have fancy HTML or values like dates (eg: ["12/25/20", "1/1/21"]) which won't sort nicely unless you add something invisible (eg: ["2020-12-25", "2021-01-01"]) for better sorting.
Hope this helps!
Condensed Zippy JavaScript Code - For All Tables
sortable_tables.js: (you can just tag this into <script> tags at the end of your HTML page)
const GetCellValueForSorting = (tr, idx) => tr.children[idx].getAttribute('data-sort-value') || tr.children[idx].innerText || tr.children[idx].textContent || tr.children[idx].innerHTML;
const comparer = (idx, asc) => (a, b) => ((v1, v2) =>
v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ?
v1 - v2 :
v1.toString().localeCompare(v2)
)(GetCellValueForSorting(asc ? a : b, idx),
GetCellValueForSorting(asc ? b : a, idx));
// NOTE: Using querySelectorAll('table.sortable th'), only tables with the
// 'sortable' class are be affected. Use ('th') for all tables.
document.querySelectorAll('th').forEach(th => th.addEventListener('click', (() => {
const table = th.closest('table');
const rows = table.querySelectorAll('tr:nth-child(n+2)');
// Filter out cells that are from nested tables:
var immediateRows = [];
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
if (row.closest('table') == table) {
immediateRows.push(row);
}
}
Array.from(immediateRows)
.sort(comparer(Array.from(th.parentNode.children).indexOf(th), this.asc = !this.asc))
.forEach(tr => table.appendChild(tr) );
})));
Zippy JavaScript Full Example - For Only "sortable" Tables
sortable_tables_demo.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Table Sorting Demo</title>
<!-- JAVASCRIPT: -->
<script>
/**
* Call this once to allow table sorting on any column header you click
* where the table's class includes "sortable".
*
* Your table should have <th> tags as in the first row, and all subsequent
* rows should have the same number of <td> columns as the header. If a
* 'data-sort-value' attribution not present in a <td> cell, it will compare
* the text content, and convert text to numbers when found.
*
* On alternate clicks of a tale header it will sort ascending then descending.
*
* Use this as an example:
*
* <table class="animal-table sortable">
* <tr> <th>Animal </th> <th>Count </th> </tr>
* <tr> <td>Aardvark </td> <td data-sort-value="15">15 (5 male)</td> </tr>
* <tr> <td>Buffalo </td> <td data-sort-value="5">5 (1 male) </td> </tr>
* </table>
*
* WARNING: This may mess up tables in cases where you have used
* 'colspan' in your rows.
*
* Code has been modified from:
* https://stackoverflow.com/questions/14267781/sorting-html-table-with-javascript
*/
function EnableTableColumnSorting() {
// A comparer that checks first if the cell values are numbers,
// else does a lexicographical compare.
const comparer = (idx, asc) => (a, b) => ((v1, v2) =>
v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ?
v1 - v2 :
v1.toString().localeCompare(v2)
)(GetCellValueForSorting(asc ? a : b, idx),
GetCellValueForSorting(asc ? b : a, idx));
// NOTE: Using querySelectorAll('table.sortable th'), only tables with
// the 'sortable' class are be affected. Use ('table.sortable th') for
// all tables.
document.querySelectorAll('table.sortable > * > tr > th').forEach(th => th.addEventListener('click', (() => {
const table = th.closest('table');
const rows = table.querySelectorAll('tr:nth-child(n+2)');
// Filter out cells that are from nested tables:
let immediateRows = [];
for (let i = 0; i < rows.length; i++) {
let row = rows[i];
if (row.closest('table') == table) {
immediateRows.push(row);
}
}
Array.from(immediateRows)
.sort(comparer(Array.from(th.parentNode.children).indexOf(th), this.asc = !this.asc))
.forEach(tr => table.appendChild(tr) );
})));
}
/**
* Returns the "data-sort-value" of a table cell, else falls back to its text
* content else inner HTML.
*
* @param {!Element} tr A TR dom element.
* @param {number} idx The 0 based index for the column.
* @return {string} Text sort value or text content of cell.
*/
function GetCellValueForSorting(tr, idx) {
let cell = tr.children[idx];
if (!cell) {
return '0';
}
let sortValue = cell.getAttribute('data-sort-value');
if (sortValue) {
return sortValue;
}
let text = cell.innerText || cell.textContent;
if (text) {
// console.log(text);
return text;
}
// console.log(cell.innerHTML);
return cell.innerHTML;
}
</script>
<!-- CSS: -->
<style>
body { padding: 20px; }
th {
border: 1px solid #ccc;
background-color: aqua;
}
td {
border: 1px solid #ccc;
}
.sortable th {
cursor: ns-resize;
}
.sortable th:hover {
background-color: deepskyblue;
}
</style>
</head>
<body>
<h2>Sort on Any Column (ASC/DESC)</h2>
Simple example (without "data-sort-values"):
<table class="sortable">
<tr> <th>Name</th> <th>Age</th> </tr>
<tr> <td>Andrew</td> <td>27</td> </tr>
<tr> <td>Jenny</td> <td>18</td> </tr>
<tr> <td>Jessie</td> <td>9</td> </tr>
</table>
<hr>
More fun example (with "data-sort-values"):
<table class="animal-table sortable">
<tr>
<th>Animal</th>
<th>Count</th>
<th>Life Expectency</th>
</tr>
<tr>
<td>Aardvark</td>
<td data-sort-value="15"><b>15</b> (5 male, 10 female)</td>
<td data-sort-value="9">9-15 years</td>
</tr>
<tr>
<td>Buffalo</td>
<td data-sort-value="5"><b>5</b> (1 male, 4 females) </td>
<td data-sort-value="50">50 years</td>
</tr>
<tr>
<td>Butterfly</td>
<td data-sort-value="900">~<b>900</b></td>
<td data-sort-value="0.2">53 days</td>
</tr>
<tr>
<td>Chinchilla</td>
<td data-sort-value="15"><b>33</b></td>
<td data-sort-value="10">10-12 years</td>
</tr>
</table>
<hr>
<!-- EXTRA JAVASCRIPT: -->
<script>EnableTableColumnSorting();</script>
</body>
</html>
Code license | For all of the code on my site... if there are specific instruction or licence comments please leave them in. If you copy my code with minimum modifications to another webpage, or into any code other people will see I would love an acknowledgment to my site.... otherwise, the license for this code is more-or-less WTFPL (do what you want)! If only copying <20 lines, then don't bother. That said - if you'd like to add a web-link to my site www.andrewnoske.com or (better yet) the specific page with code, that's a really sweet gestures! Links to the page may be useful to yourself or your users and helps increase traffic to my site. Hope my code is useful! :)
|
Links
- Stackoverflow: "Sorting HTML table with JavaScript" - quick solution on which my example was based.