diff --git a/layouts/index.html b/layouts/index.html index 739ae88..1c894a0 100644 --- a/layouts/index.html +++ b/layouts/index.html @@ -9,6 +9,7 @@ {{ .Site.Title }} + {{ .Content }} diff --git a/static/main.css b/static/main.css index 7e0c8d8..453fa10 100644 --- a/static/main.css +++ b/static/main.css @@ -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. */ diff --git a/static/sorttable.js b/static/sorttable.js new file mode 100644 index 0000000..9df4500 --- /dev/null +++ b/static/sorttable.js @@ -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) ); + }))); +}