DEV Community

Steve Alex
Steve Alex

Posted on • Edited on

1 1

Wrapping a generic sort_table javascript function in a stimulus controller

Several year ago I decided I needed to add a table sort feature to an app. Now there are hundred of "sort_table" functions out there. Some ranging from 50 lines of code to a couple hundred.

I'm not into javascript but get by. I may have even been using CoffeeScript at the time. I picked one that I almost understood that was short and sweet. It did a basic table sort and even offered an ascending/descending option. I'm sorry but I've lost track of were I found this JS so I can't point to the source, but maybe someone can. At some point, probably early 2020 I wrapped this in a Stimulus controller and have used it ever since.

One of my new uses had a table that had a numeric column and it didn't handle it well. I went looking for another sort_table function and found a few, but I was too lazy to try to wrap the function(s) into a Stimulus controller. I already had something that worked, I just had to modify it by adding a simple numeric switch that I found in one of the searches.

So we'll start with the markup of one of my tables using slim.

table.small-table id="current_members"
  thead[data-controller="sortTable"]
    tr
      th Del
      th[data-action="click->sortTable#sortBy" data-stype="T"]
        i.fas.fa-sort.noprint
        |Name
      th.numeric[data-action="click->sortTable#sortBy" data-stype="N"]
        i.fas.fa-sort.noprint
        |RQ

      th Tee
      th[data-stype="N" data-action="click->sortTable#sortBy"]
        i.fas.fa-sort.noprint
        |TM

  -  @game.current_players_name.each do |p|
    tr[data-schedule-target="currentPlayers"]
      td = check_box_tag "deleted[]", value=p.id,nil,data:{action:'schedule#update_player'}
      td = p.name
      td = p.player.rquota_limited
      td.bg-green-100.select = select_tag 'tee[]', tee_options(@game.group,p,true), class:"border border-green-500 select",data:{action:'schedule#update_player'}
      td = p.team
Enter fullscreen mode Exit fullscreen mode

I add the sortTable controller to the table and data-action="click->sortTable#sortBy" to any TH column I want to sort. There is also a little css and font awesome icons that are optional. There is also another controller actions.

The numeric option is triggered by add a numeric class to the TH.

Now the Stimulus controller, again 90% of code I copied from somewhere:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = [ ]

  connect() {
    this.numeric = false
  }

  sortBy(){
    const th = event.target
    this.numeric = th.classList.contains('numeric')
    const tr = th.closest('tr')
    const table = tr.closest('table')
    var idx = Array.from(tr.children).indexOf(th)
    this.sortTable(table,idx)
  }

  sortTable(table,idx) {
    var  rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
    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")[idx];
        y = rows[i + 1].getElementsByTagName("TD")[idx];
        // Added this check if there is a row that has a colspan e.g. ending balance row
        if ((x == undefined) || (y == undefined)){continue}
        /* Check if the two rows should switch place,
        based on the direction, asc or desc: */

        // Check if numeric sort (th has class numeric) added by ME
        if (!this.numeric) {
          var compx = x.innerHTML.toLowerCase()
          var compy = y.innerHTML.toLowerCase()
        }else{
          var compx = /\d/.test(x.innerHTML) ? parseFloat(x.innerHTML) : 0 
          var compy = /\d/.test(y.innerHTML) ? parseFloat(y.innerHTML) : 0 
        }

        if (dir == "asc") {
          if (compx > compy) {
            // If so, mark as a switch and break the loop:
            shouldSwitch = true;
            break;
          }
        } else if (dir == "desc") {
          if (compx < compy) {
            // If so, mark as a switch and break the loop:
            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;
        }
      }
    }
  }

}
Enter fullscreen mode Exit fullscreen mode

I'm sure this is an old fashioned bubble sort and there maybe better functions, but it worked for me. I just wanted to point out it's not that hard to wrap a JS function into Stimulus.

SurveyJS custom survey software

JavaScript UI Libraries for Surveys and Forms

SurveyJS lets you build a JSON-based form management system that integrates with any backend, giving you full control over your data and no user limits. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more.

Learn more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more