Skip to content

ActionHandler

ActionHandler is the low-level dispatch contract that receives named actions from the UI. In most cases you do not implement it directly — Mateu’s annotation processor generates the dispatch code for methods annotated with @Action, @Button, @Toolbar, and similar. Implement it yourself when you need runtime control over which action IDs your class accepts and how it responds.

public interface ActionHandler {
default List<String> supportedActions() { return List.of("*"); }
default boolean supportsAction(String actionId) { /* see below */ }
Object handleAction(String actionId, HttpRequest httpRequest);
}
MethodDescription
handleAction(actionId, httpRequest)Required. Called when the framework routes an action to this handler. Return a result object or null
supportsAction(actionId)Return true when this handler accepts the given action ID. The default implementation excludes all framework-reserved IDs
supportedActions()Declare the set of action IDs this handler claims. The default is ["*"] (catch-all)
Return typeEffect
nullNo UI change — Mateu re-renders the current state
Any UI componentReplaces the current view with the returned component
DataUpdates the component state with new key/value pairs
A form or page objectOpens the returned object as a new view or dialog

The default supportsAction implementation excludes the following IDs so the framework can handle them internally. Returning true for any of these in a custom implementation overrides the built-in behaviour.

Excluded suffixes:

SuffixPurpose
_createOpen the creation form
_create-and-staySave and remain on the creation form
_addAdd a row to an inline grid
_selectOpen a lookup/select dialog
_selectedConfirm a lookup selection
_prevNavigate to the previous record
_nextNavigate to the next record
_saveSave the current form
_removeDelete the current record
_move-upMove a row up in an ordered list
_move-downMove a row down in an ordered list
_cancelCancel the current edit

Excluded prefix: any action ID that starts with search-.

Prefer annotations (@Action, @Button, @Toolbar) for the common case. Implement ActionHandler directly when:

  • You need to handle action IDs determined at runtime (e.g. plugin-supplied IDs).
  • You are building a reusable component that must intercept a family of action names.
  • You are integrating an external action source and need full dispatch control.
@Override
public Object handleAction(String actionId, HttpRequest httpRequest) {
return switch (actionId) {
case "save" -> {
var state = httpRequest.getComponentState(OrderState.class);
orderService.save(state.toOrder());
yield null;
}
case "cancel" -> new Text("Cancelled");
default -> null;
};
}
@Override
public List<String> supportedActions() {
return List.of("approve", "reject", "escalate");
}
@Override
public Object handleAction(String actionId, HttpRequest httpRequest) {
var workflowId = httpRequest.lastPathItem();
workflowService.transition(workflowId, actionId);
return null;
}
@Override
public Object handleAction(String actionId, HttpRequest httpRequest) {
if ("export".equals(actionId)) {
var selected = httpRequest.getSelectedRows(CustomerRow.class);
exportService.export(selected);
}
return null;
}

ListingBackend and ReactiveListingBackend both extend ActionHandler. Their supportsAction override restricts dispatch to "search" and the action-on-row-* / action-on-view-* families, and their handleAction override invokes search(...) automatically. When you implement ListingBackend you are also providing an ActionHandler — you only need to override handleAction for row-level or toolbar actions not covered by search.