Skip to content

Multiple Columns

Enterprise

Group Display Types introduced the three ways to surface a group hierarchy. This page is a deep dive on displayType: 'multipleColumns' — instead of collapsing every level into one indented column (Single Column), each groupBy level gets its own leading column. A row’s group header sits in the column that matches its nesting level; leaf rows stay blank in the group columns and render as ordinary data rows. Aggregates render in their own data column, formatted by that column’s renderer.

Ask the manager for those columns with getGroupColumns() — it returns one column per grouping level — and spread them ahead of your data columns:

const grouping = rowGrouping({
groupBy: [2, 5], // Region, then Status — a column each
displayType: 'multipleColumns',
aggregations: { 3: 'sum' }, // renders in the real Revenue column
});
new Zengrid(mount, {
columns: [...grouping.getGroupColumns(), ...dataColumns],
fullWidthProvider: grouping.provider,
});

Because the manager reshapes the rows to match the prepended columns, keep your data columns contiguous from the first source index — the group columns own the leading slots.

With one column per level, the tuning is per column. All of these are options on rowGrouping():

  • groupColumnLabel(index, spec) — the header text for each group column. index is the level (outermost is 0) and spec carries the col it groups by, so you can name each column after its source column.
  • groupColumnWidth — a single number sizes every group column the same, or pass a function (index, spec) => number to size each level’s column independently (e.g. a wide Region column and a narrow Status one).
  • suppressCount — hide the (N) leaf-count suffix after each label.
  • groupCellRenderer(params) — draw the group cell yourself. It’s called for every group column, so branch on params.level to render each level differently. Return an HTML string or an HTMLElement; the chevron and click-to-toggle stay wired.
const labels = { 2: 'Region', 5: 'Status' };
rowGrouping({
groupBy: [2, 5],
displayType: 'multipleColumns',
groupColumnLabel: (_index, spec) => labels[spec.col],
groupColumnWidth: (_index, spec) => (spec.col === 2 ? 220 : 140),
groupCellRenderer: ({ label, level }) =>
`${level === 0 ? '🌐' : '•'} <strong>${label}</strong>`,
});

The default multiple-columns layout: Region and Status each get their own leading column (Group 1, Group 2). The Revenue aggregate renders in the real Revenue column. Collapse a Region and watch its Status column blank out.

  • One column per level. getGroupColumns() returns exactly as many columns as you have groupBy levels. Each header lands in the column matching its nesting depth; deeper columns blank out for shallower rows. For the one-column, indent-by-depth layout use Single Column.
  • Per-column, not per-level indent. indentPerLevel is a single-column concern and is ignored here — the hierarchy is expressed by which column holds the header, not by indentation.
  • Aggregates render in their column. With no full-width header to host them, each configured aggregate lands in its real data column, formatted by that column’s own renderer (here, currency).
  • groupCellRenderer runs for every group column. Branch on params.level to style each level differently; it only replaces the label + count, so the chevron and click-to-toggle stay wired around your content.
  • Keep data columns contiguous. With positional array rows the group columns take the leading slots; your data columns must follow in source order so each cell lands under the right column.