Editing
A column becomes editable when the grid has an editor for it. Edits leave the grid as events: the application writes the value where the rows come from (a server, a database, the store) and answers, while the grid keeps the draft, the order and the cell’s state correct under live updates. The orders example edits orders in place while the market streams in.
Editors
Section titled “Editors”with_editor(column, editor) gives a column an editor, set_editor changes it later. orders uses every built-in one:
let grid = LiveGridState::new(model::schema(), Orders { commands }, store, cx) .with_selection_mode(SelectionMode::RowSet) .with_change_paint(gpui_kit_grid::ChangePaint::Directional(DirectionalChanges)) .with_editor(OrderColumn::Side, SelectEditor::new(options(&model::SIDES))) .with_editor(OrderColumn::Tif, SelectEditor::new(options(&model::TIFS))) .with_editor( OrderColumn::Quantity, NumberEditor::new().with_min(OwnedValue::Int(1)), ) .with_editor(OrderColumn::Limit, NumberEditor::new().with_scale(2)) .with_editor(OrderColumn::Expiry, DateEditor::date()) .with_editor(OrderColumn::Active, CheckboxEditor) .with_editor(OrderColumn::Note, TextEditor::new().with_max_length(80)) .with_row_drag_gesture(SelectedRowsDrag::new()) .with_row_drop_policy(AccountDrop);| Editor | Edits |
|---|---|
TextEditor |
Text; with_max_length |
NumberEditor |
An integer, a float or a decimal; minimum, maximum, scale, decimal separator |
SelectEditor |
One of a searchable list of SelectOptions |
DateEditor |
A date from a calendar (date()), optionally with a time |
CheckboxEditor |
A boolean; toggles at once, with no field |
CellEditor is the trait for your own.
Which cells
Section titled “Which cells”LiveGridDelegate::is_editablerefuses a cell for now: a filled order, a row the user may not change.validate_editrefuses a value with a message. The field stays open with a red frame and the message under the cell;render_edit_messagereplaces the message’s look.
The keys follow Excel:
- Typing, F2, Backspace or Delete (with an empty draft) and a double click on an editable cell open the field.
- In the field, Enter commits and moves down, Shift+Enter up, Tab right, Shift+Tab left; ↑ and ↓ commit and move; Escape cancels.
- Leaving the field commits a valid draft.
- Outside the field, Tab still leaves the grid.
The field’s keys are bindings in the LiveGridEditor key context. with_edit_gesture replaces the rest of the gesture: an AG Grid-like single-click edit, or no double-click edit where a double click must run the primary command. Two defaults change earlier behavior: a double click on an editable cell edits instead of running the primary command, and + and - on an editable cell type into the draft instead of expanding or collapsing. A custom EditGesture restores either.
Commits
Section titled “Commits”A commit leaves the grid as LiveGridEvent::EditCommit. In orders, the window sends it to a simulated server thread:
LiveGridEvent::EditCommit { edit, key, column, value, base, current,} => { let conflict = if base == current { "" } else { " (over a newer value)" }; self.last_commit = Some( format!( "Sent {} of order {key}: {}{conflict}", column.id(), value_text(value) ) .into(), ); self.last_answer = Some("Server: thinking…".into()); let _ = self.commands.try_send(Command::Edit { edit: *edit, key: *key, column: *column, value: value.clone(), });}EditCommit carries base, the value when the edit started, and current, the store’s value at the commit: when they differ, the commit goes over someone else’s change, and the application decides what that means.
The answer comes back with resolve_edit, on the UI thread:
// The server's answers settle the commits.let answering = cx.spawn(async move |this, cx| { while let Ok((edit, result)) = answers.recv().await { let alive = this.update(cx, |root, cx| { root.last_answer = Some(match &result { Ok(()) => "Server: accepted".into(), Err(reason) => format!("Server: {reason}").into(), }); let result = result.map_err(EditError::new); root.grid .update(cx, |grid, cx| grid.resolve_edit(edit, result, cx)); cx.notify(); }); if alive.is_err() { break; } }});- After a commit the cell shows the new value, muted, until the store has it: at most 5 s after
resolve_edit(Ok). Sorting, filters, groups and aggregates follow the store, not the optimistic value. resolve_edit(Err)brings back the store’s value with an error mark until the cell is edited again.- There is no answer timeout (answer with an error on your own timer) and no undo.
CellStyleContext::edit_state lets cell_style restyle cells that wait or failed.
Under live updates
Section titled “Under live updates”- The field is bound to the row key and the column, not to a position. While it is open the built-in reorder policies hold the order, so the row stays under it.
- When the store changes the cell under an open draft, the draft stays and the field shows the conflict (“Changed to … while you edited”). With
with_edit_policy(FollowLiveValue), a draft the user has not touched follows the store instead. - If a filter or a collapsed group hides the row, the edit is cancelled and
EditCancelreturns the draft.
EditPolicy holds these rules; StandardEditPolicy is the default.
Controls
Section titled “Controls”A checkbox or a button in a Control column commits through the same path as an editor: CellElementContext::edit() returns a handle whose commit emits EditCommit (Cells).
Events
Section titled “Events”EditStart, EditCommit and EditCancel report the session with the key and the column; the orders status bar shows what every edit came to. start_edit(&key, column, window, cx) opens an editor from code: orders opens the limit’s editor for its Amend command.
Apache-2.0, built on gpui-kit. Repository Sponsor More from GigLabo Privacy Licenses