【前端练习】根据JSON数据页面动态形成表格

题目描述

  此题算是以前的前端练习的综合训练了。刚看到题的时候一脸懵逼,不懂得从哪里开始下手。实际上这就是前后端分离典型的题,以前我练习的前端题的数据都是写死在HTML里的,不会产生动态的变化。确实是,前端是负责数据的展示,而数据是复杂多变的,是需要查询的,而这些工作就需要后端来做了。前端负责展示(HTML),渲染(CSS),控制更改(JavaScript)数据,而后端就负责这些数据从哪来的工作,两者各司其职,达到分离的原则。而json数据就是桥梁,后端查询到数据形成json,前端对json数据进行解析展示在页面中。

题目要求:

  1.  要根据json数据形成相应的表格,同时随着json数据的改变表格也会随之改变,例如,新增加的物品形成新的一列,数量和金额需要计算明细。
  2.  输入数据这里的功能,因为我现在还不会模态框,所以暂时用输入框代替实现。
  3.  需要为+号绑定展开明细表格的事件。

在这里插入图片描述

题解过程

描述json数据

  经过上述分析,我们知道最重要的就是json数据,因此从它开始入手。先不管从无到有如何形成,我们先给几个具体的例子,看看如何描述。

<script>
    var datajson = [
        {
            typeName:"cloth",
            desc:"this is cloth",
            items:[
                {
                    gname:"jacket",
                    qty:"10",
                    price:"300",
                    desc: ""
                },
                {
                    gname: "pants",
                    qty: "90",
                    price: "100",
                    desc: ""
                }
            ]
        },
        {
            typeName:"fruit",
            desc:"this is fruit",
            items:[
                {
                    gname:"watermelon",
                    qty:"10",
                    price:"20",
                    desc: ""
                },
                {
                    gname: "orange",
                    qty: "9",
                    price: "10",
                    desc: ""
                }
            ]
        },
    ]
</script>

声明datajson为全局变量,因为以后它会随着输入数据而改变。

基本已确定的HTML

然后写相应的、已经可以确定的(表头和输入框)基本HTML。

<body>

<div>
    <table border="1" cellpadding="0" cellspacing="0" style="width: 100%;" id="table"><!--等会要在这里更新数据,定好id-->
        <tr style="background-color: gray">
            <td>物品</td>
            <td>数量</td>
            <td>金额</td>
            <td>备注</td>
        </tr>

    </table>
</div>

<div>
    <label>物品名称:<input type="text" name="goodsName"></label>
    <label>备注:<input type="text" name="goodsDesc"></label><br/>
    <label>明细:<input type="text" name="detailName"></label>
    <label>数量:<input type="text" name="detailCount"></label>
    <label>单价:<input type="text" name="detailPrice"></label>
    <label>备注:<input type="text" name="detailDesc"></label><br/>
    <input type="button" value="提交" id="submitButton">
</div>

</body>

加载读取json数据

