developer guide
Export and clipboard: JavaScript Data Grid Guide
Excel and CSV export written with no dependency, bringing rows in from a CSV or a pasted block with a preview, clipboard interop with Excel and Sheets, the <lattice-grid> web component, and the full event API.
Developer guide › Export and clipboard
Export
CSV, Excel, clipboard, print
Import
The mirror of export: rows coming in from a CSV or TSV file, a drop, or a pasted spreadsheet block. The pipeline is parse, infer each column's type, map the columns onto your fields, then preview and confirm - nothing lands until the user says so.
Clipboard
Copy produces the tab-separated form Excel, Numbers and Sheets all read, so a range pastes as cells rather than as one lump of text. Values go through each column's clipboard hook: a lookup copies its label, a date copies an unambiguous form.
- Guarding against formula injection on paste
- Previewing a bulk paste
- Clearing cells with Delete
- Undo and redo from the keyboard
Web component
<lattice-grid> is the grid as a custom element, shipped as a self-contained
module bundle. It exists for pages without a bundler, a Rails, Django or Laravel template
that wants a grid without adopting a front-end build.
The whole integration
<link rel="stylesheet" href="dist/lattice-grid.min.css">
<script type="module" src="dist/modules/webcomponent.esm.min.js"></script>
<lattice-grid row-key="id"
columns='[{"field":"id"},{"field":"city"}]'
rows='[{"id":"A","city":"Leeds"},{"id":"B","city":"Cardiff"}]'></lattice-grid>
Load this or the main bundle, not both. The module is self-contained: it
carries the grid with it, so a page that also loads lattice-grid.esm.js
downloads and evaluates the grid twice.
Driven from script
// Structures are properties; scalars are attributes.
const el = document.createElement('lattice-grid');
el.setAttribute('row-key', 'id');
el.columns = [{ field: 'id' }, { field: 'charge', type: 'number' }];
el.rows = data;
document.body.appendChild(el);
// The full imperative API is on `.grid`.
el.grid.sort.set([{ col: 'charge', dir: 'desc' }]);
Assign before appending where you can. The element builds its grid once, at the end of the task in which it connects, so everything set in that task arrives as one configuration rather than as a series of updates. Setting properties later still works: they go through the live configuration path, but the grid is built empty first and repainted after, which is a visible flash on a large set.
Reading .grid forces the build immediately, so the property is never briefly
null.
Events
el.addEventListener('lattice-cell-changed', (e) => {
console.log(e.detail.key, e.detail.colId, e.detail.value);
});
Every grid event is re-dispatched as a CustomEvent named lattice-
plus the grid name with colons hyphenated, so cell:edit:start becomes
lattice-cell-edit-start. The payload is event.detail. The prefix is
not decoration: the grid emits an event called scroll, and an unprefixed
CustomEvent of that name would be indistinguishable from the platform's own.
Forwarding is a wildcard subscription, so events added to the grid later appear here with no
change to the component.
Light DOM, deliberately. The element renders into itself rather than a
shadow root, because the grid's generated decoration rules are injected into
document.head and the theme stylesheet is a <link> the page
owns: neither crosses a shadow boundary. A shadowed grid would be structurally correct and
completely unstyled, and subtly so: the --lattice-* tokens do inherit
through a shadow root, so the colours would arrive while every pill, bar and heat cell stayed
bare. Light DOM keeps every documented theming route working unchanged.
Events
One bus, 111 events. The table below is the working set, the ones most applications actually reach for; a complete index by area follows it, and the reference lists them all.