Lattice Grid Buy a licence

demo D139

The cell context menu in htmx

An item that writes is never offered on a cell that cannot be written

cell:contextmenu

This grid offers per-value actions, copy, clear, comment or edit, on right-clicking a cell, and never offers a write action on a cell that cannot be written. It puts the right actions under the pointer, so the menu matches what a reader can actually do to that cell.

Building…
Loading a live grid…

This is the htmx version. A server-rendered table is upgraded in place, so the page is readable before any script runs and gains the full grid when the module loads, which suits a progressively enhanced app.

The configuration

<link rel="stylesheet" href="https://cdn.latticegrid.dev/lattice-grid@1.108.0/lattice-grid.min.css">

<!-- A real, server-rendered table: readable with JavaScript off. data-field on
     each header names the column that cell becomes, and data-id on each row is
     the key the grid carries it under. -->
<table id="cell-context-menu-table" data-lattice-grid>
  <thead>
    <tr>
      <th data-field="service">Service (read only)</th>
      <th data-field="team">Team (read only)</th>
      <th data-field="owner">Owner (editable)</th>
      <th data-field="region">Region (editable)</th>
      <th data-field="instances">Instances (edit: false)</th>
    </tr>
  </thead>
  <tbody>
    <tr data-id="1"><td>svc-00001</td><td>Search</td><td>k.yilmaz</td><td>Malmö</td><td>49</td></tr>
    <tr data-id="2"><td>svc-00002</td><td>Mobile</td><td>m.svensson</td><td>Leeds</td><td>162</td></tr>
    <tr data-id="3"><td>svc-00003</td><td>Identity</td><td>j.okafor</td><td>Zürich</td><td>20</td></tr>
    <!-- the rest of the rows, rendered by your server -->
  </tbody>
</table>

<script type="module">
  import { hydrateTable } from 'https://cdn.latticegrid.dev/lattice-grid@1.108.0/modules/htmx.esm.min.js';

  // The markup carries the rows; a server-rendered table's column types are
  // declared in the config hydrateTable takes. The grid replaces the table
  // where it stood.
  const grid = hydrateTable(document.getElementById('cell-context-menu-table'), {
    selection: 'multiple',
    edit: { enabled: true },
    // Right-click any cell for its menu. The actions offered fit the cell, so an
    // editable cell offers the edit actions a read-only one leaves out.
    columns: [
      { field: 'service', title: 'Service (read only)', layout: { pin: 'start', width: 190 } },
      { field: 'team', title: 'Team (read only)', layout: { width: 150 } },
      { field: 'owner', title: 'Owner (editable)', edit: true, layout: { width: 170 } },
      { field: 'region', title: 'Region (editable)', edit: true, layout: { flex: 1, min: 180 } },
      { field: 'instances', title: 'Instances (edit: false)', type: 'number', edit: false, layout: { width: 180 } },
    ],
  });
</script>

Right-clicking a cell for a menu built from what that cell allows

A cell context menu is the natural place for per-value actions: copy, clear, comment, or edit, offered against the record under the pointer rather than as a permanent toolbar entry. The difficult part is not opening the menu but deciding its contents, since an item that writes is never offered on a cell that cannot be written. A read-only column, a cell disabled by a per-row rule, or a value a viewer lacks permission to change must each produce a shorter menu, not a full one with a disabled entry. Any JavaScript data grid handling mixed editable and locked columns needs this filtering done before the menu paints, not as a visual afterthought.

Lattice Grid exposes the interaction through the cell:contextmenu event, fired with the row, column, and value under the pointer, so a host application can inspect the same edit rules the grid uses and build a menu matching what the cell will accept. The default menu closes on outside click, on Escape, and once an item is chosen, and it is reachable without a mouse: the context-menu key and Shift+F10 open it against the focused cell. Menu construction runs once per invocation rather than being pre-built for every cell, so a wide grid with thousands of visible cells pays the permission-check cost only for the cell actually right-clicked.

Why does a cell’s context menu sometimes show fewer options than another cell in the same column?

Menu contents are computed per cell at the moment of the cell:contextmenu event, not fixed per column. A row-level rule, a permission check, or a value that fails validation can each remove a write action, so two cells in one column can legitimately offer different menus depending on the record behind them.