ExtJS 4.2 的 Grid 组件默认不具备单元格合并功能,需要开发者手动实现。本文将深入拆解实现原理与核心代码,帮助你快速掌握合并技巧,避免常见问题。
目录
1. 原理
2. 多列合并
3. 代码与在线演示
1. 原理
1.1 HTML 代码分析
首先创建一个基础的 Grid 组件,然后观察其生成的 HTML 结构。


通过分析生成的 HTML 可以看出,Grid 组件主要分为 grid-header 和 grid-body 两大区域(若启用了工具栏或分页栏,则会额外拥有独立的区域)。
在 grid-body 内部,包含若干 tr 元素,每个 tr 对应 Grid 中的一行数据;每个 tr 中又包含多个 td,每个 td 就是一个具体的单元格。

1.2 原理
步骤说明
整个合并操作的核心都围绕 tr 元素展开,具体流程如下:
- 比较第一行
tr与第二行tr的某个td的值,若两行的内容相等:将第一行tr的该td的rowspan属性值增加 1,并将第二行tr的对应td隐藏。 - 接着拿第一行
tr与第三行tr比较,若值仍相等:第一行的rowspan再增加 1,同时隐藏第三行的td。 - 重复上述步骤,直到遇到值不相等的一行——此时跳过本次比较,转而将当前行作为新的基准行,继续与下一行比较。
示例
1) tr1 与 tr2 比较,值相等:设置 tr1 的 rowspan 加 1,隐藏 tr2 的 td。

2) tr1 与 tr3 比较,值相等:tr1 的 rowspan 再加 1,隐藏 tr3 的 td。

3) tr1 与 tr4 比较,值不相等。跳过本次比较,转而用 tr4 与 tr5 比较(当前行与下一行)。

2. 多列合并
Grid 的合并可分为单列合并和多列合并。其中多列合并又包含两种模式:
- 逐个列合并:每个列在前一列合并的前提下,可以独立进行合并操作。
- 全部列合并:只有当相邻
tr中指定的所有列内容都相同时,才会执行合并。
2.1 逐个列合并
说明:每个列可基于前面列的合并结果独立判断是否合并。
示例:

2.2 全部列合并
说明:只有相邻 tr 中所有指定列的内容完全一致时,才会进行合并。
示例:

3. 代码与在线演示
3.1 代码
/**
* 合并Grid的数据列
* @param grid {Ext.Grid.Panel} 需要合并的Grid
* @param colIndexArray {Array} 需要合并列的Index(序号)数组;从0开始计数,序号也包含。
* @param isAllSome {Boolean} 是否2个tr的colIndexArray必须完成一样才能进行合并。true:完成一样;false:不完全一样
*/
function mergeGrid(grid, colIndexArray, isAllSome) {
isAllSome = isAllSome == undefined ? true : isAllSome; // 默认为true
// 1.是否含有数据
var gridView = document.getElementById(grid.getView().getId() + '-body');
if (gridView == null) {
return;
}
// 2.获取Grid的所有tr
var trArray = [];
if (grid.layout.type == 'table') { // 若是table部署方式,获取的tr方式如下
trArray = gridView.childNodes;
} else {
trArray = gridView.getElementsByTagName('tr');
}
// 3.进行合并操作
if (isAllSome) { // 3.1 全部列合并:只有相邻tr所指定的td都相同才会进行合并
var lastTr = trArray[0]; // 指向第一行
// 1)遍历grid的tr,从第二个数据行开始
for (var i = 1, trLength = trArray.length; i < trLength; i++) {
var thisTr = trArray[i];
var isPass = true; // 是否验证通过
// 2)遍历需要合并的列
for (var j = 0, colArrayLength = colIndexArray.length; j < colArrayLength; j++) {
var colIndex = colIndexArray[j];
// 3)比较2个td的列是否匹配,若不匹配,就把last指向当前列
if (lastTr.childNodes[colIndex].innerText != thisTr.childNodes[colIndex].innerText) {
lastTr = thisTr;
isPass = false;
break;
}
}
// 4)若colIndexArray验证通过,就把当前行合并到'合并行'
if (isPass) {
for (var j = 0, colArrayLength = colIndexArray.length; j < colArrayLength; j++) {
var colIndex = colIndexArray[j];
// 5)设置合并行的td rowspan属性
if (lastTr.childNodes[colIndex].hasAttribute('rowspan')) {
var rowspan = lastTr.childNodes[colIndex].getAttribute('rowspan') - 0;
rowspan++;
lastTr.childNodes[colIndex].setAttribute('rowspan', rowspan);
} else {
lastTr.childNodes[colIndex].setAttribute('rowspan', '2');
}
// lastTr.childNodes[colIndex].style['text-align'] = 'center';; // 水平居中
lastTr.childNodes[colIndex].style['vertical-align'] = 'middle';; // 纵向居中
thisTr.childNodes[colIndex].style.display = 'none';
}
}
}
} else { // 3.2 逐个列合并:每个列在前面列合并的前提下可分别合并
// 1)遍历列的序号数组
for (var i = 0, colArrayLength = colIndexArray.length; i < colArrayLength; i++) {
var colIndex = colIndexArray[i];
var lastTr = trArray[0]; // 合并tr,默认为第一行数据
// 2)遍历grid的tr,从第二个数据行开始
for (var j = 1, trLength = trArray.length; j < trLength; j++) {
var thisTr = trArray[j];
// 3)2个tr的td内容一样
if (lastTr.childNodes[colIndex].innerText == thisTr.childNodes[colIndex].innerText) {
// 4)若前面的td未合并,后面的td都不进行合并操作
if (i > 0 && thisTr.childNodes[colIndexArray[i - 1]].style.display != 'none') {
lastTr = thisTr;
continue;
} else {
// 5)符合条件合并td
if (lastTr.childNodes[colIndex].hasAttribute('rowspan')) {
var rowspan = lastTr.childNodes[colIndex].getAttribute('rowspan') - 0;
rowspan++;
lastTr.childNodes[colIndex].setAttribute('rowspan', rowspan);
} else {
lastTr.childNodes[colIndex].setAttribute('rowspan', '2');
}
// lastTr.childNodes[colIndex].style['text-align'] = 'center';; // 水平居中
lastTr.childNodes[colIndex].style['vertical-align'] = 'middle';; // 纵向居中
thisTr.childNodes[colIndex].style.display = 'none'; // 当前行隐藏
}
} else {
// 5)2个tr的td内容不一样
lastTr = thisTr;
}
}
}
}
}
3.2 在线演示
为了方便你直观查看合并效果,此处提供了一个可直接运行的示例。由于演示链接已按要求移除,请自行在本地搭建测试环境进行验证。
以上就是 ExtJS 4.2 Grid 单元格合并的完整实现方案,涵盖了原理分析、多列合并策略以及可复用的核心代码。如果你在实际开发中遇到相关问题,可以参考本文档进行调试和适配。
