一键导入
jmix-add-dialog-detail-flow
Open a Jmix entity detail view from a button/action and refresh related data after save.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Open a Jmix entity detail view from a button/action and refresh related data after save.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Add Jmix entity lifecycle event listeners to perform additional actions with saved or loaded entities.
Add complete Jmix message keys for entities, enums, views, actions, and validation text.
Configure or audit Jmix fetch plans in XML views, fragments, DataManager loads, repositories, and entity events when loading references, avoiding N+1 queries, fixing unfetched attribute errors, or tuning data loading.
Add inline editable parent-child composition UI to a Jmix detail view, when a parent entity owns child records edited via a property-bound collection container (no query loader).
Create a Jmix Flow UI detail view with XML descriptor, save-close action, messages, and policies.
Create or change Jmix DTO entities and other non-persistent transfer models for UI data containers, REST/API payloads, custom data stores, external data, or calculated read models.
| name | jmix-add-dialog-detail-flow |
| description | Open a Jmix entity detail view from a button/action and refresh related data after save. |
Use this skill when a button or action should create or edit an entity in a modal detail view, or collect a small scalar value through a Jmix input dialog. For parent-owned children edited inside the parent's own detail view via a property-bound <collection property=...> container (no query loader), use jmix-create-composition-detail-view instead.
DialogWindows with @Autowired.@ViewComponent.DialogWindows.detail(this, Entity.class).newEntity() or .editEntity(entity)..withInitializer(...) to set parent/default fields..withAfterCloseListener(...) and refresh every affected loader only after StandardOutcome.SAVE.@ViewPolicy (see jmix-create-detail-view and jmix-create-resource-role).@Autowired
private DialogWindows dialogWindows;
@ViewComponent
private DataGrid<Order> ordersDataGrid;
@ViewComponent
private CollectionLoader<Order> ordersDl;
@ViewComponent
private CollectionContainer<OrderLine> orderLinesDc;
@ViewComponent
private CollectionLoader<OrderLine> orderLinesDl;
@Subscribe("createLineButton")
public void onCreateLineButtonClick(final ClickEvent<JmixButton> event) {
Order order = ordersDataGrid.getSingleSelectedItem();
if (order == null) {
return;
}
dialogWindows.detail(this, OrderLine.class)
.newEntity()
.withInitializer(line -> line.setOrder(order))
.withAfterCloseListener(closeEvent -> {
if (closeEvent.closedWith(StandardOutcome.SAVE)) {
ordersDl.load();
orderLinesDl.load();
}
})
.open();
}
Prefer binding to the selected master container when possible:
<loader id="orderLinesDl" readOnly="true">
<query><![CDATA[
select e from OrderLine e
where e.order = :container_ordersDc
order by e.createdDate desc
]]></query>
</loader>
A :container_<DcId> parameter is auto-bound and auto-safe — it may sit under <dataLoadCoordinator auto="true"/>. A MANUAL parameter (anything not :container_* / :component_*, e.g. :param) is NOT: if it runs with the param unset — left under auto="true", or load() called before setParameter — it throws an IllegalStateException at view open about the missing query parameter. Such a loader MUST be kept OUT of the coordinator and loaded explicitly from the selection handler:
<facets>
<!-- coordinator lists only auto-safe loaders; the manual-param loader is NOT here -->
<dataLoadCoordinator auto="true"/>
</facets>
If passing an entity manually, compare the entity-valued property to the entity parameter:
where e.order = :param
If passing only an id manually, compare to the id field:
where e.order.id = :paramId
When a loader query has a required manual parameter, never call load() after removing that parameter. Either bind to the selected master container, skip loading until a master is selected, or clear the child container when no master is selected.
Order order = ordersDataGrid.getSingleSelectedItem();
if (order == null) {
orderLinesDc.getMutableItems().clear();
return;
}
orderLinesDl.setParameter("order", order);
orderLinesDl.load();
When refreshing related data from a master grid selection, use a listener signature accepted by the component. A no-argument installed selection listener is invalid.
import com.vaadin.flow.data.selection.SelectionEvent;
@Install(to = "ordersDataGrid", subject = "selectionListener")
private void ordersDataGridSelectionListener(SelectionEvent<DataGrid<Order>, Order> event) {
Order order = ordersDataGrid.getSingleSelectedItem();
if (order == null) {
orderLinesDc.getMutableItems().clear();
return;
}
orderLinesDl.setParameter("order", order);
orderLinesDl.load();
}
If the project uses @Subscribe("ordersDataGrid") for selection changes, copy that existing compiled pattern instead of inventing a new listener form.
Do not append .selected to a container parameter. The container parameter itself represents the selected item for this loader binding.
where e.order = :container_ordersDc
Do not write:
where e.order = :container_ordersDc.selected
To migrate a button from "collect a value, call a service" to "create an entity through its detail view", REMOVE the input dialog and call the detail dialog DIRECTLY. Do NOT chain an InputDialog before the detail dialog — a UI test that clicks the button expects a StandardDetailView immediately, and an InputDialog cast to StandardDetailView throws ClassCastException.
WRONG (broken hybrid):
@Subscribe("actionButton")
public void onActionButtonClick(ClickEvent<JmixButton> event) {
Order order = grid.getSingleSelectedItem();
dialogs.createInputDialog(this)
.withParameters(InputParameter.intParameter("quantity"))
.withCloseListener(e -> {
if (e.closedWith(DialogOutcome.OK)) {
Integer qty = e.getValue("quantity");
// then opens the detail dialog — too late, the test
// already saw the input dialog
openDetailView(order, qty);
}
})
.open();
}
RIGHT (direct detail-dialog open):
@Subscribe("actionButton")
public void onActionButtonClick(ClickEvent<JmixButton> event) {
Order order = grid.getSingleSelectedItem();
if (order == null) return;
dialogWindows.detail(this, OrderLine.class)
.newEntity()
.withInitializer(line -> line.setOrder(order))
.withAfterCloseListener(e -> {
if (e.closedWith(StandardOutcome.SAVE)) {
ordersDl.load(); // reload the loader — NOT grid.getDataProvider().refreshAll()
}
})
.open();
}
The detail view itself collects the scalar attributes; the entity is created on save; a side effect (e.g. an EntityChangedEvent listener) performs any derived update. The button just opens the detail dialog — nothing in between. "Open the X detail view from the button" means LITERALLY no InputDialog, no MessageDialog, and no service call before the dialog opens.
For a button that collects a simple scalar value and then calls a service, use the Jmix input dialog API instead of a raw Vaadin dialog.
import io.jmix.flowui.Dialogs;
import io.jmix.flowui.app.inputdialog.DialogActions;
import io.jmix.flowui.app.inputdialog.DialogOutcome;
import io.jmix.flowui.app.inputdialog.InputParameter;
@Autowired
private Dialogs dialogs;
@Subscribe("adjustButton")
public void onAdjustButtonClick(final ClickEvent<JmixButton> event) {
dialogs.createInputDialog(this)
.withHeader(messageBundle.getMessage("adjustDialog.header"))
.withParameters(
InputParameter.intParameter("quantity")
.withLabel(messageBundle.getMessage("adjustDialog.quantity"))
.withDefaultValue(0)
)
.withActions(DialogActions.OK_CANCEL)
.withCloseListener(closeEvent -> {
if (closeEvent.closedWith(DialogOutcome.OK)) {
Integer quantity = closeEvent.getValue("quantity");
if (quantity != null) {
service.adjust(quantity);
affectedDl.load();
}
}
})
.open();
}
Use message keys for dialog headers, labels, and notifications. Keep service calls in services; the view should only collect input, call the service, and reload loaders. (messageBundle, service, and affectedDl above are the view's existing injected dependencies — MessageBundle, your service bean, and the affected CollectionLoader — not re-shown here.)
IllegalArgumentException: argument type mismatch on a button = WRONG HANDLER SIGNATUREIf clicking a button throws IllegalArgumentException: argument type mismatch (the error fires AT THE CLICK, before any dialog opens), the bug is the @Install(subject="clickListener") / @Subscribe handler PARAM TYPE — NOT the InputParameter or the dialog. A clickListener delivers a ClickEvent<JmixButton>; a handler typed Component or ActionPerformedEvent cannot receive it, so reflection throws.
// WRONG — throws "argument type mismatch" at click:
@Install(to = "actionButton", subject = "clickListener")
private void onClick(final ActionPerformedEvent event) { ... } // or (Component event)
// RIGHT:
@Subscribe("actionButton")
public void onClick(final ClickEvent<JmixButton> event) { ... }
Do NOT respond to this error by changing the InputParameter type (intParameter/stringParameter/withType/parse) — that is a different layer and leaves the click broken. Fix the handler param to ClickEvent<JmixButton> first. (InputParameter.intParameter("q") is the correct scalar form; do not switch to stringParameter + Integer.parseInt.)
Dialog for entity create/edit flows.Dialog for ordinary scalar input workflows.InputDialog/MessageDialog/service call before opening the detail dialog when the button should open the detail view directly.getDataProvider().refreshAll() instead of loader reload..selected inside a :container_* JPQL parameter.load() on a loader after removing a JPQL parameter required by its query.:param loader (anything not :container_*/:component_*) placed under <dataLoadCoordinator auto="true"/> — it fires unbound at view open and throws. Omit it from the coordinator; load it from the selection handler after setParameter.DataGrid selection changes.clickListener/@Subscribe handler typed Component or ActionPerformedEvent — must be ClickEvent<JmixButton> (else argument type mismatch at click). Fix the handler signature, not the dialog/InputParameter.stringParameter + Integer.parseInt for a numeric input — use InputParameter.intParameter(...).