Related
I'm after a table sorting solution (in JavaScript) but I can't seem to find a suitable one yet. I just need it to sort each column alphabetically. It doesn't need to ignore any code or any numbers or to work with currency. Just a click on the column header switches it from sorted a-z/z-a.
Does anyone know of a really simple solution like this?
Just revisiting an old solution, I thought I'd give it a facelift for it's ~5 year anniversary!
Plain Javascript (ES6)
Does alpha and numeric sorting - ascending and descending
Works in Chrome, Firefox, Safari (and IE11, see below)
Quick explanation
add a click event to all header (th) cells...
for the current table, find all rows (except the first)...
sort the rows, based on the value of the clicked column...
insert the rows back into the table, in the new order.
const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent;
const comparer = (idx, asc) => (a, b) => ((v1, v2) =>
v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2)
)(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
// do the work...
document.querySelectorAll('th').forEach(th => th.addEventListener('click', (() => {
const table = th.closest('table');
Array.from(table.querySelectorAll('tr:nth-child(n+2)'))
.sort(comparer(Array.from(th.parentNode.children).indexOf(th), this.asc = !this.asc))
.forEach(tr => table.appendChild(tr) );
})));
table, th, td {
border: 1px solid black;
}
th {
cursor: pointer;
}
<table>
<tr><th>Country</th><th>Date</th><th>Size</th></tr>
<tr><td>France</td><td>2001-01-01</td><td><i>25</i></td></tr>
<tr><td><a href=#>spain</a></td><td><i>2005-05-05</i></td><td></td></tr>
<tr><td><b>Lebanon</b></td><td><a href=#>2002-02-02</a></td><td><b>-17</b></td></tr>
<tr><td><i>Argentina</i></td><td>2005-04-04</td><td><a href=#>100</a></td></tr>
<tr><td>USA</td><td></td><td>-6</td></tr>
</table>
IE11 Support (non-ES6)
If you want to support IE11, you'll need to ditch the ES6 syntax and use alternatives to Array.from and Element.closest.
i.e.
var getCellValue = function(tr, idx){ return tr.children[idx].innerText || tr.children[idx].textContent; }
var comparer = function(idx, asc) { return function(a, b) { return function(v1, v2) {
return v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2);
}(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
}};
// do the work...
Array.prototype.slice.call(document.querySelectorAll('th')).forEach(function(th) { th.addEventListener('click', function() {
var table = th.parentNode
while(table.tagName.toUpperCase() != 'TABLE') table = table.parentNode;
Array.prototype.slice.call(table.querySelectorAll('tr:nth-child(n+2)'))
.sort(comparer(Array.prototype.slice.call(th.parentNode.children).indexOf(th), this.asc = !this.asc))
.forEach(function(tr) { table.appendChild(tr) });
})
});
Comparer function breakdown
For the sake of brevity, I compacted the comparer() function. It's a little complex/hard to read, so here it is again exploded/formatted/commented.
// Returns a function responsible for sorting a specific column index
// (idx = columnIndex, asc = ascending order?).
var comparer = function(idx, asc) {
// This is used by the array.sort() function...
return function(a, b) {
// This is a transient function, that is called straight away.
// It allows passing in different order of args, based on
// the ascending/descending order.
return function(v1, v2) {
// sort based on a numeric or localeCompare, based on type...
return (v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2))
? v1 - v2
: v1.toString().localeCompare(v2);
}(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
}
};
I wrote up some code that will sort a table by a row, assuming only one <tbody> and cells don't have a colspan.
function sortTable(table, col, reverse) {
var tb = table.tBodies[0], // use `<tbody>` to ignore `<thead>` and `<tfoot>` rows
tr = Array.prototype.slice.call(tb.rows, 0), // put rows into array
i;
reverse = -((+reverse) || -1);
tr = tr.sort(function (a, b) { // sort rows
return reverse // `-1 *` if want opposite order
* (a.cells[col].textContent.trim() // using `.textContent.trim()` for test
.localeCompare(b.cells[col].textContent.trim())
);
});
for(i = 0; i < tr.length; ++i) tb.appendChild(tr[i]); // append each row in order
}
// sortTable(tableNode, columId, false);
If you don't want to make the assumptions above, you'd need to consider how you want to behave in each circumstance. (e.g. put everything into one <tbody> or add up all the preceeding colspan values, etc.)
You could then attach this to each of your tables, e.g. assuming titles are in <thead>
function makeSortable(table) {
var th = table.tHead, i;
th && (th = th.rows[0]) && (th = th.cells);
if (th) i = th.length;
else return; // if no `<thead>` then do nothing
while (--i >= 0) (function (i) {
var dir = 1;
th[i].addEventListener('click', function () {sortTable(table, i, (dir = 1 - dir))});
}(i));
}
function makeAllSortable(parent) {
parent = parent || document.body;
var t = parent.getElementsByTagName('table'), i = t.length;
while (--i >= 0) makeSortable(t[i]);
}
and then invoking makeAllSortable onload.
Example fiddle of it working on a table.
Nick Grealy's accepted answer is great but acts a bit quirky if your rows are inside a <tbody> tag (the first row isn't ever sorted and after sorting rows end up outside of the tbody tag, possibly losing formatting).
This is a simple fix, however:
Just change:
document.querySelectorAll('th').forEach(th => th.addEventListener('click', (() => {
const table = th.closest('table');
Array.from(table.querySelectorAll('tr:nth-child(n+2)'))
.sort(comparer(Array.from(th.parentNode.children).indexOf(th), this.asc = !this.asc))
.forEach(tr => table.appendChild(tr) );
to:
document.querySelectorAll('th').forEach(th => th.addEventListener('click', (() => {
const table = th.closest('table');
const tbody = table.querySelector('tbody');
Array.from(tbody.querySelectorAll('tr'))
.sort(comparer(Array.from(th.parentNode.children).indexOf(th), this.asc = !this.asc))
.forEach(tr => tbody.appendChild(tr) );
The best way I know to sort HTML table with javascript is with the following function.
Just pass to it the id of the table you'd like to sort and the column number on the row. it assumes that the column you are sorting is numeric or has numbers in it and will do regex replace to get the number itself (great for currencies and other numbers with symbols in it).
function sortTable(table_id, sortColumn){
var tableData = document.getElementById(table_id).getElementsByTagName('tbody').item(0);
var rowData = tableData.getElementsByTagName('tr');
for(var i = 0; i < rowData.length - 1; i++){
for(var j = 0; j < rowData.length - (i + 1); j++){
if(Number(rowData.item(j).getElementsByTagName('td').item(sortColumn).innerHTML.replace(/[^0-9\.]+/g, "")) < Number(rowData.item(j+1).getElementsByTagName('td').item(sortColumn).innerHTML.replace(/[^0-9\.]+/g, ""))){
tableData.insertBefore(rowData.item(j+1),rowData.item(j));
}
}
}
}
Using example:
$(function(){
// pass the id and the <td> place you want to sort by (td counts from 0)
sortTable('table_id', 3);
});
It does WAY more than "just sorting", but dataTables.net does what you need. I use it daily and is well supported and VERY fast (does require jQuery)
http://datatables.net/
DataTables is a plug-in for the jQuery Javascript library. It is a highly flexible tool, based upon the foundations of progressive enhancement, which will add advanced interaction controls to any HTML table.
Google Visualizations is another option, but requires a bit more setup that dataTables, but does NOT require any particular framework/library (other than google.visualizations):
http://code.google.com/apis/ajax/playground/?type=visualization#table
And there are other options to... especially if you're using one of the other JS frameworks. Dojo, Prototype, etc all have usable "table enhancement" plugins that provide at minimum table sorting functionality. Many provide more, but I'll restate...I've yet to come across one as powerful and as FAST as datatables.net.
Table Sorting with :hover arrows effect. Simply add the class .order to the <th> element of each column to be ordered
function table_sort() {
const styleSheet = document.createElement('style')
styleSheet.innerHTML = `
.order-inactive span {
visibility:hidden;
}
.order-inactive:hover span {
visibility:visible;
}
.order-active span {
visibility: visible;
}
`
document.head.appendChild(styleSheet)
document.querySelectorAll('th.order').forEach(th_elem => {
let asc = true
const span_elem = document.createElement('span')
span_elem.style = "font-size:0.8rem; margin-left:0.5rem"
span_elem.innerHTML = "▼"
th_elem.appendChild(span_elem)
th_elem.classList.add('order-inactive')
const index = Array.from(th_elem.parentNode.children).indexOf(th_elem)
th_elem.addEventListener('click', (e) => {
document.querySelectorAll('th.order').forEach(elem => {
elem.classList.remove('order-active')
elem.classList.add('order-inactive')
})
th_elem.classList.remove('order-inactive')
th_elem.classList.add('order-active')
if (!asc) {
th_elem.querySelector('span').innerHTML = '▲'
} else {
th_elem.querySelector('span').innerHTML = '▼'
}
const arr = Array.from(th_elem.closest("table").querySelectorAll('tbody tr'))
arr.sort((a, b) => {
const a_val = a.children[index].innerText
const b_val = b.children[index].innerText
return (asc) ? a_val.localeCompare(b_val) : b_val.localeCompare(a_val)
})
arr.forEach(elem => {
th_elem.closest("table").querySelector("tbody").appendChild(elem)
})
asc = !asc
})
})
}
table_sort()
<table>
<thead>
<tr>
<th class="order">Country</th>
<th class="order">Date</th>
<th class="order">Size</th>
</tr>
</thead>
<tbody>
<tr>
<td>France</td>
<td>2001-01-01</td>
<td><i>25</i></td>
</tr>
<tr>
<td><a href=#>spain</a></td>
<td><i>2005-05-05</i></td>
<td></td>
</tr>
<tr>
<td><b>Lebanon</b></td>
<td><a href=#>2002-02-02</a></td>
<td><b>-17</b></td>
</tr>
<tr>
<td><i>Argentina</i></td>
<td>2005-04-04</td>
<td><a href=#>100</a></td>
</tr>
<tr>
<td>USA</td>
<td></td>
<td>-6</td>
</tr>
</tbody>
</table>
You could deal with a json array and the sort function. It is a pretty easy maintanable structure to manipulate (ex: sorting).
Untested, but here's the idea. That would support multiple ordering and sequential ordering if you pass in a array in which you put the columns in the order they should be ordered by.
var DATA_TABLE = {
{name: 'George', lastname: 'Blarr', age:45},
{name: 'Bob', lastname: 'Arr', age: 20}
//...
};
function sortDataTable(arrayColNames, asc) { // if not asc, desc
for (var i=0;i<arrayColNames.length;i++) {
var columnName = arrayColNames[i];
DATA_TABLE = DATA_TABLE.sort(function(a,b){
if (asc) {
return (a[columnName] > b[columnName]) ? 1 : -1;
} else {
return (a[columnName] < b[columnName]) ? 1 : -1;
}
});
}
}
function updateHTMLTable() {
// update innerHTML / textContent according to DATA_TABLE
// Note: textContent for firefox, innerHTML for others
}
Now let's imagine you need to order by lastname, then name, and finally by age.
var orderAsc = true;
sortDataTable(['lastname', 'name', 'age'], orderAsc);
It should result in something like :
{name: 'Jack', lastname: 'Ahrl', age: 20},
{name: 'Jack', lastname: 'Ahrl', age: 22},
//...
Here is a complete example using pure JavaScript. The algorithm used for sorting is basically BubbleSort. Here is a Fiddle.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<script type="text/javascript">
function sort(ascending, columnClassName, tableId) {
var tbody = document.getElementById(tableId).getElementsByTagName(
"tbody")[0];
var rows = tbody.getElementsByTagName("tr");
var unsorted = true;
while (unsorted) {
unsorted = false
for (var r = 0; r < rows.length - 1; r++) {
var row = rows[r];
var nextRow = rows[r + 1];
var value = row.getElementsByClassName(columnClassName)[0].innerHTML;
var nextValue = nextRow.getElementsByClassName(columnClassName)[0].innerHTML;
value = value.replace(',', '.'); // in case a comma is used in float number
nextValue = nextValue.replace(',', '.');
if (!isNaN(value)) {
value = parseFloat(value);
nextValue = parseFloat(nextValue);
}
if (ascending ? value > nextValue : value < nextValue) {
tbody.insertBefore(nextRow, row);
unsorted = true;
}
}
}
};
</script>
</head>
<body>
<table id="content-table">
<thead>
<tr>
<th class="id">ID asc des
</th>
<th class="country">Country asc des
</th>
<th class="some-fact">Some fact asc
des
<th>
</tr>
</thead>
<tbody>
<tr>
<td class="id">001</td>
<td class="country">Germany</td>
<td class="some-fact">16.405</td>
</tr>
<tr>
<td class="id">002</td>
<td class="country">France</td>
<td class="some-fact">10.625</td>
</tr>
<tr>
<td class="id">003</td>
<td class="country">UK</td>
<td class="some-fact">15.04</td>
</tr>
<tr>
<td class="id">004</td>
<td class="country">China</td>
<td class="some-fact">13.536</td>
</tr>
</tbody>
</table>
</body>
</html>
You can also check out the source from here: https://github.com/wmentzel/table-sort
Sorting table rows by cell.
1. Little simpler and has some features.
2. Distinguish 'number' and 'string' on sorting
3. Add toggle to sort by ASC, DESC
var index; // cell index
var toggleBool; // sorting asc, desc
function sorting(tbody, index){
this.index = index;
if(toggleBool){
toggleBool = false;
}else{
toggleBool = true;
}
var datas= new Array();
var tbodyLength = tbody.rows.length;
for(var i=0; i<tbodyLength; i++){
datas[i] = tbody.rows[i];
}
// sort by cell[index]
datas.sort(compareCells);
for(var i=0; i<tbody.rows.length; i++){
// rearrange table rows by sorted rows
tbody.appendChild(datas[i]);
}
}
function compareCells(a,b) {
var aVal = a.cells[index].innerText;
var bVal = b.cells[index].innerText;
aVal = aVal.replace(/\,/g, '');
bVal = bVal.replace(/\,/g, '');
if(toggleBool){
var temp = aVal;
aVal = bVal;
bVal = temp;
}
if(aVal.match(/^[0-9]+$/) && bVal.match(/^[0-9]+$/)){
return parseFloat(aVal) - parseFloat(bVal);
}
else{
if (aVal < bVal){
return -1;
}else if (aVal > bVal){
return 1;
}else{
return 0;
}
}
}
below is html sample
<table summary="Pioneer">
<thead>
<tr>
<th scope="col" onclick="sorting(tbody01, 0)">No.</th>
<th scope="col" onclick="sorting(tbody01, 1)">Name</th>
<th scope="col" onclick="sorting(tbody01, 2)">Belong</th>
<th scope="col" onclick="sorting(tbody01, 3)">Current Networth</th>
<th scope="col" onclick="sorting(tbody01, 4)">BirthDay</th>
<th scope="col" onclick="sorting(tbody01, 5)">Just Number</th>
</tr>
</thead>
<tbody id="tbody01">
<tr>
<td>1</td>
<td>Gwanshic Yi</td>
<td>Gwanshic Home</td>
<td>120000</td>
<td>1982-03-20</td>
<td>124,124,523</td>
</tr>
<tr>
<td>2</td>
<td>Steve Jobs</td>
<td>Apple</td>
<td>19000000000</td>
<td>1955-02-24</td>
<td>194,523</td>
</tr>
<tr>
<td>3</td>
<td>Bill Gates</td>
<td>MicroSoft</td>
<td>84300000000</td>
<td>1955-10-28</td>
<td>1,524,124,523</td>
</tr>
<tr>
<td>4</td>
<td>Larry Page</td>
<td>Google</td>
<td>39100000000</td>
<td>1973-03-26</td>
<td>11,124,523</td>
</tr>
</tbody>
</table>
In case your table does not have ths but only tds (with headers included) you can try the following which is based on Nick Grealy's answer above:
const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent;
const comparer = (idx, asc) => (a, b) => ((v1, v2) =>
v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2)
)(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
// do the work...
document.querySelectorAll('tr:first-child td').forEach(td => td.addEventListener('click', (() => {
const table = td.closest('table');
Array.from(table.querySelectorAll('tr:nth-child(n+2)'))
.sort(comparer(Array.from(td.parentNode.children).indexOf(td), this.asc = !this.asc))
.forEach(tr => table.appendChild(tr) );
})));
#charset "UTF-8";
#import url('https://fonts.googleapis.com/css?family=Roboto');
*{
font-family: 'Roboto', sans-serif;
text-transform:capitalize;
overflow:hidden;
margin: 0 auto;
text-align:left;
}
table {
color:#666;
font-size:12px;
background:#124;
border:#ccc 1px solid;
-moz-border-radius:3px;
-webkit-border-radius:3px;
border-radius:3px;
border-collapse: collapse;
width: 100%;
}
table td {
padding:10px;
border-top: 1px solid #ffffff;
border-bottom:1px solid #e0e0e0;
border-left: 1px solid #e0e0e0;
background: #fafafa;
background: -webkit-gradient(linear, left top, left bottom, from(#fbfbfb), to(#fafafa));
background: -moz-linear-gradient(top, #fbfbfb, #fafafa);
width: 6.9in;
}
table tbody tr:first-child td
{
background: #124!important;
color:#fff;
}
table tbody tr th
{
padding:10px;
border-left: 1px solid #e0e0e0;
background: #124!important;
color:#fff;
}
<table>
<tr><td>Country</td><td>Date</td><td>Size</td></tr>
<tr><td>France</td><td>2001-01-01</td><td><i>25</i></td></tr>
<tr><td>spain</td><td>2005-05-05</td><td></td></tr>
<tr><td>Lebanon</td><td>2002-02-02</td><td><b>-17</b></td></tr>
<tr><td>Argentina</td><td>2005-04-04</td><td>100</td></tr>
<tr><td>USA</td><td></td><td>-6</td></tr>
</table>
Another approach to sort HTML table. (based on W3.JS HTML Sort)
var collection = [{
"Country": "France",
"Date": "2001-01-01",
"Size": "25",
}, {
"Country": "spain",
"Date": "2005-05-05",
"Size": "",
}, {
"Country": "Lebanon",
"Date": "2002-02-02",
"Size": "-17",
}, {
"Country": "Argentina",
"Date": "2005-04-04",
"Size": "100",
}, {
"Country": "USA",
"Date": "",
"Size": "-6",
}]
for (var j = 0; j < 3; j++) {
$("#myTable th:eq(" + j + ")").addClass("control-label clickable");
$("#myTable th:eq(" + j + ")").attr('onClick', "w3.sortHTML('#myTable', '.item', 'td:nth-child(" + (j + 1) + ")')");
}
$tbody = $("#myTable").append('<tbody></tbody>');
for (var i = 0; i < collection.length; i++) {
$tbody = $tbody.append('<tr class="item"><td>' + collection[i]["Country"] + '</td><td>' + collection[i]["Date"] + '</td><td>' + collection[i]["Size"] + '</td></tr>');
}
.control-label:after {
content: "*";
color: red;
}
.clickable {
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://www.w3schools.com/lib/w3.js"></script>
<link href="https://www.w3schools.com/w3css/4/w3.css" rel="stylesheet" />
<p>Click the <strong>table headers</strong> to sort the table accordingly:</p>
<table id="myTable" class="w3-table-all">
<thead>
<tr>
<th>Country</th>
<th>Date</th>
<th>Size</th>
</tr>
</thead>
</table>
Another compact but readable solution:
It just requires adding the class .order to the <th> element of each column to be ordered
document.querySelectorAll('th.order').forEach(th_elem => {
let asc=true
const index = Array.from(th_elem.parentNode.children).indexOf(th_elem)
th_elem.addEventListener('click', (e) => {
const arr = [... th_elem.closest("table").querySelectorAll('tbody tr')]
arr.sort( (a, b) => {
const a_val = a.children[index].innerText
const b_val = b.children[index].innerText
return (asc) ? a_val.localeCompare(b_val) : b_val.localeCompare(a_val)
})
arr.forEach(elem => {
th_elem.closest("table").querySelector("tbody").appendChild(elem)
})
asc = !asc
})
})
Sorting html table column on page load
var table = $('table#all_items_table');
var rows = table.find('tr:gt(0)').toArray().sort(comparer(3));
for (var i = 0; i < rows.length; i++) {
table.append(rows[i])
}
function comparer(index) {
return function (a, b) {
var v1= getCellValue(a, index),
v2= getCellValue(b, index);
return $.isNumeric(v2) && $.isNumeric(v1) ? v2 - v1: v2.localeCompare(v1)
}
}
function getCellValue(row, index) {
return parseFloat($(row).children('td').eq(index).html().replace(/,/g,'')); //1234234.45645->1234234
}
<!DOCTYPE html>
<html>
<head>
<style>
table, td, th {
border: 1px solid;
border-collapse: collapse;
}
td , th {
padding: 5px;
width: 100px;
}
th {
background-color: lightgreen;
}
</style>
</head>
<body>
<h2>JavaScript Array Sort</h2>
<p>Click the buttons to sort car objects on age.</p>
<p id="demo"></p>
<script>
var nameArrow = "", yearArrow = "";
var cars = [
{type:"Volvo", year:2016},
{type:"Saab", year:2001},
{type:"BMW", year:2010}
];
yearACS = true;
function sortYear() {
if (yearACS) {
nameArrow = "";
yearArrow = "🔽";
cars.sort(function(a,b) {
return a.year - b.year;
});
yearACS = false;
}else {
nameArrow = "";
yearArrow = "🔼";
cars.sort(function(a,b) {
return b.year - a.year;
});
yearACS = true;
}
displayCars();
}
nameACS = true;
function sortName() {
if (nameACS) {
nameArrow = "🔽";
yearArrow = "";
cars.sort(function(a,b) {
x = a.type.toLowerCase();
y = b.type.toLowerCase();
if (x > y) {return 1;}
if (x < y) {return -1};
return 0;
});
nameACS = false;
} else {
nameArrow = "🔼";
yearArrow = "";
cars.sort(function(a,b) {
x = a.type.toUpperCase();
y = b.type.toUpperCase();
if (x > y) { return -1};
if (x <y) { return 1 };
return 0;
});
nameACS = true;
}
displayCars();
}
displayCars();
function displayCars() {
var txt = "<table><tr><th onclick='sortName()'>name " + nameArrow + "</th><th onclick='sortYear()'>year " + yearArrow + "</th><tr>";
for (let i = 0; i < cars.length; i++) {
txt += "<tr><td>"+ cars[i].type + "</td><td>" + cars[i].year + "</td></tr>";
}
txt += "</table>";
document.getElementById("demo").innerHTML = txt;
}
</script>
</body>
</html>
I have edited the code from one of the example here to use jquery.
It's still not 100% jquery though. Any thoughts on the two different versions, like what are the pros and cons of each?
function column_sort() {
getCellValue = (tr, idx) => $(tr).find('td').eq( idx ).text();
comparer = (idx, asc) => (a, b) => ((v1, v2) =>
v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2)
)(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
table = $(this).closest('table')[0];
tbody = $(table).find('tbody')[0];
elm = $(this)[0];
children = elm.parentNode.children;
Array.from(tbody.querySelectorAll('tr')).sort( comparer(
Array.from(children).indexOf(elm), table.asc = !table.asc))
.forEach(tr => tbody.appendChild(tr) );
}
table.find('thead th').on('click', column_sort);
I'm very grateful for the accepted answer and jedwards' fix, but I also find them poorly readable. So here's my refactored and verbose version:
// Remember that strings are false positives for isNaN
const isEmptyOrNaN = (obj) => obj === "" || isNaN(obj);
const getCellValueInColumn = (tr, columnIdx) =>
tr.children[columnIdx].innerText || tr.children[columnIdx].textContent;
const compareCellValues = (cellValue1, cellValue2) => {
return isEmptyOrNaN(cellValue1) || isEmptyOrNaN(cellValue2)
? cellValue1.toString().localeCompare(cellValue2)
: cellValue1 - cellValue2;
};
const compareFnFactory = (columnIdx, ascending) => (firstEl, secondEl) => {
const cellValue1 = getCellValueInColumn(firstEl, columnIdx);
const cellValue2 = getCellValueInColumn(secondEl, columnIdx);
return ascending
? compareCellValues(cellValue1, cellValue2)
: compareCellValues(cellValue2, cellValue1);
};
document.querySelectorAll("th").forEach((th) =>
th.addEventListener("click", () => {
const table = th.closest("table");
const tbody = table.querySelector("tbody");
const columnIdx = Array.from(th.parentNode.children).indexOf(th);
const compareFn = compareFnFactory(columnIdx, (this.ascending = !this.ascending));
Array.from(tbody.querySelectorAll("tr"))
.sort(compareFn)
.forEach((tr) => tbody.appendChild(tr));
})
);
If you find any extra spaces or parenthesis, unnecessary indents, etc., it's because I've formatted the code with prettier.
I've wrapped this code inside a:
javascript: (function () {
// Code here
})();
and put it into a bookmarklet, so now I can sort columns inside Keycloak Admin Console.
I've found myself using #NickGrealy method to sort items and it works great!
(https://stackoverflow.com/a/49041392/18045902)
Issue I had is that i'm using a different format for date: dd-mm-YY instead of the ISO one.
As I'm passing data from a .php file as a string I had to convert the string in a date then compare with ><==
Substitute the compare function
// Returns a function responsible for sorting a specific column index
// (idx = columnIndex, asc = ascending order?).
var comparer = function(idx, asc) {
// This is used by the array.sort() function...
return function(a, b) {
// This is a transient function, that is called straight away.
// It allows passing in different order of args, based on
// the ascending/descending order.
return function(v1, v2) {
// sort based on a numeric or localeCompare, based on type...
return (v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2))
? v1 - v2
: v1.toString().localeCompare(v2);
}(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
}
};
with this:
var comparer = function(idx, asc) {
// This is used by the array.sort() function...
return function(a, b) {
// This is a transient function, that is called straight away.
// It allows passing in different order of args, based on
// the ascending/descending order.
return function(v1, v2) {
if(v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2)){
x = v1 - v2;
console.log(v1);
} else if(v1.includes("-")) {
var partsArray1 = v1.split('-');
var partsArray2 = v2.split('-');
var data1 = new Date(partsArray1[2],partsArray1[1],partsArray1[0]);
var data2 = new Date(partsArray2[2],partsArray2[1],partsArray2[0]);
if(data1>data2){
x=1;
} else if (data1<data2) {
x=-1;
} else if (data1==data2) {
x=0;
}
} else {
x = v1.toString().localeCompare(v2);
}
// sort based on a numeric or localeCompare, based on type...
//return (v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2))
// ? v1 - v2
// : v1.toString().localeCompare(v2);
return x;
}(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
}
};
NOTE: this will only work if the date you are trying to parse is a string in the dd-mm-YY format.
If you need a different format change the includes() and the split() character (in my case is "-") and the order of the date you are creating with Date().
If there's something wrong with this method please comment.
i have data from my db that used in table.
And I use some sorting javascript code to sort each column. But sorting result for number was not right (unordered number output).
function sortTable(f, n) {
var rows = $('#Product tbody tr').get();
rows.sort(function(a, b) {
var A = getVal(a);
var B = getVal(b);
if (A < B) {
return -1 * f;
}
if (A > B) {
return 1 * f;
}
return 0;
});
function getVal(elm) {
var v = $(elm).children('td').eq(n).text().toUpperCase();
if ($.isNumeric(v)) {
v = parseInt(v, 10);
}
return v;
}
$.each(rows, function(index, row) {
$('#Product').children('tbody').append(row);
});
}
var f_sort_price = 1;
var f_sort_quantity = 1;
$("#sort_price").click(function() {
f_sort_price *= -1;
var n = $(this).prevAll().length;
sortTable(f_sort_price, n);
});
$("#sort_quantity").click(function() {
f_sort_quantity *= -1;
var n = $(this).prevAll().length;
sortTable(f_sort_quantity, n);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table id="Product">
<thead>
<tr>
<th>Product</th>
<th id="sort_price">Price</th>
<th id="sort_quantity">Quantity</th>
</tr>
</thead>
<tbody>
<tr>
<td>Graphic Card</td>
<td>2,040</td>
<td>1,493</td>
</tr>
<tr>
<td>Wireless Router</td>
<td>8,420</td>
<td>2,272</td>
</tr>
<tr>
<td>Mouse</td>
<td>420</td>
<td>493</td>
</tr>
<tr>
<td>Monitor</td>
<td>4.420</td>
<td>874</td>
</tr>
</tbody>
</table>
and this is my fiddle https://jsfiddle.net/imawof/bgkneo81/18/
i want to sort the price and quantity, with formated number in td. But i have no clue how to do right sorting it with formated number (commas thousand separator, dot decimal).
I would first grab a copy of the data in the grid so that we can return to the default order when we toggle sorting off. See the tableData assignment below.
Now, to keep track of sorting, it would be better to assign data attributes to the table headers to keep track of sorting state.
For sorting, I wrote custom sorting logic as a jQuery plugin called $.fn.sort. This can be called on a table to change the sort order of the rows. The tableData is optional, but it helps give us back the original order.
(function($) {
$.parseNumeric = function(value) {
return +value.replace(/[^-0-9.]/g, '').replace(/[,]/g, '')
};
$.isNumeric2 = function(value) {
return !isNaN(+value.replace(/[$,]/g, ''));
};
$.tableDataToRows = function(data) {
return data.map(row => $('<tr>')
.append(row.map(col => $('<td>').text(col))));
};
$.fn.extractTableData = function() {
return this.find('tbody tr').toArray()
.map(tr => $(tr).find('td').toArray()
.map(td => $(td).text()));
};
$.fn.sort = function(colIndex, direction, tableData) {
var $tbody = this.find('tbody');
var $rows = $tbody.find('tr');
var sortDir = direction === 'asc' ? 1 : direction === 'desc' ? -1 : 0;
var data = tableData ? $.tableDataToRows(tableData) : $rows.toArray();
var sorted = data.sort((rowA, rowB) => {
const a = $(rowA).find(`td:eq(${colIndex})`).text();
const b = $(rowB).find(`td:eq(${colIndex})`).text();
if (!b === '') return -1;
if (!a === '') return 1;
if ($.isNumeric2(a) && $.isNumeric2(b)) {
return ($.parseNumeric(a) - $.parseNumeric(b)) * sortDir;
}
return a.localeCompare(b) * sortDir;
});
$tbody.empty().append(sorted);
}
})(jQuery);
const nextSortDirection = (sortDirection) =>
sortDirection
? sortDirection === 'asc' ? 'desc' : ''
: 'asc';
const toggleSort = ($header) => {
const sortDirection = nextSortDirection($header.data('sort-direction'));
// Clear the existing sort
$header.siblings().each(function() {
$(this).attr('data-sort-direction', '');
$(this).data('sort-direction', '');
});
// Change the sort for the current column
$header.attr('data-sort-direction', sortDirection);
$header.data('sort-direction', sortDirection);
return sortDirection;
};
const $table = $('#Products');
const tableData = $table.extractTableData();
const onHeaderSort = (e) => {
const $header = $(e.target);
const $table = $header.closest('table');
const field = $header.data('field');
const colIndex = $header.prevAll().length;
const sortDirection = toggleSort($header);
$table.sort(colIndex, sortDirection, tableData);
};
$table.find('th[data-sortable="true"]').on('click', onHeaderSort);
:root {
--header-color: #BBB;
--row-color-even: #DDD;
--row-color-odd: #FFF;
}
.styled-table {
border-collapse: collapse;
}
.styled-table {
border: thin solid grey;
}
.styled-table th, .styled-table td {
border: none;
}
.styled-table th, .styled-table td {
padding: 0.25rem;
}
.styled-table th {
position: relative;
padding-right: 1.5rem;
background: var(--header-color);
}
.styled-table tbody tr:nth-child(even) {
background: var(--row-color-even);
}
.styled-table tbody tr:nth-child(odd) {
background: var(--row-color-odd);
}
th[data-sortable]:after {
position: absolute;
content: "\21D5";
cursor: pointer;
right: 0.25rem;
}
th[data-sort-direction="asc"]:after {
content: "\21D1";
}
th[data-sort-direction="desc"]:after {
content: "\21D3";
}
tbody td:nth-child(3),
tbody td:nth-child(4) {
text-align: right;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table id="Products" class="styled-table">
<thead>
<tr>
<th data-field="id" data-sortable="true" data-sort-direction="">ID</th>
<th data-field="product" data-sortable="true" data-sort-direction="">Product</th>
<th data-field="price" data-sortable="true" data-sort-direction="">Price</th>
<th data-field="quantity" data-sortable="true" data-sort-direction="">Quantity</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Graphic Card</td>
<td>$2,040.00</td>
<td>1,493</td>
</tr>
<tr>
<td>2</td>
<td>Wireless Router</td>
<td>$8,420.00</td>
<td>2,272</td>
</tr>
<tr>
<td>3</td>
<td>Mouse</td>
<td>$420.00</td>
<td>493</td>
</tr>
<tr>
<td>4</td>
<td>Monitor</td>
<td>$4.42</td>
<td>874</td>
</tr>
</tbody>
</table>
You are mixing commas(8,420) and decimal points (4.420).
If you intend to keep the ',' as visual seperators, you have to remove them in your sort function before converting them.
I also recommend parseFloat to keep the decimal points for your Prices.
function getVal(elm) {
var v = $(elm).children('td').eq(n).text().toUpperCase();
v = v.replace(",","");
if ($.isNumeric(v)) {
v = parseFloat(v);
}
return v;
}
I'm after a table sorting solution (in JavaScript) but I can't seem to find a suitable one yet. I just need it to sort each column alphabetically. It doesn't need to ignore any code or any numbers or to work with currency. Just a click on the column header switches it from sorted a-z/z-a.
Does anyone know of a really simple solution like this?
Just revisiting an old solution, I thought I'd give it a facelift for it's ~5 year anniversary!
Plain Javascript (ES6)
Does alpha and numeric sorting - ascending and descending
Works in Chrome, Firefox, Safari (and IE11, see below)
Quick explanation
add a click event to all header (th) cells...
for the current table, find all rows (except the first)...
sort the rows, based on the value of the clicked column...
insert the rows back into the table, in the new order.
const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent;
const comparer = (idx, asc) => (a, b) => ((v1, v2) =>
v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2)
)(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
// do the work...
document.querySelectorAll('th').forEach(th => th.addEventListener('click', (() => {
const table = th.closest('table');
Array.from(table.querySelectorAll('tr:nth-child(n+2)'))
.sort(comparer(Array.from(th.parentNode.children).indexOf(th), this.asc = !this.asc))
.forEach(tr => table.appendChild(tr) );
})));
table, th, td {
border: 1px solid black;
}
th {
cursor: pointer;
}
<table>
<tr><th>Country</th><th>Date</th><th>Size</th></tr>
<tr><td>France</td><td>2001-01-01</td><td><i>25</i></td></tr>
<tr><td><a href=#>spain</a></td><td><i>2005-05-05</i></td><td></td></tr>
<tr><td><b>Lebanon</b></td><td><a href=#>2002-02-02</a></td><td><b>-17</b></td></tr>
<tr><td><i>Argentina</i></td><td>2005-04-04</td><td><a href=#>100</a></td></tr>
<tr><td>USA</td><td></td><td>-6</td></tr>
</table>
IE11 Support (non-ES6)
If you want to support IE11, you'll need to ditch the ES6 syntax and use alternatives to Array.from and Element.closest.
i.e.
var getCellValue = function(tr, idx){ return tr.children[idx].innerText || tr.children[idx].textContent; }
var comparer = function(idx, asc) { return function(a, b) { return function(v1, v2) {
return v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2);
}(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
}};
// do the work...
Array.prototype.slice.call(document.querySelectorAll('th')).forEach(function(th) { th.addEventListener('click', function() {
var table = th.parentNode
while(table.tagName.toUpperCase() != 'TABLE') table = table.parentNode;
Array.prototype.slice.call(table.querySelectorAll('tr:nth-child(n+2)'))
.sort(comparer(Array.prototype.slice.call(th.parentNode.children).indexOf(th), this.asc = !this.asc))
.forEach(function(tr) { table.appendChild(tr) });
})
});
Comparer function breakdown
For the sake of brevity, I compacted the comparer() function. It's a little complex/hard to read, so here it is again exploded/formatted/commented.
// Returns a function responsible for sorting a specific column index
// (idx = columnIndex, asc = ascending order?).
var comparer = function(idx, asc) {
// This is used by the array.sort() function...
return function(a, b) {
// This is a transient function, that is called straight away.
// It allows passing in different order of args, based on
// the ascending/descending order.
return function(v1, v2) {
// sort based on a numeric or localeCompare, based on type...
return (v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2))
? v1 - v2
: v1.toString().localeCompare(v2);
}(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
}
};
I wrote up some code that will sort a table by a row, assuming only one <tbody> and cells don't have a colspan.
function sortTable(table, col, reverse) {
var tb = table.tBodies[0], // use `<tbody>` to ignore `<thead>` and `<tfoot>` rows
tr = Array.prototype.slice.call(tb.rows, 0), // put rows into array
i;
reverse = -((+reverse) || -1);
tr = tr.sort(function (a, b) { // sort rows
return reverse // `-1 *` if want opposite order
* (a.cells[col].textContent.trim() // using `.textContent.trim()` for test
.localeCompare(b.cells[col].textContent.trim())
);
});
for(i = 0; i < tr.length; ++i) tb.appendChild(tr[i]); // append each row in order
}
// sortTable(tableNode, columId, false);
If you don't want to make the assumptions above, you'd need to consider how you want to behave in each circumstance. (e.g. put everything into one <tbody> or add up all the preceeding colspan values, etc.)
You could then attach this to each of your tables, e.g. assuming titles are in <thead>
function makeSortable(table) {
var th = table.tHead, i;
th && (th = th.rows[0]) && (th = th.cells);
if (th) i = th.length;
else return; // if no `<thead>` then do nothing
while (--i >= 0) (function (i) {
var dir = 1;
th[i].addEventListener('click', function () {sortTable(table, i, (dir = 1 - dir))});
}(i));
}
function makeAllSortable(parent) {
parent = parent || document.body;
var t = parent.getElementsByTagName('table'), i = t.length;
while (--i >= 0) makeSortable(t[i]);
}
and then invoking makeAllSortable onload.
Example fiddle of it working on a table.
Nick Grealy's accepted answer is great but acts a bit quirky if your rows are inside a <tbody> tag (the first row isn't ever sorted and after sorting rows end up outside of the tbody tag, possibly losing formatting).
This is a simple fix, however:
Just change:
document.querySelectorAll('th').forEach(th => th.addEventListener('click', (() => {
const table = th.closest('table');
Array.from(table.querySelectorAll('tr:nth-child(n+2)'))
.sort(comparer(Array.from(th.parentNode.children).indexOf(th), this.asc = !this.asc))
.forEach(tr => table.appendChild(tr) );
to:
document.querySelectorAll('th').forEach(th => th.addEventListener('click', (() => {
const table = th.closest('table');
const tbody = table.querySelector('tbody');
Array.from(tbody.querySelectorAll('tr'))
.sort(comparer(Array.from(th.parentNode.children).indexOf(th), this.asc = !this.asc))
.forEach(tr => tbody.appendChild(tr) );
The best way I know to sort HTML table with javascript is with the following function.
Just pass to it the id of the table you'd like to sort and the column number on the row. it assumes that the column you are sorting is numeric or has numbers in it and will do regex replace to get the number itself (great for currencies and other numbers with symbols in it).
function sortTable(table_id, sortColumn){
var tableData = document.getElementById(table_id).getElementsByTagName('tbody').item(0);
var rowData = tableData.getElementsByTagName('tr');
for(var i = 0; i < rowData.length - 1; i++){
for(var j = 0; j < rowData.length - (i + 1); j++){
if(Number(rowData.item(j).getElementsByTagName('td').item(sortColumn).innerHTML.replace(/[^0-9\.]+/g, "")) < Number(rowData.item(j+1).getElementsByTagName('td').item(sortColumn).innerHTML.replace(/[^0-9\.]+/g, ""))){
tableData.insertBefore(rowData.item(j+1),rowData.item(j));
}
}
}
}
Using example:
$(function(){
// pass the id and the <td> place you want to sort by (td counts from 0)
sortTable('table_id', 3);
});
It does WAY more than "just sorting", but dataTables.net does what you need. I use it daily and is well supported and VERY fast (does require jQuery)
http://datatables.net/
DataTables is a plug-in for the jQuery Javascript library. It is a highly flexible tool, based upon the foundations of progressive enhancement, which will add advanced interaction controls to any HTML table.
Google Visualizations is another option, but requires a bit more setup that dataTables, but does NOT require any particular framework/library (other than google.visualizations):
http://code.google.com/apis/ajax/playground/?type=visualization#table
And there are other options to... especially if you're using one of the other JS frameworks. Dojo, Prototype, etc all have usable "table enhancement" plugins that provide at minimum table sorting functionality. Many provide more, but I'll restate...I've yet to come across one as powerful and as FAST as datatables.net.
Table Sorting with :hover arrows effect. Simply add the class .order to the <th> element of each column to be ordered
function table_sort() {
const styleSheet = document.createElement('style')
styleSheet.innerHTML = `
.order-inactive span {
visibility:hidden;
}
.order-inactive:hover span {
visibility:visible;
}
.order-active span {
visibility: visible;
}
`
document.head.appendChild(styleSheet)
document.querySelectorAll('th.order').forEach(th_elem => {
let asc = true
const span_elem = document.createElement('span')
span_elem.style = "font-size:0.8rem; margin-left:0.5rem"
span_elem.innerHTML = "▼"
th_elem.appendChild(span_elem)
th_elem.classList.add('order-inactive')
const index = Array.from(th_elem.parentNode.children).indexOf(th_elem)
th_elem.addEventListener('click', (e) => {
document.querySelectorAll('th.order').forEach(elem => {
elem.classList.remove('order-active')
elem.classList.add('order-inactive')
})
th_elem.classList.remove('order-inactive')
th_elem.classList.add('order-active')
if (!asc) {
th_elem.querySelector('span').innerHTML = '▲'
} else {
th_elem.querySelector('span').innerHTML = '▼'
}
const arr = Array.from(th_elem.closest("table").querySelectorAll('tbody tr'))
arr.sort((a, b) => {
const a_val = a.children[index].innerText
const b_val = b.children[index].innerText
return (asc) ? a_val.localeCompare(b_val) : b_val.localeCompare(a_val)
})
arr.forEach(elem => {
th_elem.closest("table").querySelector("tbody").appendChild(elem)
})
asc = !asc
})
})
}
table_sort()
<table>
<thead>
<tr>
<th class="order">Country</th>
<th class="order">Date</th>
<th class="order">Size</th>
</tr>
</thead>
<tbody>
<tr>
<td>France</td>
<td>2001-01-01</td>
<td><i>25</i></td>
</tr>
<tr>
<td><a href=#>spain</a></td>
<td><i>2005-05-05</i></td>
<td></td>
</tr>
<tr>
<td><b>Lebanon</b></td>
<td><a href=#>2002-02-02</a></td>
<td><b>-17</b></td>
</tr>
<tr>
<td><i>Argentina</i></td>
<td>2005-04-04</td>
<td><a href=#>100</a></td>
</tr>
<tr>
<td>USA</td>
<td></td>
<td>-6</td>
</tr>
</tbody>
</table>
You could deal with a json array and the sort function. It is a pretty easy maintanable structure to manipulate (ex: sorting).
Untested, but here's the idea. That would support multiple ordering and sequential ordering if you pass in a array in which you put the columns in the order they should be ordered by.
var DATA_TABLE = {
{name: 'George', lastname: 'Blarr', age:45},
{name: 'Bob', lastname: 'Arr', age: 20}
//...
};
function sortDataTable(arrayColNames, asc) { // if not asc, desc
for (var i=0;i<arrayColNames.length;i++) {
var columnName = arrayColNames[i];
DATA_TABLE = DATA_TABLE.sort(function(a,b){
if (asc) {
return (a[columnName] > b[columnName]) ? 1 : -1;
} else {
return (a[columnName] < b[columnName]) ? 1 : -1;
}
});
}
}
function updateHTMLTable() {
// update innerHTML / textContent according to DATA_TABLE
// Note: textContent for firefox, innerHTML for others
}
Now let's imagine you need to order by lastname, then name, and finally by age.
var orderAsc = true;
sortDataTable(['lastname', 'name', 'age'], orderAsc);
It should result in something like :
{name: 'Jack', lastname: 'Ahrl', age: 20},
{name: 'Jack', lastname: 'Ahrl', age: 22},
//...
Here is a complete example using pure JavaScript. The algorithm used for sorting is basically BubbleSort. Here is a Fiddle.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<script type="text/javascript">
function sort(ascending, columnClassName, tableId) {
var tbody = document.getElementById(tableId).getElementsByTagName(
"tbody")[0];
var rows = tbody.getElementsByTagName("tr");
var unsorted = true;
while (unsorted) {
unsorted = false
for (var r = 0; r < rows.length - 1; r++) {
var row = rows[r];
var nextRow = rows[r + 1];
var value = row.getElementsByClassName(columnClassName)[0].innerHTML;
var nextValue = nextRow.getElementsByClassName(columnClassName)[0].innerHTML;
value = value.replace(',', '.'); // in case a comma is used in float number
nextValue = nextValue.replace(',', '.');
if (!isNaN(value)) {
value = parseFloat(value);
nextValue = parseFloat(nextValue);
}
if (ascending ? value > nextValue : value < nextValue) {
tbody.insertBefore(nextRow, row);
unsorted = true;
}
}
}
};
</script>
</head>
<body>
<table id="content-table">
<thead>
<tr>
<th class="id">ID asc des
</th>
<th class="country">Country asc des
</th>
<th class="some-fact">Some fact asc
des
<th>
</tr>
</thead>
<tbody>
<tr>
<td class="id">001</td>
<td class="country">Germany</td>
<td class="some-fact">16.405</td>
</tr>
<tr>
<td class="id">002</td>
<td class="country">France</td>
<td class="some-fact">10.625</td>
</tr>
<tr>
<td class="id">003</td>
<td class="country">UK</td>
<td class="some-fact">15.04</td>
</tr>
<tr>
<td class="id">004</td>
<td class="country">China</td>
<td class="some-fact">13.536</td>
</tr>
</tbody>
</table>
</body>
</html>
You can also check out the source from here: https://github.com/wmentzel/table-sort
Sorting table rows by cell.
1. Little simpler and has some features.
2. Distinguish 'number' and 'string' on sorting
3. Add toggle to sort by ASC, DESC
var index; // cell index
var toggleBool; // sorting asc, desc
function sorting(tbody, index){
this.index = index;
if(toggleBool){
toggleBool = false;
}else{
toggleBool = true;
}
var datas= new Array();
var tbodyLength = tbody.rows.length;
for(var i=0; i<tbodyLength; i++){
datas[i] = tbody.rows[i];
}
// sort by cell[index]
datas.sort(compareCells);
for(var i=0; i<tbody.rows.length; i++){
// rearrange table rows by sorted rows
tbody.appendChild(datas[i]);
}
}
function compareCells(a,b) {
var aVal = a.cells[index].innerText;
var bVal = b.cells[index].innerText;
aVal = aVal.replace(/\,/g, '');
bVal = bVal.replace(/\,/g, '');
if(toggleBool){
var temp = aVal;
aVal = bVal;
bVal = temp;
}
if(aVal.match(/^[0-9]+$/) && bVal.match(/^[0-9]+$/)){
return parseFloat(aVal) - parseFloat(bVal);
}
else{
if (aVal < bVal){
return -1;
}else if (aVal > bVal){
return 1;
}else{
return 0;
}
}
}
below is html sample
<table summary="Pioneer">
<thead>
<tr>
<th scope="col" onclick="sorting(tbody01, 0)">No.</th>
<th scope="col" onclick="sorting(tbody01, 1)">Name</th>
<th scope="col" onclick="sorting(tbody01, 2)">Belong</th>
<th scope="col" onclick="sorting(tbody01, 3)">Current Networth</th>
<th scope="col" onclick="sorting(tbody01, 4)">BirthDay</th>
<th scope="col" onclick="sorting(tbody01, 5)">Just Number</th>
</tr>
</thead>
<tbody id="tbody01">
<tr>
<td>1</td>
<td>Gwanshic Yi</td>
<td>Gwanshic Home</td>
<td>120000</td>
<td>1982-03-20</td>
<td>124,124,523</td>
</tr>
<tr>
<td>2</td>
<td>Steve Jobs</td>
<td>Apple</td>
<td>19000000000</td>
<td>1955-02-24</td>
<td>194,523</td>
</tr>
<tr>
<td>3</td>
<td>Bill Gates</td>
<td>MicroSoft</td>
<td>84300000000</td>
<td>1955-10-28</td>
<td>1,524,124,523</td>
</tr>
<tr>
<td>4</td>
<td>Larry Page</td>
<td>Google</td>
<td>39100000000</td>
<td>1973-03-26</td>
<td>11,124,523</td>
</tr>
</tbody>
</table>
In case your table does not have ths but only tds (with headers included) you can try the following which is based on Nick Grealy's answer above:
const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent;
const comparer = (idx, asc) => (a, b) => ((v1, v2) =>
v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2)
)(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
// do the work...
document.querySelectorAll('tr:first-child td').forEach(td => td.addEventListener('click', (() => {
const table = td.closest('table');
Array.from(table.querySelectorAll('tr:nth-child(n+2)'))
.sort(comparer(Array.from(td.parentNode.children).indexOf(td), this.asc = !this.asc))
.forEach(tr => table.appendChild(tr) );
})));
#charset "UTF-8";
#import url('https://fonts.googleapis.com/css?family=Roboto');
*{
font-family: 'Roboto', sans-serif;
text-transform:capitalize;
overflow:hidden;
margin: 0 auto;
text-align:left;
}
table {
color:#666;
font-size:12px;
background:#124;
border:#ccc 1px solid;
-moz-border-radius:3px;
-webkit-border-radius:3px;
border-radius:3px;
border-collapse: collapse;
width: 100%;
}
table td {
padding:10px;
border-top: 1px solid #ffffff;
border-bottom:1px solid #e0e0e0;
border-left: 1px solid #e0e0e0;
background: #fafafa;
background: -webkit-gradient(linear, left top, left bottom, from(#fbfbfb), to(#fafafa));
background: -moz-linear-gradient(top, #fbfbfb, #fafafa);
width: 6.9in;
}
table tbody tr:first-child td
{
background: #124!important;
color:#fff;
}
table tbody tr th
{
padding:10px;
border-left: 1px solid #e0e0e0;
background: #124!important;
color:#fff;
}
<table>
<tr><td>Country</td><td>Date</td><td>Size</td></tr>
<tr><td>France</td><td>2001-01-01</td><td><i>25</i></td></tr>
<tr><td>spain</td><td>2005-05-05</td><td></td></tr>
<tr><td>Lebanon</td><td>2002-02-02</td><td><b>-17</b></td></tr>
<tr><td>Argentina</td><td>2005-04-04</td><td>100</td></tr>
<tr><td>USA</td><td></td><td>-6</td></tr>
</table>
Another approach to sort HTML table. (based on W3.JS HTML Sort)
var collection = [{
"Country": "France",
"Date": "2001-01-01",
"Size": "25",
}, {
"Country": "spain",
"Date": "2005-05-05",
"Size": "",
}, {
"Country": "Lebanon",
"Date": "2002-02-02",
"Size": "-17",
}, {
"Country": "Argentina",
"Date": "2005-04-04",
"Size": "100",
}, {
"Country": "USA",
"Date": "",
"Size": "-6",
}]
for (var j = 0; j < 3; j++) {
$("#myTable th:eq(" + j + ")").addClass("control-label clickable");
$("#myTable th:eq(" + j + ")").attr('onClick', "w3.sortHTML('#myTable', '.item', 'td:nth-child(" + (j + 1) + ")')");
}
$tbody = $("#myTable").append('<tbody></tbody>');
for (var i = 0; i < collection.length; i++) {
$tbody = $tbody.append('<tr class="item"><td>' + collection[i]["Country"] + '</td><td>' + collection[i]["Date"] + '</td><td>' + collection[i]["Size"] + '</td></tr>');
}
.control-label:after {
content: "*";
color: red;
}
.clickable {
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://www.w3schools.com/lib/w3.js"></script>
<link href="https://www.w3schools.com/w3css/4/w3.css" rel="stylesheet" />
<p>Click the <strong>table headers</strong> to sort the table accordingly:</p>
<table id="myTable" class="w3-table-all">
<thead>
<tr>
<th>Country</th>
<th>Date</th>
<th>Size</th>
</tr>
</thead>
</table>
Another compact but readable solution:
It just requires adding the class .order to the <th> element of each column to be ordered
document.querySelectorAll('th.order').forEach(th_elem => {
let asc=true
const index = Array.from(th_elem.parentNode.children).indexOf(th_elem)
th_elem.addEventListener('click', (e) => {
const arr = [... th_elem.closest("table").querySelectorAll('tbody tr')]
arr.sort( (a, b) => {
const a_val = a.children[index].innerText
const b_val = b.children[index].innerText
return (asc) ? a_val.localeCompare(b_val) : b_val.localeCompare(a_val)
})
arr.forEach(elem => {
th_elem.closest("table").querySelector("tbody").appendChild(elem)
})
asc = !asc
})
})
Sorting html table column on page load
var table = $('table#all_items_table');
var rows = table.find('tr:gt(0)').toArray().sort(comparer(3));
for (var i = 0; i < rows.length; i++) {
table.append(rows[i])
}
function comparer(index) {
return function (a, b) {
var v1= getCellValue(a, index),
v2= getCellValue(b, index);
return $.isNumeric(v2) && $.isNumeric(v1) ? v2 - v1: v2.localeCompare(v1)
}
}
function getCellValue(row, index) {
return parseFloat($(row).children('td').eq(index).html().replace(/,/g,'')); //1234234.45645->1234234
}
<!DOCTYPE html>
<html>
<head>
<style>
table, td, th {
border: 1px solid;
border-collapse: collapse;
}
td , th {
padding: 5px;
width: 100px;
}
th {
background-color: lightgreen;
}
</style>
</head>
<body>
<h2>JavaScript Array Sort</h2>
<p>Click the buttons to sort car objects on age.</p>
<p id="demo"></p>
<script>
var nameArrow = "", yearArrow = "";
var cars = [
{type:"Volvo", year:2016},
{type:"Saab", year:2001},
{type:"BMW", year:2010}
];
yearACS = true;
function sortYear() {
if (yearACS) {
nameArrow = "";
yearArrow = "🔽";
cars.sort(function(a,b) {
return a.year - b.year;
});
yearACS = false;
}else {
nameArrow = "";
yearArrow = "🔼";
cars.sort(function(a,b) {
return b.year - a.year;
});
yearACS = true;
}
displayCars();
}
nameACS = true;
function sortName() {
if (nameACS) {
nameArrow = "🔽";
yearArrow = "";
cars.sort(function(a,b) {
x = a.type.toLowerCase();
y = b.type.toLowerCase();
if (x > y) {return 1;}
if (x < y) {return -1};
return 0;
});
nameACS = false;
} else {
nameArrow = "🔼";
yearArrow = "";
cars.sort(function(a,b) {
x = a.type.toUpperCase();
y = b.type.toUpperCase();
if (x > y) { return -1};
if (x <y) { return 1 };
return 0;
});
nameACS = true;
}
displayCars();
}
displayCars();
function displayCars() {
var txt = "<table><tr><th onclick='sortName()'>name " + nameArrow + "</th><th onclick='sortYear()'>year " + yearArrow + "</th><tr>";
for (let i = 0; i < cars.length; i++) {
txt += "<tr><td>"+ cars[i].type + "</td><td>" + cars[i].year + "</td></tr>";
}
txt += "</table>";
document.getElementById("demo").innerHTML = txt;
}
</script>
</body>
</html>
I have edited the code from one of the example here to use jquery.
It's still not 100% jquery though. Any thoughts on the two different versions, like what are the pros and cons of each?
function column_sort() {
getCellValue = (tr, idx) => $(tr).find('td').eq( idx ).text();
comparer = (idx, asc) => (a, b) => ((v1, v2) =>
v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2)
)(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
table = $(this).closest('table')[0];
tbody = $(table).find('tbody')[0];
elm = $(this)[0];
children = elm.parentNode.children;
Array.from(tbody.querySelectorAll('tr')).sort( comparer(
Array.from(children).indexOf(elm), table.asc = !table.asc))
.forEach(tr => tbody.appendChild(tr) );
}
table.find('thead th').on('click', column_sort);
I'm very grateful for the accepted answer and jedwards' fix, but I also find them poorly readable. So here's my refactored and verbose version:
// Remember that strings are false positives for isNaN
const isEmptyOrNaN = (obj) => obj === "" || isNaN(obj);
const getCellValueInColumn = (tr, columnIdx) =>
tr.children[columnIdx].innerText || tr.children[columnIdx].textContent;
const compareCellValues = (cellValue1, cellValue2) => {
return isEmptyOrNaN(cellValue1) || isEmptyOrNaN(cellValue2)
? cellValue1.toString().localeCompare(cellValue2)
: cellValue1 - cellValue2;
};
const compareFnFactory = (columnIdx, ascending) => (firstEl, secondEl) => {
const cellValue1 = getCellValueInColumn(firstEl, columnIdx);
const cellValue2 = getCellValueInColumn(secondEl, columnIdx);
return ascending
? compareCellValues(cellValue1, cellValue2)
: compareCellValues(cellValue2, cellValue1);
};
document.querySelectorAll("th").forEach((th) =>
th.addEventListener("click", () => {
const table = th.closest("table");
const tbody = table.querySelector("tbody");
const columnIdx = Array.from(th.parentNode.children).indexOf(th);
const compareFn = compareFnFactory(columnIdx, (this.ascending = !this.ascending));
Array.from(tbody.querySelectorAll("tr"))
.sort(compareFn)
.forEach((tr) => tbody.appendChild(tr));
})
);
If you find any extra spaces or parenthesis, unnecessary indents, etc., it's because I've formatted the code with prettier.
I've wrapped this code inside a:
javascript: (function () {
// Code here
})();
and put it into a bookmarklet, so now I can sort columns inside Keycloak Admin Console.
I've found myself using #NickGrealy method to sort items and it works great!
(https://stackoverflow.com/a/49041392/18045902)
Issue I had is that i'm using a different format for date: dd-mm-YY instead of the ISO one.
As I'm passing data from a .php file as a string I had to convert the string in a date then compare with ><==
Substitute the compare function
// Returns a function responsible for sorting a specific column index
// (idx = columnIndex, asc = ascending order?).
var comparer = function(idx, asc) {
// This is used by the array.sort() function...
return function(a, b) {
// This is a transient function, that is called straight away.
// It allows passing in different order of args, based on
// the ascending/descending order.
return function(v1, v2) {
// sort based on a numeric or localeCompare, based on type...
return (v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2))
? v1 - v2
: v1.toString().localeCompare(v2);
}(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
}
};
with this:
var comparer = function(idx, asc) {
// This is used by the array.sort() function...
return function(a, b) {
// This is a transient function, that is called straight away.
// It allows passing in different order of args, based on
// the ascending/descending order.
return function(v1, v2) {
if(v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2)){
x = v1 - v2;
console.log(v1);
} else if(v1.includes("-")) {
var partsArray1 = v1.split('-');
var partsArray2 = v2.split('-');
var data1 = new Date(partsArray1[2],partsArray1[1],partsArray1[0]);
var data2 = new Date(partsArray2[2],partsArray2[1],partsArray2[0]);
if(data1>data2){
x=1;
} else if (data1<data2) {
x=-1;
} else if (data1==data2) {
x=0;
}
} else {
x = v1.toString().localeCompare(v2);
}
// sort based on a numeric or localeCompare, based on type...
//return (v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2))
// ? v1 - v2
// : v1.toString().localeCompare(v2);
return x;
}(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
}
};
NOTE: this will only work if the date you are trying to parse is a string in the dd-mm-YY format.
If you need a different format change the includes() and the split() character (in my case is "-") and the order of the date you are creating with Date().
If there's something wrong with this method please comment.
I have a table with 5 columns. the following code will filter the data on basis of all columns. I want to filter the data for each column. for ex: if there are 10 columns then 10 search fields and Also how can I make HTML part Dynamic so that I don't have to add one more search text field whenever a new column is added.
<input id="myInput" type="text" />
<script>
function filterTable(event) {
var filter = event.target.value.toUpperCase();
var rows = document.querySelector("#myTable tbody").rows;
for (var i = 1; i < rows.length; i++) {
var Col1 = rows[i].cells[0].textContent.toUpperCase();
var Col2 = rows[i].cells[1].textContent.toUpperCase();
var Col3 = rows[i].cells[2].textContent.toUpperCase();
var Col4 = rows[i].cells[3].textContent.toUpperCase();
var Col5 = rows[i].cells[4].textContent.toUpperCase();
if (Col1.indexOf(filter) > -1 || Col2.indexOf(filter) > -1 || Col3.indexOf(filter) > -1
|| Col4.indexOf(filter) > -1 || Col5.indexOf(filter) > -1) {
rows[i].style.display = "";
} else {
rows[i].style.display = "none";
}
}
}
document.querySelector(''#myInput'').addEventListener(''keyup'', filterTable, false);
</script>
I want to have this kind of functionality:
You can loop over the rows and then the columns. The Array.from method allows you to cast an element list to an array so that you can iterate over the children with Array.prototype.forEach.
All you need to do is have a show initialized to true for each row. Then if any DOES NOT column meets the filter criteria, you set show to false. After looping through all columns you display the row based on the final value of show.
Edit: Make sure you are using a browser that supports ES6+. There is a polyfill available for Array.from on the MDN site.
function filterTable(event) {
let filter = event.target.value.trim().toLowerCase();
let rows = document.querySelector('#myTable tbody').rows;
for (let i = 0; i < rows.length; i++) {
let row = rows[i], show = false;
if (filter.length > 0) {
for (let j = 0; j < row.children.length; j++) {
let col = row.children[j], text = col.textContent.toLowerCase();
if (text.indexOf(filter) > -1) {
show = true;
continue;
}
}
} else {
show = true;
}
// Avoid using 'row.styledisplay' - https://stackoverflow.com/a/28028656/1762224
// Avoid using 'row.visibility' - rows do not collapse
toggleClass(row, 'hidden-row', !show);
}
}
function toggleClass(el, className, state) {
if (el.classList) el.classList.toggle(className, state);
else {
var classes = el.className.split(' ');
var existingIndex = classes.indexOf(className);
if (state === undefined) {
if (existingIndex > -1) classes.splice(existingIndex, 1)
else classes.push(existingIndex);
} else {
if (!state) classes.splice(existingIndex, 1)
else classes.push(existingIndex);
}
el.className = classes.join(' ');
}
}
document.querySelector('#myInput').addEventListener('keyup', filterTable, false);
body {
padding: 8px;
}
.field label {
font-weight: bold;
margin-right: 0.25em;
}
#myTable {
margin-top: 0.667em;
width: 100%;
}
#myTable th {
text-transform: capitalize;
}
.hidden-row {
display: none;
}
<link href="https://unpkg.com/purecss#1.0.0/build/pure-min.css" rel="stylesheet" />
<div class="field"><label for="myInput">Filter:</label><input id="myInput" type="text" /></div>
<table id="myTable" class="pure-table pure-table-horizontal">
<thead>
<tr>
<th>name</th>
<th>drink</th>
<th>pizza</th>
<th>movie</th>
</tr>
</thead>
<tbody>
<tr>
<td>Homer</td>
<td>Squishie</td>
<td>Magheritta</td>
<td>The Avengers</td>
</tr>
<tr>
<td>Marge</td>
<td>Squishie</td>
<td>Magheritta</td>
<td>The Avengers</td>
</tr>
<tr>
<td>Bart</td>
<td>Squishie</td>
<td>Pepperoni</td>
<td>Black Dynamite</td>
</tr>
<tr>
<td>Lisa</td>
<td>Buzz Cola</td>
<td>Pepperoni</td>
<td>Iron Man</td>
</tr>
<tr>
<td>Maggie</td>
<td>Duff Beer</td>
<td>Magheritta</td>
<td>The Avengers</td>
</tr>
<tr>
<td>Kent</td>
<td>Duff Beer</td>
<td>Hawaiian</td>
<td>The Avengers</td>
</tr>
</tbody>
</table>
Search fields example
populateTable(document.getElementById('simpsons'), getData());
function dataFields(data) {
return data.reduce((r, x) => Object.keys(x).reduce((s, k) => s.indexOf(k) === -1 ? s.concat(k) : s, r), []);
}
/* Can be useful if working with raw JSON data */
function searchCriteria(fields) {
return Array.from(fields).reduce((o, field) => {
return Object.assign(o, { [field.getAttribute('placeholder')] : field.value });
}, {});
}
function onFilter(e) {
let table = e.target.parentElement.parentElement.parentElement.parentElement;
let fields = table.querySelectorAll('thead tr th input');
let criteria = searchCriteria(fields); // Unused, but useful if filtering bindable data
let searchText = Array.from(fields).map(field => field.value.trim());
Array.from(table.querySelectorAll('tbody tr')).forEach(row => {
let hideRow = false;
Array.from(row.children).forEach((col, index) => {
var value = col.innerHTML.trim().toLowerCase();
var search = searchText[index].toLowerCase();
if (search.length > 0) {
if (!value.startsWith(search)) { /* or value.indexOf(search) === -1 */
hideRow = true;
return;
}
}
});
row.classList.toggle('hidden-row', hideRow);
});
}
function populateTable(table, data) {
let fields = dataFields(data);
let thead = document.createElement('THEAD');
let tr = document.createElement('TR');
fields.forEach(field => {
let th = document.createElement('TH');
th.innerHTML = field;
tr.appendChild(th);
});
thead.appendChild(tr);
let tbody = document.createElement('TBODY');
tr = document.createElement('TR');
fields.forEach(field => {
let th = document.createElement('TH');
let input = document.createElement('INPUT');
input.setAttribute('placeholder', field);
input.addEventListener('keyup', onFilter);
th.append(input);
tr.appendChild(th);
});
thead.appendChild(tr);
data.forEach(record => {
let tr = document.createElement('TR');
fields.forEach(field => {
let td = document.createElement('TD');
td.innerHTML = record[field];
tr.appendChild(td);
});
tbody.append(tr);
});
table.appendChild(thead);
table.appendChild(tbody);
}
function getData() {
return [{
"name": "Homer",
"drink": "Squishie",
"pizza": "Magheritta",
"movie": "The Avengers"
}, {
"name": "Marge",
"drink": "Squishie",
"pizza": "Magheritta",
"movie": "The Avengers"
}, {
"name": "Bart",
"drink": "Squishie",
"pizza": "Pepperoni",
"movie": "Black Dynamite"
}, {
"name": "Lisa",
"drink": "Buzz Cola",
"pizza": "Pepperoni",
"movie": "Iron Man"
}, {
"name": "Maggie",
"drink": "Duff Beer",
"pizza": "Magheritta",
"movie": "The Avengers"
}, {
"name": "Kent",
"drink": "Duff Beer",
"pizza": "Hawaiian",
"movie": "The Avengers"
}];
}
table {
border-collapse: collapse;
width: 100%;
}
table thead tr th {
text-transform: capitalize;
}
table thead tr:last-child {
background: #eaeaea;
border-bottom: 4px double #cbcbcb;
}
table thead tr th input {
width: 100%;
}
table tbody tr:nth-child(even) {
background-color: #f2f2f2;
}
table tbody tr.hidden-row {
display: none;
}
<link href="https://unpkg.com/purecss#1.0.0/build/pure-min.css" rel="stylesheet" />
<table id="simpsons" class="pure-table pure-table-horizontal"></table>
Browser Compatibility
function onFilter(e) {
var table = e.target.parentElement.parentElement.parentElement.parentElement;
console.log(table);
var fields = table.querySelectorAll('thead tr th input');
console.log(fields);
var searchText = Array.from(fields).map(function (field) {
return field.value.trim();
});
console.log(searchText);
Array.from(table.querySelectorAll('tbody tr')).forEach(function (row) {
var hideRow = false;
Array.from(row.children).forEach(function (col, index) {
var value = col.innerHTML.trim().toLowerCase();
console.log(value);
var search = searchText[index].toLowerCase();
console.log(search);
if (search.length > 0) {
if (value.indexOf(search) === -1) {
hideRow = true;
return;
}
}
});
row.classList.toggle('hidden-row', hideRow);
});
}
All you have to do is iterate the .cells array with a for loop:
For this example, I used a variable to determine if the row should be shown.
function filterTable(event) {
var filter = event.target.value.toUpperCase();
var rows = document.querySelector("#myTable tbody").rows;
for (var i = 1; i < rows.length; i++) {
// Placeholder to indicate if a row matches the given query.
var shouldShowRow = false;
// Loop over all the cells in this row.
for (var k = 0; k < rows[i].cells.length) {
// Check to see if this cell in this row matches the query.
if (rows[i].cells[k].textContent.toUpperCase().indexOf(filter) > -1) {
// Yes, this cell matches, therefore this entire row matches.
// Flip the show row boolean to indicate that we need to show.
shouldShowRow = true;
// Because this cell matches, we do not need to check any more cells.
break;
}
}
// Change the display on the row if we need to.
if (shouldShowRow) {
rows[i].style.display = "";
} else rows[i].style.display = "none";
}
}
You can use array.
First map col to an array colArr and than using some you can match you can match filter content with content of cols .
for (var i = 1; i < rows.length; i++) {
let colArr = new Array(5).fill(0).map((e,index)=> rows[i].cells[index].textContent.toUpperCase();)
if (colArr.some(e=> e.indexOf(filter) > -1 ) {
rows[i].style.display = "";
} else {
rows[i].style.display = "none";
}
})
}
I'm after a table sorting solution (in JavaScript) but I can't seem to find a suitable one yet. I just need it to sort each column alphabetically. It doesn't need to ignore any code or any numbers or to work with currency. Just a click on the column header switches it from sorted a-z/z-a.
Does anyone know of a really simple solution like this?
Just revisiting an old solution, I thought I'd give it a facelift for it's ~5 year anniversary!
Plain Javascript (ES6)
Does alpha and numeric sorting - ascending and descending
Works in Chrome, Firefox, Safari (and IE11, see below)
Quick explanation
add a click event to all header (th) cells...
for the current table, find all rows (except the first)...
sort the rows, based on the value of the clicked column...
insert the rows back into the table, in the new order.
const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent;
const comparer = (idx, asc) => (a, b) => ((v1, v2) =>
v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2)
)(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
// do the work...
document.querySelectorAll('th').forEach(th => th.addEventListener('click', (() => {
const table = th.closest('table');
Array.from(table.querySelectorAll('tr:nth-child(n+2)'))
.sort(comparer(Array.from(th.parentNode.children).indexOf(th), this.asc = !this.asc))
.forEach(tr => table.appendChild(tr) );
})));
table, th, td {
border: 1px solid black;
}
th {
cursor: pointer;
}
<table>
<tr><th>Country</th><th>Date</th><th>Size</th></tr>
<tr><td>France</td><td>2001-01-01</td><td><i>25</i></td></tr>
<tr><td><a href=#>spain</a></td><td><i>2005-05-05</i></td><td></td></tr>
<tr><td><b>Lebanon</b></td><td><a href=#>2002-02-02</a></td><td><b>-17</b></td></tr>
<tr><td><i>Argentina</i></td><td>2005-04-04</td><td><a href=#>100</a></td></tr>
<tr><td>USA</td><td></td><td>-6</td></tr>
</table>
IE11 Support (non-ES6)
If you want to support IE11, you'll need to ditch the ES6 syntax and use alternatives to Array.from and Element.closest.
i.e.
var getCellValue = function(tr, idx){ return tr.children[idx].innerText || tr.children[idx].textContent; }
var comparer = function(idx, asc) { return function(a, b) { return function(v1, v2) {
return v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2);
}(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
}};
// do the work...
Array.prototype.slice.call(document.querySelectorAll('th')).forEach(function(th) { th.addEventListener('click', function() {
var table = th.parentNode
while(table.tagName.toUpperCase() != 'TABLE') table = table.parentNode;
Array.prototype.slice.call(table.querySelectorAll('tr:nth-child(n+2)'))
.sort(comparer(Array.prototype.slice.call(th.parentNode.children).indexOf(th), this.asc = !this.asc))
.forEach(function(tr) { table.appendChild(tr) });
})
});
Comparer function breakdown
For the sake of brevity, I compacted the comparer() function. It's a little complex/hard to read, so here it is again exploded/formatted/commented.
// Returns a function responsible for sorting a specific column index
// (idx = columnIndex, asc = ascending order?).
var comparer = function(idx, asc) {
// This is used by the array.sort() function...
return function(a, b) {
// This is a transient function, that is called straight away.
// It allows passing in different order of args, based on
// the ascending/descending order.
return function(v1, v2) {
// sort based on a numeric or localeCompare, based on type...
return (v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2))
? v1 - v2
: v1.toString().localeCompare(v2);
}(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
}
};
I wrote up some code that will sort a table by a row, assuming only one <tbody> and cells don't have a colspan.
function sortTable(table, col, reverse) {
var tb = table.tBodies[0], // use `<tbody>` to ignore `<thead>` and `<tfoot>` rows
tr = Array.prototype.slice.call(tb.rows, 0), // put rows into array
i;
reverse = -((+reverse) || -1);
tr = tr.sort(function (a, b) { // sort rows
return reverse // `-1 *` if want opposite order
* (a.cells[col].textContent.trim() // using `.textContent.trim()` for test
.localeCompare(b.cells[col].textContent.trim())
);
});
for(i = 0; i < tr.length; ++i) tb.appendChild(tr[i]); // append each row in order
}
// sortTable(tableNode, columId, false);
If you don't want to make the assumptions above, you'd need to consider how you want to behave in each circumstance. (e.g. put everything into one <tbody> or add up all the preceeding colspan values, etc.)
You could then attach this to each of your tables, e.g. assuming titles are in <thead>
function makeSortable(table) {
var th = table.tHead, i;
th && (th = th.rows[0]) && (th = th.cells);
if (th) i = th.length;
else return; // if no `<thead>` then do nothing
while (--i >= 0) (function (i) {
var dir = 1;
th[i].addEventListener('click', function () {sortTable(table, i, (dir = 1 - dir))});
}(i));
}
function makeAllSortable(parent) {
parent = parent || document.body;
var t = parent.getElementsByTagName('table'), i = t.length;
while (--i >= 0) makeSortable(t[i]);
}
and then invoking makeAllSortable onload.
Example fiddle of it working on a table.
Nick Grealy's accepted answer is great but acts a bit quirky if your rows are inside a <tbody> tag (the first row isn't ever sorted and after sorting rows end up outside of the tbody tag, possibly losing formatting).
This is a simple fix, however:
Just change:
document.querySelectorAll('th').forEach(th => th.addEventListener('click', (() => {
const table = th.closest('table');
Array.from(table.querySelectorAll('tr:nth-child(n+2)'))
.sort(comparer(Array.from(th.parentNode.children).indexOf(th), this.asc = !this.asc))
.forEach(tr => table.appendChild(tr) );
to:
document.querySelectorAll('th').forEach(th => th.addEventListener('click', (() => {
const table = th.closest('table');
const tbody = table.querySelector('tbody');
Array.from(tbody.querySelectorAll('tr'))
.sort(comparer(Array.from(th.parentNode.children).indexOf(th), this.asc = !this.asc))
.forEach(tr => tbody.appendChild(tr) );
The best way I know to sort HTML table with javascript is with the following function.
Just pass to it the id of the table you'd like to sort and the column number on the row. it assumes that the column you are sorting is numeric or has numbers in it and will do regex replace to get the number itself (great for currencies and other numbers with symbols in it).
function sortTable(table_id, sortColumn){
var tableData = document.getElementById(table_id).getElementsByTagName('tbody').item(0);
var rowData = tableData.getElementsByTagName('tr');
for(var i = 0; i < rowData.length - 1; i++){
for(var j = 0; j < rowData.length - (i + 1); j++){
if(Number(rowData.item(j).getElementsByTagName('td').item(sortColumn).innerHTML.replace(/[^0-9\.]+/g, "")) < Number(rowData.item(j+1).getElementsByTagName('td').item(sortColumn).innerHTML.replace(/[^0-9\.]+/g, ""))){
tableData.insertBefore(rowData.item(j+1),rowData.item(j));
}
}
}
}
Using example:
$(function(){
// pass the id and the <td> place you want to sort by (td counts from 0)
sortTable('table_id', 3);
});
It does WAY more than "just sorting", but dataTables.net does what you need. I use it daily and is well supported and VERY fast (does require jQuery)
http://datatables.net/
DataTables is a plug-in for the jQuery Javascript library. It is a highly flexible tool, based upon the foundations of progressive enhancement, which will add advanced interaction controls to any HTML table.
Google Visualizations is another option, but requires a bit more setup that dataTables, but does NOT require any particular framework/library (other than google.visualizations):
http://code.google.com/apis/ajax/playground/?type=visualization#table
And there are other options to... especially if you're using one of the other JS frameworks. Dojo, Prototype, etc all have usable "table enhancement" plugins that provide at minimum table sorting functionality. Many provide more, but I'll restate...I've yet to come across one as powerful and as FAST as datatables.net.
Table Sorting with :hover arrows effect. Simply add the class .order to the <th> element of each column to be ordered
function table_sort() {
const styleSheet = document.createElement('style')
styleSheet.innerHTML = `
.order-inactive span {
visibility:hidden;
}
.order-inactive:hover span {
visibility:visible;
}
.order-active span {
visibility: visible;
}
`
document.head.appendChild(styleSheet)
document.querySelectorAll('th.order').forEach(th_elem => {
let asc = true
const span_elem = document.createElement('span')
span_elem.style = "font-size:0.8rem; margin-left:0.5rem"
span_elem.innerHTML = "▼"
th_elem.appendChild(span_elem)
th_elem.classList.add('order-inactive')
const index = Array.from(th_elem.parentNode.children).indexOf(th_elem)
th_elem.addEventListener('click', (e) => {
document.querySelectorAll('th.order').forEach(elem => {
elem.classList.remove('order-active')
elem.classList.add('order-inactive')
})
th_elem.classList.remove('order-inactive')
th_elem.classList.add('order-active')
if (!asc) {
th_elem.querySelector('span').innerHTML = '▲'
} else {
th_elem.querySelector('span').innerHTML = '▼'
}
const arr = Array.from(th_elem.closest("table").querySelectorAll('tbody tr'))
arr.sort((a, b) => {
const a_val = a.children[index].innerText
const b_val = b.children[index].innerText
return (asc) ? a_val.localeCompare(b_val) : b_val.localeCompare(a_val)
})
arr.forEach(elem => {
th_elem.closest("table").querySelector("tbody").appendChild(elem)
})
asc = !asc
})
})
}
table_sort()
<table>
<thead>
<tr>
<th class="order">Country</th>
<th class="order">Date</th>
<th class="order">Size</th>
</tr>
</thead>
<tbody>
<tr>
<td>France</td>
<td>2001-01-01</td>
<td><i>25</i></td>
</tr>
<tr>
<td><a href=#>spain</a></td>
<td><i>2005-05-05</i></td>
<td></td>
</tr>
<tr>
<td><b>Lebanon</b></td>
<td><a href=#>2002-02-02</a></td>
<td><b>-17</b></td>
</tr>
<tr>
<td><i>Argentina</i></td>
<td>2005-04-04</td>
<td><a href=#>100</a></td>
</tr>
<tr>
<td>USA</td>
<td></td>
<td>-6</td>
</tr>
</tbody>
</table>
You could deal with a json array and the sort function. It is a pretty easy maintanable structure to manipulate (ex: sorting).
Untested, but here's the idea. That would support multiple ordering and sequential ordering if you pass in a array in which you put the columns in the order they should be ordered by.
var DATA_TABLE = {
{name: 'George', lastname: 'Blarr', age:45},
{name: 'Bob', lastname: 'Arr', age: 20}
//...
};
function sortDataTable(arrayColNames, asc) { // if not asc, desc
for (var i=0;i<arrayColNames.length;i++) {
var columnName = arrayColNames[i];
DATA_TABLE = DATA_TABLE.sort(function(a,b){
if (asc) {
return (a[columnName] > b[columnName]) ? 1 : -1;
} else {
return (a[columnName] < b[columnName]) ? 1 : -1;
}
});
}
}
function updateHTMLTable() {
// update innerHTML / textContent according to DATA_TABLE
// Note: textContent for firefox, innerHTML for others
}
Now let's imagine you need to order by lastname, then name, and finally by age.
var orderAsc = true;
sortDataTable(['lastname', 'name', 'age'], orderAsc);
It should result in something like :
{name: 'Jack', lastname: 'Ahrl', age: 20},
{name: 'Jack', lastname: 'Ahrl', age: 22},
//...
Here is a complete example using pure JavaScript. The algorithm used for sorting is basically BubbleSort. Here is a Fiddle.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<script type="text/javascript">
function sort(ascending, columnClassName, tableId) {
var tbody = document.getElementById(tableId).getElementsByTagName(
"tbody")[0];
var rows = tbody.getElementsByTagName("tr");
var unsorted = true;
while (unsorted) {
unsorted = false
for (var r = 0; r < rows.length - 1; r++) {
var row = rows[r];
var nextRow = rows[r + 1];
var value = row.getElementsByClassName(columnClassName)[0].innerHTML;
var nextValue = nextRow.getElementsByClassName(columnClassName)[0].innerHTML;
value = value.replace(',', '.'); // in case a comma is used in float number
nextValue = nextValue.replace(',', '.');
if (!isNaN(value)) {
value = parseFloat(value);
nextValue = parseFloat(nextValue);
}
if (ascending ? value > nextValue : value < nextValue) {
tbody.insertBefore(nextRow, row);
unsorted = true;
}
}
}
};
</script>
</head>
<body>
<table id="content-table">
<thead>
<tr>
<th class="id">ID asc des
</th>
<th class="country">Country asc des
</th>
<th class="some-fact">Some fact asc
des
<th>
</tr>
</thead>
<tbody>
<tr>
<td class="id">001</td>
<td class="country">Germany</td>
<td class="some-fact">16.405</td>
</tr>
<tr>
<td class="id">002</td>
<td class="country">France</td>
<td class="some-fact">10.625</td>
</tr>
<tr>
<td class="id">003</td>
<td class="country">UK</td>
<td class="some-fact">15.04</td>
</tr>
<tr>
<td class="id">004</td>
<td class="country">China</td>
<td class="some-fact">13.536</td>
</tr>
</tbody>
</table>
</body>
</html>
You can also check out the source from here: https://github.com/wmentzel/table-sort
Sorting table rows by cell.
1. Little simpler and has some features.
2. Distinguish 'number' and 'string' on sorting
3. Add toggle to sort by ASC, DESC
var index; // cell index
var toggleBool; // sorting asc, desc
function sorting(tbody, index){
this.index = index;
if(toggleBool){
toggleBool = false;
}else{
toggleBool = true;
}
var datas= new Array();
var tbodyLength = tbody.rows.length;
for(var i=0; i<tbodyLength; i++){
datas[i] = tbody.rows[i];
}
// sort by cell[index]
datas.sort(compareCells);
for(var i=0; i<tbody.rows.length; i++){
// rearrange table rows by sorted rows
tbody.appendChild(datas[i]);
}
}
function compareCells(a,b) {
var aVal = a.cells[index].innerText;
var bVal = b.cells[index].innerText;
aVal = aVal.replace(/\,/g, '');
bVal = bVal.replace(/\,/g, '');
if(toggleBool){
var temp = aVal;
aVal = bVal;
bVal = temp;
}
if(aVal.match(/^[0-9]+$/) && bVal.match(/^[0-9]+$/)){
return parseFloat(aVal) - parseFloat(bVal);
}
else{
if (aVal < bVal){
return -1;
}else if (aVal > bVal){
return 1;
}else{
return 0;
}
}
}
below is html sample
<table summary="Pioneer">
<thead>
<tr>
<th scope="col" onclick="sorting(tbody01, 0)">No.</th>
<th scope="col" onclick="sorting(tbody01, 1)">Name</th>
<th scope="col" onclick="sorting(tbody01, 2)">Belong</th>
<th scope="col" onclick="sorting(tbody01, 3)">Current Networth</th>
<th scope="col" onclick="sorting(tbody01, 4)">BirthDay</th>
<th scope="col" onclick="sorting(tbody01, 5)">Just Number</th>
</tr>
</thead>
<tbody id="tbody01">
<tr>
<td>1</td>
<td>Gwanshic Yi</td>
<td>Gwanshic Home</td>
<td>120000</td>
<td>1982-03-20</td>
<td>124,124,523</td>
</tr>
<tr>
<td>2</td>
<td>Steve Jobs</td>
<td>Apple</td>
<td>19000000000</td>
<td>1955-02-24</td>
<td>194,523</td>
</tr>
<tr>
<td>3</td>
<td>Bill Gates</td>
<td>MicroSoft</td>
<td>84300000000</td>
<td>1955-10-28</td>
<td>1,524,124,523</td>
</tr>
<tr>
<td>4</td>
<td>Larry Page</td>
<td>Google</td>
<td>39100000000</td>
<td>1973-03-26</td>
<td>11,124,523</td>
</tr>
</tbody>
</table>
In case your table does not have ths but only tds (with headers included) you can try the following which is based on Nick Grealy's answer above:
const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent;
const comparer = (idx, asc) => (a, b) => ((v1, v2) =>
v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2)
)(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
// do the work...
document.querySelectorAll('tr:first-child td').forEach(td => td.addEventListener('click', (() => {
const table = td.closest('table');
Array.from(table.querySelectorAll('tr:nth-child(n+2)'))
.sort(comparer(Array.from(td.parentNode.children).indexOf(td), this.asc = !this.asc))
.forEach(tr => table.appendChild(tr) );
})));
#charset "UTF-8";
#import url('https://fonts.googleapis.com/css?family=Roboto');
*{
font-family: 'Roboto', sans-serif;
text-transform:capitalize;
overflow:hidden;
margin: 0 auto;
text-align:left;
}
table {
color:#666;
font-size:12px;
background:#124;
border:#ccc 1px solid;
-moz-border-radius:3px;
-webkit-border-radius:3px;
border-radius:3px;
border-collapse: collapse;
width: 100%;
}
table td {
padding:10px;
border-top: 1px solid #ffffff;
border-bottom:1px solid #e0e0e0;
border-left: 1px solid #e0e0e0;
background: #fafafa;
background: -webkit-gradient(linear, left top, left bottom, from(#fbfbfb), to(#fafafa));
background: -moz-linear-gradient(top, #fbfbfb, #fafafa);
width: 6.9in;
}
table tbody tr:first-child td
{
background: #124!important;
color:#fff;
}
table tbody tr th
{
padding:10px;
border-left: 1px solid #e0e0e0;
background: #124!important;
color:#fff;
}
<table>
<tr><td>Country</td><td>Date</td><td>Size</td></tr>
<tr><td>France</td><td>2001-01-01</td><td><i>25</i></td></tr>
<tr><td>spain</td><td>2005-05-05</td><td></td></tr>
<tr><td>Lebanon</td><td>2002-02-02</td><td><b>-17</b></td></tr>
<tr><td>Argentina</td><td>2005-04-04</td><td>100</td></tr>
<tr><td>USA</td><td></td><td>-6</td></tr>
</table>
Another approach to sort HTML table. (based on W3.JS HTML Sort)
var collection = [{
"Country": "France",
"Date": "2001-01-01",
"Size": "25",
}, {
"Country": "spain",
"Date": "2005-05-05",
"Size": "",
}, {
"Country": "Lebanon",
"Date": "2002-02-02",
"Size": "-17",
}, {
"Country": "Argentina",
"Date": "2005-04-04",
"Size": "100",
}, {
"Country": "USA",
"Date": "",
"Size": "-6",
}]
for (var j = 0; j < 3; j++) {
$("#myTable th:eq(" + j + ")").addClass("control-label clickable");
$("#myTable th:eq(" + j + ")").attr('onClick', "w3.sortHTML('#myTable', '.item', 'td:nth-child(" + (j + 1) + ")')");
}
$tbody = $("#myTable").append('<tbody></tbody>');
for (var i = 0; i < collection.length; i++) {
$tbody = $tbody.append('<tr class="item"><td>' + collection[i]["Country"] + '</td><td>' + collection[i]["Date"] + '</td><td>' + collection[i]["Size"] + '</td></tr>');
}
.control-label:after {
content: "*";
color: red;
}
.clickable {
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://www.w3schools.com/lib/w3.js"></script>
<link href="https://www.w3schools.com/w3css/4/w3.css" rel="stylesheet" />
<p>Click the <strong>table headers</strong> to sort the table accordingly:</p>
<table id="myTable" class="w3-table-all">
<thead>
<tr>
<th>Country</th>
<th>Date</th>
<th>Size</th>
</tr>
</thead>
</table>
Another compact but readable solution:
It just requires adding the class .order to the <th> element of each column to be ordered
document.querySelectorAll('th.order').forEach(th_elem => {
let asc=true
const index = Array.from(th_elem.parentNode.children).indexOf(th_elem)
th_elem.addEventListener('click', (e) => {
const arr = [... th_elem.closest("table").querySelectorAll('tbody tr')]
arr.sort( (a, b) => {
const a_val = a.children[index].innerText
const b_val = b.children[index].innerText
return (asc) ? a_val.localeCompare(b_val) : b_val.localeCompare(a_val)
})
arr.forEach(elem => {
th_elem.closest("table").querySelector("tbody").appendChild(elem)
})
asc = !asc
})
})
Sorting html table column on page load
var table = $('table#all_items_table');
var rows = table.find('tr:gt(0)').toArray().sort(comparer(3));
for (var i = 0; i < rows.length; i++) {
table.append(rows[i])
}
function comparer(index) {
return function (a, b) {
var v1= getCellValue(a, index),
v2= getCellValue(b, index);
return $.isNumeric(v2) && $.isNumeric(v1) ? v2 - v1: v2.localeCompare(v1)
}
}
function getCellValue(row, index) {
return parseFloat($(row).children('td').eq(index).html().replace(/,/g,'')); //1234234.45645->1234234
}
<!DOCTYPE html>
<html>
<head>
<style>
table, td, th {
border: 1px solid;
border-collapse: collapse;
}
td , th {
padding: 5px;
width: 100px;
}
th {
background-color: lightgreen;
}
</style>
</head>
<body>
<h2>JavaScript Array Sort</h2>
<p>Click the buttons to sort car objects on age.</p>
<p id="demo"></p>
<script>
var nameArrow = "", yearArrow = "";
var cars = [
{type:"Volvo", year:2016},
{type:"Saab", year:2001},
{type:"BMW", year:2010}
];
yearACS = true;
function sortYear() {
if (yearACS) {
nameArrow = "";
yearArrow = "🔽";
cars.sort(function(a,b) {
return a.year - b.year;
});
yearACS = false;
}else {
nameArrow = "";
yearArrow = "🔼";
cars.sort(function(a,b) {
return b.year - a.year;
});
yearACS = true;
}
displayCars();
}
nameACS = true;
function sortName() {
if (nameACS) {
nameArrow = "🔽";
yearArrow = "";
cars.sort(function(a,b) {
x = a.type.toLowerCase();
y = b.type.toLowerCase();
if (x > y) {return 1;}
if (x < y) {return -1};
return 0;
});
nameACS = false;
} else {
nameArrow = "🔼";
yearArrow = "";
cars.sort(function(a,b) {
x = a.type.toUpperCase();
y = b.type.toUpperCase();
if (x > y) { return -1};
if (x <y) { return 1 };
return 0;
});
nameACS = true;
}
displayCars();
}
displayCars();
function displayCars() {
var txt = "<table><tr><th onclick='sortName()'>name " + nameArrow + "</th><th onclick='sortYear()'>year " + yearArrow + "</th><tr>";
for (let i = 0; i < cars.length; i++) {
txt += "<tr><td>"+ cars[i].type + "</td><td>" + cars[i].year + "</td></tr>";
}
txt += "</table>";
document.getElementById("demo").innerHTML = txt;
}
</script>
</body>
</html>
I have edited the code from one of the example here to use jquery.
It's still not 100% jquery though. Any thoughts on the two different versions, like what are the pros and cons of each?
function column_sort() {
getCellValue = (tr, idx) => $(tr).find('td').eq( idx ).text();
comparer = (idx, asc) => (a, b) => ((v1, v2) =>
v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2)
)(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
table = $(this).closest('table')[0];
tbody = $(table).find('tbody')[0];
elm = $(this)[0];
children = elm.parentNode.children;
Array.from(tbody.querySelectorAll('tr')).sort( comparer(
Array.from(children).indexOf(elm), table.asc = !table.asc))
.forEach(tr => tbody.appendChild(tr) );
}
table.find('thead th').on('click', column_sort);
I'm very grateful for the accepted answer and jedwards' fix, but I also find them poorly readable. So here's my refactored and verbose version:
// Remember that strings are false positives for isNaN
const isEmptyOrNaN = (obj) => obj === "" || isNaN(obj);
const getCellValueInColumn = (tr, columnIdx) =>
tr.children[columnIdx].innerText || tr.children[columnIdx].textContent;
const compareCellValues = (cellValue1, cellValue2) => {
return isEmptyOrNaN(cellValue1) || isEmptyOrNaN(cellValue2)
? cellValue1.toString().localeCompare(cellValue2)
: cellValue1 - cellValue2;
};
const compareFnFactory = (columnIdx, ascending) => (firstEl, secondEl) => {
const cellValue1 = getCellValueInColumn(firstEl, columnIdx);
const cellValue2 = getCellValueInColumn(secondEl, columnIdx);
return ascending
? compareCellValues(cellValue1, cellValue2)
: compareCellValues(cellValue2, cellValue1);
};
document.querySelectorAll("th").forEach((th) =>
th.addEventListener("click", () => {
const table = th.closest("table");
const tbody = table.querySelector("tbody");
const columnIdx = Array.from(th.parentNode.children).indexOf(th);
const compareFn = compareFnFactory(columnIdx, (this.ascending = !this.ascending));
Array.from(tbody.querySelectorAll("tr"))
.sort(compareFn)
.forEach((tr) => tbody.appendChild(tr));
})
);
If you find any extra spaces or parenthesis, unnecessary indents, etc., it's because I've formatted the code with prettier.
I've wrapped this code inside a:
javascript: (function () {
// Code here
})();
and put it into a bookmarklet, so now I can sort columns inside Keycloak Admin Console.
I've found myself using #NickGrealy method to sort items and it works great!
(https://stackoverflow.com/a/49041392/18045902)
Issue I had is that i'm using a different format for date: dd-mm-YY instead of the ISO one.
As I'm passing data from a .php file as a string I had to convert the string in a date then compare with ><==
Substitute the compare function
// Returns a function responsible for sorting a specific column index
// (idx = columnIndex, asc = ascending order?).
var comparer = function(idx, asc) {
// This is used by the array.sort() function...
return function(a, b) {
// This is a transient function, that is called straight away.
// It allows passing in different order of args, based on
// the ascending/descending order.
return function(v1, v2) {
// sort based on a numeric or localeCompare, based on type...
return (v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2))
? v1 - v2
: v1.toString().localeCompare(v2);
}(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
}
};
with this:
var comparer = function(idx, asc) {
// This is used by the array.sort() function...
return function(a, b) {
// This is a transient function, that is called straight away.
// It allows passing in different order of args, based on
// the ascending/descending order.
return function(v1, v2) {
if(v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2)){
x = v1 - v2;
console.log(v1);
} else if(v1.includes("-")) {
var partsArray1 = v1.split('-');
var partsArray2 = v2.split('-');
var data1 = new Date(partsArray1[2],partsArray1[1],partsArray1[0]);
var data2 = new Date(partsArray2[2],partsArray2[1],partsArray2[0]);
if(data1>data2){
x=1;
} else if (data1<data2) {
x=-1;
} else if (data1==data2) {
x=0;
}
} else {
x = v1.toString().localeCompare(v2);
}
// sort based on a numeric or localeCompare, based on type...
//return (v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2))
// ? v1 - v2
// : v1.toString().localeCompare(v2);
return x;
}(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
}
};
NOTE: this will only work if the date you are trying to parse is a string in the dd-mm-YY format.
If you need a different format change the includes() and the split() character (in my case is "-") and the order of the date you are creating with Date().
If there's something wrong with this method please comment.