0

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>
Mike
  • 2,051
  • 4
  • 28
  • 46
  • 2
    Your nesting is invalid (the only valid child-element of a `` is a `` or `` (you've got at least one `` wrapping a ``); the `` element is obsolete as of HTML 4.1, The `align`, `cellpadding`, and `cellspacing` attributes should be controlled with CSS (`text-align`, `padding` and `border-spacing` respectively). I haven't looked through the JavaScript yet, but I'd strongly suggest you tidy up, and correct, your HTML. – David Thomas Apr 02 '23 at 11:38
  • Looks like invalid nesting is the problem like David mentioned. You are wrapping children inside tr in a span. and js code is looping that to determine the sorting, so this will consider there is only one child under tr. Clean your code with correct nesting. – CodeThing Apr 02 '23 at 11:53
  • Thanks for the reply, I removed all other tags and spans (except the ones in the title which is needed for the js script to run, but still have the same issue with the sorting. Or if there is an easier js code the could be used for sorting same content. – Mike Apr 02 '23 at 13:49

1 Answers1

0

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>
Dave B
  • 1,105
  • 11
  • 17
  • I discovered something locally, that sometimes when sorting it removes the spaces in addition to the tabs of the nesting. Where in the code can this issue be fixed? (I edited the original table by adding "enter" after the space in line 16 - after `book `). – Mike Apr 02 '23 at 22:23
  • 1
    I used a function that condenses the HTML. The function name is `condenseHTML`. Replace the call to this function in the line: `const condensedHtml = condenseHTML(rowEl.outerHTML);` and replace it with your original code that retrieved the outer HTML of the row: `const condensedHtml = rowEl.outerHTML.replace(/(\t)|(\n)/g, '');`. The `replace` method, though, replaces tabs (`\t`) with an empty string. Remove the use of `\t` if you plan on keeping tabs. – Dave B Apr 02 '23 at 23:17