Tables
A plugin describes a table by its columns — a title, an optional description and a registered value type — and passes the rows. The shell renders each cell with the column’s type; an editable cell opens that type’s editor on double-click (or Enter) and reports the validated value. The table never writes state: you emit an event, a handler updates the store.
const { Table } = ctx.ui;
const columns = [ { key: "title", type: "text", title: ctx.i18n.key("table.title"), description: ctx.i18n.key("table.titleHint"), editable: true, width: "w-64", order: 10 }, { key: "due", type: "date", title: ctx.i18n.key("table.due"), order: 20 }, { key: "effort", type: "duration", title: ctx.i18n.key("table.effort"), order: 30 },];
<Table columns={columns} rows={rows} // [{ id, title, due, effort }, …] emptyText={ctx.i18n.key("table.empty")} onRowClick={(rowId) => ctx.emit("notes/document.select", { docId: rowId })} onCommit={(rowId, key, value) => ctx.emit("notes/document.rename", { value, context: { docId: rowId } })}/>- A column’s
typeis any registered value type id, including plugin types. title/descriptionare i18n keys, translated at render time; the description shows as the header tooltip.- Without
editable: true(or withoutonCommit) the column is read-only. Escape cancels an edit in progress.onRowClickonly fires on read-only cells, so it never fights with the double-click that starts an edit. configis passed to the value type (enum options, duration units…).- A row is identified by its
idfield;idKeynames another field. - In TypeScript you can also import
ValueTablefrom@/kerneldirectly. - Try it: install
/plugins/notes-tsx/index.jsand run Notes: Documents table — the title column is editable and reuses the rename event.
Sorting, large and asynchronous data
Section intitulée « Sorting, large and asynchronous data »Clicking a header cycles the sort: ascending, descending, none. The sort is saved with the table’s per-view state, like column widths. Only the visible rows are drawn (virtualized), so long tables stay fast.
A tables binding gives either the whole row list (sorted in memory by the
shell) or a paged provider, sync or async, for a database or a server:
ctx.ui.define({ tables: { customerRows: { async getRows({ offset, limit, sort }, context) { const { rows, total } = await db.query(context.viewId, { offset, limit, sort }); return { rows, total }; }, subscribe: (listener) => db.onChange(listener), // drop cached pages }, },});The table asks pages of 100 rows covering what is on screen, draws
placeholders while they load, and asks again when the sort changes or
subscribe fires. With a provider, sorting is yours: honour request.sort.
Adding and deleting rows
Section intitulée « Adding and deleting rows »Two table-level flags turn on a control column on the left. They are
independent of the per-column editable flag, which is what makes a cell
editable — including in the input row.
<Table columns={columns} rows={rows} creatable // input row on top, with a "+" button deletable // checkboxes + trash button (Delete key) onCreate={(values) => ctx.emit("notes-tsx/task.add", { docId, task: values })} onDelete={(rowIds) => ctx.emit("notes-tsx/task.remove", { docId, taskIds: rowIds })}/>creatableshows an extra row on top of the existing ones: every editable column is filled there with its type’s editor, and the+button of the control column reports the collected values as one object throughonCreate(Escape clears the input row). It stays disabled until something is typed, and the row is cleared once the values are reported.deletablegives each row a checkbox — plus a “select all” checkbox in the header — and a trash button that reports the checked row identifiers throughonDelete. TheDeletekey does the same when the table has focus and no cell is being edited.- Same rule as everywhere: the table emits, the handler writes. The table creates and deletes nothing by itself, and forgets a selected identifier as soon as that row leaves the data.
- Try it: install
/plugins/notes-tsx/index.js, create a note, unfold it in the Notes panel and open Tasks — name, description, due date and a done checkbox, with creation and deletion enabled.
Filters, column manager and auto-fit
Section intitulée « Filters, column manager and auto-fit »Every table gets a compact toolbar, with nothing to declare:
- Filter shows a filter row under the header. The editor follows the
column: options in
config.options→ multi-choice,boolean→ yes/no,number/integer/percent/duration/currencyanddate/datetime→ min/max range, anything else → “contains” on the value text. Active filters show as removable chips. Setfilterable: falseon a column to opt out. - Columns opens a small table of every column: drag a row to reorder, tick to show/hide, tick the right checkboxes and Fit selection to auto-fit a subset.
- Fit auto-fits every visible column; double-clicking a header edge or the header context menu fits one column (widest rendered cell, 60–480 px).
Filters live in the per-view state (TableViewState.filters,
filterRowVisible) like order, widths and sort. In-memory rows are filtered
by the shell (filterTableRows); a paged TableDataProvider receives them in
request.filters and applies them itself (arrayTableProvider already does).
Sub-rows (hierarchical tables)
Section intitulée « Sub-rows (hierarchical tables) »Add children to a table declaration to fold sub-items under each row:
"children": { "rows": { "ref": "subtaskRows" }, "hasChildren": "subtaskCount", "createKey": "name", "createEvent": "my-plugin/sub.add", "commitEvent": "my-plugin/sub.update", "deleteEvent": "my-plugin/sub.remove", "selectEvent": "my-plugin/sub.select"}- A chevron unfolds a row; unfolded rows are kept in per-view state (
expanded). rowsis atablesbinding evaluated with the view context plusparentId, only for unfolded parents. Children use the parent’s columns, by key.hasChildrennames a row field (number > 0 or true). WithcreateKeyand a create event, an unfolded parent shows a “+ sub-item” input that emits{ parentId, values: { [createKey]: text } }.- Commit / delete events default to the parent’s and always carry
parentId. A child click emitschildren.selectEvent(one level per selection). - Sorting applies within each level; children stay under their parent. Paged providers are not unfolded (in-memory rows only).
Documentation sous licence CC BY 4.0 · © les contributeurs de SCFlow ·Retour à supplychainflow.org