0

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>
Lee Taylor
  • 7,761
  • 16
  • 33
  • 49
  • What do you mean by "sort by tbody"? You want to click on tbody to trigger sorting? How would you know by which column should it sort? – Konrad Apr 19 '22 at 22:12
  • Sorry, maybe I was unclear on that. I want to click the and sort that column by the tbody instead of the . Am I explaining this correct? – user3437098 Apr 19 '22 at 22:17
  • Oh, now I understand. I will try to make a working example – Konrad Apr 20 '22 at 17:19

1 Answers1

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.

Konrad
  • 21,590
  • 4
  • 28
  • 64
  • 1
    Holy! This is really nice! Is it possible to get the graph to follow the sorting as well? What I mean is the graph with 40% should always follow ID 1. I tried to modify it myself, but I'm not that famillear with vanilla JS – user3437098 Apr 22 '22 at 16:47