本文详细讲解如何使用纯原生JavaScript实现HTML表格的动态增删功能,重点解决IE8及以下版本的兼容性问题。内容涵盖DOM元素创建、事件绑定、排序算法及针对IE特殊属性的处理方案,提供完整代码示例。
纯原生JS实现Table表格增删及IE兼容处理
公司实习生询问如何实现表格的增删操作,使用jQuery可以轻松完成。随后他又提问,如果不使用jQuery,仅依靠JavaScript如何实现这一功能。
面对这种情况,我通常秉持“不理解,但支持”的态度。
虽然长期使用jQuery让人变得“懒惰”,但我还是尝试用原生JS实现了该功能,过程中发现最大的难点在于IE浏览器的兼容性。
如果只想查看代码,可以跳过分析部分,完整代码附在文章末尾。
以下是具体的编码过程:
HTML结构代码
HTML结构代码
构建一个基础的表格结构,并添加简单的样式。包含三个按钮,分别对应创建新行、清空表格以及一个预留功能。
<!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元素作为模板,基于此执行创建操作。但为了避免破坏HTML整体结构,最终决定通过JavaScript动态生成tr对象并追加到页面中。
为了确保DOM操作在页面加载完成后执行,将<script>标签放置在</body>之前。
基于表格中的tbody进行增删操作,首先声明一个全局变量来引用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)。为解决此问题,可以采取特定策略。
对于Firefox、Chrome等浏览器,使用setAttribute(class, value)。
对于IE浏览器,使用setAttribute(className, value)。
这里采用另一种方式.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()
构造函数完成后,完善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()
创建一个动态排序方法reSequence(),存在一个兼容性问题:innerText在Firefox下无效,因此改用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()
清空行方法clearTrs()
移除或销毁某个DOM对象,首先想到的是remove()方法,但不幸的是,这在IE浏览器中存在兼容性问题。因此,采用一种更简便的方式,对DOM对象执行innerHTML="",代码如下:
function clearTrs() {
vTbody.innerHTML = '';
}
IE8报错“未知的运行错误”。经查,因为IE8中table.innerHTML是只读属性。再次修改:
function clearTrs() {
while(vTbody.rows.length >0) {
vTbody.deleteRow();
}
}
删除行方法addBtnDelsListener()
删除行方法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()
复制行方法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;
}
}
原因:?:预期返回值应该是一个变量或函数,而不应该是一个表达式操作。
有一点需要注意:js最佳实现经常看到要使用===替换==。但是本示例中的==null,如果替换成===null会在ie8以下版本中出现问題。
完整代码
至此,一个完全基于原生JavaScript,并且兼容至IE6的表格增删功能完成了。
还是想吐槽一下,如果不兼容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>
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持本站!