Создать таблицу с кликабельными ячейками, которые реагируют на ивент click
У меня задача: создать HTML таблицу при помощи JS, где ячейками выступают кнопки
Логика работы сайта: На вход подается .csv файл. Логика заточена под определенный файл, где по началу выводится таблица дат.
Задача: нужно сделать так, чтобы при нажатии на ячейку, вместо двух дат выводилась полная строчка с данными. То есть, нажав на ячейку с датой, чтобы выводилась вся строка. (Пример внизу под кодом)
Я хотел сделать это в виде таблицы из кнопок, но проблема в том что кнопки создаются внутри цикла, и в таком случае нет доступа к addEventListener("click")
Внизу на картинке представлен первый вывод таблицы. Входной файл под который заточен сайт содержит в себе 28 колонок. На картинке изображены две из них
.html:
/**
* @param {*} inputForm - form element
* @param {*} file - input with type="file"
* @param {*} filterInput - input filter word
*/
const inputForm = document.getElementById("input-form")
const file = document.getElementById("file-choose")
const filterInput = document.getElementById("test-input")
const dataTable = document.getElementById("data-table")
const emptyMessage = document.getElementById('empty-message')
const dateInput = document.getElementById("date-input")
const rowLimiter = document.getElementById("row-limiter")
const timeInput = document.getElementById("time-input")
/**
* @param {*} table - creating element table
* @param {*} thead - creating element table header
* @param {*} tbody - creating element table body
*/
/**
* @function csvToArray - function that converts csv file data into an array
* @param {string} str
* @param {char} delimiter
* @return {Array} Array with all information from file
*/
function csvToArray(str, delimiter=',') {
/**
* @param {Array} headers - array, is created from the first row elements. String slices from index 0, to index of first '\n'
* @param {Array} rows - array of rows. Each row - object with key:value
* */
const headers = str.slice(0, str.indexOf("\n")).split(delimiter)
const rows = str.slice(str.indexOf("\n") + 1).split("\n")
// As last element of headers is '\r', it erases
headers.pop()
/**
* @param {Array} arr - Data array. Includes objects with data as array elements
* @param {Array} values - Values from the row splitted by separator '-'
* @param {Object} element - Object that appears from headers Array as setting key:value
* @return {Array} arr - Result array
*/
const arr = rows.map((row) => {
const values = row.split(delimiter)
const element = headers.reduce((object, header, index) => {
object[header] = values[index]
return object
}, {})
return element
})
return arr
}
/**
*
* @param {Array} data - Array of objects
* @returns {Array} initialArray - Array of arrays, each element of whom - is array with two specific values
*
* As by the assignment table has to print only two dates first, we fill array with arrays of those two dates from each object
*/
function setInitialTable(data) {
let initialArray = [['tLogIn', 'tLogOut']]
for (let i = 0; i < data.length; i++) {
initialArray.push([data[i].tLogIn, data[i].tLogOut])
}
return initialArray
}
inputForm.addEventListener("submit", (e) => {
/**
* e.preventDefault() - prevents reload when click Submit button
*/
e.preventDefault()
// A files property returnes files list. So [0] will return us first file
const input = file.files[0]
/**
* @param table - table element. Will be used to append thead and tbody
* @param thead - table header element. Will be used to append tr and th inside
* @param tbody - table body element. Will be used to append tr and td inside
* @param reader - FileReader variable. Will be used to work with file
*/
let table = document.createElement("table")
let thead = document.createElement("thead")
let tbody = document.createElement("tbody")
const reader = new FileReader()
// Check if file chosen or not. If not - prints following message
if (file.value == '') {
emptyMessage.innerHTML = "File not chosen"
dataTable.innerHTML = ''
}
else {
reader.onload = function(e) {
// Recieving table info in text form separated with coma
const text = e.target.result
// If recieved file is empty, message with be revealed with file name
if (text.length === 0) {
if (file.DOCUMENT_NODE > 0) {
dataTable.innerHTML = ''
table.innerHTML = ''
thead.innerHTML = ''
tbody.innerHTML = ''
}
/**
* file.value will be recieved as C:\\....
* So to recieve file name, we need to split it by '\\', and it will be ["C:", ..., "file.name"]
*/
const arrFromFileName = file.value.replaceAll('\\', ',').split(',')
// To print file name, we need to take the last element of array
emptyMessage.innerHTML = `File <span>${arrFromFileName[arrFromFileName.length - 1]}</span> is empty`
}
// If file is not empty:
else {
// Cleaning emptyMessage field if it was printed before
if (emptyMessage.value != 0)
emptyMessage.innerHTML = ''
/**
* @param data - Data array with Array of Objects
* @param initialArray - Array with only two columns
*/
const data = csvToArray(text)
const initialArray = setInitialTable(data)
// Cleaning table if it was printed before
dataTable.innerHTML = ''
table.innerHTML = ''
thead.innerHTML = ''
tbody.innerHTML = ''
// Adding thead and tbody to the table
table.appendChild(thead)
table.appendChild(tbody)
// Adding table to our parent table declared in HTML file
document.getElementById('data-table').appendChild(table)
/**
* @param hrow - Header row
*
* First row in InitialArray are headers names as it's written in csvToArray() function when adding headers
* So we create a loop with 2 iterations as we have only 2 columns, and take first row as headers names
*/
let hrow = document.createElement('tr')
for (let i = 0; i < initialArray[i].length; i++) {
let theader = document.createElement('th')
// Writing header name into theader slot
theader.innerHTML = initialArray[0][i]
// Appending theader slot to header row
hrow.appendChild(theader)
}
// Appending header row into Header section
thead.appendChild(hrow)
/**
* @param {Number} rowLimiter - rowLimiter from HTML input with type="number".
* Unary plus converts recieved string into a number
* @param table_data - table data slot
* @param table_data_button - a button inserting into a table slot
* @param body_row - row for table body (tbody)
* @param tbody - table body
*
* Iterations going from 1 to rowLimiter. From 1, because index 0 in initialArray is headers array, and we need only data
*/
for (let i = 1; i < +rowLimiter.value; i++) {
let body_row = document.createElement('tr')
for (let j = 0; j < 2; j++) {
let table_data = document.createElement('td')
var table_data_button = document.createElement('button')
if (initialArray[i][j].length !== 0) {
table_data_button.innerHTML = initialArray[i][j]
table_data.appendChild(table_data_button)
}
body_row.appendChild(table_data)
}
tbody.appendChild(body_row)
}
table_data_button.addEventListener("click", (e) => {
e.preventDefault()
console.log("Click!")
})
table.appendChild(thead)
table.appendChild(tbody)
dataTable.appendChild(table)
}
}
// Using to read input file
reader.readAsText(input)
}
})
* {
font-family: 'Courier New', Courier, monospace;
}
body {
margin: 0;
}
#file-choose {
max-width: fit-content;
}
#data-table table {
margin: none;
}
#data-table {
margin: auto;
border-collapse: collapse;
border-radius: 5px;
font-size: 0.9em;
font-family: sans-serif;
min-width: fit-content;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
}
#data-table {
max-width: 1400px;
}
#data-table thead tr {
background-color: #009897;
color: #ffffff;
text-align: left;
}
#data-table th, #data-table td {
padding: 12px 15px;
}
#data-table tbody tr {
border-bottom: 1px solid #dddddd;
}
#data-table tbody tr:nth-child(even) {
background-color: #f3f3f3;
}
#data-table tbody tr:last-of-type {
border-bottom: 2px solid #009897;
border-radius: 50px;
}
#data-table tbody td button {
border: none;
background-color: transparent;
cursor: pointer;
}
#empty-message {
text-align: center;
font-size: 40px;
}
#file-choose {
margin-right: -0px;
max-width: 90px;
}
#input-section {
display: flex;
flex-direction: row;
justify-content: space-evenly;
margin: 0 250px;
width: 70%;
}
#input-form {
position: static;
min-height: 100px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-bottom-left-radius: 15px;
border-bottom-right-radius: 15px;
}
#row-limiter {
max-width: 85px;
}
#test-input {
}
.submit-button {
padding: 3px 8px;
font-size: 15px;
border-radius: 3px;
border: 0.1px solid #000
}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Excel Transform Site</title>
<link rel="stylesheet" href="./excel-transfrom.css">
</head>
<body>
<form id="input-form">
<section id="input-section">
<input type="file" id="file-choose" apply=".csv"><br>
<p id="chosen-file"></p>
<div>
<label class="test-input-label">Filter word:</label>
<input type="text" id="test-input">
</div>
<div>
<label class="date-input-label">Date:</label><br>
<input type="date" id="date-input">
<input type="time" id="time-input">
</div>
<div>
<label>Rows limit:</label>
<input type="number" id="row-limiter" placeholder="50000" value="3" min="1" max="50000">
</div>
</section><br>
<input type="submit" value="Submit" class="submit-button">
</form><br>
<table id="data-table"></table>
<p id="empty-message"></p>
<script src="./excel-transform.js"></script>
</body>
</html>
Пример: Нажав на ячейку 11/01/2022 5:00, таблица закрывается и вместо нее выводится строчка
header 1| header 2 | tLogIn | tLogOut | header 3 |
data 1 | data 2 | 11/01/2022 5:00 | 11/01/2022 8:00 | data 3 |
Данные для .csv файла на вход:
header 1,header 2,tLogIn,tLogOut,header 3
data 1,data 2,"11/01/2022 5:00", "11/01/2022 8:00",data 3,
data 11,data 22,"15/04/2019 12:00","15/04/2019 15:00",data 33
Ответы (2 шт):
Если я правильно понял Вашу проблему тогда это обычно делается как-то так:
const bd = document.querySelector('body');
const table = document.createElement('TABLE');
table.setAttribute('id', 'tb');
for (let i = 0; i < 4; ++i) {
let tr = document.createElement('TR');
for (let j = 0; j < 4; ++j) {
let td = document.createElement('TD');
td.setAttribute('id', `cell ${i} ${j}`);
td.append(`Cell ${i} ${j}`);
tr.append(td);
}
table.append(tr);
}
bd.append(table);
const tb = document.querySelector('#tb');
tb.addEventListener('click', e => {
e.target.innerHTML = `Я ячейка ${e.target.id}`;
});
td {
border: solid 1px grey;
}
нажав на ячейку с датой, чтобы выводилась вся строка
Я хотел сделать это в виде таблицы из кнопок, но проблема в том что кнопки создаются внутри цикла, и в таком случае нет доступа к addEventListener("click")
в таком случае можно юзнуть "онклико-заменитель". ну то есть, использовать например селект или форму заполненную инпутами-радио повесив на неё(форму) onchange. в случае с формой можно спрятать инпуты, а щупать не их а label'ы, расфасовав их по любому удобному контейнеру.
и да, может пора бы уже отправить <table> с её кучей мусорных тегов на покой, всё таки css grid есть. вам же не в IE 6 каком нибудь всё это открывать... или таки в нём...? :3
пара наглядных примеров для "пощупать идею лапками":
var x =
[
['a1', 'a2', 'a3', 'a4', 'a5'],
['b1', 'b2', 'b3', 'b4', 'b5'],
['c1', 'c2', 'c3', 'c4', 'c5'],
['d1', 'd2', 'd3', 'd4', 'd5']
]
#y{
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 1em;
}
#y > *{background: pink;}
/* input[name=abc]{display: none} */
<select onchange='alert(x[this.value])' size='5'>
<option>---</option>
<option>0</option>
<option>1</option>
<option>2</option>
<option>3</option>
</select>
<form onchange='console.log(x[abc.value])'>
<input id='a' type='radio' name='abc' value='0'>
<input id='b' type='radio' name='abc' value='1'>
<input id='c' type='radio' name='abc' value='2'>
<input id='d' type='radio' name='abc' value='3'>
</form>
<p id='y'>
<label for='a'>a</label>
<label for='b'>b</label>
<label for='c'>c</label>
<label for='d'>d</label>
</p>
