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
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;
}
}
}
}
}
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.
Top comments (0)