Skip to content

Actions

Actions connect user interactions to backend logic.

In the fluent API, actions are declared via ActionSupplier and handled via ActionHandler. Buttons reference actions by actionId.


@Route(value = "/my-page", parentRoute = "")
public class MyPage implements ComponentTreeSupplier, ActionSupplier, ActionHandler {
@Override
public Form component(HttpRequest httpRequest) {
return Form.builder()
.title("My page")
.content(List.of(
new Text("${state.count}"),
Button.builder()
.actionId("increment")
.label("Count")
.build()
))
.build();
}
@Override
public List<Action> actions() {
return List.of(
Action.builder()
.id("increment")
.build()
);
}
@Override
public Object handleAction(String actionId, HttpRequest httpRequest) {
return new State(Map.of("count", httpRequest.getInt("count") + 1));
}
}
  1. The Button declares which action it triggers via actionId
  2. actions() declares the action and its behavior
  3. handleAction() is called when the action fires

The default execution mode. The UI is blocked while the action runs.

Action.builder()
.id("action")
.build() // foreground by default

Use for actions that should complete before the user can interact again.


The action runs without blocking the UI. The user can keep interacting.

Action.builder()
.id("action")
.background(true)
.build()

Use for long-running operations (imports, exports, processing) that should not freeze the screen.


Stream multiple updates from the backend to the browser as the action progresses.

@Override
public List<Action> actions() {
return List.of(
Action.builder()
.id("to-server")
.sse(true)
.build()
);
}
@Override
public Object handleAction(String actionId, HttpRequest httpRequest) {
return Flux
.range(1, 10)
.map(i -> new State(Map.of("count", i)))
.delayElements(Duration.ofMillis(100));
}

Return a Flux<Object> to stream state updates. Each emitted value updates the browser progressively.

Use for: progress indicators, live data feeds, long operations with step-by-step feedback.


Runs client-side validations before calling the backend. If validation fails, the action is not called.

Action.builder()
.id("submit")
.validationRequired(true)
.build()

See Validations for how to declare the validation rules.


Shows a confirmation dialog before calling the backend.

Action.builder()
.id("delete")
.confirmationRequired(true)
.confirmationTexts(new ConfirmationTexts(
"Confirm deletion",
"Are you sure you want to delete this item?",
"Yes, delete",
"Cancel"
))
.build()

ConfirmationTexts(title, message, confirmLabel, denyLabel) customizes the dialog. Omitting it uses default texts.


Navigates the browser to an external URL instead of calling the backend.

Action.builder()
.id("go-to-google")
.href("https://www.google.es")
.build()

No handleAction call is made. The browser follows the link directly.


Runs JavaScript in the browser without a server call.

Action.builder()
.id("increment-local")
.js("state.count = state.count ? state.count + 1 : 1;")
.build()

The JS runs with access to state, data, appState, and appData in scope. No server call is made.

Use for: purely client-side interactions, quick state updates that don’t need persistence.


Fires a custom browser event instead of (or in addition to) other behavior.

Action.builder()
.id("emit-event")
.customEvent(new CustomEvent("my-event", new Detail("payload", 42)))
.js("state.count = state.count + 1;") // can also run JS at the same time
.build()

Other components or parent pages can listen to this event via OnCustomEventTrigger:

@Override
public List<Trigger> triggers(HttpRequest httpRequest) {
return List.of(
new OnCustomEventTrigger("handle-event", "my-event"),
new OnCustomEventTrigger("server-action", "my-event-to-server")
);
}

Use for: communication between nested components and parent pages.


The action is only enabled when one or more rows are selected in a listing.

Action.builder()
.id("process-selected")
.rowsSelectedRequired(true)
.build()

Inside handleAction, get the selected rows:

@Override
public Object handleAction(String actionId, HttpRequest httpRequest) {
var selected = httpRequest.getSelectedRows(Row.class);
// process selected rows
return null;
}

PropertyTypeEffect
idStringUnique identifier; matched by Button.actionId
backgroundbooleanRun without blocking UI
ssebooleanStream updates as Flux
validationRequiredbooleanRun validations first
confirmationRequiredbooleanShow confirmation dialog first
hrefStringNavigate to external URL
jsStringRun JS in browser
customEventCustomEventFire a browser custom event
rowsSelectedRequiredbooleanOnly enabled when rows selected
confirmationTextsConfirmationTextsCustomize confirmation dialog

Inside handleAction, read the current form state via HttpRequest:

@Override
public Object handleAction(String actionId, HttpRequest httpRequest) {
int count = httpRequest.getInt("count");
String name = httpRequest.getString("name");
var params = httpRequest.getParameters(Map.class);
return new State(Map.of("count", count + 1));
}