有这样一个需求:给页面中table添加下载为excel表格的功能。我想到的做法就是鼠标hover时,在表格左上角显示一个按钮,点击按钮下载。下载为excel表格文件的代码网上已经有了,所以只要关注hover显示按钮的实现。
做法一:DOM载入完成后给其中的所有table元素分别append一个按钮,初始处于隐藏状态,通过css控制hover时显示。这样做需要修改css代码,虽然并不复杂,但是多余。其次,也是最关键的,处理不到js生成的table元素,除非在以前的代码里再加入append按钮的代码,简直自虐。所以:
做法二:事件委托。
先用jQuery实现一下:
$(document)
.on('mouseenter', '.table', function() {
var $this = $(this).css('position', 'relative'); // table设置position为relative,按钮为absolute
var $btn = $('<button class="btn btn-success btn-sm table-excel-btn" style="position:absolute;left:0;top:0;z-index:10">Excel</button>').on('click', function() {
var name = $this[0].title || 'sheet';
var filename = ($this[0].title || 'table') + '.xls';
tableToExcel($this[0], name, filename);
});
$this.append($btn)
}).on('mouseleave', '.table', function() {
$(this).children('.table-excel-btn').remove();
})
很简单是吧。为什么这里用mouseenter/leave而不是mouseover/out,其实写这段代码的时候还不清楚两者的区别,只知道用后者的话,鼠标移到按钮上按钮会不停闪烁,至于为什么这样后面再分析。
然后需求又加了一条不使用jQuery。
好吧,改成原生的:
document.addEventListener('mouseenter', function(e) {
var el = e.target;
while (el && el.classList && el.classList.contains('table')) {
if (el === this) {
el = null;
break;
}
el = el.parentElement;
}
if (el) {
// 添加按钮
}
});
// mouseleave事件绑定
改完运行发现行不通,handler只会在鼠标移入和移出页面时触发,e.target
都是document
,移入或移出页面内元素时不会触发。再改用mouseover/out试试,结果和用jQuery时一样鬼畜。
这才专门去搜两者的区别 —— mouseenter/leave不冒泡,mouseover/out冒泡。所以上面的代码当然只会作用于document
元素。
前面按钮闪烁的问题,因为按钮是table的子元素,当鼠标移到按钮上时,以table左上角元素(一般是单元格th或td,此时被按钮遮挡)为目标元素的mouseout事件产生并冒泡到document,触发mouseout的handler,按钮被删除,于是鼠标指针又进入了单元格,以单元格为目标元素的mouseover事件产生并冒泡到document,触发mouseover的handler,按钮再被添加上去,如此循环。
问题来了,事件委托是利用事件的冒泡机制实现的,那jQuery是怎么做到给不冒泡的mouseenter/leave事件进行委托的呢?答案是用mouseover/out模拟实现mouseenter/leave,参见源码。
需要比较目标元素和事件的relatedTarget
这一属性 —— 当它不是目标元素的子元素或等于null
(鼠标移入或移出浏览器窗口时)时,此时的事件才相当于mouseenter/leave。
完成后的代码:
document.addEventListener('mouseover', function(e) {
var el = e.target;
while (el && el.classList && el.classList.contains('table')) {
if (el === this) {
el = null;
break;
}
el = el.parentElement;
}
if ( el &&
(!e.relatedTarget || !el.contains(e.relatedTarget)) &&
el.querySelector('tbody') && el.querySelectorAll('tbody > tr').length // 额外判断,table必须有tbody且tbody至少有一行
) {
// 添加按钮
}
});
document.addEventListener('mouseout', function(e) {
var el = e.target;
while (el && el.classList && el.classList.contains('table')) {
if (el === this) {
el = null;
break;
}
el = el.parentElement;
}
if ( el && ( !e.relatedTarget || !el.contains(e.relatedTarget) ) ) {
// 删除按钮
}
});
另外,table转excel文件的代码也需要一些修改,否则中文会显示为乱码。文件后缀是xls,但其实是xml文件,需要在head标签中加入
<meta http-equiv="content-type" content="application/vnd.ms-excel; charset=UTF-8"></meta>
指定utf-8编码,参考这里。另外还要把添进去的按钮元素去掉,以及把每个td元素中的html内容替换为纯文本:td.innerHTML = td.innerText
。