Pinned Rows (Status Bar) Plugin
The Pinned Rows plugin creates a fixed status bar at the top or bottom of the grid for displaying aggregations, row counts, or custom content. Think of it as the “totals row” you’d see in a spreadsheet—always visible regardless of scroll position.
Installation
Section titled “Installation”import '@toolbox-web/grid/features/pinned-rows';Basic Usage
Section titled “Basic Usage”Configure aggregation rows with built-in functions (sum, avg, count, min, max) or custom aggregators. Each aggregation row can have different calculations per column.
import { queryGrid } from '@toolbox-web/grid';
const grid = queryGrid('tbw-grid');grid.gridConfig = { columns: [ { field: 'product', header: 'Product' }, { field: 'quantity', header: 'Qty', type: 'number' }, { field: 'price', header: 'Price', type: 'currency' }, ], features: { pinnedRows: { position: 'bottom', showRowCount: true, aggregationRows: [ { id: 'totals', aggregators: { quantity: 'sum', price: { aggFunc: 'sum', formatter: (v) => `$${v.toFixed(2)}` }, }, cells: { product: 'Totals:' }, }, ], }, },};import '@toolbox-web/grid-react/features/pinned-rows';import { DataGrid } from '@toolbox-web/grid-react';
function OrderGrid({ orders }) { return ( <DataGrid rows={orders} columns={[ { field: 'product', header: 'Product' }, { field: 'quantity', header: 'Qty', type: 'number' }, { field: 'price', header: 'Price', type: 'currency' }, ]} pinnedRows={{ position: 'bottom', showRowCount: true, aggregationRows: [ { id: 'totals', aggregators: { quantity: 'sum', price: 'sum' }, cells: { product: 'Totals:' }, }, ], }} /> );}<script setup>import '@toolbox-web/grid-vue/features/pinned-rows';import { TbwGrid, TbwGridColumn } from '@toolbox-web/grid-vue';
const orders = [ { product: 'Widget', quantity: 10, price: 99.99 }, { product: 'Gadget', quantity: 5, price: 149.99 },];
const pinnedRowsConfig = { position: 'bottom', aggregationRows: [ { id: 'totals', aggregators: { quantity: 'sum', price: 'sum' }, cells: { product: 'Totals:' }, }, ],};</script>
<template> <TbwGrid :rows="orders" :pinned-rows="pinnedRowsConfig"> <TbwGridColumn field="product" header="Product" /> <TbwGridColumn field="quantity" header="Qty" type="number" /> <TbwGridColumn field="price" header="Price" type="currency" /> </TbwGrid></template>// Feature import - enables the [pinnedRows] inputimport '@toolbox-web/grid-angular/features/pinned-rows';
import { Component } from '@angular/core';import { Grid } from '@toolbox-web/grid-angular';import type { ColumnConfig } from '@toolbox-web/grid';
@Component({ selector: 'app-order-grid', imports: [Grid], template: ` <tbw-grid [rows]="rows" [columns]="columns" [pinnedRows]="pinnedRowsConfig" style="height: 400px; display: block;"> </tbw-grid> `,})export class OrderGridComponent { rows = [];
columns: ColumnConfig[] = [ { field: 'product', header: 'Product' }, { field: 'quantity', header: 'Qty', type: 'number' }, { field: 'price', header: 'Price', type: 'currency' }, ];
pinnedRowsConfig = { position: 'bottom' as const, showRowCount: true, aggregationRows: [ { id: 'totals', aggregators: { quantity: 'sum', price: 'sum' }, cells: { product: 'Totals:' }, }, ], };}Aggregation Rows
Section titled “Aggregation Rows”Use the controls to change position, toggle row count, switch between single and multiple aggregation rows, or enable full-width mode.
<tbw-grid style="height: 400px;"></tbw-grid>import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';import '@toolbox-web/grid/features/pinned-rows';
const names = ['Widget', 'Gadget', 'Gizmo', 'Doohickey', 'Thingamabob'];const generateData = (count: number) => Array.from({ length: count }, (_, i) => ({ id: i + 1, name: names[i % names.length], quantity: Math.floor(Math.random() * 100) + 10, price: Math.round((Math.random() * 50 + 5) * 100) / 100, }));
const columns = [ { field: 'id', header: 'ID', type: 'number' }, { field: 'name', header: 'Name' }, { field: 'quantity', header: 'Qty', type: 'number' }, { field: 'price', header: 'Price', type: 'number' },];const formatCurrency = (value: unknown) => `$${(value as number).toFixed(2)}`;const sampleData = generateData(100);const grid = queryGrid('tbw-grid')!;
function rebuild( position: 'top' | 'bottom' = 'bottom', showRowCount = true, multipleRows = false, fullWidth = false,) { const singleRow = [ { id: 'totals', position, aggregators: { name: (rows: any[]) => `${new Set(rows.map((r) => r.name)).size} unique`, quantity: 'sum', price: { aggFunc: 'sum', formatter: formatCurrency }, }, cells: { id: 'Totals:' }, }, ]; const multiRows = [ { id: 'sum', position, aggregators: { quantity: 'sum', price: { aggFunc: 'sum', formatter: formatCurrency } }, cells: { id: 'Sum:', name: '' }, }, { id: 'avg', position, aggregators: { quantity: { aggFunc: 'avg', formatter: (v: number) => v.toFixed(1) }, price: { aggFunc: 'avg', formatter: formatCurrency }, }, cells: { id: 'Avg:', name: '' }, }, { id: 'minmax', position, aggregators: { quantity: 'min', price: { aggFunc: 'max', formatter: formatCurrency } }, cells: { id: 'Min/Max:', name: '' }, }, ];
grid.gridConfig = { columns, features: { pinnedRows: { position, showRowCount, fullWidth, aggregationRows: multipleRows ? multiRows : singleRow, }, }, }; grid.rows = sampleData;}
rebuild();Custom Panels
Section titled “Custom Panels”<tbw-grid style="height: 400px;"></tbw-grid>import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';import '@toolbox-web/grid/features/pinned-rows';
// Generate sample dataconst generateData = (count: number) => { const names = ['Widget', 'Gadget', 'Gizmo', 'Doohickey', 'Thingamabob']; return Array.from({ length: count }, (_, i) => ({ id: i + 1, name: names[i % names.length], quantity: Math.floor(Math.random() * 100) + 10, price: Math.round((Math.random() * 50 + 5) * 100) / 100, }));};const columns = [ { field: 'id', header: 'ID', type: 'number' }, { field: 'name', header: 'Name' }, { field: 'quantity', header: 'Qty', type: 'number' }, { field: 'price', header: 'Price', type: 'number' },];// Currency formatter for price columnsconst formatCurrency = (value: unknown) => `$${(value as number).toFixed(2)}`;
const grid = queryGrid('tbw-grid');
grid.gridConfig = { columns, features: { pinnedRows: { position: "bottom", showRowCount: true, customPanels: [ { id: 'timestamp', position: 'center', render: () => `Last updated: ${new Date().toLocaleTimeString()}`, }, { id: 'total-value', position: 'right', render: (ctx) => { const rows = (ctx.grid).rows; const total = rows.reduce((sum, row) => sum + (row.quantity || 0) * (row.price || 0), 0); return `<strong>Value: $${total.toFixed(2)}</strong>`; }, }, ], }, }, }; grid.rows = generateData(50);Configuration Options
Section titled “Configuration Options”| Option | Type | Default | Description |
|---|---|---|---|
position | 'top' | 'bottom' | 'bottom' | Status bar position |
showRowCount | boolean | true | Display row count |
showSelectedCount | boolean | true | Display selected row count |
showFilteredCount | boolean | true | Display filtered row count |
fullWidth | boolean | false | Default fullWidth mode for all aggregation rows |
aggregationRows | AggregationRowConfig[] | [] | Aggregation footer rows |
customPanels | PinnedRowsPanel[] | [] | Custom info bar panels |
Aggregation Rows
Section titled “Aggregation Rows”Configure computed footer/header rows with aggregators:
features: { pinnedRows: { aggregationRows: [ { id: 'totals', position: 'bottom', aggregators: { // Simple string aggregator quantity: 'sum', // Object syntax with formatter price: { aggFunc: 'sum', formatter: (value) => `$${value.toFixed(2)}`, }, }, cells: { id: 'Totals:', name: '' }, }, ], },},Aggregator Syntax
Section titled “Aggregator Syntax”| Syntax | Example | Description |
|---|---|---|
| String | 'sum' | Built-in aggregator |
| Function | (rows, field) => rows.length | Custom aggregator function |
| Object | { aggFunc: 'sum', formatter: (v) => v.toFixed(2) } | Aggregator with formatter |
Built-in Aggregators
Section titled “Built-in Aggregators”sum, avg, count, min, max, first, last
Full-Width Mode
Section titled “Full-Width Mode”When fullWidth is true, aggregation rows render as a single spanning cell with the label and aggregated values displayed inline—similar to the row grouping plugin’s full-width mode. When false (the default), each column gets its own cell aligned to the grid template.
Set fullWidth globally on the plugin config or per-row on AggregationRowConfig. Per-row settings override the global default:
features: { pinnedRows: { fullWidth: true, // All aggregation rows span full width by default aggregationRows: [ { id: 'totals', label: 'Totals', aggregators: { quantity: 'sum', price: 'sum' }, }, { id: 'detail', fullWidth: false, // Override: render per-column aggregators: { quantity: 'avg', price: 'avg' }, cells: { product: 'Averages:' }, }, ], },},Custom Panels
Section titled “Custom Panels”features: { pinnedRows: { customPanels: [ { id: 'info', position: 'right', render: (ctx) => `<span>Rows: ${ctx.totalRows}</span>`, }, ], },},See Also
Section titled “See Also”- Pinned Columns — Sticky columns
- Core Configuration — Grid configuration overview