'Sort table tbody by column
I have followed the jQuery script found here: jQuery table sort (Top voted answer)
It works perfectly for a generic table setup. However, I want to sort by tbody instead of tr. I tried modifying the script to make it work and it does on some columns (might be coincidence). I made a demo so you can better see the problem. When clicking the header for the table to the right, it works as intended. It sorts the table by the content of the cell. When clicking the table to the left, it sorts the table. Just not the intended way.
Can anyone help me see what I'm doing wrong? Sorry if I make myself unclear
My table setup
//Left table
$('.t1 tbody:first-child tr td').click(function(){
var table = $(this).parents('table').eq(0);
//1st change (tbody instead of tr)
var rows = table.find('tbody:gt(0)').toArray().sort(comparer($(this).index()));
this.asc = !this.asc;
if(!this.asc){
rows = rows.reverse();
}
for(var i = 0; i < rows.length; i++){
table.append(rows[i]);
}
})
function comparer(index) {
return function(a, b) {
var valA = getCellValue(a, index), valB = getCellValue(b, index)
return $.isNumeric(valA) && $.isNumeric(valB) ? valA - valB : valA.toString().localeCompare(valB)
}
} //2nd change
function getCellValue(row, index){ return $(row).children('td').not('skip').eq(index).text() }
//Right table
$('.t2 tr:first-child td').click(function(){
var table = $(this).parents('table').eq(0);
var rows = table.find('tr:gt(0)').toArray().sort(comparer($(this).index()));
this.asc = !this.asc;
if(!this.asc){
rows = rows.reverse();
}
for(var i = 0; i < rows.length; i++){
table.append(rows[i]);
}
})
function comparer(index) {
return function(a, b) {
var valA = getCellValue(a, index), valB = getCellValue(b, index)
return $.isNumeric(valA) && $.isNumeric(valB) ? valA - valB : valA.toString().localeCompare(valB)
}
}
function getCellValue(row, index){ return $(row).children('td').eq(index).text() }
*{
color: #fff;
font-size: 16px;
border-collapse: collapse;
position: relative;
font-family: Arial;
}
body{
background: #232937;
}
table{
float: left;
margin-right: 4em;
}
table tbody tr{
border-bottom: 0.01em solid #fff;
}
table tbody tr:nth-child(1){
border-bottom: none;
}
table tbody:first-child tr{
border-bottom: 0.01em solid #fff;
cursor: pointer;
}
table tbody tr td{
padding: 0.75em;
}
.graph{
width: 100%;
height: 0.5em;
background: rgba(0, 0, 0, 0.5);
border-radius: 0.5em;
}
.graph::before{
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent 0%, #27e8a7 100%);
border-radius: 0.5em;
clip-path: inset(0 60% 0 0);
}
.g2::before{
clip-path: inset(0 30% 0 0);
}
.g3::before{
clip-path: inset(0 80% 0 0);
}
.graph::after{
font-size: 8px;
display: flex;
justify-content: center;
align-items: center;
content: '40%';
position: absolute;
top: 50%;
left: 40%;
transform: translateY(-50%);
height: 300%;
aspect-ratio: 1 / 1;
background: #232937;
border-radius: 100%;
border: 0.01em solid #fff;
}
.g2::after{
content: '70%';
left: 70%;
}
.g3::after{
content: '20%';
left: 20%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!-- Left table -->
<table class="t1">
<p>Not working as intended</p>
<!-- Table header -->
<tbody>
<tr>
<td>ID</td>
<td>Position</td>
<td>Manufacturer</td>
<td>Serial number</td>
</tr>
</tbody>
<!-- First row -->
<tbody>
<tr>
<td>1</td>
<td>209</td>
<td>BBB</td>
<td>1122</td>
</tr>
<tr>
<!-- This is a graph to display some data and should not be counted for the sorting. Hense class skip -->
<td colspan="100%" class="skip">
<div class="graph"></div>
</td>
</tr>
</tbody>
<!-- Second row -->
<tbody>
<tr>
<td>2</td>
<td>104</td>
<td>AAA</td>
<td>2211</td>
</tr>
<tr>
<td colspan="100%" class="skip">
<div class="graph g2"></div>
</td>
</tr>
</tbody>
<!-- Third row -->
<tbody>
<tr>
<td>4</td>
<td>634</td>
<td>UUU</td>
<td>1687</td>
</tr>
<tr>
<td colspan="100%" class="skip">
<div class="graph g3"></div>
</td>
</tr>
</tbody>
</table>
<!-- Right table -->
<table class="t2">
<p>Working as intended</p>
<!-- Table header -->
<tr>
<td>ID</td>
<td>Position</td>
<td>Manufacturer</td>
<td>Serial number</td>
</tr>
<!-- First row -->
<tr>
<td>1</td>
<td>209</td>
<td>BBB</td>
<td>1122</td>
</tr>
<!-- Second row -->
<tr>
<td>2</td>
<td>104</td>
<td>AAA</td>
<td>2211</td>
</tr>
<!-- Third row -->
<tr>
<td>4</td>
<td>634</td>
<td>UUU</td>
<td>1687</td>
</tr>
</table>
Solution 1:[1]
I don't know jQuery enough to fix it, but I created a script you needed using modern JavaScript
document.querySelectorAll('.t1 thead th').forEach(header => header.addEventListener('click', ({
target
}) => {
// check if already sorted and add classes
const asc = !target.classList.contains('asc');
target.classList.toggle('asc', asc)
target.classList.toggle('desc', !asc)
// get other headers
const ths = [...target.parentNode.children]
// get index of column
const index = ths.indexOf(target)
// remove classes from other headers
ths.forEach((th, i) => {
if (i === index) return
th.classList.toggle('asc', false)
th.classList.toggle('desc', false)
})
// first remove trs
const tbodies = [...document.querySelectorAll('.t1 tbody')]
const trs = tbodies.map(tbody => tbody.querySelector('tr'))
tbodies.forEach((tbody, i) => tbody.removeChild(trs[i]))
// sort trs
trs.sort((a, b) => {
const left = a.children[index].textContent
const right = b.children[index].textContent
if (Number.isNaN(+left)) {
// sort strings
return left.localeCompare(right) * (asc ? 1 : -1)
}
// sort numbers
return (left - right) * (asc ? 1 : -1)
})
// add trs back
tbodies.forEach((tbody, i) => tbody.insertBefore(trs[i], tbody.firstChild))
}))
* {
color: #fff;
font-size: 16px;
border-collapse: collapse;
position: relative;
font-family: Arial;
}
body {
background: #232937;
}
table {
float: left;
margin-right: 4em;
}
table tbody tr {
border-bottom: 0.01em solid #fff;
}
table tbody tr:nth-child(1) {
border-bottom: none;
}
table thead th {
border-bottom: 0.01em solid #fff;
cursor: pointer;
margin-right: 1em;
}
table tbody tr td {
padding: 0.75em;
}
thead th.asc::after {
content: "?"
}
thead th.desc::after {
content: "?"
}
.graph {
width: 100%;
height: 0.5em;
background: rgba(0, 0, 0, 0.5);
border-radius: 0.5em;
}
.graph::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent 0%, #27e8a7 100%);
border-radius: 0.5em;
clip-path: inset(0 60% 0 0);
}
.g2::before {
clip-path: inset(0 30% 0 0);
}
.g3::before {
clip-path: inset(0 80% 0 0);
}
.graph::after {
font-size: 8px;
display: flex;
justify-content: center;
align-items: center;
content: '40%';
position: absolute;
top: 50%;
left: 40%;
transform: translateY(-50%);
height: 300%;
aspect-ratio: 1 / 1;
background: #232937;
border-radius: 100%;
border: 0.01em solid #fff;
}
.g2::after {
content: '70%';
left: 70%;
}
.g3::after {
content: '20%';
left: 20%;
}
<table class="t1">
<!-- Table header -->
<thead>
<tr>
<th>ID</th>
<th>Position</th>
<th>Manufacturer</th>
<th>Serial number</th>
</tr>
</thead>
<!-- First row -->
<tbody>
<tr>
<td>1</td>
<td>209</td>
<td>BBB</td>
<td>1122</td>
</tr>
<tr>
<!-- This is a graph to display some data and should not be counted for the sorting. Hense class skip -->
<td colspan="100%" class="skip">
<div class="graph"></div>
</td>
</tr>
</tbody>
<!-- Second row -->
<tbody>
<tr>
<td>2</td>
<td>104</td>
<td>AAA</td>
<td>2211</td>
</tr>
<tr>
<td colspan="100%" class="skip">
<div class="graph g2"></div>
</td>
</tr>
</tbody>
<!-- Third row -->
<tbody>
<tr>
<td>4</td>
<td>634</td>
<td>UUU</td>
<td>1687</td>
</tr>
<tr>
<td colspan="100%" class="skip">
<div class="graph g3"></div>
</td>
</tr>
</tbody>
</table>
I also changed you html a little, but you can revert it to it's previous state if you want to.
I wouldn't style or grab elements in JavaScript by tag name in a real project. Use classes and ids for this.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Konrad Linkowski |
