204 lines
6.5 KiB
TypeScript
204 lines
6.5 KiB
TypeScript
/**
|
|
* @template TRow A type that the shape of a row should be. E.g.
|
|
* ```
|
|
* type User = {
|
|
* id: number;
|
|
* name: string;
|
|
* }
|
|
* ```
|
|
* Then `User` would be passed as the generic.
|
|
*/
|
|
export abstract class Table<TRow extends Object> {
|
|
|
|
table: HTMLTableElement;
|
|
tableBody: HTMLTableSectionElement;
|
|
headers: Header<TRow>[];
|
|
|
|
messageEl: HTMLElement | null;
|
|
|
|
/**
|
|
* Creates a list of Header objects to be used in a table for the header cell values and column types.
|
|
* @param exampleObject An instance of the type to be displaying. Can be an object with default/zero values for that type
|
|
* such as `0`, `""`, `false` or `true`. E.g. for a user ```{ id: 0; name: ""; }``` would be passed
|
|
* @param displayNamesAndOrder A object with the same properties, but all string type for the header cell values.
|
|
* Used by passing an object with the keys of R and the name preferred. E.g. for a user:
|
|
* ```{ id: "ID", name: "Name" }```, this makes the columns appear in the order of ID then Name.
|
|
* @param messageElement Used to display messaged related to data output. Optional.
|
|
* Omit (undefined) to use the class provided HTMLParagraphElement.
|
|
* Pass an HTMLElement to use a custom element.
|
|
* Pass null to disable automatic message output.
|
|
*/
|
|
constructor(exampleObject: TRow, displayNamesAndOrder?: Record<keyof TRow, string>, messageElement?: HTMLElement | null | undefined) {
|
|
|
|
this.table = document.createElement("table")
|
|
this.tableBody = document.createElement("tbody");
|
|
|
|
this.headers = this.createHeadersFromType(exampleObject, displayNamesAndOrder);
|
|
this.setHeaderRow();
|
|
|
|
this.table.appendChild(this.tableBody);
|
|
|
|
this.loadCSS()
|
|
|
|
this.messageEl = messageElement === undefined ? document.createElement("p") : messageElement;
|
|
}
|
|
|
|
createHeadersFromType(
|
|
exampleObject: TRow, // Needed for runtime, since types don't exist in JS.
|
|
displayNamesAndOrder?: Record<keyof TRow, string>
|
|
): Header<TRow>[] {
|
|
const keys = displayNamesAndOrder
|
|
? (Object.keys(displayNamesAndOrder) as (keyof TRow)[])
|
|
: (Object.keys(exampleObject) as (keyof TRow)[]);
|
|
|
|
return keys.map(key => ({
|
|
key,
|
|
value: displayNamesAndOrder?.[key] ?? String(key),
|
|
type: typeof exampleObject[key],
|
|
}));
|
|
}
|
|
|
|
setHeaderRow() {
|
|
const thead = document.createElement("thead");
|
|
const headerRow = document.createElement("tr");
|
|
|
|
for (const header of this.headers) {
|
|
const th = document.createElement("th")
|
|
th.textContent = header.value;
|
|
headerRow.appendChild(th);
|
|
}
|
|
|
|
thead.appendChild(headerRow);
|
|
this.table.appendChild(thead);
|
|
}
|
|
|
|
/**
|
|
* Loads the CSS for tables into the html file for proper styling.
|
|
*/
|
|
loadCSS() {
|
|
const link = document.createElement("link");
|
|
link.rel = "stylesheet"
|
|
link.href = "../css/table.css"; // Relative path from JS folder.
|
|
link.type = 'text/css';
|
|
document.head.appendChild(link);
|
|
}
|
|
|
|
/**
|
|
* Returns the table element of this instance for putting onto a page.
|
|
*/
|
|
getTable(): HTMLTableElement {
|
|
return this.table;
|
|
}
|
|
|
|
/**
|
|
* Appends one more row to the current rows.
|
|
* @param data Data to append as a row.
|
|
*/
|
|
appendRow(data: TRow): void {
|
|
const row = this.createPopulatedRow(data)
|
|
this.tableBody.appendChild(row)
|
|
}
|
|
|
|
appendRows(data: TRow[]): void {
|
|
if (data.length === 0) {
|
|
this.setMessage("No data to append. Empty list provided.")
|
|
return;
|
|
}
|
|
for (const element of data) {
|
|
this.appendRow(element)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a row, populated with data, for displaying in the table.
|
|
* @param data The data in the shape of `R`, with values populated.
|
|
* @return A HTMLTableRowElement that can then be set on a table.
|
|
*/
|
|
createPopulatedRow(data: TRow): HTMLTableRowElement {
|
|
const row = document.createElement("tr");
|
|
|
|
for (const header of this.headers) {
|
|
const cell: HTMLTableCellElement = document.createElement("td");
|
|
const value = data[header.key]
|
|
|
|
let cellValue = "";
|
|
|
|
switch (header.type) {
|
|
case "boolean":
|
|
cell.classList.add("table-cell-boolean");
|
|
cellValue = value ? "✔" : "✖";
|
|
cell.classList.add(value ? "table-cell-boolean-true" : "table-cell-boolean-false");
|
|
break;
|
|
|
|
case "number":
|
|
cellValue = String(value);
|
|
cell.classList.add("table-cell-number");
|
|
break;
|
|
|
|
default:
|
|
const strValue = String(value)
|
|
cellValue = value != null ? String(value) : "";
|
|
}
|
|
cell.textContent = cellValue;
|
|
row.append(cell);
|
|
}
|
|
|
|
return row;
|
|
}
|
|
|
|
/**
|
|
* Deletes all current rows.
|
|
*/
|
|
clearRows(): void {
|
|
while (this.tableBody.firstChild) {
|
|
this.tableBody.removeChild(this.tableBody.firstChild)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deletes all current rows and sets a single row to the data in data param.
|
|
* @param data The data to set the table to. Should be of shape {@link TRow}
|
|
*/
|
|
setSingleRow(data: TRow): void {
|
|
this.clearRows()
|
|
this.appendRow(data)
|
|
}
|
|
|
|
/**
|
|
* Deletes all current rows and sets the rows to the data in data param.
|
|
* @param data The data to set the table to. Should be of shape {@link TRow}
|
|
*/
|
|
setRows(data: TRow[]): void {
|
|
this.clearRows()
|
|
if (data.length === 0) {
|
|
this.setMessage("No data to show. Empty list provided.")
|
|
}
|
|
this.appendRows(data)
|
|
}
|
|
|
|
getMessageElement() {
|
|
return this.messageEl;
|
|
}
|
|
|
|
clearMessage() {
|
|
if (!this.messageEl) return;
|
|
this.messageEl.textContent = ""
|
|
}
|
|
|
|
appendMessage(message: string) {
|
|
if (!this.messageEl) return;
|
|
this.messageEl.textContent = `${this.messageEl.textContent} ${message}`
|
|
}
|
|
|
|
setMessage(message: string) {
|
|
if (!this.messageEl) return;
|
|
this.clearMessage();
|
|
this.appendMessage(message);
|
|
}
|
|
}
|
|
|
|
type Header<R> = {
|
|
key: keyof R; // The name of the field.
|
|
value: string; // The name of the header cell
|
|
type: string; // The type of the field, can be string since typesof are returned as strings.
|
|
} |