纯原生js实现table表格的增删


Posted in Javascript onJanuary 05, 2017

公司实习生问我table的增删操作,用jQuery很简单的实现了。又问我不使用jQuery,只使用js如何实现。

面对这种情况,我的一贯做法是‘不理解,但是支持'。

jQuery用的多了,人也懒了,但还是用js实现了这一操作,觉得难点在于IE兼容。。。

只是想找代码看看的可以跳过分析过程,文章底部附有完整代码。

以下是coding过程:

HTML结构代码

一个基本的table结构,增加了一些简单的样式,三个按钮分别对应创建、清空,和一个预留。

<!DOCTYPE HTML> 
<html> 
 <head> 
 <title>table</title> 
 <meta charset='utf-8' /> 
 <style type="text/css"> 
 table.base{ 
 border-collapse:collapse; 
 text-align: center; 
 border: 1px solid black; 
 } 
 table, tr, td, th{ 
 border: 1px solid black; 
 } 
 </style> 
 </head> 
 <body> 
 <div id="main-content"> 
 <table id="main-table" class="base"> 
 <thead> 
  <tr> 
  <th colspan="3">This is a table for operations by javascript</th> 
  </tr> 
  <tr> 
  <th> 
  <input type="button" value="CREATE" id="cp_btn" onclick="createTr()" /> 
  </th> 
  <th> 
  <input type="button" value="CLEAR" id="cl_btn" onclick="clearTrs()" /> 
  </th> 
  <th> 
  <input type="button" value="GUESS" id="cl_btn"/> 
  </th> 
  </tr> 
 </thead> 
 <tbody> 
 </tbody> 
 </table> 
 </div> 
 </body> 
</html>

构造函数(伪构造函数)

考虑过,创建一个隐藏的tr,基于此tr执行创建操作。为了不破坏HTML整体结构,决定通过js生成tr对象并append到页面中。

为了在页面加载完成后,再执行dom操作,所以将<script>放在代码下端</body>之前。

基于table中的tbody进行增删操作,可以先声明此全局变量

var vTbody = document.getElementById('main-table').getElementsByTagName('tbody')[0]; 

创建对象,可以使用document.createElement方法。

以面向对象的方式进行编程,先写构造函数(其实并不是标准的构造函数格式),从最内部的元素开始。

td中可能会有text和button等表单元素,所以先创建一个 input 的构造函数function myInput(vId, vClass, vType, vValue, vParent){}

这里有一个兼容性问题,就是IE内核不支持setAttribute(class, value),需要使用setAttribute(className, value),所以为了解决兼容问题,可以通过

setAttribute(class, value) for FF、Chrome..

setAttribute(className, value) for IE

这里采用的是另一种方式 .className,代码如下:

function myInput(vId, vClass, vType, vValue, vParent) { 
 var vInput = document.createElement('input'); 
 if(vId) { 
 vInput.setAttribute('id', vId); 
 } 
 vInput.setAttribute('type', vType); 
 vInput.setAttribute('value', vValue); 
 vInput.className = vClass; 
 if(vParent) { 
 vParent.appendChild(vInput); 
 } 
}

然后是td对象和tr对象的构造函数,大同小异,代码如下

function myTd(vId, vClass, vChild, vParent) { 
 var vTd = document.createElement('td'); 
 if(vId){ 
 vTd.setAttribute('id', vId); 
 } 
 vTd.className = vClass; 
 if(vChild) { 
 vTd.appendChild(vChild); 
 } 
 if(vParent) { 
 vParent.appendChild(vTd); 
 } 
 return vTd; 
} 
function myTr(vId, vClass, vChild, vParent) { 
 var vTr = document.createElement('tr'); 
 if(vId){ 
 vTr.setAttribute('id', vId); 
 } 
 vTr.className = vClass; 
 if(vChild) { 
 vTr.appendChild(vChild); 
 } 
 if(vParent) { 
 vParent.appendChild(vTr); 
 } 
 return vTr; 
}

新建行方法createTr()

构造函数完成之后,完善createTr()方法。

预想的tr结构为 序号,文本框,操作按钮。

依次创建相关对象。序号列需要动态刷新,所以先设定class名称,通过方法执行排序操作。

