TreeListViewDocumentation · v2.0.0
Pricing

What's new in 2.0

TreeListView 2.0 adds seven features. Nothing from 1.x changes: every new behaviour is behind a new property, method or event, except motion, which a grid gains by default (animations="off" removes it), and the find bar, which F3 opens (and Ctrl+F, when the grid has no filter box). Code written against 1.x runs unmodified.

New in 2.0
Rows added, removed and moved addNodes, removeNodes, moveNodes, tlvRowsChange
Change tracking trackChanges, getChanges, acceptChanges, rejectChanges, tlvChangesChange
Moving rows by drag and keyboard dropBehavior="move", Alt+Shift+arrows, tlvNodeMove
Pinned parents stickyAncestors
Tree lines treeLines
Value filter valueFilters, in / notIn, getColumnValues, setValueFilter
Find allowFind, find, findNext, findPrevious, closeFind
Motion animations

Rows added, removed and moved

await grid.addNodes([{ id: 'eng-9', cells: { name: 'Search', owner: 'Ana' } }], { nodeId: 'eng' });
await grid.addNodes([{ id: 'ops', cells: { name: 'Operations' } }], { nodeId: 'eng', position: 'after' });
await grid.moveNodes(['eng-9'], { nodeId: 'ops', position: 'inside', index: 0 });
await grid.removeNodes(['eng-3', 'eng-4']);

A target is a row (nodeId) and a position: 'inside' (the default; index places the rows among its children), 'before' or 'after'. With no nodeId the rows go to the top level. New nodes may bring children. A row whose ancestor is also moved or removed goes with that ancestor.

With allowUndo on, each call is one undo step. Every change fires tlvRowsChange:

grid.addEventListener('tlvRowsChange', e => {
  const { kind, nodes, places, source } = e.detail; // 'add' | 'remove' | 'move'; 'api' | 'drag' | 'keyboard' | 'undo' | 'redo'
  saveStructure(kind, nodes, places);
});

With a server-side dataSource the rows belong to the server, and all three methods return without changing anything.

Change tracking

<bm-treelistview track-changes="true" editable="true" allow-undo="true"></bm-treelistview>

Edited cells get a corner mark, new rows a green edge, moved rows a violet one. getChanges() lists everything since tracking began, the data was replaced, or changes were last accepted:

const { added, updated, moved, removed } = await grid.getChanges();
// updated: [{ node, cells: [{ nodeId, columnId, previousValue, value }] }]
await api.save({ added, updated, moved, removed });
await grid.acceptChanges();   // the rows as they are now become the starting point
// or: await grid.rejectChanges();  // every row back as it was

tlvChangesChange reports the counts ({ hasChanges, added, updated, moved, removed }) whenever they move, which is enough to enable a Save button. Values your application writes with setData, setItemsData, updateNode or updateNodeChildren are the data's own, never changes.

Marks: --tlv-change-color, --tlv-new-row-color, --tlv-moved-row-color. Screen readers hear "changed", "new row" and "moved row" with the cell or row.

Moving rows by drag and keyboard

<bm-treelistview allow-drag-drop="true" drop-behavior="move" allow-undo="true"></bm-treelistview>

With dropBehavior: 'move' the grid moves the rows itself (the default, 'report', keeps 1.x behaviour: tlvNodeDrop only). Dragging a selected row drags every selected row. Holding over a collapsed parent opens it; holding near the top or bottom edge scrolls. A drop into a dragged row is refused.

From the keyboard: Alt+Shift+↑/↓ move the active row among its siblings, Alt+Shift+→ puts it under the row above, Alt+Shift+← lifts it out of its parent.

Refuse a move by cancelling tlvNodeMove:

grid.addEventListener('tlvNodeMove', e => {
  if (e.detail.nodes.some(node => node.cells.locked)) e.preventDefault();
});

Pinned parents

<bm-treelistview sticky-ancestors="3"></bm-treelistview>

While scrolling a deep tree, the parents of the rows at the top stay pinned under the header, up to the number of levels given. Clicking one scrolls back to it. --tlv-sticky-bg, --tlv-sticky-shadow; parts sticky-ancestors, sticky-row.

Tree lines

<bm-treelistview tree-lines="solid"></bm-treelistview>   <!-- or "dotted" -->

Connector lines from each parent to its children. --tlv-tree-line-color, --tlv-tree-line-width.

Value filter

With show-column-filters="true", each filter cell gains a button that opens a searchable checklist of the column's values with their counts, blanks included. An active value filter shows as a chip such as "3 values". Turn the button off with value-filters="false".

Two new filter operators carry it, and work with setColumnFilters too:

await grid.setColumnFilters([{ columnId: 'region', operator: 'in', value: ['EMEA', 'APAC'] }]);
await grid.setValueFilter('status', ['Open', null]);   // null stands for blank cells
const values = await grid.getColumnValues('owner');     // [{ value, label, count }]

Find

F3 inside the grid opens a find bar (Ctrl+F does too, unless show-filter is on, where Ctrl+F still focuses the filter box). It finds text in every row, collapsed ones included, in display order and within the current filters; shows "3 of 17"; highlights every match; and Enter, F3 and Shift+Enter or Shift+F3 step through them, opening the way to each. Escape closes it. From code:

const count = await grid.find('invoice');
await grid.findNext();
await grid.closeFind();

allowFind="false" turns the bar off. Parts find-bar, find-input, find-count, find-button.

Motion

Rows ease in as a parent opens or rows are added, glide to their new places after a sort or a move, changed cells glow for a moment - including values updated live with updateNode - and the expander turns. animations="off" turns it all off, and it is off whenever the reader's system asks for reduced motion. --tlv-motion-duration, --tlv-flash-color.