Lattice Grid Buy a licence

api reference

Data View API Reference

createDataView: items laid out as template-drawn cards that wrap into rows, with selection, keyboard navigation in two dimensions, and drag to reorder or between views.

API reference › The data view module

All 29 pages Everything on one page → Developer guide →

The data view module

modules/dataview is the list view's card-grid sibling: items laid out as cards that wrap into rows, each card drawn by your own template. A person selects cards (single and multi), moves through them with the arrow keys in two dimensions, and drags a card to a new position; a drop indicator shows where it lands. It builds on nothing but the DOM - there is no grid factory to inject - and draws only the rows of cards in (or just beyond) the viewport, so 5,000 items cost the same as a screenful.

import { createDataView } from '@toclocoinc/lattice-grid/modules/dataview';

const view = createDataView(document.querySelector('#cards'), {   // give the host a height
  data: [{ id: 1, name: 'Ada' }, { id: 2, name: 'Grace' }],
  template: (item) => `<h4>${item.name}</h4>`,
  itemWidth: 180, itemHeight: 96, gap: 12,    // or columns: 4 to fill the width
  selection: 'multi',
  dragMode: 'both',
});

Layout. Cards are itemWidth by itemHeight (default 200 by 120) with gap (default 8) between them, and as many as fit go on a row. Set columns: n instead and exactly n cards share the width, so the row is always full; itemWidth is then ignored. The view resizes with its host. Give the host element a height - the view fills it and scrolls inside it.

The list view's API, reused. view.data, view.selection and the event names are the list view's, and live in one shared file both modules import, so serialize(), filter(), sort() and the rest mean the same thing on both and always read back what is on screen. A returned template string goes through the same sanitiser the grid applies to template output; a returned element is used as it is.

Keyboard and pointer. A click selects a card; the platform modifier (Cmd on macOS, Ctrl elsewhere) toggles one into a 'multi' selection and Shift extends a range. One card is in the tab order; the arrow keys move between cards in two dimensions (Left and Right wrap across rows, Up and Down keep the column), Home and End go to the ends of a row (with the platform modifier, of the whole set), PageUp and PageDown move a screenful, Space toggles, Enter activates (itemDoubleClick) and Shift with an arrow extends the selection. In 'single' mode the selection follows the focus.

Drag and drop. dragMode is the list view's: 'source' lets a card be picked up (reordering within the view, or sending it to another), 'target' receives, 'both' does either and false (the default) neither; dragCopy: true leaves the source card behind. A card dropped before the card whose left half the pointer is in lands there; afterDrop reports the new order and serialize() already matches the screen. A drag between two data views, or to and from a listview list, goes through the target's single beforeDrop: return false (or a Promise of it) and both sides are left exactly as they were. A reorder within one view is refused, with the reason announced, while a sort or filter is active, because the drop position would have no single meaning in the stored order.

Accessibility. The view is an ARIA grid of rows of gridcell cards with aria-rowcount, aria-colcount, aria-rowindex and aria-colindex, and aria-selected when selection is on. With no items it shows emptyText (default “No items”).

MemberDescription
createDataView(el, config)Create the view. config.template is required; the host needs a height.
data.parse / add / remove / removeAll / update / getItem / getIndex / getLength / forEach / serialize / filter / sortThe list view's Data API, over the view's own items: serialize() is always the order on screen, and filter(fn) hides without losing.
selection.add(id) / selection.remove(id) / selection.getId() / selection.clear()Select, deselect, read (bare id/null in 'single' mode, always an array in 'multi'), or clear the selection.
on(name, fn) / off(name, fn)Events, the list view's: itemClick, itemDoubleClick, the cancellable beforeDrop, afterDrop, dragOut, selectionChange, and template-bubbled input/change.
destroy()Remove the view's DOM and listeners, drop every handler. The host still owns the element.

Layout, the data API and selection, executed

Cards per row from a width and from a column count, five items seeded, an insert, a patch, a sort, a filter that hides without losing anything, selection, and the redrawn cards in the same order as serialize() - run headless on every build with the suite's own DOM test double.

const { createTestDom } = await import('../packages/dom/src/renderer/testdom.js');
const { createDataView } = await import('../packages/modules/dataview/index.js');
const { computeColumns } = await import('../packages/modules/dataview/layout.js');

const fit = computeColumns({ width: 640, itemWidth: 100, gap: 10 }).columns;   // 5 cards of 100 and 4 gaps of 10 = 540
const filled = computeColumns({ width: 640, columns: 4, gap: 10 }).cardWidth;   // (640 - 3 * 10) / 4

