In-place description editing¶
Detail pages can let users edit a rich-text description without opening the full edit form. The UI follows the classic task detail: a "Description" heading with a pencil, an "Add description" empty state, and CKEditor opened in place with Save and Cancel. Saving updates the block over AJAX.
The shared pieces live in core:
| Piece | Purpose |
|---|---|
EasyControllersConcerns::InlineDescription |
inline_description macro, description_edit and description_update actions |
InlineDescriptionHelper |
element ids and the image-upload rule |
easy_engines/easy_extensions/app/views/common/_inline_description.html.erb |
description block for the detail page |
common/_inline_description_form.html.erb, common/inline_description_edit.js.erb, common/inline_description_update.js.erb |
in-place editor and its responses |
spec/support/inline_description_requests.rb |
shared request examples |
CRM entities (accounts, contact groups, leads, partners, personal contacts, campaigns and opportunities) are the current users. The classic task detail still has its own implementation in EasyIssuesController.
Controller¶
Include the concern, call inline_description with the existing finder, and define a private inline_description_entity that returns the record the finder loaded. The attribute defaults to :description.
| Controller wiring | |
|---|---|
inline_description runs the finder for description_edit and description_update, so the finder's own only: list stays unchanged. Call it right after the finder so the filter order is preserved. Override inline_description_saved for side effects that the controller's update action performs and the description save also needs, such as notifications sent from the controller.
By default the user can edit when safe_attribute? allows the attribute and the record's editable? returns true. A controller can override inline_description_editable?(entity) instead; the override replaces the whole rule and also decides whether the page shows the pencil and the empty state. Contact groups do this with their controller's visibility rule (contact_group_visible?), because the model has no editable?.
Responses:
- a record the user cannot edit returns 403;
- a missing or nested attribute returns 400;
- a failed validation, or a save that loses an optimistic-locking race, returns 422 with
{ errors: [...] }, shown as a flash while the editor stays open; - a successful save re-renders the block, and for models with
lock_versionit updateswindow.easy_lock_versionfor the full edit form and inline attribute edits on the page.
Routes and permissions¶
| config/routes.rb | |
|---|---|
Add the member routes to the resource named after the model, because the views and shared specs build URLs with polymorphic_path. They may sit inside a rys_feature block like the rest of the resource. Grant description_edit and description_update in every permission that grants update, including permissions extended from other plugins with Redmine::AccessControl.update_permission.
View¶
| Detail page | |
|---|---|
Pass the model, not a decorator (for example @easy_lead.model when the view decorated @easy_lead). Place the block right after the header attributes or custom fields, before attachments, related lists and tabs. Nothing is rendered when the description is empty and the user cannot edit it.
Image upload is offered when Model.strips_inline_images?(attribute) is true, which matches what the full edit forms offer. Cancel keeps the CKEditor autosave draft, and reopening offers to restore it; a successful save clears the draft.
Specs¶
All shared examples need entity and attribute lets. "inline description editing" and "inline description parameter handling" need a record the user can edit; "inline description editing forbidden" needs one the user can see but not edit.