Adding pervasive column sorting
This commit is contained in:
1 parent
34679d53e8
commit
2fb662080b
27 files changed
+614
-111
No files matched your search
@@ -247,6 +247,37 @@ h2 {
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
/* Sortable table headers */
|
||||
table.sortable thead th:not([data-sort-off]) {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
table.sortable thead th:not([data-sort-off]):hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
table.sortable thead th[aria-sort]::after {
|
||||
margin-left: 0.3rem;
|
||||
}
|
||||
|
||||
table.sortable thead th[aria-sort="ascending"]::after {
|
||||
content: "\25B2";
|
||||
}
|
||||
|
||||
table.sortable thead th[aria-sort="descending"]::after {
|
||||
content: "\25BC";
|
||||
}
|
||||
|
||||
.sort-link {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.sort-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Tags / definition lists */
|
||||
.tag {
|
||||
display: inline-block;
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function getCellText(row, index) {
|
||||
var cell = row.children[index];
|
||||
return cell ? cell.textContent.trim() : "";
|
||||
}
|
||||
|
||||
function isEmptyValue(text) {
|
||||
return text === "" || text === "—";
|
||||
}
|
||||
|
||||
function isPureNumber(text) {
|
||||
return /^-?\d+(\.\d+)?$/.test(text);
|
||||
}
|
||||
|
||||
function columnIsNumeric(rows, index) {
|
||||
var sawValue = false;
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
var text = getCellText(rows[i], index);
|
||||
if (isEmptyValue(text)) continue;
|
||||
sawValue = true;
|
||||
if (!isPureNumber(text)) return false;
|
||||
}
|
||||
return sawValue;
|
||||
}
|
||||
|
||||
function compareRows(a, b, index, numeric, dir) {
|
||||
var textA = getCellText(a, index);
|
||||
var textB = getCellText(b, index);
|
||||
var emptyA = isEmptyValue(textA);
|
||||
var emptyB = isEmptyValue(textB);
|
||||
if (emptyA && emptyB) return 0;
|
||||
if (emptyA) return 1;
|
||||
if (emptyB) return -1;
|
||||
|
||||
var result = numeric
|
||||
? parseFloat(textA) - parseFloat(textB)
|
||||
: textA.localeCompare(textB, undefined, { numeric: true, sensitivity: "base" });
|
||||
return dir === "desc" ? -result : result;
|
||||
}
|
||||
|
||||
function sortTable(table, th) {
|
||||
var thead = table.tHead;
|
||||
var tbody = table.tBodies[0];
|
||||
if (!thead || !tbody) return;
|
||||
|
||||
var headerRow = thead.rows[0];
|
||||
var index = Array.prototype.indexOf.call(headerRow.children, th);
|
||||
if (index === -1) return;
|
||||
|
||||
var rows = Array.prototype.slice.call(tbody.rows);
|
||||
if (rows.length < 2) return;
|
||||
|
||||
var nextDir = th.getAttribute("data-sort-dir") === "asc" ? "desc" : "asc";
|
||||
Array.prototype.forEach.call(headerRow.children, function (cell) {
|
||||
cell.removeAttribute("data-sort-dir");
|
||||
cell.removeAttribute("aria-sort");
|
||||
});
|
||||
th.setAttribute("data-sort-dir", nextDir);
|
||||
th.setAttribute("aria-sort", nextDir === "asc" ? "ascending" : "descending");
|
||||
|
||||
var numeric = columnIsNumeric(rows, index);
|
||||
rows.sort(function (a, b) {
|
||||
return compareRows(a, b, index, numeric, nextDir);
|
||||
});
|
||||
rows.forEach(function (row) {
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("click", function (event) {
|
||||
var th = event.target.closest("table.sortable thead th");
|
||||
if (!th || th.hasAttribute("data-sort-off") || th.textContent.trim() === "") return;
|
||||
sortTable(th.closest("table"), th);
|
||||
});
|
||||
})();
|
||||
Reference in new issue
Block a user