diff --git a/articles/flow/advanced/long-running-tasks.adoc b/articles/flow/advanced/long-running-tasks.adoc index 8df4743ea1..aa7d82e6bd 100644 --- a/articles/flow/advanced/long-running-tasks.adoc +++ b/articles/flow/advanced/long-running-tasks.adoc @@ -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 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 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()); + } ); }); @@ -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`. diff --git a/articles/flow/binding-data/data-provider.adoc b/articles/flow/binding-data/data-provider.adoc index 0d57d65e9b..51a11a15bf 100644 --- a/articles/flow/binding-data/data-provider.adoc +++ b/articles/flow/binding-data/data-provider.adoc @@ -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. diff --git a/articles/flow/binding-data/field.adoc b/articles/flow/binding-data/field.adoc index 8b7c2ae0f8..76682cc77b 100644 --- a/articles/flow/binding-data/field.adoc +++ b/articles/flow/binding-data/field.adoc @@ -301,18 +301,21 @@ In the constructor: [source,java] ---- +private final ValueSignal 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 -> @@ -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]