index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<title>ExtJS Merge Cells</title>
<link type="text/css" rel="stylesheet" href="../extjs4/resources/css/ext-all-neptune.css" />
<link type="text/css" rel="stylesheet" href="../extjs4/resources/theme/ext-theme-neptune-all.css" />
<script type="text/javascript" src="../extjs4/ext-all-debug.js"></script>
<script type="text/javascript" src="../MergeCellTable.js"></script>
<script type="text/javascript" src="../MergeCellPanel.js"></script>
<script type="text/javascript" src="example.js"></script>
<style type="text/css">
.x-grid-td {
border-width: 0;
overflow: hidden;
vertical-align: middle;
}
</style>
</head>
<body>
</body>
</html>
example.js
Ext.onReady(function() {
Ext.create('Ext.data.Store', {
storeId:'simpsonsStore',
fields:['cat1', 'cat2', 'cat2_desc','name'],
data:{'items':[
{ "cat1": "分類1", "cat2": "分類21", "name": "名稱1" },
{ "cat1": "分類1", "cat2": "分類22", "name": "名稱1" },
{ "cat1": "分類1", "cat2": "分類22", "name": "名稱2" },
{ "cat1": "分類1", "cat2": "分類23", "name": "名稱1" },
{ "cat1": "分類2", "cat2": "分類21", "name": "名稱1" },
{ "cat1": "分類2", "cat2": "分類21", "name": "名稱2" },
{ "cat1": "分類2", "cat2": "分類22", "name": "名稱1" },
{ "cat1": "分類2", "cat2": "分類23", "name": "名稱1" },
{ "cat1": "分類2", "cat2": "分類24", "name": "名稱1" },
{ "cat1": "分類2", "cat2": "分類24", "name": "名稱2" },
{ "cat1": "分類2", "cat2": "分類24", "name": "名稱3" },
{ "cat1": "分類3", "cat2": "分類21", "name": "名稱1" },
{ "cat1": "分類3", "cat2": "分類22", "name": "名稱1" },
{ "cat1": "分類3", "cat2": "分類23", "name": "名稱1" },
{ "cat1": "分類3", "cat2": "分類24", "name": "名稱1" },
{ "cat1": "分類3", "cat2": "分類24", "name": "名稱2" },
{ "cat1": "分類3", "cat2": "分類25", "name": "名稱1" }
]},
proxy: {
type: 'memory',
reader: {
type: 'json',
root: 'items'
}
}
});
Ext.create('Ext.ux.grid.MergeCellPanel', {
title: 'Simpsons',
renderTo: Ext.getBody(),
viewConfig: {
mergeColumns: 'cat1>cat2|cat2_desc'
},
store: Ext.data.StoreManager.lookup('simpsonsStore'),
columns: [
{ text: '分類1', dataIndex: 'cat1' },
{ text: '分類2', dataIndex: 'cat2' },
{ text: '分類2描述', dataIndex: 'cat2_desc', flex: 1 },
{ text: '名稱', dataIndex: 'name' },
{ text: '列合併',
columns:[
{ text: 'C1' },
{ text: 'C2' },
{ text: 'C3' },
{ text: 'C4' },
{ text: 'C5' }
]}
]
});
});
重要核心文件如下
MergeCellTable.js
Ext.define('Ext.ux.view.MergeCellTable', {
extend: 'Ext.view.Table',
alias: ['widget.mergecelltableview'],
type: 'mergecelltableview',
baseCls: Ext.baseCSSPrefix + 'mergegrid-view',
stripeRows: false,
separator: '>', // 不同級別分割符
sameLevelSeparator: '|', // 同級別分割符
__rowspans: null, // 緩存要合併的列中每個單元格的rowspan, refresh時會刪除重新計算
__merge_columns: null, // 緩存要合併的列, refresh時會刪除重新獲取
cellTpl: [ // 增加了rowspan屬性
'<td role="gridcell" class="{tdCls}" {tdAttr} id="{[Ext.id()]}" rowspan="{rowspan}" <tpl if="hidden">style="display:none"</tpl>>',
'<div {unselectableAttr} class="' + Ext.baseCSSPrefix + 'grid-cell-inner {innerCls}"',
'style="height:auto;text-align:{align};<tpl if="style">{style}</tpl>">{value}</div>',
'</td>', {
priority: 0
}
],
renderCell: function(column, record, recordIndex, columnIndex, out) { // 添加rowspan與隱藏td
var me = this,
rowspans = me.getRowspans();
var rowspan = (rowspans[recordIndex] || {})[column.dataIndex];
cellValues = me.cellValues,
cellValues.rowspan = rowspan;
// cellTpl採用了display:none而不是不生成td, 因爲若不生成td在使用rowediting時會出錯
cellValues.hidden = rowspan === 0;
me.callParent(arguments);
},
onAdd: function(store, records, index, cfg) {
this.refresh();
},
onRemove: function() {
this.refresh();
},
onUpdate: function() {
this.refresh();
},
refresh: function() {
var me = this;
// 刷新時要重新計算rowspan
delete me.__rowspans;
delete me.__merge_columns;
me.callParent(arguments);
},
getRowspans: function() {
var me = this;
// 已經計算過直接返回
var rowspans = me.__rowspans;
if (rowspans != null) {
return rowspans;
}
// 計算rowspan
rowspans = [];
var store = me.dataSource,
mergeColumns = me.getMergeColumns();
var setSameLevelRowspan = function(rowspans, rowIndex, columns, rowspan) {// 設置同級其他列的rowspan
var i, temp, len = columns.length;
for (i = 1; i < len; i++) {
temp = columns[i];
rowspans[rowIndex][temp] = rowspan;
}
};
var calculateRowspans = function(rowspans, mergeColumns, currentColumnIndex, store, from, to) {
if (currentColumnIndex >= mergeColumns.length) {
return;
}
var columns = mergeColumns[currentColumnIndex],
i, current, prev, mergeStart = 0;
var column = columns[0];
try {
for (i = from; i < to + 1; i++) {
current = store.getAt(i).get(column);
if (current !== prev) {
rowspans[i] = rowspans[i] || {};
rowspans[i][column] = 1;
setSameLevelRowspan(rowspans, i, columns, 1);
// 遞歸獲取子列
if (prev != null) {
calculateRowspans(rowspans, mergeColumns, currentColumnIndex + 1, store, mergeStart, i - 1);
}
prev = current;
mergeStart = i;
} else {
rowspans[mergeStart][column]++;
setSameLevelRowspan(rowspans, mergeStart, columns, rowspans[mergeStart][column]);
rowspans[i] = rowspans[i] || {};
rowspans[i][column] = 0;
setSameLevelRowspan(rowspans, i, columns, 0);
}
}
if (i > mergeStart) {
calculateRowspans(rowspans, mergeColumns, currentColumnIndex + 1, store, mergeStart, i - 1);
}
} catch(e) {
if (console) {
console.error(e);
}
}
};
calculateRowspans(rowspans, mergeColumns, 0, store, 0, store.data.length - 1);
// 緩存
me.__rowspans = rowspans;
return rowspans;
},
getMergeColumns: function() {
var me = this;
var columns = me.__merge_columns;
if (columns != null) {
return columns;
}
// 未配置時直接返回
var mergeColumns = me.mergeColumns;
if (Ext.isEmpty(mergeColumns)) {
return [];
}
// 轉換合併規則
var separator = me.separator,
sameLevelSeparator = me.sameLevelSeparator;
columns = mergeColumns.split(separator);
var i, len = columns.length;
for (i = 0; i < len; i++) {
columns[i] = columns[i].split(sameLevelSeparator);
}
// 緩存
me.__merge_columns = columns;
return columns;
}
});
MergeCellPanel.js
Ext.define('Ext.ux.grid.MergeCellPanel', {
extend: 'Ext.grid.Panel',
alias: ['widget.mergecellgrid'],
viewType: 'mergecelltableview',
initComponent: function() {
var me = this;
Ext.apply(me, {
selType: 'cellmodel', // 按單元格選擇
sortableColumns: false, // 禁止排序
columnLines: true, // 顯示網格線
rowLines: true, // 顯示網格線
trackMouseOver: false // 禁止跟蹤鼠標變色
});
me.callParent();
}
});