function createTr() { 
 var vTr = new myTr(null, null, null, vTbody); 
 //序列td 
 var vTdSeq = new myTd(null, 'seq', null, vTr); 
 //文本框td 
 var vTdText = new myTd(null, null, null, vTr); 
 var vInputText = new myInput(null, 'td-inp-txt', 'text', '', vTdText); 
 //操作按钮td 
 var vTdBtn = new myTd(null, null, null, vTr); 
 var vInputBtnCp = new myInput(null, 'td-inp-btn-cp', 'button', 'COPY', vTdBtn); 
 var vInputBtnDel = new myInput(null, 'td-inp-btn-del', 'button', 'DELETE', vTdBtn); 
}

排序方法reSequence()

创建一个动态排序方法reSequence() ,有一个兼容性问题 innerText在火狐下无效果,所以使用innerHTML。代码如下

function reSequence() { 
 var vObj = vTbody.getElementsByClassName('seq'); 
 for (var i=0, len=vObj.length; i<len; i++) { 
 vObj[i].innerHTML = i+1; 
 } 
}

有一个兼容性问题,IE8及以下不支持getElementsByClassName()方法,网上找到了解决方案

if(!document.getElementsByClassName){ 
 document.getElementsByClassName = function(className, element){ 
 var children = (element || document).getElementsByTagName('*'); 
 var elements = new Array(); 
 for (var i=0; i<children.length; i++){ 
 var child = children[i]; 
 var classNames = child.className.split(' '); 
 for (var j=0; j<classNames.length; j++){ 
 if (classNames[j] == className){ 
  elements.push(child); 
  break; 
 } 
 } 
 } 
 return elements; 
 }; 
}

试图在Object或者是HTMLTableSectionElement的原型上增加此方法,如

HTMLTableSectionElement.prototype.getElementsByClassName = function(){} 

可惜没有实现。

修改后的代码为

function reSequence() { 
 var vObj = vTbody.getElementsByClassName == null?document.getElementsByClassName('seq', vTbody):vTbody.getElementsByClassName('seq'); 
 for (var i=0, len=vObj.length; i<len; i++) { 
 vObj[i].innerHTML = i+1; 
 } 
}

除了排序外,还需其他操作,所以我们创建一个init()方法,集中管理reSequence()这些方法,在createTr()方法的结尾调用init()方法。

清空行方法clearTrs()

移除/销毁某个dom对象,首先想到的是remove()方法,不幸的是,存在IE浏览器兼容问题,因此,采用了一个更简便的方式,对dom对象执行innerHTML="",代码如下

function clearTrs() { 
 vTbody.innerHTML = ''; 
}

IE8报错,'未知的运行错误'。查了以下,因为ie8的table.innerHTML是只读属性,妹的!再改:

function clearTrs() { 
 while(vTbody.rows.length >0) { 
 vTbody.deleteRow(); 
 } 
}

删除行方法addBtnDelsListener()

接下来,给DELETE按钮绑定删除当前行的方法。

为了解决兼容性问题,网上给出的方法是针对不同浏览器(IE、非IE)分别使用addEventListener、attachEvent方法,

我采用的是另一种解决方案:

obj.onclick = function(){};

匿名函数的方法体,吸取了上面clearTrs()方法的经验教训,直接采用deleteRow(index)方法。

有一点需要注意thisTr.rowIndex获取的行数,比当前行要大2,因为thead中还有两行。所以当前的索引数=thisTr.rowIndex-vThead.rows.length

代码如下:

function addBtnDelsListener() { 
 var vBtnDels = vTbody.getElementsByClassName == null?document.getElementsByClassName('td-inp-btn-del', vTbody):vTbody.getElementsByClassName('td-inp-btn-del'); 
 for (var i=0, len=vBtnDels.length; i<len; i++) { 
 vBtnDels[i].onclick = function() { 
 var vTr = this.parentElement.parentElement; 
 vTbody.deleteRow(vTr.rowIndex-vTbody.parentNode.getElementsByTagName('thead')[0].rows.length); 
 reSequence(); 
 }; 
 } 
}

执行完删除操作后,通过reSequence()方法重新排序。

同时将addBtnDelsListener()方法加入到init()方法中。

复制行方法addBtnCpsListener()

再来看一下COPY按钮,添加事件监听的方式同上。

如果innerHTML不是只读的话,可以createElement一个tr元素 然后newTr.innerHTML=thisTr.innerHTML,

为了兼容性,必须做些改变。

其实可以将复制看做是新建,唯一的不同在于新建行的文本输入框的内容要等同于被复制行。

这就简单了。我可以先调用createTr()方法,再将最后一个元素lastChild中的文本框的value等于被复制行。

思路有了,代码如下:

