demo D17
Custom headers
A header rendering a sparkline, a filter chip or a unit toggle
column.header.render
The configuration
import * as ng from '@angular/core';
import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { createGrid } from '@toclocoinc/lattice-grid';
import '@toclocoinc/lattice-grid/css';
import createLatticeGrid from '@toclocoinc/lattice-grid/modules/angular';
const { LatticeGridComponent } = createLatticeGrid({ ng, createGrid });
// A short numeric sample the sparkline is drawn from, and a hand-built inline
// SVG that turns it into one polyline scaled into a small box. No library.
const SPARK_SAMPLE = [4, 9, 6, 12, 8, 15, 11, 18, 14, 21];
function makeSparkline(values, doc) {
const W = 56, H = 18, PAD = 1.5;
const min = Math.min(...values);
const max = Math.max(...values);
const span = max - min || 1;
const step = values.length > 1 ? (W - PAD * 2) / (values.length - 1) : 0;
const points = values
.map((v, i) => {
const x = PAD + i * step;
const y = PAD + (H - PAD * 2) * (1 - (v - min) / span);
return `${x.toFixed(1)},${y.toFixed(1)}`;
})
.join(' ');
const NS = 'http://www.w3.org/2000/svg';
const svg = doc.createElementNS(NS, 'svg');
svg.setAttribute('class', 'demo-hdr-spark-svg');
svg.setAttribute('viewBox', `0 0 ${W} ${H}`);
svg.setAttribute('preserveAspectRatio', 'none');
svg.setAttribute('aria-hidden', 'true');
const line = doc.createElementNS(NS, 'polyline');
line.setAttribute('points', points);
svg.appendChild(line);
return svg;
}
const components = {
// A count chip beside the title. A component writes into the label it is
// handed and its return value is not read, so it appends.
countChip: class {
render(label, params) {
const doc = params.document;
const wrap = doc.createElement('span');
wrap.className = 'demo-hdr-chip-wrap';
const t = doc.createElement('span');
t.textContent = params.title;
const chip = doc.createElement('span');
chip.className = 'demo-hdr-chip';
const n = params.grid?.rows?.count?.() ?? 0;
chip.textContent = n ? n.toLocaleString() : 'all';
wrap.append(t, chip);
label.appendChild(wrap);
}
},
// A unit toggle: a real styled button, appended into the label.
unitToggle: class {
render(label, params) {
const doc = params.document;
const b = doc.createElement('button');
b.type = 'button';
b.className = 'demo-hdr-unit';
b.textContent = 'GB / TB';
label.appendChild(b);
}
},
};
const columns = [
// A function renderer, returning a node the grid appends: the title beside
// an inline-SVG sparkline built from a short numeric sample.
{
field: 'service', title: 'Service', layout: { width: 200 },
header: {
render: (label, params) => {
const doc = params.document;
const wrap = doc.createElement('span');
wrap.className = 'demo-hdr-spark';
const t = doc.createElement('span');
t.className = 'demo-hdr-spark-title';
t.textContent = params.title;
wrap.append(t, makeSparkline(SPARK_SAMPLE, doc));
return wrap;
},
},
},
// A component registered by name in components above.
{ field: 'requests', title: 'Requests', type: 'number', layout: { width: 170 }, header: { render: 'countChip' } },
// Another component: the unit toggle button.
{ field: 'memoryGb', title: 'Memory', type: 'number', layout: { width: 150 }, header: { render: 'unitToggle' } },
// header.class lands on the heading cell; the label text stays and the
// accent rule styles it.
{ field: 'errorRate', title: 'Error rate', type: 'number', layout: { width: 150 }, header: { class: 'demo-hdr-accent' } },
// header.template is not read, so this heading is the stock one.
{ field: 'p95Ms', title: 'p95 ms', type: 'number', layout: { width: 130 }, header: { template: '{{title}} ▲' } },
];
const rows = [/* 3,000 service records */];
@Component({
selector: 'app-root',
standalone: true,
imports: [LatticeGridComponent],
template: '<lattice-grid [config]="grid" style="display:block;height:540px"></lattice-grid>',
})
export class AppComponent {
grid = {
rowKey: 'id',
components: components,
columns: columns,
rows: rows,
};
}
bootstrapApplication(AppComponent);
Rendering sparklines, filter chips and unit toggles in a column header
A custom header replaces the default label-and-sort-arrow cell with a rendering function, so the header itself carries information rather than only naming the column beneath it. Reach for this when a header needs to show a sparkline for the column’s distribution, a chip reflecting an active filter, or a control that toggles the unit a column displays, such as switching a price column between currency and percentage without a separate settings panel. Lattice Grid exposes this through column.header.render, a function that receives the column definition and returns the header’s contents, so the sort click target, resize handle and keyboard focus ring stay in place around whatever markup that function produces.
Because the render function runs once per header cell rather than per row, a sparkline or a filter chip in a header costs nothing extra as row count grows in this JavaScript data grid; the expense scales with column count, not with the million rows that might sit beneath it. The generated header retains role="columnheader" and aria-sort on the underlying element regardless of what column.header.render draws inside it, so a screen reader still announces sort direction correctly even when the visible label has been replaced by a chip or a small chart. Custom headers compose with column groups and pinned columns without extra wiring.
How do you customise a column header in a JavaScript data grid?
Set column.header.render to a function that returns the header cell’s contents, such as a sparkline, a filter chip or a unit toggle. Lattice Grid keeps the sort control, resize handle and aria-sort attribute on the surrounding header element, so custom content sits inside the grid’s existing interaction and accessibility handling rather than replacing it.