1
0
Fork 1

Add table sort indicators

pull/18/head
gravel 3 years ago
parent d945c09304
commit d3878aa553
Signed by: gravel
SSH Key Fingerprint: SHA256:p4HP49CCk4YQMkJpWJ09L8peEPQWjERtdCRAFxPfbOY

@ -33,7 +33,7 @@ export const ATTRIBUTES = {
ACTIVE: 'data-sort',
ASCENDING: 'data-sort-asc',
COLUMN: 'data-sorted-by',
COLUMN_LITERAL: 'sorted-by'
// COLUMN_LITERAL: 'sorted-by'
}
};
@ -41,7 +41,14 @@ export function columnAscendingByDefault(column) {
return column != COLUMN.USERS;
}
export function columnIsSortable(column) { return column != COLUMN.QR_CODE; }
export function columnIsSortable(column) {
return ![
COLUMN.QR_CODE,
COLUMN.PREVIEW,
// Join URL contents are not guaranteed to have visible text.
COLUMN.JOIN_URL
].includes(column);
}
export function columnNeedsCasefold(column) {
return [
@ -64,22 +71,22 @@ export function columnIsNumeric(column) {
* @returns {HTMLElement}
*/
function createElement(tag, ...args) {
const element = document.createElement(tag);
if (args.length === 0) return element;
const propsCandidate = args[0];
if (typeof propsCandidate !== "string" && !(propsCandidate instanceof Element)) {
// args[0] is not child element or text node
// must be props object
Object.assign(element, propsCandidate);
args.shift();
}
element.append(...args);
return element;
const element = document.createElement(tag);
if (args.length === 0) return element;
const propsCandidate = args[0];
if (typeof propsCandidate !== "string" && !(propsCandidate instanceof Element)) {
// args[0] is not child element or text node
// must be props object
Object.assign(element, propsCandidate);
args.shift();
}
element.append(...args);
return element;
}
export const element = new Proxy({}, {
get(_, key) {
return (...args) => createElement(key, ...args)
}
get(_, key) {
return (...args) => createElement(key, ...args)
}
});

@ -51,11 +51,11 @@ const filteredCommunities = {
// This can be achieved with `text-overflow: ellipsis` instead
// and generated entirely server-side.
const transformJoinURL = (join_link) => {
return element.button({
textContent: "Copy",
className: "copy_button",
onclick: () => copyToClipboard(join_link)
});
return element.button({
textContent: "Copy",
className: "copy_button",
onclick: () => copyToClipboard(join_link)
});
}
function onLoad(timestamp) {
@ -101,7 +101,7 @@ function hideBadCommunities() {
* Removes an element by its ID and returns the number of elements removed.
*/
function hideElementByID(id) {
const element = document.getElementById(id);
const element = document.getElementById(id);
element?.remove();
return element ? 1 : 0;
}
@ -245,17 +245,22 @@ function setSortState(table, { ascending, column }) {
}
table.setAttribute(ATTRIBUTES.SORTING.ASCENDING, ascending);
table.setAttribute(ATTRIBUTES.SORTING.COLUMN, column);
// This can be used to style column headers in a consistent way, i.e.
// #tbl_communities[data-sort-asc=true][sorted-by=name]::after #th_name, ...
table.setAttribute(ATTRIBUTES.SORTING.COLUMN_LITERAL, COLUMN_LITERAL[column]);
// No way around this for brief CSS.
const headers = table.querySelectorAll("th");
headers.forEach((th, colno) => {
th.removeAttribute(ATTRIBUTES.SORTING.ACTIVE);
});
headers[column].setAttribute(ATTRIBUTES.SORTING.ACTIVE, true);
}
// This is best done in JS, as it would require <noscript> styles otherwise.
function markSortableColumns() {
const table = dom.tbl_communities();
for (const th of table.querySelectorAll('th')) {
if (th.id.includes("qr_code")) continue;
th.classList.add('sortable');
const header_cells = table.querySelectorAll('th');
for (let colno = 0; colno < header_cells.length; colno++) {
if (!columnIsSortable(colno)) continue;
header_cells[colno].classList.add('sortable');
}
}

@ -23,12 +23,57 @@ header {
flex-grow: 1;
}
#tbl_communities { width:100%; }
#tbl_communities {
/* Browser defaults. */
--cell-padding-h: 1px;
--cell-padding-v: 1px;
width:100%;
}
#tbl_communities th {
white-space: nowrap;
}
#tbl_communities :is(th, td) {
padding: var(--cell-padding-v) var(--cell-padding-h);
}
#tbl_communities th.sortable {
position: relative;
padding-right: calc( 1.5em + var(--cell-padding-h) );
}
#tbl_communities th.sortable::after {
position: absolute;
right: 0.25em;
top: 50%;
transform: translateY(-50%);
/* content: "\25C7"; */ /* White diamond */
/* content: "\2195"; */ /* Up-down arrow */
/* content: "\25A1"; */ /* White square */
/* content: "\25B8"; */ /* Small right pointing triangle */
content: "\2B25"; /* Black medium diamond */
color: grey;
}
#tbl_communities[data-sort-asc=true] th[data-sort=true]::after {
content: "\25B2"; /* Black up pointing triangle */
color: initial;
}
#tbl_communities[data-sort-asc=false] th[data-sort=true]::after {
content: "\25BC"; /* Black up pointing triangle */
color: initial;
}
/* Hide the identifier column before removal. */
#th_identifier { display: none; }
.td_identifier { display: none; font-family: monospace; }
.td_language {
text-align: center;
font-size: 1.25em;
}
.td_language:empty::after {
content: "\2753";
}
@ -70,7 +115,7 @@ header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
justify-content: space-around;
}
.copy_button { font-size: inherit }
@ -209,3 +254,4 @@ footer nav a {
to {bottom: 0; opacity: 0;}
}

@ -3,14 +3,15 @@
// Once handlers are attached in JS, this check ceases to be useful.
function column_sortable($id) {
return $id != "qr";
// Join URL contents are not guaranteed to have visible text.
return $id != "qr" && $id != "preview" && $id != "join_url";
}
function sort_onclick($colno) {
global $TABLE_COLUMNS;
$column = $TABLE_COLUMNS[$colno];
if (!column_sortable($column['id'])) return "";
return " onclick='sortTable($colno)'";
return " onclick='sortTable($colno)' title='Click to sort this column'";
}
$TABLE_COLUMNS = [