Inline Edit Changeset
When inline editing is enabled on a Table block, the changeset system tracks every cell edit before anything is saved — giving users (and your app) a chance to review, batch-save, or discard all changes at once. This page covers the changeset data model, state bindings, save/discard actions, and the recommended pending-changes UX pattern.
Overview
Without changeset mode, each cell edit saves immediately as soon as the user leaves the cell. With changeset mode enabled (enableChangeset: true), edits are held in a client-side buffer — the changeset — and the table shows modified cells with a visual indicator (typically a yellow dot or border) until the user explicitly saves or discards. This makes bulk editing significantly safer and more user-friendly.
Note: Use changeset mode when users may edit multiple rows or fields before committing, or when you want an explicit Save/Discard confirmation flow. Use immediate save when each cell change should persist instantly — for example, toggling a status column in an admin tool where the change is self-contained and reversible.
Changeset State Variables
Access changeset state from anywhere on the page using the table block's ID:
| State Key | Type | Description |
{{ table.changeset }} | array | All pending changes. Each element is a changeset entry object (see Changeset Object Shape below). Empty array when no unsaved changes exist. |
{{ table.changesetCount }} | number | Number of modified cells in the changeset. Note: one row with three edited fields contributes 3 to this count, not 1. |
{{ table.isDirty }} | boolean | true when there is at least one unsaved change in the changeset. Use this to show/hide a "Save changes" bar or to warn users before navigating away. |
{{ table.changesetRowCount }} | number | Number of distinct rows that have been modified. Different from changesetCount when a single row has multiple edited fields. |
Changeset Object Shape
Each item in {{ table.changeset }} has the following structure:
| Key | Type | Description |
rowId | string or number | The unique identifier of the modified row, from the column designated as the row ID. |
field | string | The field name (column key) that was modified. |
oldValue | any | The original value of the cell before any edits. Preserved at the time the user first enters the cell for editing. |
newValue | any | The current (pending) value of the cell. Updates every time the user changes the cell's value while it is in the buffer. |
rowData | object | The full row record with all fields, with the modified field(s) for this row reflecting their pending values. Use this when your save action needs the full record context, not just the changed field. |
Configuration Properties
| Property | Type | Default | Description |
enableChangeset | boolean | false | Turn on changeset buffering. When false, inline edits save immediately. When true, all edits are held in the changeset until explicitly saved or discarded. |
changesetSaveAction | data source action | — | The data source action called when the changeset is saved. Typically a bulk-update endpoint that accepts an array of changed records. The action receives {{ table.changeset }} as its input. |
conflictMode | overwrite | merge | ask | overwrite | Determines how the save handles conflicts when the underlying data has changed since the user started editing. overwrite — the pending value wins without asking. merge — non-conflicting fields from the server version are preserved; the edited fields are overwritten. ask — shows a conflict resolution dialog for each affected row. |
Save and Discard Actions
Two block-level methods manage the changeset:
| Method | Description |
saveChangeset | Triggers the changesetSaveAction with the current changeset as input. On success, clears the changeset and re-fetches the table data to reflect the saved values. On failure, the changeset is preserved — the user can retry. |
discardChangeset | Resets all edited cells to their original values and clears the changeset. The table display reverts to the last-fetched data. No API call is made. |
Call these methods from any button or event handler using the standard Call Method action targeting the table block.
Best Practice: Pending Changes Bar
The recommended UX pattern is a floating bar that appears at the top or bottom of the table whenever there are unsaved changes. It shows the count of pending changes and provides Save and Discard buttons.
Floating pending changes bar — binding configuration
// Container: visibility condition — show only when changes are pending visibility: {{ orders_table.isDirty }} // Text label inside the bar label: {{ orders_table.changesetRowCount + " row(s) with unsaved changes" }} // "Save" button onClick → Call Method: orders_table.saveChangeset // "Discard" button onClick → Call Method: orders_table.discardChangeset // Optional: navigate-away warning // Bind the page's onBeforeUnload event to check isDirty and show a confirm dialog
Tip: Place the pending-changes bar in a sticky container above or below the table, not inside it. This ensures it stays visible even when the user scrolls down through a long table of edited rows.
Example: Bulk Price Editor
Changeset configuration for a product price bulk-edit table
// Table configuration enableChangeset: true changesetSaveAction: bulkUpdatePrices conflictMode: ask // bulkUpdatePrices data source input (REST — PATCH /api/products/bulk) body: {{ JSON.stringify(products_table.changeset.map(c => ({ id: c.rowId, price: c.newValue // assuming only the price column is editable }))) }} // Confirmation dialog before saving (optional extra step): // Add a Confirm Dialog action before saveChangeset in the action chain: // "You are about to update prices for {{ products_table.changesetRowCount }} products. Continue?"
Frequently Asked Questions
Does the changeset persist across page refreshes?
No. The changeset lives in memory for the duration of the page session. A full page refresh clears all pending changes without saving them. If users need to preserve draft edits across sessions, save the changeset to a data source or page variable on change and restore it on load — though this is rarely necessary for typical inline-edit workflows.
Can I save only some changes from the changeset, not all of them?
The built-in saveChangeset method saves the entire changeset. To save selectively, read {{ table.changeset }} into a JS expression, filter the entries you want to save, and pass the filtered array directly to a Trigger Data Source action. The changeset itself is not automatically cleared in this case — call discardChangeset afterward or implement a custom clear mechanism.
What happens if the save API call fails?
The changeset is preserved on save failure — cells remain in their edited state with pending indicators. The table does not revert. This allows the user to retry saving after the underlying issue is resolved (e.g. network error, server validation failure). Handle the data source error event to show a notification explaining the failure.