demo D145
The row types
Leaf, group, footer, grand total, detail, synthesised and pinned, in one grid
Row.group · detail · grandTotal
The configuration
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.13.0/lattice-grid.min.css">
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.13.0/lattice-grid.min.js"></script>
<div id="grid" style="height: 540px"></div>
<script>
const grid = LatticeGrid.createGrid(document.getElementById('grid'), {
rowKey: 'id',
selection: 'multiple',
toolPanel: {
side: 'left',
panels: ['columns', 'filters', 'views', 'quick'],
actions: ['undo', 'redo', 'export', 'restore', 'maximise'],
exportName: 'lattice-demo',
},
groupFooter: true,
grandTotalRow: true,
columns: [
{ field: 'account', title: 'Account', filter: { type: 'set' }, group: { enabled: true, index: 0 } },
{ field: 'unit', title: 'Unit', filter: { type: 'set' }, layout: { width: 150 } },
{ field: 'service', title: 'Service', filter: { type: 'set' }, layout: { width: 150 } },
{ field: 'region', title: 'Region', filter: { type: 'set' }, layout: { width: 140 } },
{ field: 'cost', title: 'Monthly cost', type: 'number',
format: { style: 'currency', currency: 'USD', decimals: 2 }, total: 'sum', layout: { width: 180 } },
],
detail: {
rows: (row) => row.data.charges,
height: 160,
config: {
rowKey: 'line',
columns: [
{ field: 'line', title: 'Charge line', layout: { width: 200 } },
{ field: 'quantity', title: 'Qty', type: 'number', layout: { width: 100 } },
{ field: 'amount', title: 'Amount', type: 'number',
format: { style: 'currency', currency: 'USD', decimals: 2 }, total: 'sum' },
],
},
},
rows, // allocations, each carrying a nested charge list
});
// Open the first leaf's detail row on arrival so the detail kind is visible
// without a click.
for (let i = 0; i < grid.rows.count(); i++) {
const row = grid.rows.get(i);
if (row && !row.group && !row.detail) { grid.detail.open(row.key); break; }
}
</script>
Mixing leaf, group, footer and detail rows in a JavaScript data grid
A single dataset rarely fits one row shape. Leaf rows carry the raw records, group rows summarise the leaves beneath them, footer and grand total rows roll figures up to a subtotal or a final sum, detail rows expand a leaf into a nested view, and synthesised rows exist only in the grid to carry a computed value that has no backing record. Pinned rows sit outside the scrollable body entirely, fixed above or below it. A developer reaches for this mix when a report needs subtotals per group alongside a running total, or when a master row needs an inline detail panel without navigating away. Row.group, Row.detail and Row.grandTotal mark a row’s kind so the grid can render, sort and export each one according to its role rather than treating every row as an equal leaf. Because row kind is a property of the row rather than a separate rendering path, virtual scrolling treats leaf, group and footer rows as one continuous list, so scroll position and row recycling work the same regardless of how many kinds are mixed into the current view. Each row kind keeps its correct role and aria-level in the accessibility tree, so a screen reader announces a group heading as a heading and a leaf as a data row, not as indistinguishable table cells.
How do you show subtotals and a grand total in a JavaScript data grid?
Mark the rows that should aggregate using Row.group for a per-group subtotal and Row.grandTotal for the overall figure, and the grid computes and renders those values from the leaf rows beneath them. Pinned rows can hold a grand total at the top or bottom of the viewport so it stays visible while the body scrolls.