const seen = [];
const host = createTestDom().root;
const view = createDataView(host, {
  data: [{ id: 1, name: 'Ada' }, { id: 2, name: 'Grace' }, { id: 3, name: 'Linus' }],
  key: 'id',
  template: (item) => `<h4>${item.name}</h4>`,
  itemWidth: 100,
  itemHeight: 60,
  columns: 2,
  gap: 10,
  css: 'person',
  selection: 'multi',
  dragMode: 'both',
  dragCopy: false,
  emptyText: 'No people',
});
view.on('selectionChange', (e) => seen.push(`sel:${e.ids.join(',')}`));

view.data.add({ id: 4, name: 'Margaret' }, 1);
view.data.update(2, { name: 'Grace H.' });
const added = view.data.serialize().map((i) => i.id).join(',');
view.data.sort('name', 'desc');
const sorted = view.data.serialize().map((i) => i.id).join(',');
view.data.filter((item) => item.name.length < 6);
const shown = `${view.data.serialize().length}/${view.data.getLength()}`;
view.data.filter();
view.data.sort();
view.selection.add([1, 3]);
view.selection.remove(1);
const selected = view.selection.getId().join(',');
view.selection.clear();
await Promise.resolve();                                                     // the cards redraw once, after a burst of changes
const cards = [...host.querySelectorAll('.lat-dv-card.person')]
  .sort((a, b) => a.getAttribute('data-index') - b.getAttribute('data-index'))
  .map((c) => c.getAttribute('data-key')).join(',');
const rows = host.querySelector('[role="grid"]').getAttribute('aria-rowcount');
view.destroy();
return [fit, filled, added, sorted, shown, selected, cards, rows, seen.join('|')].join(' || ');

Type reference

Generated from the type declarations, so it always matches the release. Each surface lists its properties, its methods and the events it raises as three tables; an option or value type lists its members once.

The data view module

DataViewConfig

The configuration for {@link createDataView}: items laid out as cards that wrap into rows, each card drawn by `template`. Cards are `itemWidth` wide and as many as fit go on a row, or `columns` of them share the width.

PropertyTypeDescription
dataDataViewItem[]The initial items. Default `[]`. (optional)
keystringThe field each item's identity comes from. Default `'id'`. (optional)
itemWidthnumberCard width in pixels; as many cards as fit go on a row. Default `200`. Ignored when `columns` is set. (optional)
itemHeightnumberCard height in pixels. Default `120`. (optional)
columnsnumberA fixed number of cards per row; the cards share the width equally, so `columns: n` fills it. (optional)
gapnumberThe gap between cards, in pixels. Default `8`. (optional)
cssstring | string[]Class name(s) added to every card. (optional)
selectionfalse | 'single' | 'multi'Selection mode. Default `false`. (optional)
dragModefalse | 'source' | 'target' | 'both'This view's role in a drag: `'source'` and `'both'` reorder within the view and send to another; `'target'` and `'both'` receive. Default `false`. (optional)
dragCopyboolean`true` leaves the source card in place on a drop between views (copy, not move). Default `false`. (optional)
emptyTextstring | HTMLElementThe empty-state message or element. (optional)
MethodSignatureParametersReturnsDescription
template(item: DataViewItem) => string | HTMLElementitem: DataViewItem=> string | HTMLElementCalled per item to draw its card; a returned string goes through the grid's template sanitiser, an element is used as is.

DataViewDataApi

`view.data`: the list view's Data API, over the view's own items. `serialize()` is always the order on screen.

MethodSignatureParametersReturnsDescription
parse(items: DataViewItem[]): voiditems: DataViewItem[]voidReplace the whole dataset, keyed by `DataViewConfig.key`.
add(item: DataViewItem, index?: number): voiditem: DataViewItem
index?: number
voidInsert an item at a display index (default: end).
remove(id: unknown): voidid: unknownvoidRemove the item with this identity.
removeAll(): void - voidRemove every item.
update(id: unknown, patch: Partial<DataViewItem>): voidid: unknown
patch: Partial<DataViewItem>
voidShallow-merge `patch` into the item identified by `id`.
getItem(id: unknown): DataViewItem | undefinedid: unknownDataViewItem | undefinedThe item stored under an identity, or undefined.
getIndex(id: unknown): numberid: unknownnumberThe item's current display index, or -1 when not currently displayed.
getLength(): number - numberThe total stored item count, filtered or not.
forEach(fn: (item: DataViewItem, index: number) => void): voidfn: (item: DataViewItem, index: number) => voidvoidIterate the items, in current display order.
serialize(): DataViewItem[] - DataViewItem[]The items in current on-screen order.
filter(fn?: (item: DataViewItem) => boolean): voidfn?: (item: DataViewItem) => booleanvoidHide items a predicate refuses, without removing them; call with no arguments to clear.
sort(fnOrField?: ((a: DataViewItem, b: DataViewItem) => number) | string, dir?: 'asc' | 'desc'): voidfnOrField?: ((a: DataViewItem, b: DataViewItem) => number) | string, dir?: 'asc' | 'desc'voidChange display order: a comparator, or a field name with `dir` (default `'asc'`).