function addBtnCpsListener() { 
 var vBtnCps = vTbody.getElementsByClassName == null?document.getElementsByClassName('td-inp-btn-cp', vTbody):vTbody.getElementsByClassName('td-inp-btn-cp'); 
 for (var i=0, len=vBtnCps.length; i<len; i++) { 
 vBtnCps[i].onclick = function() { 
 createTr(); 
 var vNewTr = vTbody.lastChild; 
 var vTr = this.parentElement.parentElement; 
 vNewTr.getElementsByClassName == null?document.getElementsByClassName('td-inp-txt', vNewTr)[0].value = document.getElementsByClassName('td-inp-txt', vTr)[0].value:vNewTr.getElementsByClassName('td-inp-txt')[0].value = vTr.getElementsByClassName('td-inp-txt')[0].value; 
 } 
 } 
}

优化修改

进行一些优化修改工作:

var elements = new Array();

修改为:var elements = [];

原因:数组用[]更好

addBtnDelsListener方法中的vBtnDels[i].onclick = function() {

修改为:vBtnDels[i].onclick = delTr;

外部新创建一个函数

function delTr() { 
 var vTr = this.parentElement.parentElement; 
 vTbody.deleteRow(vTr.rowIndex-vTbody.parentNode.getElementsByTagName('thead')[0].rows.length); 
 reSequence(); 
 }

原因:Don't make functions within a loop.

同理,将addBtnCpsListener中的vBtnCps[i].onclick = function() {

修改为:vBtnCps[i].onclick = copyTr;

外部新创建一个函数

<pre code_snippet_id="139791" snippet_file_name="blog_20140103_15_6784659" name="code" class="javascript"> function copyTr() { 
 createTr(); 
 var vNewTr = vTbody.lastChild; 
 var vTr = this.parentElement.parentElement; 
 vNewTr.getElementsByClassName === null? 
 document.getElementsByClassName('td-inp-txt', vNewTr)[0].value = 
 document.getElementsByClassName('td-inp-txt', vTr)[0].value: 
 vNewTr.getElementsByClassName('td-inp-txt')[0].value = 
 vTr.getElementsByClassName('td-inp-txt')[0].value; 
 }</pre> 
<pre></pre> 
<pre></pre>

copyTr()方法中的?:格式修改为if else函数

修改为:

function copyTr() { 
 createTr(); 
 var vNewTr = vTbody.lastChild; 
 var vTr = this.parentElement.parentElement; 
 if(vNewTr.getElementsByClassName) { 
 vNewTr.getElementsByClassName('td-inp-txt')[0].value = 
 vTr.getElementsByClassName('td-inp-txt')[0].value; 
 } else { 
 document.getElementsByClassName('td-inp-txt', vNewTr)[0].value = 
 document.getElementsByClassName('td-inp-txt', vTr)[0].value; 
 } 
}

原因:?:预期返回值应该是一个变量or函数,而不应该是一个表达式操作。

有一点需要注意:js最佳实现经常看到要使用===替换==。但是本示例中的==null,如果替换成===null会在ie8一下版本中出现问题。

完整代码

至此,一个完全基于原生JavaScript,并且兼容至IE6的table增删完成了。

还是想吐槽一下,如果不兼容IE10以下的版本,可以节省50%的代码。如果使用jQuery,又可以节省50%的代码。对于实用主义的我而言,这一过程备受煎熬。不过还是从中有所收益的(违心。。)

以下为完整代码:

<!DOCTYPE HTML> 
<html> 
 <head> 
 <title>table</title> 
 <meta charset='utf-8' /> 
 <style type="text/css"> 
 table.base{ 
 border-collapse:collapse; 
 text-align: center; 
 border: 1px solid black; 
 } 
 table, tr, td, th{ 
 border: 1px solid black; 
 } 
 </style> 
 </head> 
 <body> 
 <div id="main-content"> 
 <table id="main-table" class="base"> 
 <thead> 
  <tr> 
  <th colspan="3">This is a table for operations by javascript</th> 
  </tr> 
  <tr> 
  <th> 
  <input type="button" value="CREATE" id="cp_btn" onclick="createTr()" /> 
  </th> 
  <th> 
  <input type="button" value="CLEAR" id="cl_btn" onclick="clearTrs()" /> 
  </th> 
  <th> 
  <input type="button" value="GUESS" id="cl_btn"/> 
  </th> 
  </tr> 
 </thead> 
 <tbody> 
 </tbody> 
 </table> 
 </div> 
 <script type="text/javascript"> 
 if(!document.getElementsByClassName){ 
 document.getElementsByClassName = function(className, element){ 
  var children = (element || document).getElementsByTagName('*'); 
  var elements = []; 
  for (var i=0; i<children.length; i++){ 
  var child = children[i]; 
  var classNames = child.className.split(' '); 
  for (var j=0; j<classNames.length; j++){ 
  if (classNames[j] == className){ 
  elements.push(child); 
  break; 
  } 
  } 
  } 
  return elements; 
 }; 
 } 
 var vTbody = document.getElementById('main-table').getElementsByTagName('tbody')[0]; 
 function myInput(vId, vClass, vType, vValue, vParent) { 
 var vInput = document.createElement('input'); 
 if(vId) { 
  vInput.setAttribute('id', vId); 
 } 
 vInput.setAttribute('type', vType); 
 vInput.setAttribute('value', vValue); 
 vInput.className = vClass; 
 if(vParent) { 
  vParent.appendChild(vInput); 
 } 
 return vInput; 
 } 
 function myTd(vId, vClass, vChild, vParent) { 
 var vTd = document.createElement('td'); 
 if(vId){ 
  vTd.setAttribute('id', vId); 
 } 
 vTd.className = vClass; 
 if(vChild) { 
  vTd.appendChild(vChild); 
 } 
 if(vParent) { 
  vParent.appendChild(vTd); 
 } 
 return vTd; 
 } 
 function myTr(vId, vClass, vChild, vParent) { 
 var vTr = document.createElement('tr'); 
 if(vId){ 
  vTr.setAttribute('id', vId); 
 } 
 vTr.className = vClass; 
 if(vChild) { 
  vTr.appendChild(vChild); 
 } 
 if(vParent) { 
  vParent.appendChild(vTr); 
 } 
 return vTr; 
 } 
 function createTr() { 
 var vTr = new myTr(null, null, null, vTbody); 
 //序列td 
 var vTdSeq = new myTd(null, 'seq', null, vTr); 
 //文本框td 
 var vTdText = new myTd(null, null, null, vTr); 
 var vInputText = new myInput(null, 'td-inp-txt', 'text', '', vTdText); 
 //操作按钮td 
 var vTdBtn = new myTd(null, null, null, vTr); 
 var vInputBtnCp = new myInput(null, 'td-inp-btn-cp', 'button', 'COPY', vTdBtn); 
 var vInputBtnDel = new myInput(null, 'td-inp-btn-del', 'button', 'DELETE', vTdBtn); 
 init(); 
 } 
 function clearTrs() { 
 while(vTbody.rows.length >0) { 
  vTbody.deleteRow(); 
 } 
 } 
 function init(){ 
 reSequence(); 
 addBtnDelsListener(); 
 addBtnCpsListener(); 
 } 
 function reSequence() { 
 var vObj = vTbody.getElementsByClassName == null? 
document.getElementsByClassName('seq', vTbody): 
vTbody.getElementsByClassName('seq'); 
 for (var i=0, len=vObj.length; i<len; i++) { 
  vObj[i].innerHTML = i+1; 
 } 
 } 
 function addBtnDelsListener() { 
 var vBtnDels = vTbody.getElementsByClassName == null? 
document.getElementsByClassName('td-inp-btn-del', vTbody): 
vTbody.getElementsByClassName('td-inp-btn-del'); 
 for (var i=0, len=vBtnDels.length; i<len; i++) { 
  vBtnDels[i].onclick = delTr; 
 } 
 } 
 function delTr() { 
 var vTr = this.parentElement.parentElement; 
 vTbody.deleteRow(vTr.rowIndex-vTbody.parentNode.getElementsByTagName('thead')[0].rows.length); 
 reSequence(); 
 } 
 function addBtnCpsListener() { 
 var vBtnCps = vTbody.getElementsByClassNamenull == null? 
document.getElementsByClassName('td-inp-btn-cp', vTbody): 
vTbody.getElementsByClassName('td-inp-btn-cp'); 
for (var i=0, len=vBtnCps.length; i<len; i++) { 
  vBtnCps[i].onclick = copyTr; 
 } 
 } 
 function copyTr() { 
 createTr(); 
 var vNewTr = vTbody.lastChild; 
 var vTr = this.parentElement.parentElement; 
 if(vNewTr.getElementsByClassName) { 
  vNewTr.getElementsByClassName('td-inp-txt')[0].value = 
  vTr.getElementsByClassName('td-inp-txt')[0].value; 
 } else { 
  document.getElementsByClassName('td-inp-txt', vNewTr)[0].value = 
  document.getElementsByClassName('td-inp-txt', vTr)[0].value; 
 } 
 } 
 </script> 
 </body> 
</html>

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持三水点靠木!

Javascript 相关文章推荐
Javascript面向对象编程(二) 构造函数的继承
Aug 28 Javascript
javascript 系统文件夹文件操作及参数介绍
Jan 08 Javascript
javascript自启动函数的问题探讨
Oct 05 Javascript
jquery中map函数遍历数组用法实例
May 18 Javascript
jQuery实现淡入淡出二级下拉导航菜单的方法
Aug 28 Javascript
初步使用bootstrap快速创建页面
Mar 03 Javascript
JQuery异步加载PartialView的方法
Jun 07 Javascript
less简单入门(CSS 预处理语言)
Mar 08 Javascript
JavaScript正则表达式校验与递归函数实际应用实例解析
Aug 04 Javascript
vue-router项目实战总结篇
Feb 11 Javascript
在js文件中引入(调用)另一个js文件的三种方法
Sep 11 Javascript
JavaScript 中的六种循环方法
Jan 06 Javascript
微信小程序 tabs选项卡效果的实现
Jan 05 #Javascript
jQuery密码强度验证控件使用详解
Jan 05 #Javascript
jquery广告无缝轮播实例
Jan 05 #Javascript
JavaScript自定义浏览器滚动条兼容IE、 火狐和chrome
Jan 05 #Javascript
javascript添加前置0(补零)的几种方法
Jan 05 #Javascript
微信小程序 实战实例开发流程详细介绍
Jan 05 #Javascript
利用jquery禁止外层滚动条的滚动
Jan 05 #Javascript
You might like
PHP中strpos、strstr和stripos、stristr函数分析
2016/06/11 PHP
PHP  Yii清理缓存的实现方法
2016/11/10 PHP
PHP截取发动短信内容的方法
2017/07/04 PHP
ThinkPHP5 验证器的具体使用
2018/05/31 PHP
一个高效的JavaScript压缩工具下载集合
2007/03/06 Javascript
JavaScript中继承的一些示例方法与属性参考
2010/08/07 Javascript
有趣的javascript数组定义方法
2010/09/10 Javascript
JS函数验证总结(方便js客户端输入验证)
2010/10/29 Javascript
jQuery处理xml格式的返回数据(实例解析)
2013/11/28 Javascript
Jquery Easyui自定义下拉框组件使用详解(21)
2020/12/31 Javascript
JS中IP地址与整数相互转换的实现代码
2017/04/10 Javascript
全面解析vue中的数据双向绑定
2017/05/10 Javascript
基于JavaScript实现带数据验证和复选框的表单提交
2017/08/23 Javascript
nodejs的路径问题的解决
2018/06/30 NodeJs
vue+webpack中配置ESLint
2018/11/07 Javascript
layui表格 列自动适应大小失效的解决方法
2019/09/06 Javascript
js页面加载后执行的几种方式小结
2020/01/30 Javascript
[09:37]2018DOTA2国际邀请赛寻真——不懈追梦的Team Serenity
2018/08/13 DOTA
Python序列之list和tuple常用方法以及注意事项
2015/01/09 Python
python中(str,list,tuple)基础知识汇总
2018/02/20 Python
Python 3.3实现计算两个日期间隔秒数/天数的方法示例
2019/01/07 Python
Django中使用Whoosh进行全文检索的方法
2019/03/31 Python
打印tensorflow恢复模型中所有变量与操作节点方式
2020/05/26 Python
详解使用HTML5 Canvas创建动态粒子网格动画
2016/12/14 HTML / CSS
ZWILLING双立人英国网上商店:德国刀具锅具厨具品牌
2018/05/15 全球购物
莫斯科制造商的廉价皮大衣:Fursk
2020/06/09 全球购物
年终总结会主持词
2014/03/25 职场文书
关于责任的演讲稿
2014/05/20 职场文书
2014年党员自我评价材料
2014/09/22 职场文书
高中校园广播稿3篇
2014/09/29 职场文书
2014年文员工作总结
2014/11/18 职场文书
干部个人考察材料
2014/12/24 职场文书
银行大堂经理培训心得体会
2016/01/09 职场文书
2016拓展训练心得体会范文
2016/01/12 职场文书
九年级语文教学反思
2016/03/03 职场文书
python绘图subplots函数使用模板的示例代码
2021/04/30 Python