Compare commits
9 Commits
026de5987b
...
4c5c444002
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c5c444002 | ||
|
|
fbaf215078 | ||
|
|
73f80d874b | ||
|
|
0eb45263a4 | ||
|
|
ccf81fa085 | ||
|
|
47ac4dacf8 | ||
|
|
4e5428eee2 | ||
|
|
8d8107ec49 | ||
|
|
d7c2851008 |
@@ -1,3 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Optional configuration for a {@link Table}.
|
||||||
|
* @template TRow The row shape; see {@link Table}.
|
||||||
|
*/
|
||||||
|
export type TableOptions<TRow extends object> = {
|
||||||
|
/**
|
||||||
|
* A object with the same properties, but all string type for the header cell values.
|
||||||
|
* Used by passing an object with the keys of {@link TRow} 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.
|
||||||
|
*/
|
||||||
|
displayNamesAndOrder?: Record<keyof TRow, string>;
|
||||||
|
/**
|
||||||
|
* Used to display messages 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.
|
||||||
|
*/
|
||||||
|
messageElement?: HTMLElement | null;
|
||||||
|
/** The symbol used for boolean cells that are true. Optional, default "✔". */
|
||||||
|
trueSymbol?: string;
|
||||||
|
/** The symbol used for boolean cells that are false. Optional, default "✖". */
|
||||||
|
falseSymbol?: string;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @template TRow A type that the shape of a row should be. E.g.
|
* @template TRow A type that the shape of a row should be. E.g.
|
||||||
* ```
|
* ```
|
||||||
@@ -8,46 +32,53 @@
|
|||||||
* ```
|
* ```
|
||||||
* Then `User` would be passed as the generic.
|
* Then `User` would be passed as the generic.
|
||||||
*/
|
*/
|
||||||
export abstract class Table<TRow extends Object> {
|
export abstract class Table<TRow extends object> {
|
||||||
|
|
||||||
table: HTMLTableElement;
|
table: HTMLTableElement;
|
||||||
tableBody: HTMLTableSectionElement;
|
tableBody: HTMLTableSectionElement;
|
||||||
headers: Header<TRow>[];
|
columns: Column<TRow>[];
|
||||||
|
|
||||||
messageEl: HTMLElement | null;
|
messageEl: HTMLElement | null;
|
||||||
|
|
||||||
|
trueSymbol: string;
|
||||||
|
falseSymbol: string;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a list of Header objects to be used in a table for the header cell values and column types.
|
* Constructs a table based on the parameters given.
|
||||||
* @param exampleObject An instance of the type to be displaying. Can be an object with default/zero values for that type
|
* @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
|
* 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.
|
* @param options Optional configuration. See {@link TableOptions}.
|
||||||
* 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) {
|
constructor(exampleObject: TRow, options: TableOptions<TRow> = {}) {
|
||||||
|
|
||||||
this.table = document.createElement("table")
|
this.table = document.createElement("table")
|
||||||
this.tableBody = document.createElement("tbody");
|
this.tableBody = document.createElement("tbody");
|
||||||
|
|
||||||
this.headers = this.createHeadersFromType(exampleObject, displayNamesAndOrder);
|
this.columns = this.createColumnsFromType(exampleObject, options.displayNamesAndOrder);
|
||||||
this.setHeaderRow();
|
this.setHeaderRow();
|
||||||
|
|
||||||
this.table.appendChild(this.tableBody);
|
this.table.appendChild(this.tableBody);
|
||||||
|
|
||||||
this.loadCSS()
|
// If null it means disabled
|
||||||
|
this.messageEl = options.messageElement === undefined ? document.createElement("p") : options.messageElement;
|
||||||
|
|
||||||
this.messageEl = messageElement === undefined ? document.createElement("p") : messageElement;
|
this.trueSymbol = options.trueSymbol ?? "✔";
|
||||||
|
this.falseSymbol = options.falseSymbol ?? "✖";
|
||||||
}
|
}
|
||||||
|
|
||||||
createHeadersFromType(
|
/**
|
||||||
|
* Creates a list of {@link Column} objects to be used in a table for the header cell values, column types, and column order (if provided, otherwise inferred).
|
||||||
|
* @param exampleObject An example object of TRow, e.g. User = { id: 0, name: "Name"}. Used to store the types for each column.
|
||||||
|
* @param displayNamesAndOrder The order and names of the columns on the table.
|
||||||
|
* e.g. User = { fullname: "Full Name", id: "User ID" }, the columns would be "Full Name" and "User ID" in that order,
|
||||||
|
* @return An array of {@link Column} type shape that holds the column info.
|
||||||
|
*/
|
||||||
|
createColumnsFromType(
|
||||||
exampleObject: TRow, // Needed for runtime, since types don't exist in JS.
|
exampleObject: TRow, // Needed for runtime, since types don't exist in JS.
|
||||||
displayNamesAndOrder?: Record<keyof TRow, string>
|
displayNamesAndOrder?: Record<keyof TRow, string>
|
||||||
): Header<TRow>[] {
|
): Column<TRow>[] {
|
||||||
// Checks if display names and order was passed. If not, derives the keys to use for the header from the example object.
|
// Checks if display names and order was passed. If not, derives the keys to use for the columns from the example object.
|
||||||
const keys = displayNamesAndOrder
|
const keys = displayNamesAndOrder
|
||||||
? (Object.keys(displayNamesAndOrder) as (keyof TRow)[])
|
? (Object.keys(displayNamesAndOrder) as (keyof TRow)[])
|
||||||
: (Object.keys(exampleObject) as (keyof TRow)[]);
|
: (Object.keys(exampleObject) as (keyof TRow)[]);
|
||||||
@@ -59,13 +90,16 @@ export abstract class Table<TRow extends Object> {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the header cell values on the header row from the {@link columns} on this object.
|
||||||
|
*/
|
||||||
setHeaderRow() {
|
setHeaderRow() {
|
||||||
const thead = document.createElement("thead");
|
const thead = document.createElement("thead");
|
||||||
const headerRow = document.createElement("tr");
|
const headerRow = document.createElement("tr");
|
||||||
|
|
||||||
for (const header of this.headers) {
|
for (const column of this.columns) {
|
||||||
const th = document.createElement("th")
|
const th = document.createElement("th")
|
||||||
th.textContent = header.value;
|
th.textContent = column.value;
|
||||||
headerRow.appendChild(th);
|
headerRow.appendChild(th);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,17 +107,6 @@ export abstract class Table<TRow extends Object> {
|
|||||||
this.table.appendChild(thead);
|
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.
|
* Returns the table element of this instance for putting onto a page.
|
||||||
*/
|
*/
|
||||||
@@ -100,6 +123,10 @@ export abstract class Table<TRow extends Object> {
|
|||||||
this.tableBody.appendChild(row)
|
this.tableBody.appendChild(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends multiple rows to the table, at the end off the table.
|
||||||
|
* @param data The rows to append to the table, in shape of {@link TRow}.
|
||||||
|
*/
|
||||||
appendRows(data: TRow[]): void {
|
appendRows(data: TRow[]): void {
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
this.setMessage("No data to append. Empty list provided.")
|
this.setMessage("No data to append. Empty list provided.")
|
||||||
@@ -112,22 +139,29 @@ export abstract class Table<TRow extends Object> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a row, populated with data, for displaying in the table.
|
* Creates a row, populated with data, for displaying in the table.
|
||||||
* @param data The data in the shape of `R`, with values populated.
|
* @param data The data in the shape of `TRow`, with values populated.
|
||||||
* @return A HTMLTableRowElement that can then be set on a table.
|
* @return A HTMLTableRowElement that can then be set on a table.
|
||||||
*/
|
*/
|
||||||
createPopulatedRow(data: TRow): HTMLTableRowElement {
|
createPopulatedRow(data: TRow): HTMLTableRowElement {
|
||||||
const row = document.createElement("tr");
|
const row = document.createElement("tr");
|
||||||
|
|
||||||
for (const header of this.headers) {
|
for (const column of this.columns) {
|
||||||
const cell: HTMLTableCellElement = document.createElement("td");
|
const cell: HTMLTableCellElement = document.createElement("td");
|
||||||
const value = data[header.key]
|
const value = data[column.key]
|
||||||
|
|
||||||
|
// Element values are inserted as children, not stringified.
|
||||||
|
if (value instanceof HTMLElement) {
|
||||||
|
cell.appendChild(value);
|
||||||
|
row.append(cell);
|
||||||
|
continue; // skip the textContent path that would wipe it
|
||||||
|
}
|
||||||
|
|
||||||
let cellValue = "";
|
let cellValue = "";
|
||||||
|
|
||||||
switch (header.type) {
|
switch (column.type) {
|
||||||
case "boolean":
|
case "boolean":
|
||||||
cell.classList.add("table-cell-boolean");
|
cell.classList.add("table-cell-boolean");
|
||||||
cellValue = value ? "✔" : "✖";
|
cellValue = value ? this.trueSymbol : this.falseSymbol;
|
||||||
cell.classList.add(value ? "table-cell-boolean-true" : "table-cell-boolean-false");
|
cell.classList.add(value ? "table-cell-boolean-true" : "table-cell-boolean-false");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -137,7 +171,6 @@ export abstract class Table<TRow extends Object> {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
const strValue = String(value)
|
|
||||||
cellValue = value != null ? String(value) : "";
|
cellValue = value != null ? String(value) : "";
|
||||||
}
|
}
|
||||||
cell.textContent = cellValue;
|
cell.textContent = cellValue;
|
||||||
@@ -173,33 +206,68 @@ export abstract class Table<TRow extends Object> {
|
|||||||
this.clearRows()
|
this.clearRows()
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
this.setMessage("No data to show. Empty list provided.")
|
this.setMessage("No data to show. Empty list provided.")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
this.appendRows(data)
|
this.appendRows(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
getMessageElement() {
|
/**
|
||||||
|
* Get the message element for the table.
|
||||||
|
* @return The HTMLElement thats used for showing messaged. Or null if its not set.
|
||||||
|
*/
|
||||||
|
getMessageElement(): HTMLElement | null {
|
||||||
return this.messageEl;
|
return this.messageEl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the {@link messageEl}.textContent value to "". If the message element is not set it does nothing.
|
||||||
|
*/
|
||||||
clearMessage() {
|
clearMessage() {
|
||||||
if (!this.messageEl) return;
|
if (!this.messageEl) return;
|
||||||
this.messageEl.textContent = ""
|
this.messageEl.textContent = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a message to the already existing table message, if the message element is set. Works even if current message is empty ("").
|
||||||
|
* @param message The message to append to the current message.
|
||||||
|
*/
|
||||||
appendMessage(message: string) {
|
appendMessage(message: string) {
|
||||||
if (!this.messageEl) return;
|
if (!this.messageEl) return;
|
||||||
this.messageEl.textContent = `${this.messageEl.textContent} ${message}`
|
this.messageEl.textContent = `${this.messageEl.textContent} ${message}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the message, overwriting what it was before. If the message element is not set, it does nothing.
|
||||||
|
* @param message The message to set the message element to.
|
||||||
|
*/
|
||||||
setMessage(message: string) {
|
setMessage(message: string) {
|
||||||
if (!this.messageEl) return;
|
if (!this.messageEl) return;
|
||||||
this.clearMessage();
|
this.clearMessage();
|
||||||
this.appendMessage(message);
|
this.appendMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the symbols for boolean cells on the table.
|
||||||
|
* @param trueSymbol What the true symbol should be.
|
||||||
|
* @param falseSymbol What the false symbol should be.
|
||||||
|
* @param updateExisting Whether to update the existing cells. Default to true.
|
||||||
|
*/
|
||||||
|
changeBooleanSymbols(trueSymbol: string, falseSymbol: string, updateExisting: boolean = true) {
|
||||||
|
this.trueSymbol = trueSymbol;
|
||||||
|
this.falseSymbol = falseSymbol;
|
||||||
|
|
||||||
|
// Change the symbol on already rendered boolean cells.
|
||||||
|
if (updateExisting) {
|
||||||
|
this.tableBody.querySelectorAll(".table-cell-boolean-true")
|
||||||
|
.forEach(c => c.textContent = trueSymbol);
|
||||||
|
this.tableBody.querySelectorAll(".table-cell-boolean-false")
|
||||||
|
.forEach(c => c.textContent = falseSymbol);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Header<R> = {
|
export type Column<TRow> = {
|
||||||
key: keyof R; // The name of the field.
|
key: keyof TRow; // The name of the field.
|
||||||
value: string; // The name of the header cell
|
value: string; // The name of the header cell
|
||||||
type: string; // The type of the field, can be string since typesof are returned as strings.
|
type: string; // The type of the field, can be string since typesof are returned as strings.
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user