skip to Main Content

I’ve a HTML table that contain several columns. I’ve found a JSscript w3school to sort this table. I’ve slightly modifyed this script to be able to sort also a columns that contain only number. It works well except for 1 column that contain values like this ‘ 548m / 1797ft’ . This kind of value go after higher one?! I really don’t understand what’s going wrong for this particular column.

<TABLE id="table" class="resp">
            <thead>
                <TR>
                    <th onclick="sortTable(0)" class="sortable" scope="col"> Position </th>
                    <th onclick="sortTable(1)" class="sortable" scope="col"> Sommet </th>
                    <th onclick="sortTable(2)" class="sortable" scope="col"> Altitude </th>
                    
                </TR>
            </thead>
                <tr>
                        
                        <td data-label="Position"> 1 </td>
                        <td data-label="Sommet"> Mont Marcy </td>
                        <td data-label="Altitude"> 1629m / 5343ft </td>
                        
                </tr>
                <tr>
                        
                        <td data-label="Position"> 2 </td>
                        <td data-label="Sommet"> Mont Algonquin </td>
                        <td data-label="Altitude"> 1559m / 5114ft </td>                      
                </tr>
                <tr>
                        
                        <td data-label="Position"> 3 </td>
                        <td data-label="Sommet"> Mont Haystack </td>
                        <td data-label="Altitude"> 1510m / 4953ft </td>                         
                </tr>
                <tr>
                        
                        <td data-label="Position"> 4 </td>
                        <td data-label="Sommet"> Mont Skylight </td>
                        <td data-label="Altitude"> 1501m / 4923ft </td>
                        
                </tr>
                <tr>
                        
                        <td data-label="Position"> 5 </td>
                        <td data-label="Sommet"> Mont Whiteface</td>
                        <td data-label="Altitude">1483m / 4864ft </td>
 
                </tr>
                <tr>
 
                        <td data-label="Position"> 6 </td>
                        <td data-label="Sommet"> Mont Dix </td>
                        <td data-label="Altitude"> 1481m / 4858ft </td> 
                        
                </tr>
                <tr>
                        
                        <td data-label="Position"> 7  </td>
                        <td data-label="Sommet"> Mont Gray </td>
                        <td data-label="Altitude"> 1475m / 4838ft </td> 
                        
                </tr>
                <tr>
                        <td data-label="Position"> 8 </td>
                        <td data-label="Sommet"> Mont Iroquois </td>
                        <td data-label="Altitude"> 1475m / 4838ft </td> 
                        
                </tr>
                <tr>
                        
                        <td data-label="Position"> 9 </td>
                        <td data-label="Sommet"> Mont Iroquois </td>
                        <td data-label="Altitude"> 1001m / 3283ft </td> 
                        
                </tr>
                <tr>
                        
                        <td data-label="Position"> 10 </td>
                        <td data-label="Sommet"> Mont St-Bruno </td>
                        <td data-label="Altitude"> 548m / 1797ft </td>
                        
                </tr>
                <tr>
                        
                        <td data-label="Position"> 11 </td>
                        <td data-label="Sommet"> Mont Royal </td>
                        <td data-label="Altitude"> 472m / 1548ft </td>
                        
                </tr>
                    
    </table>

i’ve removed several column as they are not involved in this problem

And the script part

function sortTable(n) {
    var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
    table = document.getElementById("table");
    switching = true;
    // Set the sorting direction to ascending:
    dir = "asc";
    /* Make a loop that will continue until
    no switching has been done: */
    while (switching) {
      // Start by saying: no switching is done:
      switching = false;
      rows = table.rows;
      /* Loop through all table rows (except the
      first, which contains table headers): */
      for (i = 1; i < (rows.length - 1); i++) {
        // Start by saying there should be no switching:
        shouldSwitch = false;
        /* Get the two elements you want to compare,
        one from current row and one from the next: */
        x = rows[i].getElementsByTagName("TD")[n];
        y = rows[i + 1].getElementsByTagName("TD")[n];
        /* Check if the two rows should switch place,
        based on the direction, asc or desc: */
        if (!isNaN(x.innerHTML)) {
            // NUMERIC
            if (dir == "asc") {
                if (Number(x.innerHTML) > Number(y.innerHTML)) {
                    shouldSwitch = true;
                    break;
                  }
              } else if (dir == "desc") {
                if (Number(x.innerHTML) < Number(y.innerHTML)) {
                    shouldSwitch = true;
                    break;
                  }
              }

        } else {

            // ALPHABETIC
        if (dir == "asc") {
            if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
              // If so, mark as a switch and break the loop:
              shouldSwitch = true;
              break;
            }
          } else if (dir == "desc") {
            if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
              // If so, mark as a switch and break the loop:
              shouldSwitch = true;
              break;
            }
          }
        }

        }  // FOR LOOP
            
        
      if (shouldSwitch) {
        /* If a switch has been marked, make the switch
        and mark that a switch has been done: */
        rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
        switching = true;
        // Each time a switch is done, increase this count by 1:
        switchcount ++;
      } else {
        /* If no switching has been done AND the direction is "asc",
        set the direction to "desc" and run the while loop again. */
        if (switchcount == 0 && dir == "asc") {
          dir = "desc";
          switching = true;
        }
      }
    }
  }

