Search code examples
javascripthtmlsortinghtml-tablenumbers

Sort html table alphabetically and numerically


The following JavaScript is working for most cases (alphabetically), and it should be ignoring new lines or spaces, and also it should be ignoring the inner HTML. However when sorting column 2, it doesn't follow the logical numbering 1, 2, 14, 17.. as it sorts first the 1s.. then 2s.. etc. Not right numerically.

I need it to follow both rules of sorting alphabetically and numerically at the same time. I saw this post about the issue, but can't seem to implement it.

window.onload = function() {
  document.querySelectorAll('th').forEach((element) => { // Table headers
    element.addEventListener('click', function() {
      let table = this.closest('table');

      // If the column is sortable
      if (this.querySelector('span')) {
        let order_icon = this.querySelector('span');
        let order = encodeURI(order_icon.innerHTML).includes('%E2%86%91') ? 'desc' : 'asc';
        let separator = '-----'; // Separate the value of it's index, so data keeps intact

        let value_list = {}; // <tr> Object
        let obj_key = []; // Values of selected column

        let string_count = 0;
        let number_count = 0;

        // <tbody> rows
        table.querySelectorAll('tbody tr').forEach((line, index_line) => {
          // Value of each field
          let key = line.children[element.cellIndex].textContent.toUpperCase().trim();

          // Check if value is date, numeric or string
          if (line.children[element.cellIndex].hasAttribute('data-timestamp')) {
            // if value is date, we store it's timestamp, so we can sort like a number
            key = line.children[element.cellIndex].getAttribute('data-timestamp');
          } else if (key.replace('-', '').match(/^[0-9,.]*$/g)) {
            number_count++;
          } else {
            string_count++;
          }

          value_list[key + separator + index_line] = line.outerHTML.replace(/(\t)|(\n)/g, ''); // Adding <tr> to object
          obj_key.push(key + separator + index_line);
        });
        if (string_count === 0) { // If all values are numeric
          obj_key.sort(function(a, b) {
            return a.split(separator)[0] - b.split(separator)[0];
          });
        } else {
          obj_key.sort();
        }

        if (order === 'desc') {
          obj_key.reverse();
          order_icon.innerHTML = '&darr;';
        } else {
          order_icon.innerHTML = '&uarr;';
        }

        let html = '';
        obj_key.forEach(function(chave) {
          html += value_list[chave];
        });
        table.getElementsByTagName('tbody')[0].innerHTML = html;
      }
    });
  });
}
<table border="1" id="myTable2" class="resize-table-font">
  <thead>
    <tr>
      <th>
        <p align="center">name <span>&uarr;</span></p>
      </th>
      <th>
        <p align="center">ref <span>&uarr;</span></p>
      </th>
    </tr>
  </thead>
  <tr>
    <td>

      1</td>
    <td>
      (book 
      3: 16)</td>

  </tr>
  <tr>
    <td>
      22</td>
    <td>
      (book 3: 16)</td>


  </tr>
  <tr>
    <td>

      book</td>

    <td>
      (book 2: 1-3)</td>
  </tr>
  <tr>
    <td>
      2</td>

    <td>
      (book 19: 12)</td>
  </tr>
  <tr>
    <td>
      how</td>
    <td>
      (book 19: 10, 12)</td>

  </tr>
  <tr>
    <td>
      when</td>
    <td>
      (book 19: 28; 21: 31)؟</td>

  </tr>
  <tr>
    <td>
      love</td>
    <td>
      (book 19: 30; 21: 31)؟</td>

  </tr>
  <tr>
    <td>
      3</td>
    <td>
      (book 10: 41; 14: 6, 7; 15: 3)</td>

  </tr>
  <tr>
    <td>
      tg</td>
    <td>
      (book 21: 30)</td>

  </tr>
  <tr>
    <td>

      web</td>
    <td>
      (book 19: 26; 21: 30)</td>

  </tr>
  <tr>
    <td>
      internet</td>
    <td>
      (book 15: 32; 19: 7)</td>

  </tr>
  <tr>
    <td>
      far4</td>
    <td>
      (book 19: 13)</td>

  </tr>
  <tr>
    <td>
      far3</td>
    <td>
      (book 21: 32)</td>


  </tr>
  <tr>
    <td>
      far33</td>
    <td>
      (book 20: 8; 21: 27)</td>

  </tr>
</table>


