api reference
List View API Reference
createList: a standalone, template-driven list over a real grid, with selection, drag to reorder, move or copy between lists, and a data API.
API reference › The list view module
All 29 pages Everything on one page → Developer guide →
The list view module
modules/listview is an opt-in, standalone list: selection, drag-and-drop reorder and
transfer between lists, and a data API, over one column of a real Lattice grid. House rule:
a module's list or table is a real grid - createList configures the
page's own createGrid (injected, never bundled, so the list shares the page's one
licensed core) with no header, no grid lines, one row per item, the row's cell drawn by your own
template. Virtual scrolling, keyboard navigation, ARIA and row drag all come from the
grid underneath for nothing - this module is wiring, not a second implementation of any of
them.
import { createList } from '@toclocoinc/lattice-grid/modules/listview';
const list = createList(document.querySelector('#list'), {
createGrid, // the page's own grid factory (injected, never bundled)
data: [{ id: 1, name: 'Ada' }, { id: 2, name: 'Grace' }],
template: (item) => `<div class="row">${item.name}</div>`,
selection: 'multi',
dragMode: 'both',
});
Drag and drop is two already-shipped grid primitives, wired, not rebuilt.
dragMode maps onto the inner grid's own rowReorder (the handle, and
reorder within one list) and rowTransfer (rows crossing between two grid instances):
'source' only sends, 'target' only receives, 'both' does
either, and false (the default) does neither. Neither list needs a shared group to
interact - any two lists on a page already match - so moving or copying
(dragCopy: true) between two listview instances needs no pairing
registry of its own. A target's beforeDrop handler may veto a drop by returning
false (or a Promise of it): the same veto-before-mutation gate the grid's own
rowTransfer uses guarantees a veto leaves both lists exactly as they were, and
afterDrop fires only once a move or copy has actually committed. A drag that leaves a
list without landing on another fires dragOut.
The Data API never drifts from what is on screen. list.data drives
every mutation through the inner grid's own keyed-diff, sort and filter surface, so
serialize() - the items in current on-screen order - can never disagree
with what is actually painted, and filter(fn) hides items without removing them from
the store (call with no arguments to clear it). list.selection is driven through the
same inner grid's own selection model, so a click, a keypress and a direct API call all agree; in
'single' mode getId() is the selected id or null, and in
'multi' mode it is always an array. An input or change event
fired by a control inside a row's own template bubbles out through list.on carrying
that row's item id, so a host never has to walk the DOM to know which item a field belongs to.
Row size, controls in templates and where a drag starts. The single item column
fills the list's width and follows host resizes, so a template's block content spans the row.
itemHeight is a number of pixels, or 'auto': every row is then sized to
its template with the grid's own auto row height (multi-line content is shown in full, it measures
at any list length, and a row whose item changes through data.update is measured
again). An HTML-string template is sanitised by the grid, which drops form controls; with
allowUnsafeTemplates: true it keeps input, select,
textarea, button and label elements and their attributes,
while script elements, event-handler attributes and javascript: URLs are
still removed. Typing or clicking in such a control reaches list.on('input' | 'change')
with the item id, and a click on a link, button, input, select, textarea or label does not also
select the row unless the control (or an ancestor) carries data-row-select.
dragHandle is 'grip' (the default, the drawn handle) or 'row',
which also starts a drag from anywhere on the row except on those controls or an element marked
data-no-drag; a double-click still selects a word. A row moved to another list leaves
the source completely - its rows, paint, order and selection - and a copy leaves the
source untouched.
| Member | Description |
|---|---|
| createList(el, config) | Create the list. config.createGrid and config.template are required. |
| data.parse(items) / data.add(item, index) / data.remove(id) / data.removeAll() | Replace the whole dataset, insert at a display index (default the end), remove by identity, or clear everything. |
| data.update(id, patch) / data.getItem(id) | Shallow-merge a patch into one item and re-render only that row; read the item stored under an identity. |
| data.getIndex(id) / data.getLength() / data.forEach(fn) | The item's current display index (-1 when filtered out or unknown); the total stored count; iterate in display order. |
| data.serialize() | The items in current on-screen order - always what is painted. |
| data.filter(fn) / data.sort(fnOrField, dir) | Hide items a predicate refuses without losing them (no arguments clears it); reorder by a comparator or a field name. |
| 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: itemClick, itemDoubleClick, the cancellable beforeDrop, afterDrop, dragOut, selectionChange, and template-bubbled input/change. |
| destroy() | Destroy the inner grid, remove the delegated listeners, drop every handler. The host still owns the element. |
The data API, selection and config knobs, executed
Three items seeded, an insert, a patch, a field sort, a filter that hides without losing anything, selection add/remove/clear, a removal and a full clear - run headless on every build with the suite's own DOM test double, since this module always needs a real element and an injected grid.
const { createTestDom, flushFrames } = await import('../packages/dom/src/renderer/testdom.js');
const { createGrid } = await import('../packages/dom/src/index.js');
const { createList } = await import('../packages/modules/listview/index.js');
const seen = [];
const host = createTestDom().root;
const list = createList(host, {
createGrid,
data: [{ id: 1, name: 'Ada', done: false }, { id: 2, name: 'Grace', done: true }, { id: 3, name: 'Linus', done: false }],
key: 'id',
template: (item) => `<div class="row">${item.name}</div>`,
itemHeight: 40,
css: 'demo-row',
selection: 'multi',
dragMode: 'both',
dragCopy: false,
dragHandle: 'row',
emptyText: 'No items',
});
list.on('selectionChange', (e) => seen.push(`sel:${e.ids.join(',')}`));
list.on('afterDrop', (e) => seen.push('afterDrop'));
const initial = list.data.serialize().map((i) => i.name).join(','); // Ada,Grace,Linus
list.data.add({ id: 4, name: 'Margaret', done: false }, 1);
const afterAdd = list.data.serialize().map((i) => i.id).join(','); // 1,4,2,3
list.data.update(2, { name: 'Grace H.' });
const updated = list.data.getItem(2).name; // Grace H.
list.data.sort('name');
const sorted = list.data.serialize().map((i) => i.id).join(',');
list.data.filter((item) => item.done === false);
const filteredCount = list.data.serialize().length;
const totalCount = list.data.getLength();
list.data.filter();
list.data.sort();
const restored = list.data.serialize().map((i) => i.id).join(','); // 1,4,2,3
const indexOf4 = list.data.getIndex(4);
list.selection.add([1, 4]);
const selected = list.selection.getId().join(',');
list.selection.remove(1);
const afterRemove = list.selection.getId().join(',');
list.selection.clear();
const cleared = list.selection.getId().join(',') || 'none';
list.data.remove(3);
const afterDelete = list.data.serialize().map((i) => i.id).join(',');
list.data.removeAll();
const empty = list.data.getLength();
const events = seen.join('|');
list.destroy();
return [initial, afterAdd, updated, sorted, filteredCount, totalCount, restored, indexOf4, selected, afterRemove, cleared, afterDelete, empty, events].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 list view module
ListViewConfig
The configuration for {@link createList}. `createGrid` is the page's own grid factory, injected so the list shares the page's one licensed core - `createList` wraps it as a one-column, headerless, borderless grid, one row per item, the cell drawn by `template`.
| Property | Type | Description |
|---|---|---|
| data | ListViewItem[] | The initial items. Default `[]`. (optional) |
| key | string | The field each item's identity comes from. Default `'id'`. (optional) |
| itemHeight | number | 'auto' | Row height in pixels, or `'auto'` to size every row to its template: multi-line content is shown in full (the grid's own auto row height, measured at any list length), and a row whose item changes through `list.data.update` is measured again. Default: the grid's row height. (optional) |
| allowUnsafeTemplates | boolean | `true` lets an HTML-string `template` keep form controls: `input`, `select` (with `option`), `textarea`, `button` and `label` elements and their attributes. `script`, `iframe`, `object`, `embed`, `form` and similar elements, event-handler attributes and `javascript:`, `data:` or `vbscript:` URLs are still removed. Default `false`, where the grid's own sanitiser also drops the controls. Typing into or clicking a control reaches `list.on('input' | 'change')` with the item's id; a click on a link, button, input, select, textarea or label does not also select the row, unless the control or an ancestor carries `data-row-select`. (optional) |
| dragHandle | 'grip' | 'row' | Where a drag starts. `'grip'` (default) is the drawn handle. `'row'` starts it from anywhere on the row except a link, button, input, select, textarea, label or an element marked `data-no-drag`; a double-click still selects a word, and the grip is still drawn. Applies to the lists that can start a drag (`dragMode` `'source'` or `'both'`). (optional) |
| css | string | string[] | Class name(s) added to every row. (optional) |
| selection | false | 'single' | 'multi' | Selection mode. Default `false`. (optional) |
| dragMode | false | 'source' | 'target' | 'both' | This list's role in a cross-list drag. Default `false`. (optional) |
| dragCopy | boolean | `true` leaves the source row in place on a drop (copy, not move). Default `false`. (optional) |
| emptyText | string | HTMLElement | The empty-state message or element. (optional) |
| contextMenu | boolean | The grid's own cell right-click menu (Paste, Clear, Fill down). Default `false` - a right-click on a row is `itemContextMenu`, not a menu meant for spreadsheet cells. (optional) |
| Method | Signature | Parameters | Returns | Description |
|---|---|---|---|---|
| createGrid | (el: HTMLElement, config: unknown) => unknown | el: HTMLElementconfig: unknown | => unknown | The grid factory the list mounts through (required). |
| template | (item: ListViewItem) => string | HTMLElement | item: ListViewItem | => string | HTMLElement | Called per item to draw its row; a returned string goes through the grid's own sanitiser (see `allowUnsafeTemplates`). |
ListViewDataApi
`list.data`: the Data API. Every method runs through the inner grid's own keyed-diff/sort/filter surface, so {@link serialize} never drifts from what is on screen.
| Method | Signature | Parameters | Returns | Description |
|---|---|---|---|---|
| parse | (items: ListViewItem[]): void | items: ListViewItem[] | void | Replace the whole dataset, keyed by `ListViewConfig.key`. |
| add | (item: ListViewItem, index?: number): void | item: ListViewItemindex?: number | void | Insert an item at a display index (default: end). |
| remove | (id: unknown): void | id: unknown | void | Remove the item with this identity. |
| removeAll | (): void | - | void | Remove every item. |
| update | (id: unknown, patch: Partial<ListViewItem>): void | id: unknownpatch: Partial<ListViewItem> | void | Shallow-merge `patch` into the item identified by `id`. |
| getItem | (id: unknown): ListViewItem | undefined | id: unknown | ListViewItem | undefined | The item stored under an identity, or undefined. |
| getIndex | (id: unknown): number | id: unknown | number | The item's current display index, or -1 when not currently displayed. |
| getLength | (): number | - | number | The total stored item count, filtered or not. |
| forEach | (fn: (item: ListViewItem, index: number) => void): void | fn: (item: ListViewItem, index: number) => void | void | Iterate the items, in current display order. |
| serialize | (): ListViewItem[] | - | ListViewItem[] | The items in current on-screen order. |
| filter | (fn?: (item: ListViewItem) => boolean): void | fn?: (item: ListViewItem) => boolean | void | Hide items a predicate refuses, without removing them; call with no arguments to clear. |
| sort | (fnOrField?: ((a: ListViewItem, b: ListViewItem) => number) | string, dir?: 'asc' | 'desc'): void | fnOrField?: ((a: ListViewItem, b: ListViewItem) => number) | string, dir?: 'asc' | 'desc' | void | Change display order: a comparator, or a field name with `dir` (default `'asc'`). |
ListViewSelectionApi
`list.selection`: the selection API, driven through the inner grid's own selection model.
| Method | Signature | Parameters | Returns | Description |
|---|---|---|---|---|
| add | (id: unknown | unknown[]): void | id: unknown | unknown[] | void | Select one item, or several, alongside whatever is already selected. |
| remove | (id: unknown | unknown[]): void | id: unknown | unknown[] | void | Deselect one item, or several. |
| getId | (): unknown | unknown[] | null | - | unknown | unknown[] | null | In `'single'` mode: the selected id, or null. In `'multi'` mode: always an array. |
| clear | (): void | - | void | Deselect everything. |
ListViewItemEvent
The payload `itemClick`/`itemDoubleClick` carry.
| Property | Type | Description |
|---|---|---|
| id | unknown | The clicked item's identity. |
| item | ListViewItem | null | The clicked item, or null. |
| index | number | The item's display index. |
| originalEvent | unknown | The originating DOM event. |
ListViewBeforeDropEvent
The cancellable `beforeDrop` payload: return `false` (or a Promise of it) to veto, leaving both lists unchanged.
| Property | Type | Description |
|---|---|---|
| source | ListView | import('lattice-grid/modules/dataview').DataView | null | The list or data view the dragged item(s) came from, or null when it did not come from one. |
| target | ListView | This list, the drop target. |
| ids | unknown[] | The dragged item's identity (identities), as a one-element array. |
| index | number | The candidate display index of the drop. |
ListViewAfterDropEvent
The `afterDrop` payload: a move or copy between two lists (or a reorder within one) committed.
| Property | Type | Description |
|---|---|---|
| source | ListView | import('lattice-grid/modules/dataview').DataView | null | The list or data view the item(s) came from. |
| target | ListView | import('lattice-grid/modules/dataview').DataView | null | The list or data view the item(s) landed in. |
| ids | unknown[] | The moved/copied item's identity (identities), as a one-element array. |
| index | number | The index the item(s) landed at in the target. |
ListViewDragOutEvent
The `dragOut` payload: a drag left this list without a cross-list drop landing.
| Property | Type | Description |
|---|---|---|
| id | unknown | The dragged item's identity. |
| index | number | The item's display index before the drag. |
ListViewSelectionChangeEvent
The `selectionChange` payload: always an array, regardless of `ListViewConfig.selection` mode.
| Property | Type | Description |
|---|---|---|
| ids | unknown[] | The selected ids. |
ListViewTemplateEvent
The `input`/`change` payload: an event bubbled from inside a row's template, carrying the item's identity.
| Property | Type | Description |
|---|---|---|
| id | unknown | The row's item identity. |
| item | ListViewItem | null | The row's item, or null. |
| originalEvent | unknown | The originating DOM event. |
ListViewItemContextMenuEvent
The `itemContextMenu` payload: a right-click on a row (and the context-menu key on a focused one).
| Property | Type | Description |
|---|---|---|
| id | unknown | The row's item identity. |
| item | ListViewItem | null | The row's item, or null. |
| event | Event | undefined | The originating DOM event, when the right-click came from the pointer (absent from the keyboard route). Call `preventDefault()` on it to suppress the browser's own menu; left alone otherwise. |
ListViewEventPayloads
The payload each list event name carries.
| Property | Type | Description |
|---|---|---|
| itemClick | ListViewItemEvent | A row was clicked. |
| itemDoubleClick | ListViewItemEvent | A row was double-clicked. |
| beforeDrop | ListViewBeforeDropEvent | A cross-list drop is about to land; return `false` (or a Promise of it) to veto it. |
| afterDrop | ListViewAfterDropEvent | A reorder, move or copy committed. |
| dragOut | ListViewDragOutEvent | A drag left this list without a cross-list drop landing. |
| selectionChange | ListViewSelectionChangeEvent | The selection changed. |
| input | ListViewTemplateEvent | An `input` event bubbled from inside a row's template. |
| change | ListViewTemplateEvent | A `change` event bubbled from inside a row's template. |
| itemContextMenu | ListViewItemContextMenuEvent | A row was right-clicked, or its context-menu key pressed while focused. |
ListView
The list instance {@link createList} returns.
Properties
| Property | Type | Description |
|---|---|---|
| data | ListViewDataApi | The Data API. |
| selection | ListViewSelectionApi | The selection API. |
Methods
| Method | Signature | Parameters | Returns | Description |
|---|---|---|---|---|
| on | <K extends ListViewEventName>(name: K, fn: (payload: ListViewEventPayloads[K]) => void): () => void | name: Kfn: (payload: ListViewEventPayloads[K]) => void | () => void | Subscribe to a list event; returns an unsubscribe function. |
| off | <K extends ListViewEventName>(name: K, fn: (payload: ListViewEventPayloads[K]) => void): void | name: Kfn: (payload: ListViewEventPayloads[K]) => void | void | Remove an event handler registered with `on`. |
| destroy | (): void | - | void | Tear the list down: destroy the inner grid, remove 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.