Sortable Table

Product inventory with sortable columns for name, category, price, and stock
Wireless Mouse Accessories $29.99 120
Mechanical Keyboard Accessories $89.99 45
USB-C Hub Accessories $49.50 78
4K Monitor Displays $399.99 12
Laptop Stand Office $34.25 64
Noise Cancelling Headphones Audio $199.99 23
Webcam Video $79.00 39
Portable SSD 1TB Storage $129.99 56
Ergonomic Chair Office $299.00 8
Desk Lamp Office $22.50 91

The HTML

							
								<table class="table" id="sortable_table">
								    <caption class="sr-only">Product inventory with sortable columns for name, category, price, and stock</caption>
								    <thead>
								        <tr>
								            <th scope="col">
								                <button type="button" class="sort_button" data-column="0" aria-sort="none">Product Name</button>
								            </th>
								            <th scope="col">
								                <button type="button" class="sort_button" data-column="1" aria-sort="none">Category</button>
								            </th>
								            <th scope="col">
								                <button type="button" class="sort_button" data-column="2" aria-sort="none">Price</button>
								            </th>
								            <th scope="col">
								                <button type="button" class="sort_button" data-column="3" aria-sort="none">Stock</button>
								            </th>
								        </tr>
								    </thead>
								    <tbody>
								        <tr>
								            <th scope="row">Wireless Mouse</th>
								            <td>Accessories</td>
								            <td data-value="29.99">$29.99</td>
								            <td data-value="120">120</td>
								        </tr>
								        <tr>
								            <th scope="row">Mechanical Keyboard</th>
								            <td>Accessories</td>
								            <td data-value="89.99">$89.99</td>
								            <td data-value="45">45</td>
								        </tr>
								        <tr>
								            <th scope="row">USB-C Hub</th>
								            <td>Accessories</td>
								            <td data-value="49.5">$49.50</td>
								            <td data-value="78">78</td>
								        </tr>
								        <tr>
								            <th scope="row">4K Monitor</th>
								            <td>Displays</td>
								            <td data-value="399.99">$399.99</td>
								            <td data-value="12">12</td>
								        </tr>
								        <tr>
								            <th scope="row">Laptop Stand</th>
								            <td>Office</td>
								            <td data-value="34.25">$34.25</td>
								            <td data-value="64">64</td>
								        </tr>
								        <tr>
								            <th scope="row">Noise Cancelling Headphones</th>
								            <td>Audio</td>
								            <td data-value="199.99">$199.99</td>
								            <td data-value="23">23</td>
								        </tr>
								        <tr>
								            <th scope="row">Webcam</th>
								            <td>Video</td>
								            <td data-value="79">$79.00</td>
								            <td data-value="39">39</td>
								        </tr>
								        <tr>
								            <th scope="row">Portable SSD 1TB</th>
								            <td>Storage</td>
								            <td data-value="129.99">$129.99</td>
								            <td data-value="56">56</td>
								        </tr>
								        <tr>
								            <th scope="row">Ergonomic Chair</th>
								            <td>Office</td>
								            <td data-value="299">$299.00</td>
								            <td data-value="8">8</td>
								        </tr>
								        <tr>
								            <th scope="row">Desk Lamp</th>
								            <td>Office</td>
								            <td data-value="22.5">$22.50</td>
								            <td data-value="91">91</td>
								        </tr>
								    </tbody>
								</table>
							
						

The JavaScript

							
								function initSortableTable() {
								    const root = document.querySelector("#sortable_table");
								    if (!root) return;

								    const tbody = root.querySelector("tbody");
								    if (!tbody) return;

								    // get all the buttons (node list)
								    const buttons = root.querySelectorAll(".sort_button");

								    // loop through each button
								    buttons.forEach((button) => {
								        // when clicking on a button...
								        button.addEventListener("click", () => {
								            // get all rows (node list) + make an array
								            const rowsArray = Array.from(tbody.querySelectorAll("tr"));
								            // get the column number from the data-* attribute
								            const columnIndex = Number(button.dataset.column);

								            // get the aria-sort attribute + check the status
								            const currentSort = button.getAttribute("aria-sort");
								            const nextSort = currentSort === "ascending" ? "descending" : "ascending";

								            // reset aria-sort=none
								            buttons.forEach((btn) => {
								                btn.setAttribute("aria-sort", "none");
								            });

								            // sort the rows arrays asc or desc
								            rowsArray.sort((a, b) => {
								                // get the children of the rows
								                const cellA = a.children[columnIndex];
								                const cellB = b.children[columnIndex];
								                // check the children for either a data-* (number) or text
								                const valueA = cellA.dataset.value ? cellA.dataset.value : cellA.textContent.trim();
								                const valueB = cellB.dataset.value ? cellB.dataset.value : cellB.textContent.trim();

								                // check the aria-sort + sort by number accordingly
								                if (cellA.dataset.value) {
								                    return nextSort === "ascending" ? Number(valueA) - Number(valueB) : Number(valueB) - Number(valueA);
								                }

								                // check the text and sort a-z or z-a
								                return nextSort === "ascending" ? valueA.localeCompare(valueB) : valueB.localeCompare(valueA);
								            });

								            // append the rows by sort order
								            rowsArray.forEach((row) => {
								                tbody.append(row);
								            });

								            // set the aria-sort to the new sort value
								            button.setAttribute("aria-sort", nextSort);
								        });
								    });
								}
								initSortableTable();