CnObjectKanban
Kanban board over a schema property's distinct values — one column per value
of groupByField, cards paginated per column ("load more"), and drag-to-move
wired to the host's own object write (there is deliberately no bespoke "move
card" endpoint).
Wraps: CnCellRenderer, vuedraggable
Try it
Loading CnObjectKanban playground…
Two rendering modes
- Pre-built columns — pass
columns, the shape returned by OpenRegister'sGET /api/views/{id}/kanban([{ value, cards, total, limit, offset }]). The host owns pagination: clicking "load more" only emitsload-more, and the host re-fetches and passes updatedcolumns. - Flat objects — pass
objects+groupByField(optionallycolumnOrderandschema). The component derives and paginates columns itself, using the same precedence as the backend (ViewPresentationService::deriveColumnValues()): explicitcolumnOrder> the property's schemaenumorder > distinct values discovered inobjects.
Props
Props
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
objects | Array<object> | [] | Flat objects to derive columns from (ignored when columns is set). | |
columns | union | null | Pre-built columns, e.g. the response of GET /api/views/{id}/kanban. | |
groupByField | string | ✓ | — | The schema property whose distinct values become columns. |
columnOrder | array | null | Explicit column order. Takes precedence over the schema's enum order. | |
cardFields | array | [] | Object fields rendered on each card (in order). | |
schema | object | null | Schema definition, used to resolve enum column order and card field types. | |
loading | boolean | false | Overall loading state (initial board fetch). | |
loadingColumns | array | [] | Column values currently loading more cards (drives the per-column spinner). | |
pageSize | number | 20 | Cards shown per column before "load more", in local (objects-driven) mode. | |
rowKey | string | 'id' | Object property used as each card's identity. |
Events
| Name | Payload | Description |
|---|---|---|
card-click | — | Emitted when a card is clicked (not dragged). |
load-more | — | Emitted when a column's "load more" is clicked. |
move | — | Emitted after a card is optimistically moved to another column. The host performs the actual object write through the existing guarded PATCH/PUT endpoint and calls resolveMove/ rejectMove on this component to confirm or roll back. |
move-rejected | — | Emitted after rejectMove() rolls a card back to its origin column. |
Slots
| Name | Bindings | Description |
|---|---|---|
empty | — | empty Custom empty state shown when there are no columns to render. |
column-header | column | column-header Override a column's header. |
card | object, column | card Fully replace the default card rendering. |
Methods
| Name | Description |
|---|---|
resolveMove | Confirm a pending move succeeded — clears its pending visual state. The card already sits in the destination column (optimistic UI); no further mutation is needed. |
rejectMove | Roll back a rejected move: the card returns to its origin column and move-rejected fires with the server's reason. |
Besides objects/columns/groupByField/columnOrder/cardFields/schema
above: loading shows the board-level loading state; loadingColumns marks
which column values are currently fetching a "load more" page (per-column
spinner); pageSize sets how many cards a locally-derived column shows
before "load more"; rowKey is the object property used as each card's
identity (defaults to id).
Drag-to-move contract
Dragging a card to another column does not write anything by itself. The component:
- Applies the move optimistically (the card renders in the destination column immediately).
- Emits
movewith{ object, groupByField, fromValue, toValue }. - Waits for the host to call back:
resolveMove(objectId)— the write succeeded; clears the pending state.rejectMove(objectId, reason)— the write was rejected (e.g. an illegalx-openregister-lifecycletransition); the card snaps back to its origin column andmove-rejectedfires with the reason to surface.
<CnObjectKanban
ref="kanban"
:objects="objects"
group-by-field="status"
:column-order="['todo', 'doing', 'done']"
:card-fields="['title', 'assignee']"
:schema="schema"
@move="onMove"
@move-rejected="onMoveRejected"
@load-more="fetchMoreCards" />
async onMove({ object, groupByField, toValue }) {
try {
await patchObject(object.id, { [groupByField]: toValue })
// Optional — clears the pending style sooner than the next refetch:
this.$refs.kanban.resolveMove(object.id)
} catch (e) {
this.$refs.kanban.rejectMove(object.id, e.message)
}
},
onMoveRejected({ reason }) {
showError(reason)
},
Slots
#empty— custom empty state when there are no columns.#column-header="{ column }"— override a column's header.#card="{ object, column }"— fully replace the default card rendering (the default renders a title plus each configuredcardFieldsentry viaCnCellRenderer).