import {deserializeTable, serializeTable, calculateColumnLength} from "./tables.mjs"; let createDialog = document.querySelector("#create-dialog"); let loadDialog = document.querySelector("#load-dialog"); let itemContextMenu = document.querySelector("#item-context-menu"); let deleteRow = itemContextMenu.querySelector("#delete-row"); let deleteColumn = itemContextMenu.querySelector("#delete-column"); let insertRowAbove = itemContextMenu.querySelector("#insert-row-above"); let insertRowBelow = itemContextMenu.querySelector("#insert-row-below"); let insertColumnLeft = itemContextMenu.querySelector("#insert-column-left"); let insertColumnRight = itemContextMenu.querySelector("#insert-column-right"); let output = document.querySelector("#output"); let tableElement = document.querySelector("table"); let table = { columns: [ {text: "ABC", leftAligned: false, rightAligned: false}, {text: "DEF", leftAligned: false, rightAligned: false}, {text: "GHI", leftAligned: false, rightAligned: false} ], rows: [ ["1", "2", "3"], ["4", "5", "6"], ["7", "8", "9"] ] }; Array.prototype.insert = function(index, item) { this.splice(index, 0, item); }; Array.prototype.remove = function(index) { this.splice(index, 1); }; Node.prototype.insertAfter = function(newNode, referenceNode) { this.insertBefore(newNode, referenceNode?.nextSibling); }; NodeList.prototype.indexOf = function(searchElement) { for (let [index, element] of this.entries()) { if (element === searchElement) { return index; } } return -1; }; function findTableIndices(input) { let index = tableElement.querySelectorAll("input[type=text]").indexOf(input); let columnIndex = index % table.columns.length; let rowIndex = -1; if (index >= table.columns.length) { rowIndex = Math.floor(index / table.columns.length - 1); } return {columnIndex, rowIndex}; } function updateItemAlignment(input, column) { input.classList.remove("left"); input.classList.remove("right"); if (column.leftAligned) { input.classList.add("left"); } if (column.rightAligned) { input.classList.add("right"); } } function createItemElement(item, column) { let input = document.createElement("input"); input.type = "text"; input.size = column.maxLength; input.value = item; if (column.leftAligned) { input.classList.add("left"); } if (column.rightAligned) { input.classList.add("right"); } return input; } function createRowElement(row, isHeader) { let tr = document.createElement(isHeader ? "th" : "tr"); for (let i = 0; i < row.length; i++) { let td = document.createElement("td"); td.appendChild(createItemElement(row[i], table.columns[i])); tr.appendChild(td); } return tr; } function reloadTable() { for (let i = 0; i < table.columns.length; i++) { table.columns[i].maxLength = calculateColumnLength(table.columns[i], table.rows, i); } while (tableElement.lastChild) { tableElement.removeChild(tableElement.lastChild); } let columnRow = []; for (let column of table.columns) { columnRow.push(column.text); } tableElement.appendChild(createRowElement(columnRow, true)); for (let row of table.rows) { tableElement.appendChild(createRowElement(row, false)); } output.value = serializeTable(table); } createDialog.addEventListener("input", function(event) { if (event.target.value === "") { event.target.setCustomValidity("Please enter a value"); } else { event.target.setCustomValidity(""); } event.target.checkValidity(); }); createDialog.addEventListener("submit", function() { let columns = createDialog.querySelector("#cols").valueAsNumber; let rows = createDialog.querySelector("#rows").valueAsNumber; table.columns = []; for (let i = 0; i < columns; i++) { table.columns.push({text: "", leftAligned: false, rightAligned: false}); } table.rows = []; for (let i = 0; i < rows; i++) { let row = new Array(columns); row.fill(""); table.rows.push(row); } reloadTable(); }); document.querySelector("#open-create-button").addEventListener("click", function() { createDialog.querySelector("form").reset(); createDialog.showModal(); }, {passive: true}); loadDialog.querySelector("#load-button").addEventListener("click", function() { try { table = deserializeTable(loadDialog.querySelector("textarea").value); reloadTable(); loadDialog.close(); } catch (e) { loadDialog.querySelector("#error").innerText = e.message; } }, {passive: true}); document.querySelector("#open-load-button").addEventListener("click", function() { loadDialog.querySelector("textarea").value = ""; loadDialog.querySelector("#error").innerText = ""; loadDialog.showModal(); }, {passive: true}); itemContextMenu.querySelector("form").addEventListener("change", function(event) { let leftAligned = ["left", "center"].includes(event.target.id); let rightAligned = ["center", "right"].includes(event.target.id); let columnIndex = parseInt(itemContextMenu.dataset.columnIndex, 10); let column = table.columns[columnIndex]; column.leftAligned = leftAligned; column.rightAligned = rightAligned; updateItemAlignment(tableElement.querySelectorAll("th input[type=text]")[columnIndex], column); for (let tr of tableElement.querySelectorAll("tr")) { updateItemAlignment(tr.querySelectorAll("input[type=text]")[columnIndex], column); } output.value = serializeTable(table); }); deleteRow.addEventListener("click", function() { let rowIndex = parseInt(itemContextMenu.dataset.rowIndex, 10); table.rows.remove(rowIndex); tableElement.querySelectorAll("tr")[rowIndex].remove(); output.value = serializeTable(table); itemContextMenu.close(); }, {passive: true}); deleteColumn.addEventListener("click", function() { let columnIndex = parseInt(itemContextMenu.dataset.columnIndex, 10); table.columns.remove(columnIndex); for (let row of table.rows) { row.remove(columnIndex); } for (let tr of tableElement.querySelectorAll("th, tr")) { tr.querySelectorAll("td")[columnIndex].remove(); } output.value = serializeTable(table); itemContextMenu.close(); }, {passive: true}); insertRowAbove.addEventListener("click", function() { let rowIndex = parseInt(itemContextMenu.dataset.rowIndex, 10); let newRow = new Array(table.columns.length); newRow.fill(""); table.rows.insert(rowIndex, newRow); tableElement.insertBefore(createRowElement(newRow, false), tableElement.querySelectorAll("tr")[rowIndex]); output.value = serializeTable(table); itemContextMenu.close(); }, {passive: true}); insertRowBelow.addEventListener("click", function() { let rowIndex = parseInt(itemContextMenu.dataset.rowIndex, 10); let newRow = new Array(table.columns.length); newRow.fill(""); table.rows.insert(rowIndex + 1, newRow); let rowElement = createRowElement(newRow, false); let referenceNode = rowIndex >= 0 ? tableElement.querySelectorAll("tr")[rowIndex] : tableElement.querySelector("th"); tableElement.insertAfter(rowElement, referenceNode); output.value = serializeTable(table); itemContextMenu.close(); }, {passive: true}); insertColumnLeft.addEventListener("click", function() { let columnIndex = parseInt(itemContextMenu.dataset.columnIndex, 10); let column = { text: "", leftAligned: false, rightAligned: false, maxLength: 3 }; table.columns.insert(columnIndex, column); for (let row of table.rows) { row.insert(columnIndex, ""); } let td = document.createElement("td"); td.appendChild(createItemElement("", column)); let th = tableElement.querySelector("th"); for (let tr of tableElement.querySelectorAll("th, tr")) { tr.insertBefore(td.cloneNode(true), tr.querySelectorAll("td")[columnIndex]); } output.value = serializeTable(table); itemContextMenu.close(); }, {passive: true}); insertColumnRight.addEventListener("click", function() { let columnIndex = parseInt(itemContextMenu.dataset.columnIndex, 10); let column = { text: "", leftAligned: false, rightAligned: false, maxLength: 3 }; table.columns.insert(columnIndex + 1, column); for (let row of table.rows) { row.insert(columnIndex + 1, ""); } let td = document.createElement("td"); td.appendChild(createItemElement("", column)); let th = tableElement.querySelector("th"); for (let tr of tableElement.querySelectorAll("th, tr")) { tr.insertAfter(td.cloneNode(true), tr.querySelectorAll("td")[columnIndex]); } output.value = serializeTable(table); itemContextMenu.close(); }, {passive: true}); tableElement.addEventListener("input", function(event) { let {columnIndex, rowIndex} = findTableIndices(event.target); if (rowIndex < 0) { // is modifying a header table.columns[columnIndex].text = event.target.value; } else { // is modifying a row table.rows[rowIndex][columnIndex] = event.target.value; } output.value = serializeTable(table); table.columns[columnIndex].maxLength = calculateColumnLength(table.columns[columnIndex], table.rows, columnIndex); for (let tr of tableElement.querySelectorAll("th, tr")) { tr.querySelectorAll("input[type=text]")[columnIndex].size = table.columns[columnIndex].maxLength; } }); tableElement.addEventListener("contextmenu", function(event) { if (event.shiftKey || event.target.localName !== "input") { return; } let {columnIndex, rowIndex} = findTableIndices(event.target); itemContextMenu.dataset.columnIndex = columnIndex; itemContextMenu.dataset.rowIndex = rowIndex; itemContextMenu.style.margin = 0; itemContextMenu.style.top = itemContextMenu.style.left = "0px"; itemContextMenu.showModal(); let rect = itemContextMenu.getBoundingClientRect(); let top = event.clientY; let left = event.clientX; if (top + rect.height > window.innerHeight) { top = window.innerHeight - rect.height; } if (left + rect.width > window.innerWidth) { left = window.innerWidth - rect.width; } itemContextMenu.style.margin = 0; itemContextMenu.style.top = `${top}px`; itemContextMenu.style.left = `${left}px`; let column = table.columns[columnIndex]; if (column.leftAligned && column.rightAligned) { itemContextMenu.querySelector("#center").checked = true; } else if (column.leftAligned) { itemContextMenu.querySelector("#left").checked = true; } else if (column.rightAligned) { itemContextMenu.querySelector("#right").checked = true; } else { itemContextMenu.querySelector("#none").checked = true; } deleteRow.disabled = event.target.closest("th") !== null; deleteColumn.disabled = table.columns.length < 2; insertRowAbove.disabled = event.target.closest("th") !== null; event.preventDefault(); }); document.querySelector("#copy-to-clipboard").addEventListener("click", function() { output.select(); navigator.clipboard.writeText(output.value).then( function() { // success }, function(reason) { // TODO: make visible to user console.error(`Failed to copy to clipboard: ${reason}`); } ); }, {passive: true}); window.addEventListener("click", function(event) { if (event.target.localName !== "dialog") { return; } let rect = event.target.getBoundingClientRect(); if (rect.top <= event.clientY && rect.left <= event.clientX && rect.bottom >= event.clientY && rect.right >= event.clientX) { return; } event.target.close(); }, {passive: true}); for (let cancelButton of document.querySelectorAll("dialog #cancel-button")) { cancelButton.addEventListener("click", function(event) { event.preventDefault(); event.target.closest("dialog").close(); }); } reloadTable();