You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
354 lines
14 KiB
354 lines
14 KiB
/**
|
|
* 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)
|
|
}
|
|
}
|