Make the class reference table sortable (#28)

The main table's headers are now clickable to sort columns in ascending or descending order.
String columns are sorted alphabetically, description columns are sorted by MISSING vs. OK, and numeric columns are sorted by relative percentages.
A custom JS sorting implementation is used, both to avoid a large dependency and to handle the unique format of these table entries.
The cursor when hovering over table headers indicates that they can be interacted with.
This commit is contained in:
TechnoPorg
2023-10-11 15:06:11 -06:00
committed by GitHub
parent 4fc119a55c
commit c9714e938f
3 changed files with 104 additions and 0 deletions

View File

@@ -9,6 +9,7 @@
<title>{{ .Site.Title }}</title>
<link rel="icon" href="favicon.png">
<link rel="stylesheet" href="main.css">
<script src="sorttable.js"></script>
</head>
<body>
{{ .Content }}

View File

@@ -76,6 +76,7 @@ th {
position: sticky;
z-index: 1; /* Show on top of table cells. */
top: 0; /* Stick to the top of the screen. */
cursor: pointer; /* Visually hint that headers can be interacted with. */
}
th:first-child {
border-left: 1px solid var(--table-sticky-background-color); /* Fixes left border during scroll; must have a valid color, transparent doesn't work. */

102
static/sorttable.js Normal file
View File

@@ -0,0 +1,102 @@
// Make the class reference table sortable in ascending or descending order
// when a header is clicked.
// Helper function to return the content from the idx-th cell of row tr
const getCellValue = (tr, idx) => tr.children[idx].textContent;
// Array sorting functions for different columns used by the comparer
// Compares the Desc. and Brief Desc. columns
// "MISSING" comes first in ascending order
const descriptionComparer = function(v1, v2) {
if(v1 == v2) return 0
if(v1 == "OK") return 1
return -1
}
// Compares the Name and Docs URL columns using a basic string sort
const stringComparer = (new Intl.Collator()).compare
// Compares the Overall column by completion percentage
const overallComparer = function(v1, v2) {
return Number(v1.replace("%", "")) - Number(v2.replace("%", ""))
}
// Compares the other columns (constructors, methods, members, etc.)
// by the percentage they're finished.
// If two have the same percentage, they're compared by denominator size.
const fractionComparer = (asc) => function(v1, v2) {
if(v1 == v2) return 0
// Always send 0/0 values to the bottom
// The "asc" parameter is needed for that purpose.
if(v1 == "0/0") {
return asc ? 1 : -1
}
if(v2 == "0/0") {
return asc ? -1 : 1
}
var v1fraction = v1.split("/")
var v2fraction = v2.split("/")
var v1decimal = Number(v1fraction[0]) / Number(v1fraction[1])
var v2decimal = Number(v2fraction[0]) / Number(v2fraction[1])
if(v1decimal == v2decimal) return v1fraction[1] - v2fraction[1]
return v1decimal - v2decimal
}
// Returns a function responsible for sorting a specific table column
// (column = column object, asc = ascending order?).
const comparer = function(column, asc) {
// This is used by the array.sort() function...
return function(a, b) {
const colIdx = Array.from(column.parentNode.children).indexOf(column)
const colName = column.textContent
// Select a function based on which column is being called.
var columnComparer
switch(colName) {
case "Brief Desc.":
case "Desc.":
columnComparer = descriptionComparer
break
case "Name":
case "Docs URL":
columnComparer = stringComparer
break
case "Overall":
columnComparer = overallComparer
break
default:
columnComparer = fractionComparer(column.asc)
break
}
// Switch the order of the values depending on whether it's an ascending or descending sort.
return columnComparer(getCellValue(asc ? a : b, colIdx), getCellValue(asc ? b : a, colIdx));
}
};
const SKIP_END_ROWS = 5 // The number of footer rows generated by doc_status.py
// Set up event listeners that will sort the table when headers are clicked.
window.onload = function()
{
document.querySelectorAll('th')
.forEach(th =>
th.addEventListener('click', (() =>
{
const table = th.closest('table');
const tbody = table.querySelector('tbody')
const trows = Array.from(tbody.querySelectorAll('tr'))
trows.slice(0, -SKIP_END_ROWS)
.sort(comparer(th, th.asc = !th.asc)) // Give each column an individual sort direction
.concat(trows.splice(-SKIP_END_ROWS)) // Don't sort the last rows, as they are not class reference entries.
.forEach(tr => tbody.appendChild(tr) );
})));
}