{{ .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) );
+ })));
+}