I would think that this particular column would be treated like a string but it seem to not… So i really don’t know waht to do more.

P.S.: SOrry for my bad english, it’s not my language

2

Answers


  1. You need to convert the string into a number before passing it through the sort function. Here’s an implementation:

    function sortTable(n) {
        var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
        table = document.getElementById("table");
        switching = true;
        // Set the sorting direction to ascending:
        dir = "asc";
        /* Make a loop that will continue until
        no switching has been done: */
        while (switching) {
          // Start by saying: no switching is done:
          switching = false;
          rows = table.rows;
          /* Loop through all table rows (except the
          first, which contains table headers): */
          for (i = 1; i < (rows.length - 1); i++) {
            // Start by saying there should be no switching:
            shouldSwitch = false;
            /* Get the two elements you want to compare,
            one from current row and one from the next: */
            x = rows[i].getElementsByTagName("TD")[n];
            y = rows[i + 1].getElementsByTagName("TD")[n];
            /* Check if the two rows should switch place,
            based on the direction, asc or desc: */
            if (!isNaN(x.innerHTML)) {
                // NUMERIC
                if (dir == "asc") {
                    if (Number(x.innerHTML) > Number(y.innerHTML)) {
                        shouldSwitch = true;
                        break;
                      }
                  } else if (dir == "desc") {
                    if (Number(x.innerHTML) < Number(y.innerHTML)) {
                        shouldSwitch = true;
                        break;
                      }
                  }
    
            } else {
            if (/m / /.test(x.innerHTML)) { // Altitude
                if (dir == "asc") {
                    if (Number(x.innerHTML.split('m')[0]) > Number(y.innerHTML.split('m')[0])) {
                        // If so, mark as a switch and break the loop:
                        shouldSwitch = true;
                        break;
                    }
                } else if (dir == "desc") {
                    if (Number(x.innerHTML.split('m')[0]) < Number(y.innerHTML.split('m')[0])) {
                        // If so, mark as a switch and break the loop:
                        shouldSwitch = true;
                        break;
                    }
                }
            } else if (dir == "asc") { // ALPHABETIC
                if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
                  // If so, mark as a switch and break the loop:
                  shouldSwitch = true;
                  break;
                }
              } else if (dir == "desc") {
                if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
                  // If so, mark as a switch and break the loop:
                  shouldSwitch = true;
                  break;
                }
              }
            }
    
            }  // FOR LOOP
                
            
          if (shouldSwitch) {
            /* If a switch has been marked, make the switch
            and mark that a switch has been done: */
            rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
            switching = true;
            // Each time a switch is done, increase this count by 1:
            switchcount ++;
          } else {
            /* If no switching has been done AND the direction is "asc",
            set the direction to "desc" and run the while loop again. */
            if (switchcount == 0 && dir == "asc") {
              dir = "desc";
              switching = true;
            }
          }
        }
    }
    

    The above uses a regular expression to check for the "m / " in the altitude string. If it’s present, we discard everything after the "m".

    Below is the same code, just trimmed down and optimized a bit by reorganizing it:

    function sortTable(n) {
      const table = document.getElementById("table");
      const rows = table.rows;
      let i;
      let switching = true;
      let dir = "asc";
      let switchCount = 0;
      /* Make a loop that will continue until no switching has been done: */
      while (switching) {
        switching = false;
        let shouldSwitch = false;
        /* Loop through all table rows (except the first, which contains table headers): */
        for (i = 1; i < (rows.length - 1); i++) {
          /* Get the two elements you want to compare, one from current row and one from the next: */
          let x = rows[i].getElementsByTagName("TD")[n].innerHTML.toLowerCase();
          let y = rows[i + 1].getElementsByTagName("TD")[n].innerHTML.toLowerCase();
          /* Test for Altitude Measurement and use only the "number part of the string" */
          if (/m / /.test(x)) {
            x = x.split('m')[0];
            y = y.split('m')[0];
          }
          /* Convert to Numbers for comparison when possible */
          if (!isNaN(x)) {
            x = Number(x);
            y = Number(y);
          }
          /* Check if the two rows should switch place, based on the direction, asc or desc: */
          if ((dir === "asc" && x > y) || (dir === "desc" && x < y)) {
            shouldSwitch = true;
            break;
          }
        }
        if (shouldSwitch) {
          /* If a switch has been marked, make the switch and mark that a switch has been done: */
          rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
          switching = true;
          // Each time a switch is done, increase this count by 1:
          switchCount++;
        } else {
          /* If no switching has been done AND the direction is "asc", set the direction to "desc" and run the while loop again. */
          if (switchCount === 0 && dir === "asc") {
            dir = "desc";
            switching = true;
          }
        }
      }
    }
    
    Login or Signup to reply.
  2. Some remarks on your attempt:

    1. You can use what is called natural sort. This would also sort things like "a13b8c", "a13b50", "a2" in that order, and it doesn’t require you to first check whether the data are numbers or not: it works in either case.
    2. There is no need to implement a bubble sort algorithm; you can use the native sort function
    3. Don’t use innerHTML, as that might give you HTML entities like &nbsp; or &lt;. Instead use textContent or innerText.
    4. You should trim the values before comparing
    5. rows[i].getElementsByTagName("TD")[n]; is really a verbose way to do rows[i].cells[n].

    If you apply the above suggestions, the code can be reduced to only a few lines. I didn’t touch your HTML (except indentation/whitespace):

    function sortTable(col) {
        // Prepare for performing a "natural" sort
        const collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'});
        // Get the rows that should be sorted, excluding the header/footer rows
        const rows = table.querySelectorAll("#table tr:not(thead>tr):not(tfoot>tr)");
        // Get the container element for these rows (tbody):
        const parent = rows?.[0]?.parentNode;
        // Get the texts of the selected column in combination with the row indices and TR elements
        const triplets = Array.from(rows, (row, i) => [row.cells[col].textContent, i, row])
                         // ... and apply natural sort on the texts
                        .sort(([a], [b]) => collator.compare(a.trim(), b.trim()));
        // If sorting didn't change anything then reverse:
        if (triplets.every(([,i], j) => i === j)) triplets.reverse();
        // Repopulate the table rows in their new order
        for (const [,, row] of triplets) parent.appendChild(row);
    }
    <TABLE id="table" class="resp">
        <thead>
            <TR>
                <th onclick="sortTable(0)" class="sortable" scope="col"> Position </th>
                <th onclick="sortTable(1)" class="sortable" scope="col"> Sommet </th>
                <th onclick="sortTable(2)" class="sortable" scope="col"> Altitude </th>
            </TR>
        </thead>
        <tr>
            <td data-label="Position"> 1 </td>
            <td data-label="Sommet"> Mont Marcy </td>
            <td data-label="Altitude"> 1629m / 5343ft </td>
        </tr>
        <tr>
            <td data-label="Position"> 2 </td>
            <td data-label="Sommet"> Mont Algonquin </td>
            <td data-label="Altitude"> 1559m / 5114ft </td>                      
        </tr>
        <tr>
            <td data-label="Position"> 3 </td>
            <td data-label="Sommet"> Mont Haystack </td>
            <td data-label="Altitude"> 1510m / 4953ft </td>                         
        </tr>
        <tr>
            <td data-label="Position"> 4 </td>
            <td data-label="Sommet"> Mont Skylight </td>
            <td data-label="Altitude"> 1501m / 4923ft </td>
        </tr>
        <tr>
            <td data-label="Position"> 5 </td>
            <td data-label="Sommet"> Mont Whiteface</td>
            <td data-label="Altitude">1483m / 4864ft </td>
        </tr>
        <tr>
            <td data-label="Position"> 6 </td>
            <td data-label="Sommet"> Mont Dix </td>
            <td data-label="Altitude"> 1481m / 4858ft </td> 
        </tr>
        <tr>
            <td data-label="Position"> 7  </td>
            <td data-label="Sommet"> Mont Gray </td>
            <td data-label="Altitude"> 1475m / 4838ft </td> 
        </tr>
        <tr>
            <td data-label="Position"> 8 </td>
            <td data-label="Sommet"> Mont Iroquois </td>
            <td data-label="Altitude"> 1475m / 4838ft </td> 
        </tr>
        <tr>
            <td data-label="Position"> 9 </td>
            <td data-label="Sommet"> Mont Iroquois </td>
            <td data-label="Altitude"> 1001m / 3283ft </td> 
        </tr>
        <tr>
            <td data-label="Position"> 10 </td>
            <td data-label="Sommet"> Mont St-Bruno </td>
            <td data-label="Altitude"> 548m / 1797ft </td>
        </tr>
        <tr>
            <td data-label="Position"> 11 </td>
            <td data-label="Sommet"> Mont Royal </td>
            <td data-label="Altitude"> 472m / 1548ft </td>
        </tr>
    </table>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search