JS 可编辑表格的实现(进阶)

发布于 2021-05-09  1,152 次阅读


1、前言

在普通的可编辑表格的基础上,改进可编辑表格。数据来自外部的json(模拟服务端),通过json数据生成可编辑表格。根据实际情况,表格没有新增数据功能。表格的可编辑列,计算的列,每列的数据大小,以及是否删除都可进行配置,在修改单元格内容和删除行数据都会映射到相应数据集中。

实现效果:

在这里插入图片描述

2、设计思路与方法

  • 现将基本样式写好,将所有的数据写在json文件里。定义两个空数组,通过Ajax将表格的标题和内容成绩读取并分别保存在数组title_data和grade_data里。
  • 定义getHTML方法,先取出每一行的数据,对于表格的表头,通过Object.values()直接取出数据,并通过模板字符串直接渲染到页面中。对于表格内容,通过Object.keys()获取每行数据的键名数组,先定义一个temp_grade并赋值<tr>,通过for in 获取下标并取出每一个键名,判断当前索引值是否等于键名数组的长度减一,若满足条件,则temp_grade加等于td并拼接上</tr>,反之,直接等于td。然后把temp_grade插入到HTML中。在最下方调用totalScoreBar(),actionBar(),setAllScore([2, 3, 4]),setEditable([2, 3, 4]),updateScore()方法,初始化整个表格。
  • 定义totalScoreBar方法用于在thead里添加总分这一列,并通过setAttribute给它设置rname属性值为allgrade。
  • 定义actionBar方法用于在thead里添加操作栏这一列。并在每个tr里添加button标签。
  • 定义flag开关,设置是否调用actionBar方法
  • 定义setEditable方法,用于设置哪些可编辑。传入一个数组arr表示可编辑的单元格列。通过for循环先获取表格行和列,再通过arr.forEach()和setAttribute方法给arr元素表示的那一列单元格设置name为editable。
  • 定义setAllScore方法,用于设置哪些可以计算分数。传入一个数组arr表示可计算的单元格列。通过for循环先获取表格行和列,再通过arr.forEach()和setAttribute方法给arr元素表示的那一列单元格设置class为grade。
  • 定义一个updateScore方法,用来计算分数。通过class取出每行的分数的值,再取出总成绩的值。每个人总成绩等于每行分数相加。
  • 定义一个addAnimate方法,表示单元格输入错误时的动画提示。
  • 定义一个delRow方法,用于表示删除单元格行操作。通过rowindex获取当前行,再定义一个delindex变量,把rowindex-1的值赋给它。然后通过deleteRow(rowindex)删除行。然后取出当前单元格所在行的id为ediId,遍历原始数据。然后取出的当前单元格对应的数据的下标,然后判断ediId是否等于原数组的id,若等于,则删除当前行的数据。
  • 定义setCellCilck方法,用于给class为grade的单元格添加点击事件。在里面定义一个scorearr数组,用于存放各科目的满分成绩。并将该数组传入到updateCell里面。
  • 定义updateCell方法,用于更新单元格的内容。传入ele和定义好的scorearr,表示当前点击的单元格和各个科目满分的数组。先取出当前ele的cellIndex,获取对应的满分数据。然后获取旧的单元格数据并保存为oldhtml。然后创建一个input标签,并传入oldhtml。在input标签的聚焦事件中判断输入的input值是否合法,若不合法,则调用addAnimate方法,弹出error标签的错误提示信息,若合法,则保存当前的值。然后取出当前单元格所在行的id为ediId,遍历原始数据并通过Object.keys()取出键数组。然后取出的当前单元格所在行的列数,然后判断ediId是否等于原数组的id,若等于,则将新的值赋给它,从而实现原数组的修改。

3、源码展示

table.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;
}

table.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>

table.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 = false; // 删除栏
    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);
                    }
                }
            }
        }
    }
}

data2.json

{
    "t_title": [
        {
            "title_name": "学号"
        },
        {
            "title_name": "姓名"
        },
        {
            "title_name": "语文"
        },
        {
            "title_name": "数学"
        },
        {
            "title_name": "英语"
        },
        {
            "title_name": "政治"
        },
        {
            "title_name": "历史"
        },
        {
            "title_name": "地理"
        },
        {
            "title_name": "生物"
        }
    ],
    "t_grades": [
        {
            "id": 1101,
            "Student_name": "小王",
            "Chinese_score": 98,
            "Math_score": 80,
            "English_score": 91,
            "Political_score":92,
            "History_score":86,
            "Geographic_score":88,
            "Biological_score":96
        },
        {
            "id": 1102,
            "Student_name": "小曾",
            "Chinese_score": 80,
            "Math_score": 80,
            "English_score": 80,
            "Political_score":93,
            "History_score":95,
            "Geographic_score":86,
            "Biological_score":89
        },
        {
            "id": 1103,
            "Student_name": "小赵",
            "Chinese_score": 90,
            "Math_score": 80,
            "English_score": 90,
            "Political_score":99,
            "History_score":91,
            "Geographic_score":98,
            "Biological_score":95
        },
        {
            "id": 1104,
            "Student_name": "小丽",
            "Chinese_score": 90,
            "Math_score": 90,
            "English_score": 90,
            "Political_score":88,
            "History_score":78,
            "Geographic_score":89,
            "Biological_score":100
        },
        {
            "id": 1105,
            "Student_name": "小明",
            "Chinese_score": 90,
            "Math_score": 85,
            "English_score": 85,
            "Political_score":88,
            "History_score":89,
            "Geographic_score":100,
            "Biological_score":99
        },
        {
            "id": 1106,
            "Student_name": "小红",
            "Chinese_score": 98,
            "Math_score": 82,
            "English_score": 96,
            "Political_score":92,
            "History_score":92,
            "Geographic_score":90,
            "Biological_score":92
        }
    ]
}

可编辑表格的实现就到这里了,若有问题欢迎留言。


活的像诗一样