/// import { Controller } from "stimulus"; export default class TableController extends Controller { static targets = ["table", "template"]; declare tableTarget: HTMLTableElement; declare templateTarget: HTMLTemplateElement; getTbody() { const tbody = this.element .closest("turbo-frame") ?.querySelector("tbody"); if (!tbody) { throw new Error("tbody not found"); } return tbody; } getRowIndex(tr: HTMLTableRowElement) { let result = 0; for (const child of Array.from(this.getTbody().children)) { if (child == tr) { return result; } result++; } return -1; } addRow() { const tbody = this.getTbody(); const orig_template = this.templateTarget.innerHTML; const placeholder = this.element.getAttribute("data-table-placeholder"); if (!placeholder) { throw new Error("Could not find placeholder attribute"); } this.templateTarget.innerHTML = orig_template.replaceAll( placeholder, String(tbody.querySelectorAll("tr").length) ); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any tbody.appendChild((this.templateTarget.cloneNode(true) as any).content); this.templateTarget.innerHTML = orig_template; this.reindex(); } swapRows(index_a: number, index_b: number) { const tbody = this.getTbody(); const [a, b] = [index_a, index_b].sort().reverse(); // we want them in descending order tbody.insertBefore(tbody.children[a], tbody.children[b]); this.reindex(); } moveUp(e: MouseEvent) { const target = e.target as HTMLButtonElement; const tr = target.closest("tr"); if (!tr) { console.error("Not within a tr"); return; } const index_a = this.getRowIndex(tr); if (index_a < 0) { return; } const index_b = index_a - 1; this.swapRows(index_a, index_b); } moveDown(e: MouseEvent) { const target = e.target as HTMLButtonElement; const tr = target.closest("tr"); if (!tr) { console.error("Not within a tr"); return; } const index_a = this.getRowIndex(tr); if (index_a < 0) { return; } const index_b = index_a + 1; this.swapRows(index_a, index_b); } reindex() { let result = 0; // assign proper number to each input within a tr by changing input names for (const tr of Array.from(this.getTbody().children)) { tr.querySelectorAll("input").forEach((input) => { input.setAttribute( "name", (input.getAttribute("name") || "").replace( /[(\d+)](?!.*\d)/, // last number in a regex result.toString() ) ); }); result++; } // disable up/down buttons for first/last items // first reset all states to enabled this.element .querySelectorAll( `[data-action="sealgen-table#moveUp"], [data-action="sealgen-table#moveDown"]` ) .forEach((e: HTMLButtonElement) => (e.disabled = false)); // then disable the ones at the top and bottom this.element .querySelectorAll( 'tr:first-child [data-action="sealgen-table#moveUp"], tr:last-child [data-action="sealgen-table#moveDown"]' ) .forEach((e: HTMLButtonElement) => (e.disabled = true)); } }