<script>
    var reloadJson = function () {
        //let table = $("#table")[0]; 这里不能取这个,这个是具体table元素,没有append那些方法
        let masterTable = $("#table");  //要取这个
        for(let i = 0; i < datajson.length; i++){   //遍历datajson
            let jsonObj = datajson[i];
            let goodsName = jsonObj.typeName;
            let goodsDesc = jsonObj.desc;
            if(document.getElementById(goodsName) == null) {    //还没有相关的物品table,去建立
                let masterTableNeedBuildHtml = "<tr><td><span id='"+ goodsName +"' style='cursor: pointer'>-</span>" +
                    "&emsp;<span>"+ goodsName +"</span></td><td></td><td></td><td> "+ goodsDesc +"</td></tr><tr style='display: " +
                    "table-row'><td></td><td colspan='3' style='width: 75%'><div><table border='1' style='position: relative;" +
                    "width: 500px;height: 100px;left: 30%;margin-top: 25px' id='"+ goodsName + "DependenceTable" +"'><tr>" +
                    "<td>明细</td><td>数量</td><td>单价</td><td>备注</td></tr></table></div></td></tr>";
                //这里构建html一定要注意!!!id不要加空格!!!否则你后期找的时候不容要找。构建html语句一定看着哪里空格//TODO 1.Sql语句类比
                //可以将构建好的html输出出来看一下,并且在新的html显示下,看能否达到效果
                masterTable.append(masterTableNeedBuildHtml);
                //注意!这里是给主表位置加。一旦从dataJson得到新的物品数据, //TODO 2.主表添加问题
                // 能建立的表只能为主表加一行,并且建立该物品明细表的表头,其他什么都建立不了
            }
            //已经有了相关table或者建立好了相关table
            let dependenceTableId = "#" + goodsName + "DependenceTable";
            let dependenceTable = $(dependenceTableId);     //根据副表id查找副表控件,副表id在上面的html语句构造的
            let goodsItems = jsonObj.items;
            let partHtml = "";
            for(let j = 0; j < goodsItems.length; j++) {
                let detailObj =  goodsItems[j];
                let gname = detailObj.gname;
                let detailId = goodsName + "_"  + gname;    //设置明细数据的id,方便以后知道有没有添加在表中
                if(document.getElementById(detailId) != null) {    //如果已经加入表中了,就不用加入了
                    continue;
                }
                let qty = detailObj.qty;
                let price = detailObj.price;
                let desc = detailObj.desc;
                partHtml += "<tr id='"+ detailId +"'><td>"+ gname +"</td><td>"+ qty +"</td><td>"+ price +"</td><td>"+ desc +"</td></tr>";
                //又是构建html语句,id不要加空格!!!
            }
            dependenceTable.append(partHtml);   //TODO 2.此时副表才可以应运而生,并且加到合适的位置,而不是加在主表上
        }
    }
</script>

可以加

window.onload = function () {
        reloadJson();
    }

看是否在主页面可以将已经写好的json数据在页面显示。

写这段代码其实挺费时间的,期间遇到问题就需要一步步输出,一步步变量跟踪才能发现到问的所在。

看代码就可以明白,这里代码就是根据变量构建HTML语句,然后添加到相应位置。让以前写死的数据变为变量,接着修改HTML语句,再加到相应位置就能更改页面的效果了。其中遇到的问题总结如下。

问题一:构建HTML语句

  我以前写过手写模拟Hibernate源代码里构建SQL语句的时候,是必须并且一定语句之间加空格的,不加SQL语句就会出现错误导致查询失败。而在这里构建HTML语句时,一定要注意空格的增加。我有个编程习惯,为了美观,同时也受SQL语句的影响,总喜欢随手加空格分开两端,这就导致了在这个构造HTML语句问题上id属性前也有个空格,所以查找的时候忘了这个空格会查找不到。

  对于这种问题,最有效的解决方法就是,构造好了相关语句,先别急着往下写。在控制台输出下并仔细检查格式。再在新页面下显示下,看能否达到预期目标。

问题二:主表和副表的区别

  就是在这个问题上花的时间多。以前的做法就是构建HTML语句,然后一股脑的往table后面续(append)构建好的HTML语句。这样做的结果就是没有达到预期的界面效果。在这里变量跟踪,逐步输出试了很久都没发现问题,所以花了很多时间。然后,静心下来,自己分析下,逻辑哪里出现了错误,结合在页面上进行调试,终于找到了问题所在,添加的位置不对。

  我们看回以前写的前端题静态数据的HTML文件。

在这里插入图片描述
  当查询到datajson里面的一个物品信息后,只能在主表追加该table层级里的控件元素。记录副表的位置,再深入查询明细数据时,需要追加到副表层级后面去。主表和副表两个层级不一样,所以添加的位置要看好是哪个。

上述问题的解决方法也让我明白一个道理,由静到动。前端页面的设计,可能一开始你不知道如何动态的添加数据,那么首先就给几个静态数据,观察其异同,并从中找到规律,然后就可以设计动态功能了。也就是说,先给几个静态数据显示出来静态数据页面,然后将静态数据抽离出来,变为变量,就可以实现动态效果。也就是动态数据页面以静态数据页面为基础,两边对照着看。

绑定事件

