Data grid · ag-grid-style

34 rows
ID
Name
Role
City
Age
Salary
Active
1Ada A.EngineerIstanbul24$60,000—
2Linus B.DesignerBerlin31$61,234✓
3Grace C.PMParis38$62,468✓
4Alan D.AnalystTokyo45$63,702—
5Margaret E.SalesMadrid52$64,936✓
6Dennis F.EngineerIstanbul59$66,170✓
7Barbara G.DesignerBerlin26$67,404—
8Ken H.PMParis33$68,638✓
1–8 of 34

Double-click a cell to edit · click headers to sort, Shift-click for a second column · drag edges to resize · filter / select / paginate · 0 selected

The same grid over 100,000 rows with no pages, a filter under each column, headers to drag, pin and hide — only the rows in view are in the DOM, so scroll, sort and filter cost what they cost on ten:

100000 rows
ID
Name
Role
City
Age
Salary
Active
1Ada A.EngineerIstanbul24$60,000—
2Linus B.PMTokyo31$61,234✓
3Grace C.SalesBerlin38$62,468✓
4Alan D.DesignerMadrid45$63,702—
5Margaret E.AnalystParis52$64,936✓
6Dennis F.EngineerIstanbul59$66,170✓
7Barbara G.PMTokyo26$67,404—
8Ken H.SalesBerlin33$68,638✓
9Edsger I.DesignerMadrid40$69,872✓
10Donald J.AnalystParis47$71,106—
11Ada K.EngineerIstanbul54$72,340✓
12Linus L.PMTokyo61$73,574✓
13Grace M.SalesBerlin28$74,808—
14Alan N.DesignerMadrid35$76,042✓
15Margaret O.AnalystParis42$77,276✓
16Dennis P.EngineerIstanbul49$78,510—
17Barbara Q.PMTokyo56$79,744✓
18Ken R.SalesBerlin63$80,978✓
19Edsger S.DesignerMadrid30$82,212—
20Donald T.AnalystParis37$83,446✓
21Ada U.EngineerIstanbul44$84,680✓
22Linus V.PMTokyo51$85,914—
23Grace W.SalesBerlin58$87,148✓
24Alan X.DesignerMadrid25$88,382✓
25Margaret Y.AnalystParis32$89,616—
26Dennis Z.EngineerIstanbul39$90,850✓
27Barbara A.PMTokyo46$92,084✓
28Ken B.SalesBerlin53$93,318—
29Edsger C.DesignerMadrid60$94,552✓
30Donald D.AnalystParis27$95,786✓
31Ada E.EngineerIstanbul34$97,020—
32Linus F.PMTokyo41$98,254✓
33Grace G.SalesBerlin48$99,488✓
34Alan H.DesignerMadrid55$100,722—
35Margaret I.AnalystParis62$101,956✓
36Dennis J.EngineerIstanbul29$103,190✓

And over a source — here a function over the same rows, where a server would be — with grouping: drag a header onto the panel, or pick "Group by" from its ⋮. Every level is one question, paged on its own:

Drag a column header here to group by it0 rows · asking…
ID
Name
Role
City
Age
Salary
Active
asking…
0 rows

Usage

import { DataGrid } from "./components/grid.js";

const columns = [
  { field: "id",     header: "ID",     width: 64,  align: "right" },
  { field: "name",   header: "Name",   width: 180, editable: true },
  { field: "salary", header: "Salary", align: "right", type: "number",
    editable: true, format: (v) => "$" + v.toLocaleString() },
  { field: "active", header: "Active", format: (v) => (v ? "✓" : "—") },
];

DataGrid({
  rows: people,            // array, or a signal/getter for live data
  columns,
  selectable: true,        // row checkboxes
  pageSize: 8,
  onSelectionChange: (rows) => selCount.set(rows.length),
  onCellEdit: (e) => toast(`${e.field}: ${e.oldValue} → ${e.value}`),
})

// `rowEdit`: entering a cell opens every editable cell of its row; Tab
// walks them, Enter or leaving the row saves them as one onRowEdit
// ({row, changes}), Escape drops them. Ctrl+Z takes an edit back.
DataGrid({ rows: people, columns, rowEdit: true, onRowEdit: (e) => api.save(e.row, e.changes) })

// No pages: the table holds the rows in view and a spacer for the rest,
// however many there are. `height` fixes the viewport; rows are one
// height (measured, or `rowHeight`).
// `filters` puts a box under every column: text "contains", a number or
// date box that takes `>10`, `<=5`, `10..20`, `2024-03`, a list for
// a column of options. `filter: true` on one column does it for that one.
// `layout` lets the reader drag a header into another place, pin it left
// or right or hide it from the ⋮ on it, and bring columns back from the
// Columns button; `columnState` is that layout as a signal, to keep.
// `csv` puts a CSV button beside Columns; `apiRef.csv()` is the same rows
// as a string, `apiRef.download("rows.csv")` hands the reader the file.
DataGrid({ rows: crowd, columns, pageSize: 0, height: 320, filters: true, layout: true, csv: true })

// A source: the grid asks for every page — {page, pageSize, sort, filters,
// quick, edge, groupBy, keys} — and shows what comes back. With `groupable`
// a header dragged onto the panel groups the rows: every level is one
// question with `groupBy` and the path down to it as `keys`, answered
// with {key, count, ...aggregates} or, at the last level, the rows.
DataGrid({ source: (ask) => fetch("/rows?" + toQuery(ask)).then((r) => r.json()),
  columns, pageSize: 10, filters: true, groupable: true, jump: true })

// Every word the grid says is in `strings` (STRINGS has the English):
DataGrid({ rows, columns, strings: { filter: "Süz…", noData: "Kayıt yok",
  rows: (n) => `${n} kayıt`, range: (a, b, n) => `${n} kayıttan ${a}–${b}` } })

// The sort, the filter and the page are signals — hand them in to hold
// them, or read them off apiRef. With `total`, the rows are taken as
// already sorted, filtered and cut to the page by whoever made them: the
// grid shows what it is given and says what the reader asked for.
const sort = signal(null), filter = signal(""), page = signal(1);
const res = resource(() => (sort(), filter(), page(),
  fetch(`/api/people?${new URLSearchParams({ sort: JSON.stringify(sort()), q: filter(), page: page() })}`).then((r) => r.json())));
DataGrid({ rows: () => res.data()?.rows || [], total: () => res.data()?.total, sort, filter, page, columns })