Last active
July 7, 2025 13:00
-
-
Save terriann/b3507e57ebe87fcb908d2897980f2494 to your computer and use it in GitHub Desktop.
Sort any table userscript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Sort Any Table | |
// @namespace com.terriswallow | |
// @version 1.5 | |
// @description Add sorting functionality to any table on the page by clicking on the TH | |
// @author Terri Ann & chat GPT | |
// @match *://*/* | |
// @grant none | |
// @updateURL https://gist.githubusercontent.com/terriann/b3507e57ebe87fcb908d2897980f2494/raw/sort-table.user.js | |
// @downloadURL https://gist.githubusercontent.com/terriann/b3507e57ebe87fcb908d2897980f2494/raw/sort-table.user.js | |
// ==/UserScript== | |
(function () { | |
'use strict'; | |
const DEBUG = false; // Set to true to enable debugging logs | |
// Helper function for logging | |
function debugLog(...args) { | |
if (DEBUG) { | |
console.log(...args); | |
} | |
} | |
// Delay execution to allow the page to load fully | |
setTimeout(() => { | |
debugLog('Script is running and ready to detect clicks.'); | |
// Add a click event listener to all table headers | |
document.addEventListener('click', function (event) { | |
debugLog('Click event detected:', event.target); | |
let header; | |
if (event.target.tagName === 'TH') { | |
header = event.target; | |
} else { | |
header = event.target.closest('TH'); | |
} | |
if (!header) { | |
debugLog('Clicked element is neither a table header nor inside one.'); | |
return; | |
} | |
// Only proceed if scope is not "row" | |
if (header.getAttribute('scope') === 'row') { | |
debugLog('Header has scope="row", ignoring.'); | |
return; | |
} | |
const table = header.closest('table'); | |
if (!table) { | |
debugLog('No table found for the clicked header.'); | |
return; | |
} | |
const columnIndex = Array.from(header.parentNode.children).indexOf(header); | |
debugLog(`Column index detected: ${columnIndex}`); | |
const isAscending = header.dataset.sortOrder !== 'asc'; | |
debugLog(`Sorting order is ascending: ${isAscending}`); | |
sortTable(table, columnIndex, isAscending); | |
updateHeaderSortOrder(header, isAscending); | |
}); | |
function sortTable(table, columnIndex, isAscending) { | |
debugLog(`Sorting table at column index ${columnIndex} in ${isAscending ? 'ascending' : 'descending'} order.`); | |
const rows = Array.from(table.querySelectorAll('tbody > tr')); | |
debugLog('Number of rows to sort:', rows.length); | |
const comparator = (rowA, rowB) => { | |
const cellA = rowA.children[columnIndex]?.innerText.trim().replace(/,/g, '') || ''; | |
const cellB = rowB.children[columnIndex]?.innerText.trim().replace(/,/g, '') || ''; | |
debugLog('Comparing:', { cellA, cellB }); | |
const numA = parseFloat(cellA); | |
const numB = parseFloat(cellB); | |
if (!isNaN(numA) && !isNaN(numB)) { | |
return isAscending ? numA - numB : numB - numA; | |
} | |
return isAscending | |
? cellA.localeCompare(cellB) | |
: cellB.localeCompare(cellA); | |
}; | |
rows.sort(comparator); | |
const tbody = table.querySelector('tbody'); | |
rows.forEach(row => tbody.appendChild(row)); | |
debugLog('Table sorted successfully.'); | |
} | |
function updateHeaderSortOrder(header, isAscending) { | |
debugLog('Updating sort order on header:', header.innerText); | |
// Reset all headers in the same row | |
const headers = header.parentNode.children; | |
Array.from(headers).forEach(th => th.removeAttribute('data-sort-order')); | |
// Set the current header's sort order | |
header.dataset.sortOrder = isAscending ? 'asc' : 'desc'; | |
debugLog(`Header sort order set to: ${header.dataset.sortOrder}`); | |
} | |
}, 2000); // Adjust the delay as needed (2000ms = 2 seconds) | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment