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!