| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- import { getElements, replaceAll, findFirstAdjacent } from '../util';
- type ShowHide = 'show' | 'hide';
- function isShowHide(value: unknown): value is ShowHide {
- return typeof value === 'string' && ['show', 'hide'].includes(value);
- }
- /**
- * When this error is thrown, it's an indication that we don't need to manage this table, because
- * it doesn't contain the required elements.
- */
- class TableStateError extends Error {
- table: HTMLTableElement;
- constructor(message: string, table: HTMLTableElement) {
- super(message);
- this.table = table;
- }
- }
- /**
- * Manage the display text of a button element as well as the visibility of its corresponding rows.
- */
- class ButtonState {
- /**
- * Underlying Button DOM Element
- */
- public button: HTMLButtonElement;
- /**
- * Table rows provided in constructor
- */
- private rows: NodeListOf<HTMLTableRowElement>;
- constructor(button: HTMLButtonElement, rows: NodeListOf<HTMLTableRowElement>) {
- this.button = button;
- this.rows = rows;
- }
- /**
- * Remove visibility of button state rows.
- */
- private hideRows(): void {
- for (const row of this.rows) {
- row.classList.add('d-none');
- }
- }
- /**
- * Update the DOM element's `data-state` attribute.
- */
- public set buttonState(state: Nullable<ShowHide>) {
- if (isShowHide(state)) {
- this.button.setAttribute('data-state', state);
- }
- }
- /**
- * Get the DOM element's `data-state` attribute.
- */
- public get buttonState(): Nullable<ShowHide> {
- const state = this.button.getAttribute('data-state');
- if (isShowHide(state)) {
- return state;
- }
- return null;
- }
- /**
- * Update the DOM element's display text to reflect the action opposite the current state. For
- * example, if the current state is to hide enabled interfaces, the DOM text should say
- * "Show Enabled Interfaces".
- */
- private toggleButton(): void {
- if (this.buttonState === 'show') {
- this.button.innerText = replaceAll(this.button.innerText, 'Show', 'Hide');
- } else if (this.buttonState === 'hide') {
- this.button.innerText = replaceAll(this.button.innerHTML, 'Hide', 'Show');
- }
- }
- /**
- * Toggle the DOM element's `data-state` attribute.
- */
- private toggleState(): void {
- if (this.buttonState === 'show') {
- this.buttonState = 'hide';
- } else if (this.buttonState === 'hide') {
- this.buttonState = 'show';
- }
- }
- /**
- * Toggle all controlled elements.
- */
- private toggle(): void {
- this.toggleState();
- this.toggleButton();
- }
- /**
- * When the button is clicked, toggle all controlled elements and hide rows based on
- * buttonstate.
- */
- public handleClick(event: Event): void {
- const button = event.currentTarget as HTMLButtonElement;
- if (button.isEqualNode(this.button)) {
- this.toggle();
- }
- if (this.buttonState === 'hide') {
- this.hideRows();
- }
- }
- }
- /**
- * Manage the state of a table and its elements.
- */
- class TableState {
- /**
- * Underlying DOM Table Element.
- */
- private table: HTMLTableElement;
- /**
- * Instance of ButtonState for the 'show/hide enabled rows' button.
- */
- // @ts-expect-error null handling is performed in the constructor
- private enabledButton: ButtonState;
- /**
- * Instance of ButtonState for the 'show/hide disabled rows' button.
- */
- // @ts-expect-error null handling is performed in the constructor
- private disabledButton: ButtonState;
- /**
- * Instance of ButtonState for the 'show/hide virtual rows' button.
- */
- // @ts-expect-error null handling is performed in the constructor
- private virtualButton: ButtonState;
- /**
- * Instance of ButtonState for the 'show/hide virtual rows' button.
- */
- // @ts-expect-error null handling is performed in the constructor
- private disconnectedButton: ButtonState;
- /**
- * All table rows in table
- */
- private rows: NodeListOf<HTMLTableRowElement>;
- constructor(table: HTMLTableElement) {
- this.table = table;
- this.rows = this.table.querySelectorAll('tr');
- try {
- const toggleEnabledButton = findFirstAdjacent<HTMLButtonElement>(
- this.table,
- 'button.toggle-enabled',
- );
- const toggleDisabledButton = findFirstAdjacent<HTMLButtonElement>(
- this.table,
- 'button.toggle-disabled',
- );
- const toggleVirtualButton = findFirstAdjacent<HTMLButtonElement>(
- this.table,
- 'button.toggle-virtual',
- );
- const toggleDisconnectedButton = findFirstAdjacent<HTMLButtonElement>(
- this.table,
- 'button.toggle-disconnected',
- );
- if (toggleEnabledButton === null) {
- throw new TableStateError("Table is missing a 'toggle-enabled' button.", table);
- }
- if (toggleDisabledButton === null) {
- throw new TableStateError("Table is missing a 'toggle-disabled' button.", table);
- }
- if (toggleVirtualButton === null) {
- throw new TableStateError("Table is missing a 'toggle-virtual' button.", table);
- }
- if (toggleDisconnectedButton === null) {
- throw new TableStateError("Table is missing a 'toggle-disconnected' button.", table);
- }
- // Attach event listeners to the buttons elements.
- toggleEnabledButton.addEventListener('click', event => this.handleClick(event, this));
- toggleDisabledButton.addEventListener('click', event => this.handleClick(event, this));
- toggleVirtualButton.addEventListener('click', event => this.handleClick(event, this));
- toggleDisconnectedButton.addEventListener('click', event => this.handleClick(event, this));
- // Instantiate ButtonState for each button for state management.
- this.enabledButton = new ButtonState(
- toggleEnabledButton,
- table.querySelectorAll<HTMLTableRowElement>('tr[data-enabled="enabled"]'),
- );
- this.disabledButton = new ButtonState(
- toggleDisabledButton,
- table.querySelectorAll<HTMLTableRowElement>('tr[data-enabled="disabled"]'),
- );
- this.virtualButton = new ButtonState(
- toggleVirtualButton,
- table.querySelectorAll<HTMLTableRowElement>('tr[data-type="virtual"]'),
- );
- this.disconnectedButton = new ButtonState(
- toggleDisconnectedButton,
- table.querySelectorAll<HTMLTableRowElement>('tr[data-connected="disconnected"]'),
- );
- } catch (err) {
- if (err instanceof TableStateError) {
- // This class is useless for tables that don't have toggle buttons.
- console.debug('Table does not contain enable/disable toggle buttons');
- return;
- } else {
- throw err;
- }
- }
- }
- /**
- * When toggle buttons are clicked, reapply visability all rows and
- * pass the event to all button handlers
- *
- * @param event onClick event for toggle buttons.
- * @param instance Instance of TableState (`this` cannot be used since that's context-specific).
- */
- public handleClick(event: Event, instance: TableState): void {
- for (const row of this.rows) {
- row.classList.remove('d-none');
- }
- instance.enabledButton.handleClick(event);
- instance.disabledButton.handleClick(event);
- instance.virtualButton.handleClick(event);
- instance.disconnectedButton.handleClick(event);
- }
- }
- /**
- * Initialize table states.
- */
- export function initInterfaceTable(): void {
- for (const element of getElements<HTMLTableElement>('table')) {
- new TableState(element);
- }
- }
|