Skip to content

Instantly share code, notes, and snippets.

@terriann
Last active July 7, 2025 13:00
Show Gist options
  • Save terriann/b3507e57ebe87fcb908d2897980f2494 to your computer and use it in GitHub Desktop.
Save terriann/b3507e57ebe87fcb908d2897980f2494 to your computer and use it in GitHub Desktop.
Sort any table userscript
// ==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