DataViewSelectionApi

`view.selection`: the list view's selection API, over the view's own selection.

MethodSignatureParametersReturnsDescription
add(id: unknown | unknown[]): voidid: unknown | unknown[]voidSelect one item, or several, alongside whatever is already selected.
remove(id: unknown | unknown[]): voidid: unknown | unknown[]voidDeselect one item, or several.
getId(): unknown | unknown[] | null - unknown | unknown[] | nullIn `'single'` mode: the selected id, or null. In `'multi'` mode: always an array.
clear(): void - voidDeselect everything.

DataViewItemEvent

The payload `itemClick`/`itemDoubleClick` carry.

PropertyTypeDescription
idunknownThe clicked card's item identity.
itemDataViewItem | nullThe clicked item, or null.
indexnumberThe card's display index.
originalEventunknownThe originating DOM event.

DataViewBeforeDropEvent

The cancellable `beforeDrop` payload: return `false` (or a Promise of it) to veto, leaving both views unchanged.

PropertyTypeDescription
sourceDataViewPeer | nullThe view the dragged card came from.
targetDataViewPeerThis view, the drop target.
idsunknown[]The dragged item's identity, as a one-element array.
indexnumberThe display index the card would be inserted at.

DataViewAfterDropEvent

The `afterDrop` payload: a move or copy between two views, or a reorder within one, committed.

PropertyTypeDescription
sourceDataViewPeer | nullThe view the card came from.
targetDataViewPeer | nullThe view the card landed in.
idsunknown[]The moved/copied item's identity, as a one-element array.
indexnumberThe display index the card landed at in the target.

DataViewDragOutEvent

The `dragOut` payload: a drag moved out of this view.

PropertyTypeDescription
idunknownThe dragged item's identity.
indexnumberThe card's display index before the drag.

DataViewSelectionChangeEvent

The `selectionChange` payload: always an array, whatever the selection mode.

PropertyTypeDescription
idsunknown[]The selected ids.

DataViewTemplateEvent

The `input`/`change` payload: an event bubbled from inside a card's template, carrying the item's identity.

PropertyTypeDescription
idunknownThe card's item identity.
itemDataViewItem | nullThe card's item, or null.
originalEventunknownThe originating DOM event.

DataViewEventPayloads

The payload each data view event name carries.

PropertyTypeDescription
itemClickDataViewItemEventA card was clicked.
itemDoubleClickDataViewItemEventA card was double-clicked, or Enter was pressed on it.
beforeDropDataViewBeforeDropEventA drop between views is about to land; return `false` (or a Promise of it) to veto it.
afterDropDataViewAfterDropEventA reorder, move or copy committed.
dragOutDataViewDragOutEventA drag moved out of this view.
selectionChangeDataViewSelectionChangeEventThe selection changed.
inputDataViewTemplateEventAn `input` event bubbled from inside a card's template.
changeDataViewTemplateEventA `change` event bubbled from inside a card's template.

DataView

The instance {@link createDataView} returns.

Properties
PropertyTypeDescription
dataDataViewDataApiThe Data API.
selectionDataViewSelectionApiThe selection API.
Methods
MethodSignatureParametersReturnsDescription
on<K extends DataViewEventName>(name: K, fn: (payload: DataViewEventPayloads[K]) => void): () => voidname: K
fn: (payload: DataViewEventPayloads[K]) => void
() => voidSubscribe to a data view event; returns an unsubscribe function.
off<K extends DataViewEventName>(name: K, fn: (payload: DataViewEventPayloads[K]) => void): voidname: K
fn: (payload: DataViewEventPayloads[K]) => void
voidRemove an event handler registered with `on`.
destroy(): void - voidTear the view down: remove its DOM and listeners, drop handlers. The host still owns the element.
Events

This surface raises events, but the names are not declared: on() accepts any string. They cannot be listed here until the declaration names them.