/** * Represents a table object. */ class Table { /** * Initializes a new instance of the Table class. * @param {HTMLElement} tableElement - The HTML table element. * @param {HTMLElement} rcMenu - The right-click menu element. */ constructor(tableElement, rcMenu) { this.tableElement = tableElement this.tableBody = tableElement.querySelector("tbody") this.tableHead = tableElement.querySelector("thead") this.rcMenu = rcMenu this.importElement = document.getElementById("import") this.exportElement = document.getElementById("export") // Initialize right-click menu options this.rcAddRow = rcMenu.querySelector("#rcAddRow") this.rcDelRow = rcMenu.querySelector("#rcDelRow") this.rcAddCol = rcMenu.querySelector("#rcAddCol") this.rcDelCol = rcMenu.querySelector("#rcDelCol") this.selectedCell = null this.addEventListeners(this.rcMenu, this.tableElement) } addEventListeners() { // Hide context menu when mouse button is clicked anywhere on the window window.addEventListener('mousedown', (e) => { this.rcMenu.style.display = "none" }) // Show context menu when right-clicking on the data table this.tableElement.addEventListener('contextmenu', (e) => { const pos = { x: e.clientX, y: e.clientY } this.handleContextMenu(this.rcMenu, this.tableElement, pos) this.selectedCell = e.target // Prevent default context menu from appearing e.preventDefault() e.stopPropagation() }) this.rcAddRow.addEventListener("mousedown", (e) => { this.addRow() }) this.rcDelRow.addEventListener("mousedown", (e) => { this.delRow() }) this.rcAddCol.addEventListener("mousedown", (e) => { this.addCol() }) this.rcDelCol.addEventListener("mousedown", (e) => { this.delCol() }) this.importElement.addEventListener("input", (event) => { let file = this.importElement.files[0] let reader = new FileReader() reader.onload = (e) => { let result = e.target.result let rows = result.split("\n") let header = rows[0].split(",") rows.shift() this.selectedCell = this.tableBody.querySelector("#chart_table_0_values_0") while (this.tableBody.rows.length > 1) this.delRow() while (this.tableHead.lastElementChild.childElementCount > header.length) this.delCol() while (this.tableHead.lastElementChild.childElementCount < header.length) this.addCol() for (let i = 0; i < header.length; i++) { this.tableHead.querySelector("#chart_table_" + i +"_col_name").value = header[i] } for (let i = 1; i < rows.length - 1; i++) this.addRow() for (let i = 0; i < rows.length - 1; i++) { let cells = rows[i].split(",") for (let j = 0; j < header.length; j++) { console.log("#chart_table_" + j + "_values_" + i, +cells[j]) this.tableBody.querySelector("#chart_table_" + j + "_values_" + i).value = +cells[j] } } } reader.readAsText(file) }) this.exportElement.addEventListener("input", (e) => { let newName = "" let fileURL = "" let choice = this.exportElement.value if (choice === "png") { let iframe = document.getElementById("chartDiv") let canvas = iframe.contentWindow.document.getElementById("chartCanvas") fileURL = canvas.toDataURL("image/png") newName = 'chart.png' } else if (choice === "csv" || choice === "txt") { // Variable to store the final csv data let csv_data = [] let rows = document.getElementsByTagName('tr'); for (let i = 0; i < rows.length; i++) { // Get each column data let cols = rows[i].querySelectorAll('td,th'); // Stores each csv row data let csvrow = []; for (let j = 0; j < cols.length; j++) { // Get the text data of each cell of // a row and push it to csvrow csvrow.push(cols[j].querySelector("input").value); } // Combine each column value with comma csv_data.push(csvrow.join(",")); } // Combine each row data with new line character csv_data = csv_data.join('\n'); let CSVFile = new Blob([csv_data], { type: "text/csv" }) fileURL = window.URL.createObjectURL(CSVFile) newName = 'data.' + choice } let downloadLink = document.createElement("a") downloadLink.download = newName downloadLink.href = fileURL downloadLink.click() }) } handleContextMenu(rcMenu, tableElement, pos) { rcMenu.style.display = "block" // Position the context menu relative to the mouse pointer if (pos.x + rcMenu.clientWidth <= tableElement.clientWidth) rcMenu.style.left = pos.x + "px" else rcMenu.style.left = pos.x - rcMenu.clientWidth + "px" if (pos.y + rcMenu.clientHeight <= window.innerHeight + document.documentElement.scrollTop) rcMenu.style.top = pos.y + "px" else rcMenu.style.top = pos.y - rcMenu.clientHeight + "px" } /** * Gets the position of the cell in the table. * @param {HTMLElement} cell - The HTML cell element. * @returns {Object} An object containing the column and row indexes. */ getCellPos(cell) { // Name contains the column and row indexes. let name = cell.name let match = name.match(/\d+/g) let col = +match[0] let row = +match[1] row = isNaN(row) ? -1 : row return { col: col, row: row } } /** * Adds a new row to the table below the selected cell. */ addRow() { // Clone the last row to create a new row. let lastRow = this.tableBody.lastElementChild let newRow = lastRow.cloneNode(true) // Get all input cells in the new row. let cells = Array.from(newRow.children).map(cell => cell.querySelector("input")) // Determine the row index of the last row. let lastRowIndex = this.getCellPos(cells[0]).row // Update IDs and names of input cells in the new row. cells.forEach(cell => { let pos = this.getCellPos(cell) cell.id = `chart_table_${pos.col}_values_${pos.row + 1}` cell.name = `chart[table][${pos.col}][values][${pos.row + 1}]` }) // Shift existing row data down to make room for a new row. let pos = this.getCellPos(this.selectedCell) //pos.row = pos.row === -1 ? 0 : pos.row pos.row++ for (let i = lastRowIndex; i > pos.row; i--) { for (let j = 0; j < cells.length; j++) { let currentCell = this.tableBody.querySelector(`#chart_table_${j}_values_${i}`) let previousCell = this.tableBody.querySelector(`#chart_table_${j}_values_${i - 1}`) currentCell.value = previousCell.value } } this.tableBody.appendChild(newRow) // Clear input cells in the new row. for (let j = 0; j < cells.length; j++) { let currentCell = this.tableBody.querySelector(`#chart_table_${j}_values_${pos.row}`) currentCell.value = "" } } /** * Deletes the row containing the selected cell. */ delRow() { // Get the number of rows and columns in the table. let rowCount = this.tableBody.children.length let colCount = this.tableBody.lastElementChild.children.length // Get the position of the selected cell. let pos = this.getCellPos(this.selectedCell) // Don't delete heading row. if (pos.row === -1) return // Shift row data for (let i = pos.row; i < rowCount - 1; i++) { for (let j = 0; j < colCount; j++) { let currentCell = this.tableBody.querySelector(`#chart_table_${j}_values_${i}`) let previousCell = this.tableBody.querySelector(`#chart_table_${j}_values_${i + 1}`) currentCell.value = previousCell.value } } // Remove the last row from the table. this.tableBody.removeChild(this.tableBody.lastElementChild) } /** * Adds a new column to the table. */ addCol() { // Get the rows and column count of the table. let rows = Array.from(this.tableBody.children) let rowCount = rows.length let colCount = this.tableBody.lastElementChild.children.length // Clone the last cell in the heading row to create a new header cell for the new column. let newHeadCell = this.tableHead.lastElementChild.lastElementChild.cloneNode(true) let newHeadCellInput = newHeadCell.querySelector("input[type='text']") newHeadCellInput.id = `chart_table_${colCount}_col_name` newHeadCellInput.name = `chart[table][${colCount}][col_name]` let newHeadCellColor = newHeadCell.querySelector("input[type='color']") newHeadCellColor.id = `chart_table_${colCount}_color` newHeadCellColor.name = `chart[table][${colCount}][color]` this.tableHead.lastElementChild.appendChild(newHeadCell) // Clone the last cell in each row to create a new cell for the new column in each row. for (let i = 0; i < rowCount; i++) { let newCell = rows[i].lastElementChild.cloneNode(true) let newCellInput = newCell.querySelector("input") newCellInput.id = `chart_table_${colCount}_values_${i}` newCellInput.name = `chart[table][${colCount}][values][${i}]` rows[i].appendChild(newCell) } // Shift existing column data to the right to make room for a new column. let pos = this.getCellPos(this.selectedCell) pos.col++ for (let i = 0; i < rowCount; i++) { for (let j = colCount; j > pos.col; j--) { let currentCell = this.tableBody.querySelector(`#chart_table_${j}_values_${i}`) let previousCell = this.tableBody.querySelector(`#chart_table_${j - 1}_values_${i}`) currentCell.value = previousCell.value } } // Shift existing column headings to the right to make room for a new column heading. for (let j = colCount; j > pos.col; j--) { let currentCell = this.tableHead.querySelector(`#chart_table_${j}_col_name`) let previousCell = this.tableHead.querySelector(`#chart_table_${j - 1}_col_name`) currentCell.value = previousCell.value } // Shift existing column colors to the right to make room for a new column color. for (let j = colCount; j > pos.col; j--) { let currentCell = this.tableHead.querySelector(`#chart_table_${j}_color`) let previousCell = this.tableHead.querySelector(`#chart_table_${j - 1}_color`) currentCell.value = previousCell.value } // Clear input cells in the new column. for (let i = 0; i < rowCount; i++) { let currentCell = this.tableBody.querySelector(`#chart_table_${pos.col}_values_${i}`) currentCell.value = "" } // Clear the input and color of the new column heading. let headCell = this.tableHead.lastElementChild.children[pos.col] headCell.querySelector("input[type='text']").value = "" headCell.querySelector("input[type='color']").value = "#FFFFFF" } /** * Deletes the selected column from the table. */ delCol() { let rows = Array.from(this.tableBody.children) let rowCount = rows.length let colCount = this.tableBody.lastElementChild.children.length // Shift existing column data to the left. let pos = this.getCellPos(this.selectedCell) for (let i = 0; i < rowCount; i++) { for (let j = pos.col; j < colCount - 1; j++) { let currentCell = this.tableBody.querySelector(`#chart_table_${j}_values_${i}`) let previousCell = this.tableBody.querySelector(`#chart_table_${j + 1}_values_${i}`) currentCell.value = previousCell.value } } // Shift existing column titles to the left. for (let i = pos.col; i < colCount - 1; i++) { let currentCell = this.tableHead.querySelector(`#chart_table_${i}_col_name`) let previousCell = this.tableHead.querySelector(`#chart_table_${i + 1}_col_name`) currentCell.value = previousCell.value } // Shift existing column colors to the left. for (let i = pos.col; i < colCount - 1; i++) { let currentCell = this.tableHead.querySelector(`#chart_table_${i}_color`) let previousCell = this.tableHead.querySelector(`#chart_table_${i + 1}_color`) currentCell.value = previousCell.value } // Remove the last cell from each row. rows.forEach(row => { row.removeChild(row.lastElementChild) }) // Remove the last header cell. this.tableHead.lastElementChild.removeChild(this.tableHead.lastElementChild.lastElementChild) } }