纯原生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高级程序设计 扩展--关于动态原型
Nov 09 Javascript
jquery鼠标停止移动事件
Dec 21 Javascript
Jquery原生态实现表格header头随滚动条滚动而滚动
Mar 18 Javascript
jQuery中:contains选择器用法实例
Dec 30 Javascript
jquery实现列表上下移动功能
Feb 25 Javascript
jQuery实现邮箱下拉列表自动补全功能
Sep 08 Javascript
jQuery绑定事件的四种方式介绍
Oct 31 Javascript
详解AngularJS通过ocLazyLoad实现动态(懒)加载模块和依赖
Mar 01 Javascript
Bootstrap里的文件分别代表什么意思及其引用方法
May 01 Javascript
JS实现带动画的回到顶部效果
Dec 28 Javascript
基于jQuery.i18n实现web前端的国际化
May 04 jQuery
微信小程序学习总结(一)项目创建与目录结构分析
Jun 04 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实现指定字符串中查找子字符串的方法
2015/03/17 PHP
js判断背景图片是否加载成功使用img的width实现
2013/05/29 Javascript
深入分析jquery解析json数据
2014/12/09 Javascript
js生成随机数的方法实例
2015/10/16 Javascript
Highcharts学习之坐标轴
2016/08/02 Javascript
angular ngClick阻止冒泡使用默认行为的方法
2016/11/03 Javascript
javascript实现消灭星星小游戏简单版
2016/11/15 Javascript
jQuery窗口拖动功能的实现代码
2017/02/04 Javascript
jQuery动态生成不规则表格(前后端)
2017/02/21 Javascript
react-router实现按需加载
2017/05/09 Javascript
vue实现tab切换外加样式切换方法
2018/03/16 Javascript
基于vue-video-player自定义播放器的方法
2018/03/21 Javascript
vue路由事件beforeRouteLeave及组件内定时器的清除方法
2018/09/29 Javascript
js实现按钮开关单机下拉菜单效果
2018/11/22 Javascript
15分钟学会vue项目改造成SSR(小白教程)
2019/12/17 Javascript
js面试题之异步问题的深入理解
2020/09/20 Javascript
在vue中嵌入外部网站的实现
2020/11/13 Javascript
[02:03]DOTA2亚洲邀请赛 HGT战队出场宣传片
2015/02/07 DOTA
[01:01:29]2018DOTA2亚洲邀请赛 4.4 淘汰赛 VP vs Liquid 第一场
2018/04/05 DOTA
Python基础之函数的定义与使用示例
2019/03/23 Python
Python实现的ftp服务器功能详解【附源码下载】
2019/06/26 Python
解决Django部署设置Debug=False时xadmin后台管理系统样式丢失
2020/04/07 Python
解决Python paramiko 模块远程执行ssh 命令 nohup 不生效的问题
2020/07/14 Python
python em算法的实现
2020/10/03 Python
matplotlib实现数据实时刷新的示例代码
2021/01/05 Python
马来西亚综合购物网站:Lazada马来西亚
2018/06/05 全球购物
Spartoo荷兰:鞋子、包包和服装
2018/07/12 全球购物
英国最大的笔记本电脑直销专家:Laptops Direct
2019/07/20 全球购物
init进程的作用
2015/08/20 面试题
宿舍违规检讨书
2014/01/12 职场文书
大四自我鉴定
2014/02/08 职场文书
2015年爱国卫生月活动总结
2015/03/26 职场文书
天气温馨提示语
2015/07/14 职场文书
同事欢送会致辞
2015/07/31 职场文书
python批量更改目录名/文件名的方法
2021/04/18 Python
vue实现简易音乐播放器
2022/08/14 Vue.js