Chapter 15 of 34

Header models: multi-row and grouped

Group columns under a common header – and let users collapse the groups.

  • ⏱ 5 min
  • ▶ Video episode 015
  • git tag ep-015
  • needs @guiexpert/table@1.1.88

After this chapter you can

  • describe header groups with CellGroupIf
  • make groups collapsible
  • build a multi-row header from data

Describe the groups

The header is an area model like the body (chapter 11 (TableModel & areas)). AreaModelCellGroups builds a multi-row header from a tree of CellGroupIf: a group has a label (data) and children; a leaf has a property and becomes a column.

header-groups.ts
import {
AreaModelCellGroups, AreaModelObjectArrayWithColumndefs, type CellGroupIf, CellgroupFactory,
EventAdapter, SimpleDomService, TableFactory, TableOptions, TableScope, px80, px110, px120, px160
} from "@guiexpert/table";
import { createOrders, type Order } from "./data/orders";
const groups: CellGroupIf[] = [
{ data: "Order", children: [{ data: "ID", property: "id" }, { data: "Date", property: "date" }] },
{ data: "Customer", toggle: true, children: [ // toggle → collapsible (❯)
{ data: "Customer", property: "customer", visibility: "always" }, // stays when collapsed
{ data: "Country", property: "country" },
{ data: "City", property: "city" }] },
{ data: "Product", toggle: true, children: [
{ data: "Product", property: "product", visibility: "always" },
{ data: "Category", property: "category" }] },
{ data: "Amount", children: [
{ data: "Qty", property: "quantity" }, { data: "Price", property: "price" }, { data: "Total", property: "total" }] }
];
  • toggle: true – the group gets a ❯ button and can be collapsed.
  • visibility: "always" – this column stays visible when the group is collapsed; the others disappear.

Build columns, header and body

CellgroupFactory.buildGroupExts() and CellgroupFactory.buildColumnDefs() derive the column definitions from the groups. Header and body share them, so the body model must know them too – AreaModelObjectArrayWithColumndefs maps a column index to the property:

const columnDefs = CellgroupFactory.buildColumnDefs(CellgroupFactory.buildGroupExts(groups));
for (const def of columnDefs) { // the default width is 100 px
def.width = ["customer", "product"].includes(def.property) ? px160 : def.property === "id" ? px80 : def.property === "date" ? px120 : px110;
}
const header = new AreaModelCellGroups("header", groups, columnDefs, 34);
const body = new AreaModelObjectArrayWithColumndefs<Order>("body", createOrders(10_000), columnDefs, 34);
const tableModel = TableFactory.createTableModel({ headerAreaModel: header, bodyAreaModel: body, columnDefs });
const host = document.querySelector<HTMLDivElement>(".table-div")!;
new TableScope(host, tableModel, new SimpleDomService(), { ...new TableOptions() }, new EventAdapter())
.firstInit();
Live Grouped header
Screenshot: Grouped header Screenshot: Grouped header
Click ❯ next to “Customer”: Country and City disappear, Customer stays. Column widths are kept.

Without groups: several header rows

If you only need two header rows without collapsing, headerData with two rows is enough (chapter 14 (Array of arrays)). Spanning header cells come from AreaModelIf.getColspanAt() – exactly like in the body (chapter 29 (Rowspan & colspan)).

Next

The other end of the table: a footer with totals that follow the filter – chapter 16 (Footer totals).

API used in this chapter