Ag Grid 是一個構建資料表格的 Javascript 框架,它提供許多豐富靈活的配置,包括對整體表格以及單元格的樣式設定,或是提供不同型態的篩選和編輯功能等。
今天要為各位分享的是,如何利用 Row Spanning 來合併單元格,透過精簡表格的資料量,來增加可讀性。
情境
我們時常會遇到不同列中有許多重複的資訊,例如學生的考科成績資料表格中,同一名學生中的 name & gender & class 屬性是相同的,如果以原始表格呈現如下圖,資料會十分冗贅,如果可以將這些重複資訊合併成一個單元格,閱讀起來會舒適許多。
const data = [
{ name: "邱秉誠", gender: "男", class: "甲", subject: "國文", score: 80 },
{ name: "邱秉誠", gender: "男", class: "甲", subject: "英文", score: 82 },
{ name: "陳小英", gender: "女", class: "乙", subject: "國文", score: 95 },
{ name: "陳小英", gender: "女", class: "乙", subject: "英文", score: 80 },
{ name: "陳小英", gender: "女", class: "乙", subject: "數學", score: 78 },
]
如何合併
我們透過 columnDefs 中的 自定義的 rowSpan 屬性來決定該 column 需要合併的列數,rowSpan 這個自定義 函式會對該 column 的 每筆資料都進行判斷,並回傳一個數字,若回傳 2 ,表示往下合併一個儲存格,如果是 3,就會往下合併兩個儲存格,以此類推。
const rowSpan = (params: any) => {
// params.data 表示這一筆 (列) 資料
// 回傳值表示該欄位的這格將往下合併多少儲存格
return params.data.rowspan
}
// 讓需要合併儲存格的欄位都附上 rowSpan 自定義函示
const [columnDefs, setColumnDefs] = useState([
{ field: "name", rowSpan: rowSpan},
{ field: "gender", rowSpan: rowSpan},
{ field: "class", rowSpan: rowSpan},
{ field: "subject"},
{ field: "score"},
]);
為了方便後續的 rowSpan 判斷,我們可以先整理一下原始資料,透過 transformeForRowSpan 函式,讓原始資料都帶上 rowspan 數值,並且讓每組學生資料的首筆之外,欲被合併的欄位單元格 (name & gender & class) 都改為空字串,注意,要被合併的單元格值一定要改為其他值 (如空字串),否則無法生效!
整理後資料如下:
const data = [
{ name: "邱秉誠", gender: "男", class: "甲", subject: "國文", score: 80, rowspan:2 },
{ name: "", gender: "", class: "", subject: "英文", score: 82, rowspan:1 },
{ name: "陳小英", gender: "女", class: "乙", subject: "國文", score: 95, rowspan:3 },
{ name: "", gender: "", class: "", subject: "英文", score: 80,rowspan:1 },
{ name: "", gender: "", class: "", subject: "數學", score: 78,rowspan:1 },
]
const transformeForRowSpan = (data: any) => {
const nameMap: any = {};
// 計算每個 name 出現的次數
data.forEach((row: any) => {
if (nameMap[row.name]) {
nameMap[row.name]++;
} else {
nameMap[row.name] = 1;
}
});
// 生成新的數據結構
const result: any = [];
data.forEach((row: any) => {
if (nameMap[row.name] > 0) {
result.push({ ...row, rowspan: nameMap[row.name] });
nameMap[row.name] = 0; // 後面該 name 的 row 將被清空
} else {
result.push({ ...row, name: "", gender: "", class: "", rowspan: 1 });
}
});
return result;
};
接著,我們便可以使用轉換後原始資料,來渲染在表格上了。這時你會發現,單元儲存格已經合併了,但是文字出現在左上角,我們可以透過客製化的 CellRenderer,以 Flex 布局,設定 justifyContent & alignItems 兩個屬性為 center,來達到將單元格水平跟垂直置中的效果
const MergedCellRender = (params: any) => {
return (
<div style={{
height: '100%', display: "flex", alignItems: "center",
backgroundColor: "#fff", justifyContent: "center"
}}>
{params.value}
</div >
)
}
完整程式碼
'use strict';
import 'ag-grid-community/styles/ag-grid.css';
import "ag-grid-community/styles/ag-theme-alpine.css";
import './app.css'
import { AgGridReact } from 'ag-grid-react';
import { useEffect, useRef, useState } from 'react';
const MergedCell = () => {
const data = [
{ name: "邱秉誠", gender: "男", class: "甲", subject: "國文", score: 80 },
{ name: "邱秉誠", gender: "男", class: "甲", subject: "英文", score: 82 },
{ name: "陳小英", gender: "女", class: "乙", subject: "國文", score: 95 },
{ name: "陳小英", gender: "女", class: "乙", subject: "英文", score: 80 },
{ name: "陳小英", gender: "女", class: "乙", subject: "數學", score: 78 },
]
const [rowData, setRowData] = useState<any[]>([]);
const transformeForRowSpan = (data: any) => {
const nameMap: any = {};
// 計算每個 name 出現的次數
data.forEach((row: any) => {
if (nameMap[row.name]) {
nameMap[row.name]++;
} else {
nameMap[row.name] = 1;
}
});
// 生成新的數據結構
const result: any = [];
data.forEach((row: any) => {
if (nameMap[row.name] > 0) {
result.push({ ...row, rowspan: nameMap[row.name] });
nameMap[row.name] = 0; // 後面該 name 的 row 將被清空
} else {
result.push({ ...row, name: "", gender: "", class: "", rowspan: 1 });
}
});
return result;
};
useEffect(() => {
// Transforme for row spanning
const newData = transformeForRowSpan(data)
setRowData(newData)
}, [])
const rowSpan = (params: any) => {
console.log(params.data.rowspan)
return params.data.rowspan
}
const MergedCellRender = (params: any) => {
return (
<div style={{
height: '100%', display: "flex", alignItems: "center",
backgroundColor: "#fff", justifyContent: "center"
}}>
{params.value}
</div >
)
}
const [columnDefs, setColumnDefs] = useState([
{ field: "name", rowSpan: rowSpan, cellRenderer: MergedCellRender, width: 100 },
{ field: "gender", rowSpan: rowSpan, cellRenderer: MergedCellRender, width: 100 },
{ field: "class", rowSpan: rowSpan, cellRenderer: MergedCellRender, width: 100 },
{ field: "subject", width: 100 },
{ field: "score", width: 100 },
]);
return (
<div
style={{ height: "400px" }}
className="ag-theme-alpine"
>
<AgGridReact
rowData={rowData}
columnDefs={columnDefs}
suppressRowTransform={true}
rowHeight={35}
/>
</div>
);
};
export default MergedCell;