<script>
    var reloadJson = function () {
        //前面略
        
        //然后是给每个+按钮添加展开和收起事件
        let container = $("#"+goodsName);
        container.unbind("click").click({//先解绑,再重新绑定
            spanElement : document.getElementById(goodsName),
        },showElement);
    }
    
    var showElement = function (event) {
        let tr = event.data.spanElement.parentElement.parentElement;
        let nextTr = tr.nextElementSibling;
        if (nextTr.style.display === "none") {
            nextTr.style.display = "table-row";
            tr.cells[0].firstChild.textContent = "-";
        }else{
            nextTr.style.display = "none";
            tr.cells[0].firstChild.textContent = "+";
        }
    }
</script>

参考上一个前端题

更新数据

container.parent().siblings()[0].textContent = computeCount(jsonObj);
            container.parent().siblings()[1].textContent = computeValue(jsonObj);
            //这里出现Duplicated jQuery selector
            //其实就是重复使用了一个id选择器,JQuery建议去用一个变量去存储这个选择器,因为每次使用都会重新读取而不会缓存

var computeCount = function (jsonObj) {
        let typeName = jsonObj.typeName;
        let times = jsonObj.items.length;
        let result = 0;
        for(let i = 0; i < times; i++) {
            let num = document.getElementById(typeName)
                .parentElement.parentElement.nextElementSibling
                .lastChild.lastChild.lastChild.lastChild.childNodes[i + 1].childNodes[1].innerHTML;
            //你问我这一长串怎么来的?预览页面,控制台一步步算出来的呗。谁还能是神人,看一眼就知道位置关系。
            result += parseInt(num);
        }
        return result;
    }

    var computeValue = function (jsonObj) {
        let typeName = jsonObj.typeName;
        let times = jsonObj.items.length;
        let result = 0;
        for(let i = 0; i < times; i++) {
            let count = document.getElementById(typeName)
                .parentElement.parentElement.nextElementSibling
                .lastChild.lastChild.lastChild.lastChild.childNodes[i + 1].childNodes[1].innerHTML;
            let singlePrice = document.getElementById(typeName)
                .parentElement.parentElement.nextElementSibling
                .lastChild.lastChild.lastChild.lastChild.childNodes[i + 1].childNodes[2].innerHTML;

            result += parseInt(count) * parseInt(singlePrice);
        }
        return result;
    }

  其中的根据一个控件节点,寻找另一个控件节点。还是那句话,我也不知道它们之间是什么关系,也不可能一步写出来。只知道他们在一个nodelist下,然后找相邻元素用siblings。在控制台输出,观察,一边修改一边写。

提交数据按钮事件

var submitPress = function () {
        let goosName = document.getElementsByName("goodsName")[0].value;
        let goodsDesc = document.getElementsByName("goodsDesc")[0].value;
        let detailName = document.getElementsByName("detailName")[0].value;
        let detailCount = document.getElementsByName("detailCount")[0].value;
        let detailPrice = document.getElementsByName("detailPrice")[0].value;
        let detailDesc = document.getElementsByName("detailDesc")[0].value;
        //获得输入框的值

        let exist = -1; //TODO 2.1 这个是高级flag,没找到就是-1,找到了就返回要插入的那个下标值。一举两得!
        for(let i = 0; i < datajson.length; i++) {
            if (datajson[i]["typeName"] === goosName){
                exist = i;
            }
        }
        let itemDemo = {
            gname : detailName,
            qty : detailCount,
            price : detailPrice,
            desc : detailDesc,
        }
        let jsonOnj;

        if(exist === -1) {
            //没找到是哪个物品,说明是个新物品,更新json
            jsonOnj = {
                typeName : goosName,
                desc : goodsDesc,
                items : [itemDemo],
            }
            datajson.push(jsonOnj);
        } else {
            //找到了是哪个物品,为那个物品新加item
            jsonOnj = datajson[exist];
            jsonOnj.desc = goodsDesc;

            let insertFlag = true;  //TODO 2.2 又是一个flag的应用,去除重复的明细数据
            for (let i = 0; i < datajson[exist].items.length; i++) {
                if(datajson[exist].items[i].gname === itemDemo.gname) {
                    insertFlag = false;
                }
            }
            if(insertFlag) {
                datajson[exist].items.push(itemDemo);
            }
        }
        reloadJson();   //重新加载下datajson,更改下页面
        //清空输入框
        document.getElementsByName("goodsName")[0].value = "";
        document.getElementsByName("goodsDesc")[0].value = "";
        document.getElementsByName("detailName")[0].value = "";
        document.getElementsByName("detailCount")[0].value = "";
        document.getElementsByName("detailPrice")[0].value = "";
        document.getElementsByName("detailDesc")[0].value = "";

    }

    $("#submitButton").click(submitPress);

