Lattice Grid Buy a licence

demo D92

Undo and redo in htmx

A history stack over edits, fills and pastes

grid.history

This grid keeps a history stack across edits, fills and pastes, so a user can step back out of a mistake and forward again. It gives direct manipulation a safety net, so people edit confidently without reloading data to recover.

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.107.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="undo-and-redo-table" data-lattice-grid>
  <thead>
    <tr>
      <th data-field="id">Order</th>
      <th data-field="reference">Reference</th>
      <th data-field="team">Team</th>
      <th data-field="stage">Stage</th>
      <th data-field="due">Due</th>
      <th data-field="circuits">Circuits</th>
      <th data-field="impact">Impact</th>
      <th data-field="effort">Effort</th>
      <th data-field="budget">Budget</th>
      <th data-field="notes">Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr data-id="WO-00001"><td>WO-00001</td><td>REQ-9090</td><td>central.birmingham</td><td>done</td><td>2026-01-01</td><td>27</td><td>3</td><td>5</td><td>8580</td><td>Customer asked for an evening window.</td></tr>
    <tr data-id="WO-00002"><td>WO-00002</td><td>INC-7104</td><td>south.london</td><td>done</td><td>2026-02-02</td><td>20</td><td>4</td><td>70</td><td>23160</td><td>Second engineer required for the roof section.</td></tr>
    <tr data-id="WO-00003"><td>WO-00003</td><td>REQ-7362</td><td>south.london</td><td>blocked</td><td>2026-03-03</td><td>30</td><td>3</td><td>55</td><td>17685</td><td>Awaiting a wayleave from the landlord.</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.107.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('undo-and-redo-table'), {
    selection: { mode: 'multiple', ranges: true },
    // One history stack over every kind of write: a typed cell, a fill, a paste
    // and a range clear each land as a single step, so undo reverses the whole
    // gesture rather than one cell at a time. Ctrl-Z steps back, Ctrl-Shift-Z
    // forward, and the toolbar buttons do the same.
    edit: { enabled: true, undoDepth: 100 },
    toolPanel: { side: 'left', panels: ['columns'], actions: ['undo', 'redo', 'restore'], exportName: 'work-orders' },
    columns: [
      { field: 'id', title: 'Order', layout: { width: 120, pin: 'start' } },
      { field: 'reference', title: 'Reference', layout: { width: 130 } },
      { field: 'team', title: 'Team', filter: { type: 'set' }, edit: true },
      { field: 'stage', title: 'Stage', filter: { type: 'set' },
        lookup: { options: [
          { id: 'planned', label: 'Planned' },
          { id: 'approved', label: 'Approved' },
          { id: 'in-flight', label: 'In-flight' },
          { id: 'blocked', label: 'Blocked' },
          { id: 'done', label: 'Done' },
        ] },
        edit: { enabled: true, editor: 'select', props: { allowClear: true } } },
      { field: 'due', title: 'Due', type: 'date', layout: { width: 140 }, edit: true },
      { field: 'circuits', title: 'Circuits', type: 'number', layout: { width: 110 }, edit: true },
      { field: 'impact', title: 'Impact', type: 'number', layout: { width: 100 }, edit: true },
      { field: 'effort', title: 'Effort', type: 'number', layout: { width: 100 }, edit: true },
      { field: 'budget', title: 'Budget', type: 'number', layout: { width: 140 },
        format: { style: 'currency', currency: 'GBP' }, total: 'sum', edit: true },
      { field: 'notes', title: 'Notes', layout: { flex: 1, min: 220 }, edit: { enabled: true, editor: 'textarea' } },
    ],
  });
</script>

Reversing edits with a grid-level history stack

Undo and redo give a user a way back out of a mistaken edit, fill, or paste without reloading data or hand-correcting cells. A developer reaches for this wherever the grid accepts direct manipulation rather than a form: inline editing, drag-fill, range paste, or a bulk edit. Lattice Grid tracks these mutations through grid.history, which records each committed change as a single entry regardless of how many cells it touched, so a paste covering four hundred cells undoes in one step rather than four hundred. The stack groups by transaction rather than by keystroke, which matters for a JavaScript data grid used for data entry: a user editing one cell across several keypresses gets one undo step for the commit, not one per character. Entries store the affected range and prior values rather than a full grid snapshot, so undoing a change on a hundred-thousand-row dataset costs the same as undoing one on a hundred-row dataset, and the operation replays through the same validation and change-detection path as a live edit, so a reverted cell re-triggers any dependent formula recalculation or row-level validation exactly as the original edit did. Keyboard handling follows platform convention, with Ctrl+Z and Ctrl+Shift+Z (or Cmd on macOS) wired to the stack by default and available as direct calls for a custom toolbar.

How do I add undo and redo to a JavaScript data grid?

Use grid.history, which maintains a stack of committed edits, fills, and pastes and exposes undo() and redo() alongside the default Ctrl+Z / Ctrl+Shift+Z bindings. Each stack entry represents one transaction, so a multi-cell paste or bulk edit reverses in a single step rather than one call per affected cell.