一、设计思路:
在组件化的可编辑表格中,我们需要考虑以下几点:
- 如何动态地创建、删除和修改表格;
- 如何实现单元格编辑和保存;
- 如何处理表格数据的验证和提交。
根据以上需求,我们可以采用如下的设计思路:
- 首先使用JS动态地创建表格,可以通过HTML结构或者JS DOM操作来实现;
- 然后为表格的单元格添加点击事件,当单元格被点击时,动态地生成一个可编辑的输入框,并将该输入框显示在单元格内;
- 当用户完成单元格内容的编辑后,可以通过确认按钮或者回车键来保存编辑结果;
- 我们需要对编辑结果进行验证,例如检查用户输入的数据是否符合规定,如果有错误则需要提示用户并禁止提交;
- 如果数据验证通过,可以将表单数据提交到后台服务器进行保存。
二、实现方法:
-
动态创建表格
我们可以通过以下JS代码来创建一个简单的表格:
let stutable = document.getElementsByClassName("table")[0];
-
单元格编辑
为了实现单元格的编辑功能,我们需要为每个单元格添加一个点击事件,当用户点击单元格时,我们需要动态地创建一个可编辑的输入框,并将其插入到单元格中:
var editcell = document.getElementsByName("editable"); // 可编辑的单元格
var grades = document.getElementsByClassName("grade"); // 需要计算的单元格
var thetips = document.getElementsByClassName("err")[0];
var alltr = document.getElementsByTagName("tr"); // 获取HTML中所有的tr标签
// 设置哪些单元格可编辑
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) {
stcell[item].setAttribute("name", "editable");
});
}
setCellCilck();
}
-
数据验证和提交
对于数据验证和提交功能,我们可以在表格外部添加一个按钮或者表单,当用户点击提交按钮时,我们可以遍历所有单元格的值,并将其打包成JSON格式的数据进行提交:
// 更新单元格内容
function updateCell(ele, scorearr) {
let scoreMax = scorearr[ele.cellIndex - 2];
scoreMax = scoreMax || 100;
console.log("当前科目的满分是:" + scoreMax);
if (document.getElementsByClassName("active-input").length == 0) {
var oldhtml = ele.innerHTML;
ele.innerHTML = "";
var newInput = document.createElement("input");
newInput.setAttribute("class", "active-input");
newInput.value = oldhtml;
newInput.onblur = function () {
this.value = parseFloat(this.value);
if (this.value < 0 || this.value > scoreMax) {
console.log("err");
addAnimate();
thetips.style.display = "block";
return;
} else {
thetips.style.display = "none";
ele.innerHTML = this.value == oldhtml ? oldhtml : this.value;
/* 映射数据表 */
// 取出当前单元格数据
let ediId = ele.parentNode.children[0].innerHTML; // 获取当前修改的单元格的id
// console.log(ediId);
for (item of grade_data) {
for (let i = 0; i < item.length; i++) {
// 取出数据集的所有键名
let gradekey = Object.keys(item[i]);
// console.log(item[i].id); // 取出数据集的id
// console.log(ele.cellIndex); // 输出当前单元格所在行的列数
if (item[i].id == ediId) {
item[i][gradekey[ele.cellIndex]] = parseFloat(this.value);
}
}
}
console.log("修改后的数据是:", grade_data);
updateScore();
}
};
newInput.select();
ele.appendChild(newInput);
newInput.focus();
} else {
return;
}
}
三、核心代码
html代码:
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>table</title>
</head>
<link rel="stylesheet" href="table.css" />
<body>
<div id="tableBox">
<h2 class="title">可编辑表格</h2>
<div class="err">成绩输入有误,请重新输入!</div>
<table class="table">
<thead>
<tr></tr>
</thead>
<tbody></tbody>
</table>
</div>
</body>
<script src="table.js"></script>
</html>
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 thetips = document.getElementsByClassName("err")[0];
var alltr = document.getElementsByTagName("tr"); // 获取HTML中所有的tr标签
var flag; // 设置是否显示删除栏
// 读取本地json数据
let ajax = new XMLHttpRequest();
ajax.open("get", "data2.json");
ajax.send();
ajax.onreadystatechange = function () {
if (ajax.readyState == 4 && ajax.status == 200) {
let alldata = JSON.parse(ajax.responseText);
title_data.push(alldata.t_title); // 学生信息栏数据
grade_data.push(alldata.t_grades); // 学生成绩栏数据
getHtml(); // 生成HTML
}
};
// 渲染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, 6, 7, 8]); // 设置需要计算总分的学科
setEditable([2, 3, 4, 5, 6, 7, 8]); // 设置可编辑单元格
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 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) {
stcell[item].setAttribute("name", "editable");
});
}
setCellCilck();
}
// 设置可计算分数的表格列
function setAllScore(arr) {
// arr 表示需要计算总分的单元格
var strow = stutable.rows.length; // 获取表格行数
for (let i = 1; i < strow; i++) {
let stcell = stutable.rows[i].cells; // 获取表格列数
arr.forEach(function (item) {
stcell[item].setAttribute("class", "grade");
});
}
}
// 给单元格添加点击事件
function setCellCilck() {
let scorearr = [150, 150, 150, 100, 100, 100, 100]; // 设计单科成绩的满分
for (let i = 0; i < editcell.length; i++) {
editcell[i].onclick = function () {
updateCell(this, scorearr);
delRow();
};
}
}
// 更新单元格内容
function updateCell(ele, scorearr) {
let scoreMax = scorearr[ele.cellIndex - 2];
scoreMax = scoreMax || 100;
console.log("当前科目的满分是:" + scoreMax);
if (document.getElementsByClassName("active-input").length == 0) {
var oldhtml = ele.innerHTML;
ele.innerHTML = "";
var newInput = document.createElement("input");
newInput.setAttribute("class", "active-input");
newInput.value = oldhtml;
newInput.onblur = function () {
this.value = parseFloat(this.value);
if (this.value < 0 || this.value > scoreMax) {
console.log("err");
addAnimate();
thetips.style.display = "block";
return;
} else {
thetips.style.display = "none";
ele.innerHTML = this.value == oldhtml ? oldhtml : this.value;
/* 映射数据表 */
// 取出当前单元格数据
let ediId = ele.parentNode.children[0].innerHTML; // 获取当前修改的单元格的id
// console.log(ediId);
for (item of grade_data) {
for (let i = 0; i < item.length; i++) {
// 取出数据集的所有键名
let gradekey = Object.keys(item[i]);
// console.log(item[i].id); // 取出数据集的id
// console.log(ele.cellIndex); // 输出当前单元格所在行的列数
if (item[i].id == ediId) {
item[i][gradekey[ele.cellIndex]] = parseFloat(this.value);
}
}
}
console.log("修改后的数据是:", grade_data);
updateScore();
}
};
newInput.select();
ele.appendChild(newInput);
newInput.focus();
} else {
return;
}
}
// 添加动画
function addAnimate() {
thetips.className = "err movedown";
}
// 更新总成绩
function updateScore() {
// console.log(grades);
for (let n = 1; n < alltr.length; n++) {
var grade01 =
grades[n].parentNode.parentNode.children[n - 1].querySelectorAll(
"td[class]"
);
var grade02 =
grades[n].parentNode.parentNode.children[n - 1].querySelectorAll(
"td[rname]"
);
var sum = 0;
for (let i = 0; i < grade01.length; i++) {
sum += parseFloat(grade01[i].innerHTML);
for (let j = 0; j < grade02.length; j++) {
grade02[j].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("删除后的数据是:", grade_data);
}
}
}
};
}
}
css代码:
* {
margin: 0;
padding: 0;
--border: 2px solid rgba(121, 121, 121, 1);
}
#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;
}
.err {
display: none;
top: 95px;
width: 160px;
position: absolute;
margin-left: -100px;
left: 50%;
text-align: center;
padding: 15px 18px;
background: orange;
border-radius: 5px;
font-size: 13px;
font-weight: 600;
transition: top 1s;
z-index: -1;
}
.movedown {
top: 95px;
animation: movedown 3s;
}
@keyframes movedown {
0% {
top: 95px;
}
50% {
top: 48px;
}
100% {
top: 95px;
}
}
.title {
text-align: center;
padding: 8px 0;
}
tr,
td,
th {
border: var(--border);
}
th {
font-weight: 600;
text-align: center;
background-color: rgba(204, 204, 204, 1);
}
td > input {
width: 100px;
height: 45px;
border: none;
font-size: 16px;
}
.table > thead > tr > th,
.table > tbody > tr > td {
width: 100px;
height: 45px;
font-size: 16px;
}
.table > thead > tr {
font-family: "宋体";
}
button {
color: #fff;
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;
}
四、注解
- 首先通过JS动态地创建表格,并根据数据创建表头和表体;
- 然后遍历表格中的每个单元格,为其添加点击事件;
- 当单元格被点击时,动态地生成一个可编辑的输入框,并将输入框显示在单元格内;
- 当用户完成单元格内容的编辑后,可以通过回车键来保存编辑结果;
- 最后,添加一个提交按钮,当用户点击提交按钮时,遍历表格中的所有单元格的值,并将其打包成JSON格式的数据进行提交。