按钮点击事件十分简单,写上些逻辑代码即可,期间遇到的两个flag(标志位)问题也强调下。

问题一:存在标志位

  exist这个我本来用的是boolean值true和false,再多写个方法,查到了就立刻返回这个json对象。但在这里,经过高人指点,学到一种高级flag(我认为,这方法太巧了)。令其初值为-1,找到了则赋值为找到的下标值,遍历完了还没找到它就还是-1。把原来几十行的代码压缩到数行以内,还不需要额外写方法。这个高级flag真是太妙了,逻辑也清晰。学到了,学到了。

问题二:是否插入flag

  这个flag应用于明细数据。思考这么一个问题,用户输入已经存在的明细数据(可以牵扯到在原有的基础上进行加,需要额外简单的逻辑代码),我们此刻认为是无效输入,并且不更改明细数据,就不会造成页面的错乱了。

题解结果

完整代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>JsonTableDemo</title>
</head>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
<body>

<div>
    <table border="1" cellpadding="0" cellspacing="0" style="width: 100%;" id="table"><!--等会要在这里更新数据,定好id-->
        <tr style="background-color: gray">
            <td>物品</td>
            <td>数量</td>
            <td>金额</td>
            <td>备注</td>
        </tr>

    </table>
</div>

<div>
    <label>物品名称:<input type="text" name="goodsName"></label>
    <label>备注:<input type="text" name="goodsDesc"></label><br/>
    <label>明细:<input type="text" name="detailName"></label>
    <label>数量:<input type="text" name="detailCount"></label>
    <label>单价:<input type="text" name="detailPrice"></label>
    <label>备注:<input type="text" name="detailDesc"></label><br/>
    <input type="button" value="提交" id="submitButton">
</div>

