Trying to sort tables with w3.js but it's not sorting numbers properly

The table which I'm sorting is below.

<!DOCTYPE html>
<html>

<title>W3.JS</title>
<meta charset="utf-8">
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<script src="https://www.w3schools.com/lib/w3.js"></script>
<body class="w3-container">

<h2>Testing W3.JS with CSS</h2>

<h2>Sort Tables</h2>

<p>Click the table headers to sort the  table accordingly:</p>

<table id="myTable" class="w3-table-all">
  <tr>
    <th onclick="w3.sortHTML('#myTable', '.item', 'td:nth-child(1)')" style="cursor:pointer">Name</th>
    <th onclick="w3.sortHTML('#myTable', '.item', 'td:nth-child(2)')" style="cursor:pointer">Random Number</th>
  </tr>
  <tr class="item">
    <td>Berglunds snabbköp</td>
    <td>6</td>
  </tr>
  <tr class="item">
    <td>North/South</td>
    <td>1</td>
  </tr>
  <tr class="item">
    <td>Alfreds Futterkiste</td>
    <td>2</td>
  </tr>
  <tr class="item">
    <td>Königlich Essen</td>
    <td>5</td>
  </tr>
  <tr class="item">
    <td>Magazzini Alimentari Riuniti</td>
    <td>4</td>
  </tr>
  <tr class="item">
    <td>Paris spécialités</td>
    <td>10</td>
  </tr>
  <tr class="item">
    <td>Island Trading</td>
    <td>6</td>
  </tr>
  <tr class="item">
    <td>Laughing Bacchus Winecellars</td>
    <td>102</td>
  </tr>
</table>

</body>
</html>

The portion of the code that seems to do the sorting is here from: https://www.w3schools.com/lib/w3.js

w3.sortHTML = function(id, sel, sortvalue) {
  var a, b, i, ii, y, bytt, v1, v2, cc, j;
  a = w3.getElements(id);
  for (i = 0; i < a.length; i++) {
    for (j = 0; j < 2; j++) {
      cc = 0;
      y = 1;
      while (y == 1) {
        y = 0;
        b = a[i].querySelectorAll(sel);
        for (ii = 0; ii < (b.length - 1); ii++) {
          bytt = 0;
          if (sortvalue) {
            v1 = b[ii].querySelector(sortvalue).innerHTML.toLowerCase();
            v2 = b[ii + 1].querySelector(sortvalue).innerHTML.toLowerCase();
          } else {
            v1 = b[ii].innerHTML.toLowerCase();
            v2 = b[ii + 1].innerHTML.toLowerCase();
          }
          if ((j == 0 && (v1 > v2)) || (j == 1 && (v1 < v2))) {
            bytt = 1;
            break;
          }
        }
        if (bytt == 1) {
          b[ii].parentNode.insertBefore(b[ii + 1], b[ii]);
          y = 1;
          cc++;
        }
      }
      if (cc > 0) {break;}
    }
  }
};

How can I modify it so that it sorts numbers properly?

1 answer

  • answered 2018-11-08 07:20 CertainPerformance

    The problem is that w3.sortHTML sorts via (v1 > v2) and (v1 < v2), where v1 and v2 are each text strings - which will, of course, not take into account numeric strings and sort them the way you'd expect. Assuming you don't want to monkeypatch w3.sortHTML, you can create your own function that does something similar, but with localeCompare's numeric option:

    const myTable = document.querySelector('#myTable');
    // select all trs below the header:
    const trs = [...myTable.querySelectorAll('tr')].slice(1);
    
    myTable.addEventListener('click', ({ target }) => {
      if (!target.matches('th')) return;
      const thIndex = Array.prototype.indexOf.call(target.parentElement.children, target);
      const getText = tr => tr.children[thIndex].textContent;
      trs.sort((a, b) => getText(a).localeCompare(getText(b), undefined, { numeric: true }));
      trs.forEach(tr => myTable.appendChild(tr));
    });
    <table id="myTable" class="w3-table-all">
      <tr>
        <th style="cursor:pointer">Name</th>
        <th style="cursor:pointer">Random Number</th>
      </tr>
      <tr class="item">
        <td>Berglunds snabbköp</td>
        <td>6</td>
      </tr>
      <tr class="item">
        <td>North/South</td>
        <td>1</td>
      </tr>
      <tr class="item">
        <td>Alfreds Futterkiste</td>
        <td>2</td>
      </tr>
      <tr class="item">
        <td>Königlich Essen</td>
        <td>5</td>
      </tr>
      <tr class="item">
        <td>Magazzini Alimentari Riuniti</td>
        <td>4</td>
      </tr>
      <tr class="item">
        <td>Paris spécialités</td>
        <td>10</td>
      </tr>
      <tr class="item">
        <td>Island Trading</td>
        <td>6</td>
      </tr>
      <tr class="item">
        <td>Laughing Bacchus Winecellars</td>
        <td>102</td>
      </tr>
    </table>

    To sort in reverse after clicking twice, have a persistent object that keeps track of the last order that was sorted by:

    const myTable = document.querySelector('#myTable');
    // select all trs below the header:
    const trs = [...myTable.querySelectorAll('tr')].slice(1);
    const sortAscending = {
      0: 1,
      1: 1
    };
    myTable.addEventListener('click', ({ target }) => {
      if (!target.matches('th')) return;
      const thIndex = Array.prototype.indexOf.call(target.parentElement.children, target);
      const getText = tr => tr.children[thIndex].textContent;
      trs.sort((a, b) => (
        getText(a).localeCompare(getText(b), undefined, { numeric: true })
        * sortAscending[thIndex]
      ));
      trs.forEach(tr => myTable.appendChild(tr));
      sortAscending[thIndex] *= -1;
    });
    <table id="myTable" class="w3-table-all">
      <tr>
        <th style="cursor:pointer">Name</th>
        <th style="cursor:pointer">Random Number</th>
      </tr>
      <tr class="item">
        <td>Berglunds snabbköp</td>
        <td>6</td>
      </tr>
      <tr class="item">
        <td>North/South</td>
        <td>1</td>
      </tr>
      <tr class="item">
        <td>Alfreds Futterkiste</td>
        <td>2</td>
      </tr>
      <tr class="item">
        <td>Königlich Essen</td>
        <td>5</td>
      </tr>
      <tr class="item">
        <td>Magazzini Alimentari Riuniti</td>
        <td>4</td>
      </tr>
      <tr class="item">
        <td>Paris spécialités</td>
        <td>10</td>
      </tr>
      <tr class="item">
        <td>Island Trading</td>
        <td>6</td>
      </tr>
      <tr class="item">
        <td>Laughing Bacchus Winecellars</td>
        <td>102</td>
      </tr>
    </table>