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.
addNodesreturnsfalsewhen an id is already in the grid or the target does not exist.moveNodesreturnsfalsefor a move into a row's own subtree, next to a row being moved, or nowhere new.removeNodesreturns how many subtrees were taken out; removed rows leave the selection.
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.