diff --git a/src/frontend/components/board-game.ts b/src/frontend/components/board-game.ts index f23a1be..6ae44b3 100644 --- a/src/frontend/components/board-game.ts +++ b/src/frontend/components/board-game.ts @@ -13,6 +13,7 @@ import { GameVariantName, gamesRules } from "../../shared/game-variants/index.ts import { replaceLocation } from "../utils/navigation-utils.ts"; import { TrackingTools } from "../utils/query-tracking-utils.ts"; import { updateWithQueryParams } from "../utils/url-utils.ts"; +import { ensureColumnsNumber, ensureRowsNumber, ensureSlotTemplate } from "../utils/dom-utils.ts"; class BoardGameComponent extends HTMLElement { private readonly trackingTools = new TrackingTools(this); @@ -38,18 +39,7 @@ class BoardGameComponent extends HTMLElement { // because both slotName and templateName originate from getSlotTemplateNames and is not actually arbitrary for (const [slotName, templateName] of Object.entries(slotTemplateNames)) { this.querySelectorAll(`.${slotName}`).forEach((slot) => { - if (slot.getAttribute("data-current-template") !== templateName) { - while (slot.lastChild) { - slot.removeChild(slot.lastChild); - } - - const template = this.querySelector(`template[name="${templateName}"]`); - if (template) { - slot.appendChild(template.content.cloneNode(true)); - } - - slot.setAttribute("data-current-template", templateName); - } + ensureSlotTemplate(slot, templateName, () => this.querySelector(`template[name="${templateName}"]`)); }); } @@ -74,33 +64,17 @@ class BoardGameComponent extends HTMLElement { } this.querySelectorAll("tbody.game-board").forEach((tbody) => { - while (gameState.rows < tbody.rows.length) { - tbody.rows[0]?.remove(); - } - - while (gameState.rows > tbody.rows.length) { - tbody.insertRow(); - } + ensureRowsNumber(tbody, gameState.rows); for (let rowNumber = 0; rowNumber < tbody.rows.length; rowNumber++) { - const row = tbody.rows[rowNumber]; - if (!row) { - continue; - } - - while (gameState.columns < row.cells.length) { - row.cells[0]?.remove(); - } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- should not be empty by the definition of rowNumber + const row = tbody.rows[rowNumber]!; - while (gameState.columns > row.cells.length) { - row.insertCell(); - } + ensureColumnsNumber(row, gameState.columns); for (let columnNumber = 0; columnNumber < row.cells.length; columnNumber++) { - const cell = row.cells[columnNumber]; - if (!cell) { - continue; - } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- should not be empty by the definition of columnNumber + const cell = row.cells[columnNumber]!; if (!cell.childNodes.length) { const button = document.createElement("button"); diff --git a/src/frontend/utils/dom-utils.ts b/src/frontend/utils/dom-utils.ts new file mode 100644 index 0000000..c3d9688 --- /dev/null +++ b/src/frontend/utils/dom-utils.ts @@ -0,0 +1,38 @@ +export const ensureSlotTemplate = ( + slot: Element, + templateName: string, + getTemplate: () => HTMLTemplateElement | null, +) => { + if (slot.getAttribute("data-current-template") !== templateName) { + while (slot.lastChild) { + slot.removeChild(slot.lastChild); + } + + const template = getTemplate(); + if (template) { + slot.appendChild(template.content.cloneNode(true)); + } + + slot.setAttribute("data-current-template", templateName); + } +}; + +export const ensureRowsNumber = (tbody: HTMLTableSectionElement, targetRowsNumber: number) => { + while (targetRowsNumber < tbody.rows.length) { + tbody.deleteRow(0); + } + + while (targetRowsNumber > tbody.rows.length) { + tbody.insertRow(); + } +}; + +export const ensureColumnsNumber = (row: HTMLTableRowElement, targetColumnsNumber: number) => { + while (targetColumnsNumber < row.cells.length) { + row.cells[0]?.remove(); + } + + while (targetColumnsNumber > row.cells.length) { + row.insertCell(); + } +};