demo D29
Multi-value cells in React
Tags in one cell, with containsAny and containsAll filtering
multiple: true
This grid holds several values in one cell, the way tags or skills attach to a record, and filters them with 'contains any' and 'contains all'. It fits fields where a row belongs to more than one category, without splitting into extra columns.
This is the React version. The grid mounts through the createLatticeGrid adapter, which takes its configuration as ordinary props and hands back the live grid through a ref. The grid below is the same one every other tab runs.
The configuration
<link rel="stylesheet" href="https://cdn.latticegrid.dev/lattice-grid@1.107.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.latticegrid.dev/lattice-grid@1.107.0/lattice-grid.esm.min.js';
import createLatticeGrid from 'https://cdn.latticegrid.dev/lattice-grid@1.107.0/modules/react.esm.min.js';
const LatticeGrid = createLatticeGrid({ React, createGrid });
const TAGS = [
{ id: 'pci', label: 'PCI', variant: 'danger' },
{ id: 'gdpr', label: 'GDPR', variant: 'warning' },
{ id: 'ipv6', label: 'IPv6', variant: 'info' },
{ id: 'bgp', label: 'BGP', variant: 'info' },
{ id: 'managed', label: 'Managed', variant: 'success' },
{ id: 'legacy', label: 'Legacy', variant: 'neutral' },
{ id: 'monitored', label: 'Monitored', variant: 'success' },
];
const SITES = [
{ id: 1, label: 'Aberdeen edge', variant: 'neutral', group: 'Edge' },
{ id: 3, label: 'London core', variant: 'success', group: 'Core' },
{ id: 5, label: 'Slough transit', variant: 'info', group: 'Transit' },
{ id: 7, label: 'Retired rack', variant: 'danger', group: 'Core', disabled: true },
];
const columns = [
{ field: 'hostname', title: 'Hostname', layout: { width: 280, pin: 'start' } },
{
field: 'tags', title: 'Tags', layout: { flex: 1, min: 240 },
// multiple: true makes the cell an array rather than a value, and brings
// the set operators with it: containsAny, containsAll and containsNone,
// rather than equality against a joined string.
lookup: { options: TAGS, multiple: true, separator: ', ' },
cell: { decoration: 'pill' },
},
{
field: 'tags', id: 'count', title: 'Tag count', type: 'number', layout: { width: 130 },
value: { deps: ['tags'], pure: true, compute: (d) => (Array.isArray(d.tags) ? d.tags.length : 0) },
total: 'avg',
},
{
id: 'inScope', title: 'In audit scope', type: 'boolean', layout: { width: 150 },
// containsAll, written out. The filter below does the same to the rows;
// this does it to a value.
value: {
deps: ['tags'], pure: true,
compute: (d) => {
const t = Array.isArray(d.tags) ? d.tags : [];
return t.includes('pci') && t.includes('gdpr');
},
},
},
{
field: 'site', title: 'Site', type: 'lookup', layout: { width: 180 },
lookup: { options: SITES, unknownLabel: 'decommissioned' },
},
{ field: 'ports', title: 'Ports', type: 'number', layout: { width: 110 } },
];
const rows = [/* nodes, each with an array of tags */];
function App() {
return (
<LatticeGrid
rowKey="id"
toolPanel={{ side: 'left', panels: ['columns', 'filters', 'views', 'quick'],
actions: ['undo', 'redo', 'export', 'restore', 'maximise'], exportName: 'lattice-demo' }}
// containsAny, containsAll and containsNone match through the columnar
// store, so raising the threshold keeps the operators working across the
// whole dataset.
source={{ mode: 'memory', columnarBelow: 2000000 }}
columns={columns}
// Lands filtered, so the operator is visible rather than described. Clear
// it in the filters panel to get the other rows back.
state={{ filters: { op: 'and', conditions: [{ col: 'tags', op: 'containsAll', value: ['pci', 'gdpr'] }] } }}
rows={rows}
style={{ height: '540px' }}
/>
);
}
createRoot(document.getElementById('grid')).render(<App />);
</script>
Storing several values in one cell with multi-value columns
A multi-value cell holds more than one entry against a single row and column, the pattern behind tags, labels, skills and any field where a record can belong to several categories at once rather than exactly one. A developer reaches for it whenever a plain lookup column is too narrow for the data: a support ticket with three labels, a contact with two roles, a product in four categories. Lattice Grid turns a column into this shape with multiple: true, which changes how the cell reads and renders a value from a single code to an array of codes, each shown as its own chip within the cell, and changes how the filter menu behaves to match. Filtering follows the same split a reader would expect: containsAny matches a row if it carries at least one of the selected values, containsAll matches only rows carrying every one of them, and the two give different result sets over the same tag column whenever a row holds more than one tag. Rendering stays cheap at scale because each chip is a plain DOM node inside a recycled cell rather than a nested component, so a multi-value column in a JavaScript data grid scrolling past thousands of rows pays no more per row than the number of chips actually on screen.
How do you filter a column that holds several values per row?
Set multiple: true on the column so each cell stores an array rather than a single value, then choose the filter operator: containsAny for rows matching at least one selected tag, containsAll for rows matching every selected tag. Lattice Grid applies whichever operator is set against the full array on each row.