</body>
<script>

    window.onload = function () {
        reloadJson();
    }

    var datajson = [
        {
            typeName:"cloth",
            desc:"this is cloth",
            items:[
                {
                    gname:"jacket",
                    qty:"10",
                    price:"300",
                    desc: ""
                },
                {
                    gname: "pants",
                    qty: "90",
                    price: "100",
                    desc: ""
                }
            ]
        },
        {
            typeName:"fruit",
            desc:"this is fruit",
            items:[
                {
                    gname:"watermelon",
                    qty:"10",
                    price:"20",
                    desc: ""
                },
                {
                    gname: "orange",
                    qty: "9",
                    price: "10",
                    desc: ""
                }
            ]
        },
    ]

    var reloadJson = function () {
        let masterTable = $("#table");
        for(let i = 0; i < datajson.length; i++){
            let jsonObj = datajson[i];
            let goodsName = jsonObj.typeName;
            let goodsDesc = jsonObj.desc;
            if(document.getElementById(goodsName) == null) {
                let masterTableNeedBuildHtml = "<tr><td><span id='"+ goodsName +"' style='cursor: pointer'>-</span>" +
                    "&emsp;<span>"+ goodsName +"</span></td><td></td><td></td><td> "+ goodsDesc +"</td></tr><tr style='display: " +
                    "table-row'><td></td><td colspan='3' style='width: 75%'><div><table border='1' style='position: relative;" +
                    "width: 500px;height: 100px;left: 30%;margin-top: 25px' id='"+ goodsName + "DependenceTable" +"'><tr>" +
                    "<td>明细</td><td>数量</td><td>单价</td><td>备注</td></tr></table></div></td></tr>";
                masterTable.append(masterTableNeedBuildHtml);
            }
            let dependenceTableId = "#" + goodsName + "DependenceTable";
            let dependenceTable = $(dependenceTableId);
            let goodsItems = jsonObj.items;
            let partHtml = "";
            for(let j = 0; j < goodsItems.length; j++) {
                let detailObj =  goodsItems[j];
                let gname = detailObj.gname;
                let detailId = goodsName + "_"  + gname;
                if(document.getElementById(detailId) != null) {
                    continue;
                }
                let qty = detailObj.qty;
                let price = detailObj.price;
                let desc = detailObj.desc;
                partHtml += "<tr id='"+ detailId +"'><td>"+ gname +"</td><td>"+ qty +"</td><td>"+ price +"</td><td>"+ desc +"</td></tr>";
            }
            dependenceTable.append(partHtml);
            let container = $("#"+goodsName);
            container.unbind("click").click({
                spanElement : document.getElementById(goodsName),
            },showElement);
            container.parent().siblings()[0].textContent = computeCount(jsonObj);
            container.parent().siblings()[1].textContent = computeValue(jsonObj);
        }
    }

    var showElement = function (event) {
        let tr = event.data.spanElement.parentElement.parentElement;
        let nextTr = tr.nextElementSibling;
        if (nextTr.style.display === "none") {
            nextTr.style.display = "table-row";
            tr.cells[0].firstChild.textContent = "-";
        }else{
            nextTr.style.display = "none";
            tr.cells[0].firstChild.textContent = "+";
        }
    }

    var computeCount = function (jsonObj) {
        let typeName = jsonObj.typeName;
        let times = jsonObj.items.length;
        let result = 0;
        for(let i = 0; i < times; i++) {
            let num = document.getElementById(typeName)
                .parentElement.parentElement.nextElementSibling
                .lastChild.lastChild.lastChild.lastChild.childNodes[i + 1].childNodes[1].innerHTML;
            result += parseInt(num);
        }
        return result;
    }

    var computeValue = function (jsonObj) {
        let typeName = jsonObj.typeName;
        let times = jsonObj.items.length;
        let result = 0;
        for(let i = 0; i < times; i++) {
            let count = document.getElementById(typeName)
                .parentElement.parentElement.nextElementSibling
                .lastChild.lastChild.lastChild.lastChild.childNodes[i + 1].childNodes[1].innerHTML;
            let singlePrice = document.getElementById(typeName)
                .parentElement.parentElement.nextElementSibling
                .lastChild.lastChild.lastChild.lastChild.childNodes[i + 1].childNodes[2].innerHTML;

            result += parseInt(count) * parseInt(singlePrice);
        }
        return result;
    }

    var submitPress = function () {
        let goosName = document.getElementsByName("goodsName")[0].value;
        let goodsDesc = document.getElementsByName("goodsDesc")[0].value;
        let detailName = document.getElementsByName("detailName")[0].value;
        let detailCount = document.getElementsByName("detailCount")[0].value;
        let detailPrice = document.getElementsByName("detailPrice")[0].value;
        let detailDesc = document.getElementsByName("detailDesc")[0].value;

        let exist = -1;
        for(let i = 0; i < datajson.length; i++) {
            if (datajson[i]["typeName"] === goosName){
                exist = i;
            }
        }
        let itemDemo = {
            gname : detailName,
            qty : detailCount,
            price : detailPrice,
            desc : detailDesc,
        }
        let jsonOnj;
        if(exist === -1) {
            jsonOnj = {
                typeName : goosName,
                desc : goodsDesc,
                items : [itemDemo],
            }
            datajson.push(jsonOnj);
        } else {
            jsonOnj = datajson[exist];
            jsonOnj.desc = goodsDesc;
            let insertFlag = true;
            for (let i = 0; i < datajson[exist].items.length; i++) {
                if(datajson[exist].items[i].gname === itemDemo.gname) {
                    insertFlag = false;
                }
            }
            if(insertFlag) {
                datajson[exist].items.push(itemDemo);
            }
        }
        reloadJson();
        document.getElementsByName("goodsName")[0].value = "";
        document.getElementsByName("goodsDesc")[0].value = "";
        document.getElementsByName("detailName")[0].value = "";
        document.getElementsByName("detailCount")[0].value = "";
        document.getElementsByName("detailPrice")[0].value = "";
        document.getElementsByName("detailDesc")[0].value = "";

    }

    $("#submitButton").click(submitPress);
</script>
</html>

  第一次写解题过程,可能会有没顾及的地方,希望大家多提建议。前端学习小白,水平有限,只能写出这样的代码了,有更好的方法评论,我会学习和采纳的。另外,请大家随便用这个试例子,试出BUG叫我。

  • 3
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值