I stumbled upon Nick Grealy’s answer to a question on here about sorting tables (https://stackoverflow.com/a/49041392), with jedwards fix for people having rows inside a tbody (https://stackoverflow.com/a/53880407). It was a nice piece of code, which worked perfectly for what I wanted it for.
I am just having a hard time implementing sorting symbols in the th headers of the table for when the table is sorted ascending or descending for a specific column. I came up with this which works somewhat okay:
const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent;
const comparer = (idx, asc) => (a, b) => ((v1, v2) =>
v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2)
)(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
document.querySelectorAll('th').forEach(th => th.addEventListener('click', ((e) => { //own code: added "e" to use further down
const table = th.closest('table');
const tbody = table.querySelector('tbody');
let buttons = document.getElementsByTagName("button"); //getting all button tags
const arrayButtons = ['0', '1', '2']; //own code: for use further down, as I have 3 buttons
Array.from(tbody.querySelectorAll('tr'))
.sort(comparer(Array.from(th.parentNode.children).indexOf(th), this.asc = !this.asc))
.forEach(tr => tbody.appendChild(tr));
arrayButtons.forEach(e => buttons[e].setAttribute("data-dir", "")); //own code: reset all data-dir fields in buttons
if (this.asc) { //own code: if ascending, fill in asc in data-dir field in button, else data-dir desc gets filled in.
e.target.setAttribute("data-dir", "asc");
} else {
e.target.setAttribute("data-dir", "desc");
}
})));
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
th button {
background-color: transparent;
border: none;
cursor: pointer;
font: inherit;
color:inherit;
width: 100%;
}
th button[data-dir="asc"]::after {
content: " " url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 13 13' xml:space='preserve' width='13' height='13' transform='scale(1 -1)'%3E%3Cpath d='M2.039 5.171c.114.114.25.172.406.172h8.107a.55.55 0 0 0 .406-.172c.114-.114.172-.25.172-.406s-.057-.292-.172-.406L6.906.302C6.792.187 6.658.13 6.5.13s-.292.057-.406.172L2.039 4.355a.557.557 0 0 0-.172.406.541.541 0 0 0 .172.408zM6.5 12.473a.163.163 0 0 1-.127-.055L2.319 8.364a.159.159 0 0 1-.055-.127c0-.042.01-.081.055-.127s.084-.055.127-.055h8.107c.042 0 .081.01.127.055s.055.084.055.127a.163.163 0 0 1-.055.127l-4.052 4.054c-.047.045-.084.055-.127.055m0 .396a.55.55 0 0 0 .406-.172l4.054-4.054a.557.557 0 0 0 .172-.406.549.549 0 0 0-.172-.406.557.557 0 0 0-.406-.172H2.447a.549.549 0 0 0-.406.172.557.557 0 0 0-.172.406.55.55 0 0 0 .172.406l4.054 4.053c.114.114.25.172.406.172z' fill='%23000'/%3E%3C/svg%3E");
}
th button[data-dir="desc"]::after {
content: " " url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 13 13' xml:space='preserve' width='13' height='13'%3E%3Cpath d='M2.039 5.171c.114.114.25.172.406.172h8.107a.55.55 0 0 0 .406-.172c.114-.114.172-.25.172-.406s-.057-.292-.172-.406L6.906.302C6.792.187 6.658.13 6.5.13s-.292.057-.406.172L2.039 4.355a.557.557 0 0 0-.172.406.541.541 0 0 0 .172.408zM6.5 12.473a.163.163 0 0 1-.127-.055L2.319 8.364a.159.159 0 0 1-.055-.127c0-.042.01-.081.055-.127s.084-.055.127-.055h8.107c.042 0 .081.01.127.055s.055.084.055.127a.163.163 0 0 1-.055.127l-4.052 4.054c-.047.045-.084.055-.127.055m0 .396a.55.55 0 0 0 .406-.172l4.054-4.054a.557.557 0 0 0 .172-.406.549.549 0 0 0-.172-.406.557.557 0 0 0-.406-.172H2.447a.549.549 0 0 0-.406.172.557.557 0 0 0-.172.406.55.55 0 0 0 .172.406l4.054 4.053c.114.114.25.172.406.172z' fill='%23000'/%3E%3C/svg%3E");
}
<table width='100%'>
<thead>
<tr>
<th width='20%'>Image</th>
<th width='20%'><button data-dir=''>Number</button></th>
<th width='40%'><button data-dir=''>Name</button></th>
<th width='20%'><button data-dir=''>Postal code</button></th>
</tr>
</thead>
<tbody>
<tr>
<td>IMG1</td>
<td>xxx</td>
<td>John Johnson</td>
<td>56430</td>
</tr>
<tr>
<td>IMG2</td>
<td>yyy</td>
<td>Sally Johnson</td>
<td>56430</td>
</tr>
</tbody>
</table>
There are some problems with the code that I can’t seem to solve, and which I am asking for help to fix:
- When I click the th-header of the image-column, it sets the data-dir of the th and removes the data-dir from all the other columns and basically starts sorting the image-column. The image-column does not have a button and is not supposed to be able to trigger the sorting function or the data-dir function. Any way to fix this?
- When a table is already sorted ascending, for example the Name-column, when clicking the th-header for the name-column, it sorts it ascending first (even though it already is) and then on second-click descending, instead of descending right away. Any way to fix this?
- Is the code optimized as it is, especially the parts I have added with the let buttons, const arrayButtons and the if else for setAttribute of data-dir? Could it be more optimized?
I am trying to learn, so any feedback would be appreciated. 🙂
Thank you in advance!
2
Answers
Here is a working version, there were a couple of problems.
It was sorting even the Image column because you’ve used
document.querySelectorAll('th')
and added a click listener on all those elements instead of doing that for the<button>
elements.Your version doesn’t remember the previous sorting direction because you’ve used
this.asc
which is a global variable not something associated with the specific column.To fix that, I’ve used
button.dataset.prevDir
to keep track of the previous direction without always showing the arrows.As for the optimisation. No
arrayButtons
is not the way to go.In my example I’ve used
buttons
which is a list of all the buttons and instead ofsetAttribute
, I’ve useddataset
.