组件化的可编辑表格
一、展示
以上就是可编辑表格的页面展示,在此页面基础上,在课程栏的成绩上点击即可修改,修改以后点击界面任何位置,即可实现修改,总分也会随分数的修改而变化。点击删除按钮,即可删除这一栏的数据。但由于是访问的json文件,并不会对文件内数据进行删除,这也是需要改进的地方。
二、逻辑思路设计
- 1.通过获取元素的方式获取表格、表头、表格主体、按钮、可编辑单元格、成绩单元格和提示信息等元素。
- 2.获取 JSON 文件中的数据,并将读取到的表头和成绩数据分别存储到 title_data 和 grade_data 数组中。
- 3.根据读取到的数据生成表格标题和成绩数据,并进行一些初始化操作。
- 4.在表格标题中添加 "操作" 标题和 "删除" 按钮,并注册按钮点击事件,在每行学生的成绩单元格中添加删除按钮。
- 5.当用户输入不合法时,相应的提示信息会显示出来。当用户输入完成后,更新每行学生的总分数。当用户点击删除按钮时,删除相应的行,并更新每行学生的总分数。
三、编码逻辑思路
- 1. 通过获取元素的方式获取表格、表头、表格主体、按钮、可编辑单元格、成绩单元格和提示信息等元素。
- 2. 定义异步函数 fetchData,使用 fetch 方法获取 JSON 文件中的数据,并将读取到的表头和成绩数据分别存储到 title_data 和 grade_data 数组中。
- 3. 定义函数 getHtml,根据读取到的数据生成表格标题和成绩数据,并进行一些初始化操作,包括生成总分列并添加到表头中、设置指定列为可编辑状态、注册单元格点击事件、更新每行学生的总分数等。
- 4. 在 fetchData 函数中调用 getHtml 函数,生成完整的表格并进行必要的初始化操作。
- 5. 在 getHtml 函数中设置 flag 为 true,根据其值调用 actionBar 函数。
- 6. 定义函数 actionBar,在表格标题中添加 "操作" 标题和 "删除" 按钮,并注册按钮点击事件,在每行学生的成绩单元格中添加删除按钮。
- 7. 当用户点击单元格时,注册的 setCellCilck 函数会将选中的单元格转换为可编辑状态,并在用户输入完成后更新单元格内容。
- 8. 当用户输入不合法时,相应的提示信息会显示出来。
- 9. 当用户输入完成后,调用 updateScore 函数更新每行学生的总分数。
- 10. 当用户点击删除按钮时,调用 delRow 函数删除相应的行,并更新每行学生的总分数。
四、实现方法及核心代码
1.注意准备json文件,在整体代码完成以后在命令行运行json文件以后再运行代码。 2.编写页面body、css使其显示在页面上。
<body>
<div id="tableBox">
<h2 class="title">G7-学生成绩表</h2>
<div class="wrong" style="display: none;">当前为非数值,请重新输入</div>
<div class="wrong1" style="display: none;">超过最大输入,请重新输入</div>
<div class="wrong2" style="display: none;">低于最小输入,请重新输入</div>
<table class="table" >
<thead>
<tr>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</body>
复制代码
3.获取json文件
这段代码定义了一个异步函数 fetchData,用于通过 fetch 方法获取名为 G7.json 的 JSON 文件中的数据。如果请求成功,则将读取到的表头数据存储到数组 title_data 中,成绩数据存储到数组 grade_data 中,并调用 getHtml 函数生成表格并进行初始化操作。在实现过程中,使用 async 和 await 关键词来处理异步操作,确保获得了可用数据后再执行相关操作。最后,通过调用 fetchData 函数来触发整个流程的执行。 4.渲染DOM内容
首先定义了两个变量 titlekey 和 gradekey。 第一个循环使用 for...of 循环遍历 title_data 数组中的每个元素。对于每个元素,使用 for 循环遍历其中的每个对象键值对,然后将对象的值存储在 titlekey 中,并使用模板字符串生成一个包含该值的 HTML 表头单元格元素 temp_title。最后使用 DOM 方法 insertAdjacentHTML() 将该表头单元格插入到指定表格 stutable_title 的末尾。 第二个循环使用 for...of 循环遍历 grade_data 数组中的每个元素。对于每个元素,使用 for...in 循环遍历其中的每个对象属性名,然后将对象的属性名存储在 gradekey 中。接着使用模板字符串生成一个包含该对象所有属性值的 HTML 行元素 temp_grade,并根据当前属性名是否是最后一个来判断是否应该在该单元格后添加行结束标签 。最后使用 DOM 方法 insertAdjacentHTML() 将该行元素插入到指定表格 stutable_grade 的末尾。 5.更新总成绩
这段代码定义了一个名为 updateScore 的 JavaScript 函数,其作用是更新表格中每个学生的总分数以及对应单元格的显示值。具体来说,接着,使用 Array.from() 方法获取所有行(除表头外)中 name 和 rname 属性所在的列并保存到变量 numSum 和 numSum1 中。然后,使用 reduce() 方法将 numSum 中所有单元格内的数字加起来并返回总和 sum。最后,将 sum 赋值给 numSum1 中所有单元格的内容,并更新该单元格的显示值。
这段代码的作用是添加一个名为“总分”的表头单元格到指定表格 stutable_title 的末尾,并在每一行(对应学生)的最后一个单元格中添加一个初始值为0,并设置该单元格的自定义属性 "r name" 为 "allgrade"。这个操作通常用于表示所有考试或作业的总分数或者平均分等统计信息。
6.为所有可编辑单元格绑定点击事件,并为每个单元格设置一个更新函数和删除行函数。 7.设置可编辑单元格
函数名称 setEditable 也反映了这个目的。具体地,该函数接受一个数组 arr 作为输入参数,该数组包含需要被设置为可编辑状态的列的索引值(从0开始)。对于每一行(即每个学生),该函数遍历指定列的单元格,并为其添加自定义属性 "name",值为 "editable",表示该单元格可以被编辑。最后,该函数调用 setCellCilck() 函数,以便为所有可编辑单元格绑定点击事件。这个操作通常用于在表格中提供用户交互功能,例如允许用户修改或输入成绩或其他信息。
7.设计可计算分数的表格列
将指定表格 stutable 中所有包含在输入数组 arr 中的列的单元格添加一个名为 "grade" 的 CSS 类。具体地,该函数使用 Array.from() 方法将表格中所有单元格转换为一个数组 stcells,然后对于每个单元格,如果它所在的列的索引值在输入数组 arr 中,则将其添加一个名为 "grade" 的 CSS 类。
8.处理单元格用户交互
该代码定义了一个名为 updateCell 的 JavaScript 函数,其作用是处理表格中某个单元格的用户交互。具体来说,当用户点击该单元格时,函数会检查是否已存在输入框(class 为 active-input),若不存在,则将单元格内容替换为输入框,并聚焦到该输入框中。接着,当用户在输入框中输入数字并失去焦点时,函数会根据输入的数字进行一系列判断
9.添加操作栏
为指定表格 stutable 添加一个名为“操作”的表头单元格,并在每一行(对应学生)的最后一个单元格中添加一个“删除”按钮。
10.删除表格栏
为所有“删除”按钮绑定点击事件,并在点击按钮时删除对应的行。具体地,该函数使用 for 循环遍历所有“删除”按钮,并为其设置 onclick 事件处理程序。对于每个点击事件,该函数首先获取当前行的索引值 rowindex,然后根据该值使用 deleteRow() 方法删除该行。同时,该函数在删除行后还会更新 grade_data 数组中对应学生的成绩记录,即通过遍历 grade_data 中的对象来找到对应学生的数据,并使用数组方法 splice() 删除该行数据。
五、总结
1.js部分
let stutable = document.getElementsByClassName("table")[0]
let stutable_title = stutable.getElementsByTagName("thead")[0].getElementsByTagName("tr")[0] // 获取th标题行
let stutable_grade = stutable.getElementsByTagName("tbody")[0] // 获取tbody
let stu_trs = stutable_grade.getElementsByTagName("tr") // 获取tbody的tr标签
var title_data = [] // 存放标题数据
var grade_data = [] // 存放成绩数据
let delbtns = document.getElementsByTagName("button")
var editcell = document.getElementsByName("editable") // 可编辑的单元格
var grades = document.getElementsByClassName("grade") // 需要计算的单元格
var wrongtips = document.getElementsByClassName("wrong")[0]
var wrongtips1 = document.getElementsByClassName("wrong1")[0]
var wrongtips2 = document.getElementsByClassName("wrong2")[0]
var alltr = document.getElementsByTagName("tr") // 获取HTML中所有的tr标签
var flag; // 设置是否显示删除栏
// 读取本地json数据
async function fetchData() {
const response = await fetch('G7.json');
if (response.ok) {
const data = await response.json();
title_data.push(data.t_title);
grade_data.push(data.t_grades);
getHtml();
}
}
fetchData();
// 渲染DOM内容
function getHtml() {
let titlekey, gradekey // 用于存放对象的key
// 表头
for (item of title_data) {
for (let i = 0; i < item.length; i++) {
titlekey = Object.values(item[i]); // 取出表头数据
let temp_title = `
<th>${titlekey}</th>
`
stutable_title.insertAdjacentHTML('beforeend', temp_title);
}
}
// 表格内容
for (item of grade_data) {
for (let i = 0; i < item.length; i++) {
gradekey = Object.keys(item[i]);
let temp_grade = `<tr>`
// console.log(gradekey); // 获取到的key数组
for (let j in gradekey) {
let k = gradekey[j]; // 获取到的key值
// console.log(item[i][k]); // 取出对象中的值
// 判断是否是最后一个键名
if (j == gradekey.length - 1) {
temp_grade += `<td>${item[i][k]}</td></tr>`
} else {
temp_grade += `<td>${item[i][k]}</td>`
}
}
stutable_grade.insertAdjacentHTML('beforeend', temp_grade);
}
}
totalScoreBar() // 生成总分栏
setAllScore([2, 3, 4, 5]) // 设置需要计算总分的学科
setEditable([2, 3, 4, 5]) // 设置可编辑单元格
updateScore() // 更新总分
flag = true; // 删除栏
if (flag) actionBar()// 生成操作栏
}
// 添加总分栏
function totalScoreBar() {
let allscore = document.createElement("th")
allscore.innerText = "总分"
stutable_title.appendChild(allscore)
for (let j = 0; j < stu_trs.length; j++) {
let score = document.createElement("td")
score.innerText = "0"
stu_trs[j].appendChild(score)
score.setAttribute("rname", "allgrade")
}
}
//当前表格总分会进行累加,当前函数可以进行判断
function resetTotalScore() {
let allGradeCells = document.querySelectorAll("[rname='allgrade']");
for (let i = 0; i < allGradeCells.length; i++) {
allGradeCells[i].innerText = "0";
}
}
// 添加操作栏
function actionBar() {
let caozuo = document.createElement("th")
caozuo.innerText = "操作"
stutable_title.appendChild(caozuo)
for (let k = 0; k < stu_trs.length; k++) {
let caozuo2 = document.createElement("td")
let btn = document.createElement("button")
btn.innerText = "删除"
caozuo2.appendChild(btn)
stu_trs[k].appendChild(caozuo2)
}
delRow() // 删除操作
}
// 设置哪些单元格可编辑
function setEditable(arr) {
//arr 表示可编辑的单元格
// editable 设置单元格可编辑性
var strow = stutable.rows.length// 获取表格行数
for (let i = 1; i < strow; i++) {
let stcell = stutable.rows[i].cells // 获取表格列数
// console.log(stcell);
arr.forEach(function (item) {
if(stcell[item]) {
stcell[item].setAttribute("name", "editable")
}
})
}
setCellCilck()
}
// 设置可计算分数的表格列
function setAllScore(arr) {
const stcells = Array.from(stutable.querySelectorAll('td'));
stcells.forEach((cell) => {
if (arr.includes(cell.cellIndex)) {
cell.classList.add('grade');
}
});
}
// 给单元格添加点击事件
function setCellCilck() {
let scorearr = [100, 100, 100, 100] // 设计单科成绩的满分
for (let i = 0; i < editcell.length; i++) {
editcell[i].onclick = function () {
updateCell(this, scorearr)
// delRow()
}
}
}
// 更新单元格内容
function updateCell(upt, scorearr) {
let scoreMax = scorearr[upt.cellIndex - 2]
scoreMax = scoreMax || 100
console.log('当前科目的满分是:' + scoreMax);
if (document.getElementsByClassName("active-input").length == 0) {
var oriScript = upt.innerHTML;
upt.innerHTML = '';
var newInput = document.createElement('input');
newInput.setAttribute("class", "active-input")
newInput.value = oriScript;
newInput.onblur = function () {
switch (true) {
case (this.value === "" || isNaN(this.value)):
console.log("wrong");
wrongError();
wrongtips.style.display = "block";
wrongtips1.style.display = "none";
wrongtips2.style.display = "none";
return;
case (this.value > 100):
console.log("wrong1");
wrongError2();
wrongtips1.style.display = "block";
wrongtips.style.display = "none";
wrongtips2.style.display = "none";
return;
case (this.value < 0):
console.log("wrong2");
wrongError3();
wrongtips2.style.display = "block";
wrongtips.style.display = "none";
wrongtips1.style.display = "none";
return;
default:
wrongtips.style.display = "none";
wrongtips1.style.display = "none";
wrongtips2.style.display = "none";
// 设置一个整数和小数的判断,如果大于0.5,就向上取证,如果小于就保持原样
var num = parseFloat(this.value);
var intNum = parseInt(num);
var decimal = num - intNum;
if (decimal > 0.4) {
intNum += 1;
}
// upt.innerHTML = this.value == oriScript ? oriScript : intNum;
if (this.value == oriScript) {
upt.innerHTML = oriScript;
} else {
upt.innerHTML = intNum;
}
updateScore()
break;
}
}
newInput.select()
upt.appendChild(newInput);
newInput.focus()
} else {
return
}
}
// 添加动画
function wrongError() {
wrongtips.className = "wrong movedown"
}
function wrongError2() {
wrongtips1.className = "wrong1 movedown"
}
function wrongError3() {
wrongtips2.className = "wrong2 movedown"
}
// 更新总成绩
function updateScore() {
// console.log(grades);
resetTotalScore()
// 可以在需要重新计算总分时调用 resetTotalScore()
// 函数来重置所有含有 "allgrade" 属性的单元格。
// 这样,在下一次计算总分时
// 所有单元格都会被正确地计入总分中。
for (let n = 1; n < alltr.length; n++) {
const row = alltr[n];
const numSum = Array.from(row.querySelectorAll('td[name]'));
const numSum1 = Array.from(row.querySelectorAll('td[rname]'));
let sum = numSum.reduce((add, td) => add + parseFloat(td.innerHTML), 0);
numSum1.forEach(td => td.innerHTML = sum);
}
}
// 删除表格行
function delRow() {
for (let i = 0; i < delbtns.length; i++) {
delbtns[i].onclick = function () {
let rowindex = this.parentNode.parentNode.rowIndex; // 获取当前行
let delindex = rowindex - 1;
stutable.deleteRow(rowindex);
/* 映射数据表 */
let ediId = this.parentNode.parentNode.children[0].innerHTML; // 获取当前单元格的id
for (item of grade_data) {
for (let i = 0; i < item.length; i++) {
if (item[i].id == ediId) {
item.splice(delindex, 1)
console.log("当前数据已删除");
}
}
}
}
}
}
// 根据给出的代码,totalScoreBar() 函数只会在表格生成时调用一次,
// 它添加的每个含有 "allgrade" 属性的单元格都是初始值为 0 的。
// 因此,总分不会一直累加,而是在需要计算总分时通过遍历整个表格来动态计算。
// 如果需要避免累加的情况发生,可以在计算总分后将所有含有 "allgrade" 属性的单元格重置为初始值 0。
复制代码
2.css部分
* {
margin: 0;
padding: 0;
--border: 5px solid rgb(248, 248, 248);
}
#tableBox {
position: relative;
user-select: none;
}
.table {
margin: 0 auto;
border-spacing: 0;
border-collapse: collapse;
text-align: center;
margin-top: 47px;
z-index: 1;
}
.wrong {
display: none;
top: 95px;
width: 300px;
position: absolute;
margin-left: -100px;
left: 47%;
text-align: center;
padding: 15px 18px;
background: rgb(255, 251, 34);
border-radius: 5px;
font-size: 20px;
font-weight: 600;
transition: top 1s;
z-index: -1;
}
.wrong1 {
display: none;
top: 95px;
width: 300px;
position: absolute;
margin-left: -100px;
left: 47%;
text-align: center;
padding: 15px 18px;
background: rgb(255, 70, 70);
border-radius: 5px;
font-size: 20px;
font-weight: 600;
transition: top 1s;
z-index: -1;
}
.wrong2 {
display: none;
top: 95px;
width: 300px;
position: absolute;
margin-left: -100px;
left: 47%;
text-align: center;
padding: 15px 18px;
color: #fff;
background: rgb(160, 19, 19);
border-radius: 5px;
font-size: 20px;
font-weight: 600;
transition: top 1s;
z-index: -1;
}
.movedown {
top: 80px;
animation: moveup 3s infinite;
}
@keyframes moveup {
0% {
top: 95px
}
50% {
top: 48px
}
100% {
top: 48px
}
}
.title {
text-align: center;
padding: 8px 0;
}
tr,
td,
th {
border: var(--border)
}
th {
font-weight: 600;
text-align: center;
background-color: rgb(124, 198, 255);
}
td>input {
width: 120px;
height: 45px;
border: none;
font-size: 20px;
}
.table>thead>tr>th,
.table>tbody>tr>td {
width: 130px;
height: 45px;
font-size: 16px;
}
.table>thead>tr {
font-family: '微软雅黑';
font-weight: bolder ;
}
button {
color: #fff;
width: 130px;
height: 50px;
background-color: #d9534f;
border-color: #d43f3a;
user-select: none;
border: 1px solid transparent;
border-radius: 4px;
cursor: pointer;
padding: 10px 12px;
font-size: 14px;
text-align: center;
}
复制代码
3.总结
对于当前表格,增加操作尚未实现,实时更新JSON数据没有实现。欢迎大家提供更加优良的思路,期待大家给出问题,指出不足。
作者:菜菜的小霞
链接:https://juejin.cn/post/7231825990992298042
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。