Solution

  • Below is a code snippet that sorts your table based on the text content of cells in the table column that's to be sorted.

    I attempted to modify your existing code but there was a lot to redefine. The basic structure remains, however, and how you were attempting to sort your table is still in place. I used more descriptive variable names to make it a bit easier to follow/debug and integrate back into your original code.

    window.onload = function() {
      // Table headers
      document.querySelectorAll('th').forEach((elem) => {
        elem.addEventListener('click', headerClick);
      });
    };
    
    function headerClick(e) {
      // 'this' keyword can be used. 'e.currentTarget' makes
      // it easier to interpret what is the target element.
      const headerCellEl = e.currentTarget;
    
      // If the column is NOT sortable then exit this function
      if (!headerCellEl.querySelector('.sortable')) {
        return;
      }
    
      // Navigate up from the header cell element to get
      // the '<table>' element.
      let table = headerCellEl.closest('table');
    
      // Navigate down from the 'table' element to get
      // all of the row elements in the table's body.
      let tableRows = table.querySelectorAll('tbody tr');
    
      const cellIndex = headerCellEl.cellIndex;
    
      let order_icon = headerCellEl.querySelector('.sortable');
      let order = encodeURI(order_icon.innerHTML).includes('%E2%86%91') ? 'desc' : 'asc';
      // Update the sort arrow
      order_icon.innerHTML = order === 'desc' ? '&darr;' : '&uarr;';
    
      let cellList = [];
    
      tableRows.forEach(rowEl => {
        // Value of each field
        let textContent = rowEl.children[cellIndex].textContent.toUpperCase().trim();
    
        // stackoverflow.com/questions/31412765/regex-to-remove-white-spaces-blank-lines-and-final-line-break-in-javascript
        const condensedHtml = condenseHTML(rowEl.outerHTML);
    
        cellList.push({
          textContent: textContent,
          rowHtml: condensedHtml
        });
      });
    
      const sortedCellList = sortCellList(cellList, order);
    
      let html = '';
    
      sortedCellList.forEach(entry => {
        html += entry.rowHtml;
      });
    
      table.getElementsByTagName('tbody')[0].innerHTML = html;
    }
    
    // stackoverflow.com/questions/31412765/regex-to-remove-white-spaces-blank-lines-and-final-line-break-in-javascript
    function condenseHTML(html) {
      return html.split('\n')
        .map(s => {
          return s.replace(/^\s*|\s*$/g, "");
        })
        .filter(x => {
          return x;
        })
        .join("");
    }
    
    // https://stackoverflow.com/questions/2802341/natural-sort-of-alphanumerical-strings-in-javascript
    // "If you have an array of objects, you can do it like this: "
    // Answer: https://stackoverflow.com/a/52728388
    function sortCellList(cellList, order) {
      if (order === 'desc') {
        return cellList.sort(function(a, b) {
          return a.textContent.localeCompare(b.textContent, undefined, {
            numeric: true,
            sensitivity: 'base'
          });
        });
      }
    
      return cellList.sort(function(a, b) {
        return b.textContent.localeCompare(a.textContent, undefined, {
          numeric: true,
          sensitivity: 'base'
        });
      });
    }
    <table border="1" id="myTable2" class="resize-table-font">
      <thead>
        <tr>
          <th>
            <p align="center">name <span class="sortable">&uarr;</span></p>
          </th>
          <th>
            <p align="center">ref <span class="sortable">&uarr;</span></p>
          </th>
        </tr>
      </thead>
      <tr>
        <td>
    
          1
        </td>
        <td>
          (book 3: 16)
        </td>
    
      </tr>
      <tr>
        <td>
          22
        </td>
        <td>
          (book 3: 16)
        </td>
    
    
      </tr>
      <tr>
        <td>
    
          book
        </td>
    
        <td>
          (book 2: 1-3)
        </td>
      </tr>
      <tr>
        <td>
          2
        </td>
    
        <td>
          (book 19: 12)
        </td>
      </tr>
      <tr>
        <td>
          how
        </td>
        <td>
          (book 19: 10, 12)
        </td>
    
      </tr>
      <tr>
        <td>
          when
        </td>
        <td>
          (book 19: 28; 21: 31)؟
        </td>
    
      </tr>
      <tr>
        <td>
          love
        </td>
        <td>
          (book 19: 30; 21: 31)؟
        </td>
    
      </tr>
      <tr>
        <td>
          3
        </td>
        <td>
          (book 10: 41; 14: 6, 7; 15: 3)
        </td>
    
      </tr>
      <tr>
        <td>
          tg
        </td>
        <td>
          (book 21: 30)
        </td>
    
      </tr>
      <tr>
        <td>
    
          web
        </td>
        <td>
          (book 19: 26; 21: 30)
        </td>
    
      </tr>
      <tr>
        <td>
          internet
        </td>
        <td>
          (book 15: 32; 19: 7)
        </td>
    
      </tr>
      <tr>
        <td>
          far4
        </td>
        <td>
          (book 19: 13)
        </td>
    
      </tr>
      <tr>
        <td>
          far3
        </td>
        <td>
          (book 21: 32)
        </td>
    
    
      </tr>
      <tr>
        <td>
          far33
        </td>
        <td>
          (book 20: 8; 21: 27)
        </td>
    
      </tr>
    </table>