Lattice Grid Buy a licence

demo D280

Bring rows in from a CSV in ESM

Paste a block or drop a file, each column typed and mapped onto your fields, shown as a preview so nothing lands until you confirm; a spreadsheet block copied from Excel pastes the same way

grid.import.preview() · apply()

This grid brings rows in from a pasted block or a dropped CSV file, typing each column and mapping it onto your fields, shown as a preview so nothing lands until you confirm. It turns a spreadsheet export into grid rows without a separate import tool, and a block copied from Excel pastes the same way.

Building…
Loading a live grid…

This is the ES module version. The grid is imported straight from a CDN as a module, the same configuration as the vanilla tab without a global script tag, so it drops into any bundler or a bare module script.

The configuration

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

<textarea id="csv" rows="5" style="width: 100%; font: 12px ui-monospace, monospace">sku,name,category,price,stock
B-201,Floor lamp,Lighting,72,24
C-300,Bookshelf,Furniture,180,9</textarea>
<div style="margin: 8px 0">
  <button id="preview">Preview</button>
  <button id="confirm" disabled>Confirm and add</button>
  <!-- Only preview() writes here, so it carries its own placeholder until
       the button is clicked. -->
  <span id="note">Click "Preview" to check the text above before it lands.</span>
</div>
<div id="grid" style="height: 460px"></div>

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

  const grid = createGrid(document.getElementById('grid'), {
    rowKey: 'id',
    // import: true turns on the DOM affordances too: a drop target on the grid,
    // paste, and a cell-menu item, all running the same preview-then-confirm
    // pipeline as the buttons below. The grid.import API is present either way.
    import: true,
    columns: [
      { field: 'sku', title: 'SKU', layout: { width: 110, pin: 'start' } },
      { field: 'name', title: 'Product', layout: { flex: 1, min: 160 } },
      { field: 'category', title: 'Category', filter: { type: 'set' } },
      { field: 'price', title: 'Price', type: 'number', format: { style: 'currency', currency: 'GBP' }, total: 'sum' },
      { field: 'stock', title: 'In stock', type: 'number', total: 'sum' },
    ],
    rows,  // the rows already in the grid: sku, name, category, price, stock
  });

  let pending = null;

  // preview() parses the text, infers each column's type and maps it onto the
  // grid's own fields, and reports the row count and any warnings, all without
  // changing the grid. A spreadsheet block copied from Excel reads the same way.
  document.getElementById('preview').addEventListener('click', () => {
    pending = grid.import.preview(document.getElementById('csv').value);
    document.getElementById('note').textContent =
      'Ready to add ' + pending.rowCount + ' rows, ' + (pending.warnings?.length ?? 0) + ' warnings.';
    document.getElementById('confirm').disabled = pending.rowCount === 0;
  });

  // Nothing lands until apply(), so a ragged or mismatched file is caught first.
  // mode: 'append' adds the rows; mode: 'replace' would swap the set.
  document.getElementById('confirm').addEventListener('click', () => {
    grid.import.apply(pending, { mode: 'append' });
    pending = null;
    document.getElementById('confirm').disabled = true;
  });
</script>