demo D140
Host menu items and rail actions
Adding your own items to the grid menus from the application
menu extension
The configuration
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.28.0/lattice-grid.min.css">
<div id="grid"></div>
<script type="module">
import React from 'https://esm.sh/react@18';
import { createRoot } from 'https://esm.sh/react-dom@18/client';
import { createGrid } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.28.0/lattice-grid.esm.min.js';
import createLatticeGrid from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.28.0/modules/react.esm.min.js';
const LatticeGrid = createLatticeGrid({ React, createGrid });
// A string names a built-in; an object is the host's own, placed exactly where
// it sits so it can go between built-ins rather than only after them.
const actions = [
'undo', 'redo', 'restore', 'maximise',
{ name: 'failing-only', title: 'Filter to the failing services', icon: 'filter',
run: ({ grid }) => grid.filters.set({ col: 'state', op: 'eq', value: 'failing' }) },
{ name: 'clear-filters', title: 'Clear every filter', icon: 'restore',
run: ({ grid }) => grid.filters.clear() },
// Reads the selection rather than a cell. Nothing selected is a no-op rather
// than a sort of everything.
{ name: 'sort-by-cost', title: 'Sort by monthly cost, largest first', icon: 'sort-desc',
run: ({ grid }) => grid.sort.set([{ col: 'monthlyCost', dir: 'desc' }]) },
'-', 'export',
];
// defaults arrives already built, so the host can prepend, append, reorder or
// drop. Kept whole here and extended, keeping Copy and the rest.
const contextMenu = (params, defaults) => [
{ name: `Pin ${String(params.data?.service ?? 'this service')} to the top`, icon: 'pin',
action: () => params.grid.sort.set([{ col: 'service', dir: 'asc' }]) },
{ name: 'Filter to this owner', icon: 'filter',
// Disabled rather than hidden so the menu does not change height between
// cells, which is what makes a context menu hard to aim at.
disabled: params.colId !== 'owner',
action: () => params.grid.filters.set({ col: 'owner', op: 'eq', value: params.value }) },
{ separator: true },
...defaults,
];
const columns = [
{ field: 'service', title: 'Service', layout: { pin: 'start', width: 160 } },
{ field: 'owner', title: 'Owner', filter: { type: 'set' } },
{ field: 'state', title: 'State', filter: { type: 'set' },
cell: { decoration: 'dot', variant: { map: { healthy: 'success', degraded: 'warning', failing: 'danger', unknown: 'neutral' } } } },
{ field: 'openIncidents', title: 'Incidents', type: 'number', total: 'sum' },
{ field: 'monthlyCost', title: 'Monthly cost', type: 'number',
format: { style: 'currency', currency: 'USD' }, total: 'sum' },
];
const rows = [/* service records */];
function App() {
return (
<LatticeGrid
rowKey="id"
selection="multiple"
toolPanel={{ side: 'left', panels: ['columns', 'filters', 'quick'], exportName: 'lattice-host-actions', actions }}
contextMenu={contextMenu}
columns={columns}
rows={rows}
style={{ height: '540px' }}
/>
);
}
createRoot(document.getElementById('grid')).render(<App />);
</script>
Adding application items to the grid’s own menus
The header context menu, the cell context menu and the action rail all ship with a fixed set of built-in operations, but a host application usually has domain actions of its own: flag a row, open a record in another view, copy a value out in a different format. Menu extension puts those actions inside the grid’s existing menus rather than building a parallel one. It is the point most teams reach for once a JavaScript data grid is wired into a real application and the default sort, pin and hide items stop covering what a user needs to do from a row or a cell.
The menu extension option accepts a list of entries per menu, each with a label, a handler and an optional predicate that decides whether the item appears for the row, column or cell it was invoked against, so an action can be scoped to a single column type or hidden for read-only records. Entries render in the same list as the built-in items, sharing icon alignment and keyboard navigation rather than a bolted-on section. Because the predicate runs against the specific target of the invocation, filtering a hundred custom actions down to the two relevant ones costs a single function call per menu open, not a scan of the row or column set.
How do I add a custom action to a data grid’s context menu?
Register it through the menu extension option, supplying a label, a click handler and the menu it should appear in (header, cell or action rail). An optional predicate receives the row, column or cell the menu was opened against and returns whether the item should show, so custom actions can be scoped per column or hidden for particular rows without maintaining a separate menu.