Lattice Grid Buy a licence

demo D29

Multi-value cells

Tags in one cell, with containsAny and containsAll filtering

multiple: true

Building…
Loading a live grid…

The configuration

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.27.0/lattice-grid.min.css">

<div id="grid" style="height: 540px"></div>

<script type="module">
  import { createGrid } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.27.0/modules/htmx.esm.js';

  // A row can hold several tags at once, each shown as its own pill.
  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', group: 'Edge' },
    { id: 2, label: 'London core', group: 'Core' },
    { id: 3, label: 'Manchester core', group: 'Core' },
    { id: 4, label: 'Slough transit', group: 'Transit' },
  ];

  const grid = createGrid(document.getElementById('grid'), {
    rowKey: 'id',
    toolPanel: {
      side: 'left',
      panels: ['columns', 'filters', 'views', 'quick'],
      actions: ['undo', 'redo', 'export', 'restore', 'maximise'],
      exportName: 'lattice-demo',
    },
    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 single 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: this does to a value what the filter below
        // does to the rows.
        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 } },
    ],
    // The set operators read from the columnar store, so raise the threshold to
    // keep them working across the whole dataset.
    source: { mode: 'memory', columnarBelow: 2_000_000 },
    // Land filtered, so the operator is visible rather than described. Clear it
    // from the filters panel to get the other rows back.
    state: {
      filters: { op: 'and', conditions: [{ col: 'tags', op: 'containsAll', value: ['pci', 'gdpr'] }] },
    },
    rows,  // nodes, each with an array of tags
  });
</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.