题目描述
此题算是以前的前端练习的综合训练了。刚看到题的时候一脸懵逼,不懂得从哪里开始下手。实际上这就是前后端分离典型的题,以前我练习的前端题的数据都是写死在HTML里的,不会产生动态的变化。确实是,前端是负责数据的展示,而数据是复杂多变的,是需要查询的,而这些工作就需要后端来做了。前端负责展示(HTML),渲染(CSS),控制更改(JavaScript)数据,而后端就负责这些数据从哪来的工作,两者各司其职,达到分离的原则。而json数据就是桥梁,后端查询到数据形成json,前端对json数据进行解析展示在页面中。
题目要求:
- 要根据json数据形成相应的表格,同时随着json数据的改变表格也会随之改变,例如,新增加的物品形成新的一列,数量和金额需要计算明细。
- 输入数据这里的功能,因为我现在还不会模态框,所以暂时用输入框代替实现。
- 需要为+号绑定展开明细表格的事件。
题解过程
描述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>" +
" <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>" +
" <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叫我。