JavaScript 合并 Table 单元格简单做

标签初步分析

HTML Table 元素允许合并单元格。通常手写代码比较“反人类”思维,于是还是通过直观的可视化的工具来完成,例如奉为经典的 Dreamweaver。
JavaScript 合并 Table 单元格简单做_第1张图片
研究代码,td 行元素有一 rowspan 跨行的属性,表示跨行行数。如果当前这样有 x 个跨行,那么下面 tr > td (一共 x 行)中的每一行都可以少出现一个 td。
JavaScript 合并 Table 单元格简单做_第2张图片
如上图,上面有 rowspan 的 tr 的,包含一共八个 td;而下面的 tr 因为设置了跨行 2 个的缘故,少了一个 td。

写代码实现

我们得知标签的思路怎么实现之后,该换到代码层面来讨论如何实现的了,当然这里我们要 DOM 元素的操控,题外话——什么 RN、Vue 不用写 DOM 代码的先放一边:)。

首先是确定哪些单元格要合并的。先假设相同性质的 td 有相同的 class,都在同一列(Column)。
JavaScript 合并 Table 单元格简单做_第3张图片
这一列都是 order,显然,订单 id 为 124 的有两个,都是显示一样的内容,需要合并之。另外,必须保证要合并的单元格是连续出现的,如下图未合并之前显示的,否则不能合并。
JavaScript 合并 Table 单元格简单做_第4张图片

统计跨行行数

有 class 虽然指定哪些单元格要处理,但尚不知跨行多少个,有哪些需要合并的,——我们用代码来算算。

var arr = document.querySelectorAll('.order');
	
var map = {};
for(var i = 0, j = arr.length; i < j; i++) {
	var td = arr[i];
	var orderId = td.dataset.orderId;
	
	if(undefined === map[orderId]) map[orderId] = 1; // 统计跨行的有多少,用一个 map 装着
	else map[orderId] = ++map[orderId];
} 

得到这个 map,key 为订单id,value 是跨多少行。

标记元素

做某件事之前,必须知道目标对象究竟是什么,也就是在一个大集合中选择好实施的范围,“精确打击”——这道理亦适用于本文。我们接着就进行元素的标记,目的就是,要跨行的保留下来设置 rowspan 属性,不要的 td 标识出来通通删除之——如此就能达到跨行的目的了。

这里要引入基础知识点:队列(queue),前端小白不懂数据结构的,应要恶补一下,我博客前面几篇文章都有介绍。

var stack = [];
for(var i = 0, j = arr.length; i < j; i++) {
	var td = arr[i];
	var orderId = td.dataset.orderId;
	var tds = map[orderId];
	
	if( tds > 1 && stack.length === 0) {// 标记
		for(var q = i; q < i + tds ; q++) {// 连续 tds 个都是要合并单元格的
			if(q == i) {
				arr[q].classList.add('firtstOne');
				arr[q].dataset.rowSpan = tds;							
			}
			
			stack.push(arr[q]); // 加入队列
		}
	}

	if(stack.length && td === stack[0]) {
		stack.shift();// 弹出第一个元素
		if(td.className.indexOf('firtstOne') != -1) {
		} else { 
			td.classList.add('die'); // 要删除的元素
		}
	}
}

我们利用数组简单模拟队列,先声明 stack =[] 以备后用。现在仍是遍历该列的所有 td 元素,发现有跨行的 td 而且队列为空的(tds > 1 && stack.length === 0)进入一个 for 循环,“向前搜索” tds 个单元格,将其逐个添加到队列中。注意循环上限中不能是 tds,而是 i + tds,因为要从当前索引开始算。并且还有一件事,就是发现第一个的元素的话要进行标记并设置它的 rowspan 属性,怎么得知是第一个? i 不变, q 会变,当 q === i 时显然是第一个。

标记删除的单元格元素

到目前为止,我们的代码仍在外面的那个大 for 循环中。接着我们执行退栈的任务。为什么要这样操作呢?有同学可能会问,既然上面那个“子的 for 循环”已经知道哪个是第一个元素,哪些是不是第一个元素,不是第一个元素不就是要删除的么,怎么不干脆直接在那儿标记啊?这里稍作解释下,因为还是在当前 for 循环中,虽然先行向前“搜索”了要处理的元素,但回到大的元素中,下一个还是遇到要删除的 td,怎么确定是否第一个呢?所以这样的逻辑是相悖的。于是我们引入一个对象:栈,让它记住我们要处理的元素,开辟一段新的变量,作为我们判断逻辑的“参考条件”。栈的特性就是“有出有入”。

if(stack.length && td === stack[0]) {
		stack.shift();// 退栈  
		if(td.className.indexOf('firtstOne') != -1) {
		} else { 
			td.classList.add('die'); // 要删除的元素
		}
} 

这里的 td === stack[0] 栈头个元素就是当前循环变量 td,即 arr[i] (同理 stack.shift();// 退栈返回的也是 td);而且还是要栈不为空的情况下。

至此,我们的标记工作就完成了。栈的理解是必须的,尽管有点绕,但是是实现该功能的重点。

最后工作

最后的工作就简单清爽多了。die() 是我们直接封装的删除 dom 元素的方法。

[].forEach.call(document.querySelectorAll('.firtstOne'), i => {
	i.setAttribute('rowspan', i.dataset.rowSpan);
});
[].forEach.call(document.querySelectorAll('.die'), i => {
	i.die();
});

我们贴出完整的代码:

// 合并单元格
function megeCell(columnClass) {
	// 收集所有的列
	var arr = document.querySelectorAll(columnClass);
	
	var map = {};
	for(var i = 0, j = arr.length; i < j; i++) {
		var td = arr[i];
		var orderId = td.dataset.orderId;
		
		if(undefined === map[orderId]) map[orderId] = 1; // 统计跨行的有多少,用一个 map 装着
		else map[orderId] = ++map[orderId];
	} 
	
	var stack = [];
	for(var i = 0, j = arr.length; i < j; i++) {
		var td = arr[i];
		var orderId = td.dataset.orderId;
		var tds = map[orderId];
		
		if( tds > 1 && stack.length === 0) {// 标记
			for(var q = i; q < i + tds ; q++) {// 连续 tds 个都是要合并单元格的
				if(q == i) {
					arr[q].classList.add('firtstOne');
					arr[q].dataset.rowSpan = tds;							
				} else {
					arr[q].classList.add('die');
				}
				
				stack.push(arr[q]); // 入栈
			}
		}
	
	 	if(stack.length && td === stack[0]) {
			stack.shift();// 退栈  
			if(td.className.indexOf('firtstOne') != -1) {
			} else { 
				td.classList.add('die'); // 要删除的元素
			}
		} 
	}
	
	[].forEach.call(document.querySelectorAll('.firtstOne'), i => {
		i.setAttribute('rowspan', i.dataset.rowSpan);
	});
	[].forEach.call(document.querySelectorAll('.die'), i => {
		i.die();
	});
}

megeCell('.order');

效果如下图所示。
JavaScript 合并 Table 单元格简单做_第5张图片
如果要多列,也是没问题的,无非就是调用方法多次。

好了,跨行的完成了。最后留个作业给大家吧,如果要打造跨列的话,该怎么做呢?

你可能感兴趣的:(DHTML,&,H5)