Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 34 additions & 23 deletions articles/flow/advanced/long-running-tasks.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -244,27 +244,31 @@ The modified [classname]`MainView` class below shows how to add a [classname]`Bu
@Route("")
public class MainView extends VerticalLayout {

private ProgressBar progressBar = new ProgressBar();
private Button cancelButton = new Button("Cancel task execution");
private final ValueSignal<Boolean> taskRunning = new ValueSignal<>(false); // <1>
private final ProgressBar progressBar = new ProgressBar();
private final Button cancelButton = new Button("Cancel task execution");

public MainView(BackendService backendService) {
progressBar.setWidth("15em");
progressBar.setIndeterminate(true);

progressBar.setVisible(false);
cancelButton.setVisible(false); // <1>
progressBar.bindVisible(taskRunning); // <2>
cancelButton.bindVisible(taskRunning);

Button startButton = new Button("Start long-running task", clickEvent -> {
UI ui = clickEvent.getSource().getUI().orElseThrow();
taskRunning.value(true); // <3>
ListenableFuture<String> future = backendService.longRunningTask();

progressBar.setVisible(true);
cancelButton.setVisible(true); // <2>
cancelButton.addClickListener(e -> future.cancel(true)); // <3>
cancelButton.addClickListener(e -> future.cancel(true));

future.addCallback(
successResult -> updateUi(ui, "Task finished: " + successResult),
failureException -> updateUi(ui, "Task failed: " + failureException.getMessage())
successResult -> {
taskRunning.value(false); // <4>
Notification.show("Task finished: " + successResult);
},
failureException -> {
taskRunning.value(false);
Notification.show("Task failed: " + failureException.getMessage());
}
);
});

Expand All @@ -275,20 +279,27 @@ public class MainView extends VerticalLayout {
add(startButton, new HorizontalLayout(progressBar, cancelButton), isBlockedButton);
}

private void updateUi(UI ui, String result) {
ui.access(() -> {
Notification.show(result);
progressBar.setVisible(false);
cancelButton.setVisible(false); // <4>
});
}

}
----
<1> Like the [classname]`ProgressBar`, hide the *Cancel* [classname]`Button` by default.
<2> Show the *Cancel* [classname]`Button` when the task is started.
<3> The [classname]`Future` representing the long-running task is canceled when the *Cancel* [classname]`Button` is clicked.
<4> When the task is completed or canceled, hide the cancel [classname]`Button`.
<1> A [classname]`ValueSignal` centralizes the task running state.
<2> Both components bind their visibility to the same signal using [methodname]`bindVisible()`.
<3> Setting the signal value to `true` automatically shows both components.
<4> Setting the signal value to `false` automatically hides both components. No [methodname]`ui.access()` is needed as signals handle thread-safe UI updates internally.

:preview-feature: Signals
:feature-flag: com.vaadin.experimental.flowFullstackSignals
include::{articles}/_preview-banner.adoc[opts=optional]

[TIP]
====
Signals simplify this pattern by:

* Centralizing the "task running" state in a single [classname]`ValueSignal`
* Eliminating scattered [methodname]`setVisible()` calls throughout the code
* Removing the need for [methodname]`ui.access()` since signals handle thread-safe UI updates internally

For more about signals, see <<{articles}/flow/ui-state#,Managing UI State with Signals>>.
====

Here is the animation of the [classname]`MainView` with a *Cancel* [classname]`Button`.

Expand Down
5 changes: 5 additions & 0 deletions articles/flow/binding-data/data-provider.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,11 @@ filterTextField.addValueChangeListener(e -> dataView.refreshAll()); // <3>
<2> Passes the current filter value to the service.
<3> When a value-change event occurs, asks the data provider to load new values.

[TIP]
====
For a more reactive approach, you can use signals to manage the filter state. A [classname]`ValueSignal` can hold the filter value, and a [classname]`ComponentEffect` can trigger the refresh when the filter changes. This centralizes state management and makes the data flow more explicit. For more about signals, see <<{articles}/flow/ui-state#,Managing UI State with Signals>>.
====

If you are not using Spring, you can pass a filter value in the same way. You can also pass more complex filtering values like JPA specification instances or whatever is needed.


Expand Down
26 changes: 19 additions & 7 deletions articles/flow/binding-data/field.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -301,18 +301,21 @@ In the constructor:

[source,java]
----
private final ValueSignal<Department> department = new ValueSignal<>(); // <1>

public EmployeeField() {
super(null);

departmentSelect.setItems(
EmployeeService.getDepartments());
departmentSelect.setItems(EmployeeService.getDepartments());
departmentSelect.bindValue(department); // <2>

departmentSelect.addValueChangeListener(event -> {
Department department = event.getValue();
employeeSelect.bindEnabled(department.map(d -> d != null)); // <3>

employeeSelect.setItems(EmployeeService
.getEmployees(department));
employeeSelect.setEnabled(department != null);
ComponentEffect.effect(this, () -> { // <4>
Department d = department.value();
employeeSelect.setItems(d != null
? EmployeeService.getEmployees(d)
: List.of());
});

employeeSelect.addValueChangeListener(event ->
Expand All @@ -322,6 +325,15 @@ public EmployeeField() {
}
----

:preview-feature: Signals
:feature-flag: com.vaadin.experimental.flowFullstackSignals
include::{articles}/_preview-banner.adoc[opts=optional]

<1> A [classname]`ValueSignal` holds the selected department.
<2> Two-way binding keeps the signal and combo box in sync.
<3> The employee combo box is enabled only when a department is selected, using [methodname]`bindEnabled()` with a mapped signal.
<4> A [classname]`ComponentEffect` updates the employee items whenever the department changes. For more about signals, see <<{articles}/flow/ui-state#,Managing UI State with Signals>>.

As a next step, implement [methodname]`setPresentationValue()` to update the combo boxes according to a specified employee.

[source,java]
Expand Down